From c419917c05c4900c41e76b138f355e7b1a228882 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Tue, 19 Jul 2022 22:22:27 -0400 Subject: [PATCH 0001/1008] docs(adr): ADR #010: Monitoring Based on https://github.com/celestiaorg/celestia-node/pull/901 and likely shouldn't be merged before it --- docs/adr/adr-010-monitoring.md | 88 +++++++++++++++++++++++++++++ docs/adr/img/monitoring-diagram.svg | 1 + 2 files changed, 89 insertions(+) create mode 100644 docs/adr/adr-010-monitoring.md create mode 100644 docs/adr/img/monitoring-diagram.svg diff --git a/docs/adr/adr-010-monitoring.md b/docs/adr/adr-010-monitoring.md new file mode 100644 index 0000000000..f61532ffbf --- /dev/null +++ b/docs/adr/adr-010-monitoring.md @@ -0,0 +1,88 @@ +# ADR #010: Monitoring + +## Changelog + +- 2022-7-19: Started + +## Context + +We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). These metrics will be pushed via [OLTP Explorter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTLP Collector](https://opentelemetry.io/docs/collector/) instance. + +We would like to make the metrics collected in OLTP Collector actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard front-end. + +This document proposes a strategy for making data in an OTLP Collector available for use in internal Grafana dashboards and a public leaderboard. + +![Monitoring Diagram](./img/monitoring-diagram.svg) + +// Comment on diagram # + +### Where to export data to? + +Grafana can query data from [multiple data sources](https://grafana.com/docs/grafana/latest/datasources/#supported-data-sources). This document explores two of these data sources: + +1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. +1. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. + +If alternative data sources should be evaluated, please share them with us. + +### How to export data out of OLTP Collector? + +[Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) provide a way to export data from an OLTP Collector to a supported destination. + +We configure OLTP collector to export data to Prometheus like this: + +```yaml +exporters: + # Data sources: metrics + prometheus: + endpoint: "prometheus:8889" + namespace: "default" +``` + +We must additionally enable this exporter via configuration like this: + +```yaml +service: + pipelines: + metrics: + exporters: [prometheus] +``` + +OLTP collector support for exporting to InfluxDB is still in [beta](https://github.com/open-telemetry/opentelemetry-collector#beta=). See [InfluxDB Exporter](https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector-contrib/exporter/influxdbexporter#section-readme). + +### How to query data in Prometheus from Grafana? + +In order to query Prometheus data from Grafana, we must add a Prometheus datasource. Steps outlined [here](https://prometheus.io/docs/visualization/grafana/#creating-a-prometheus-data-source). + +### How to query data in Prometheus from incentivized testnet leaderboard? + +Prometheus server exposes an HTTP API for querying metrics (see [docs](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-exemplars)). + +Implementation details for the incentivized testnet leaderboard are not yet known (likely built by an external vendor). Two possible implementations are: + +1. If the incentivized testnet has a dedicated backend, it can query the HTTP API above +1. If the incentivized testnet has **no** dedicated backend and the frontend queries Prometheus directly, then there exists a TypeScript library: [prometheus-query-js](https://github.com/samber/prometheus-query-js) which may be helpful. + +## Status + +Proposed + +## References + +- +- +- +- + +## Open Questions + +**Q:** Should we host a Prometheus instance ourselves or use a hosted provider? +**A:** We already host a Prometheus instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development and we can punt the decision of using a hosted provider to a later date. + +**Q:** Should we host a Grafana instance ourselves or use a hosted provider? +**A:** We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development and we can punt the decision of using a hosted provider to a later date. + +**Q:** Should we host separate Prometheus instances per use case? I.e. one for internal dashboards and one for public leaderboard? +**A:** The Prometheus docs state the following with regard to [Denial of Service](https://prometheus.io/docs/operating/security/#denial-of-service): + > There are some mitigations in place for excess load or expensive queries. However, if too many or too expensive queries/metrics are provided components will fall over. It is more likely that a component will be accidentally taken out by a trusted user than by malicious action. + So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance for now and if we observe scenarios where the Prometheus instance falls over, we can explore a hosted option or running separate instances per use case. diff --git a/docs/adr/img/monitoring-diagram.svg b/docs/adr/img/monitoring-diagram.svg new file mode 100644 index 0000000000..7acb29384a --- /dev/null +++ b/docs/adr/img/monitoring-diagram.svg @@ -0,0 +1 @@ + \ No newline at end of file From c2e62ee18c14e36806d98eea0948cd96f081b945 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Fri, 22 Jul 2022 17:31:58 -0400 Subject: [PATCH 0002/1008] Add section on how node operators can monitor node --- ...adr-010-incentivized-testnet-monitoring.md | 139 ++++++++++++++++++ docs/adr/adr-010-monitoring.md | 88 ----------- ...ncentivized-testnet-monitoring-diagram.svg | 1 + docs/adr/img/monitoring-diagram.svg | 1 - 4 files changed, 140 insertions(+), 89 deletions(-) create mode 100644 docs/adr/adr-010-incentivized-testnet-monitoring.md delete mode 100644 docs/adr/adr-010-monitoring.md create mode 100644 docs/adr/img/incentivized-testnet-monitoring-diagram.svg delete mode 100644 docs/adr/img/monitoring-diagram.svg diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md new file mode 100644 index 0000000000..ed13b5d930 --- /dev/null +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -0,0 +1,139 @@ +# ADR #010: Incentivized Testnet Monitoring + +## Changelog + +- 2022-7-19: Started +- 2022-7-22: Add section on "How can node operators monitor their node?" + +## Context + +We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OLTP Explorter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. The Celestia team will manage an OLTP Collector instance during the incentivized testnet (and likely beyond) to collect metrics from nodes on the network. Celestia-node operators have the option of running their own OTEL Collector instance in order to collect metrics from their nodes. + +We would like to make the metrics collected in the Celestia team managed OLTP Collector actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. + +We would like to make it possible for node operators to monitor their own nodes by deploying their own OLTP collector instance. + +This document proposes a strategy for making data in the Celestia team managed OTEL Collector available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can monitor their own node by deploying their own OLTP collector instance. + +![Incentivized Testnet Monitoring Diagram](./img/incentivized-testnet-monitoring-diagram.svg) + +// Comment on diagram # + +### Where to export data to? + +Grafana can query data from [multiple data sources](https://grafana.com/docs/grafana/latest/datasources/#supported-data-sources). This document explores two of these data sources: + +1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. +1. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. + +If alternative data sources should be evaluated, please share them with us. + +### How to export data out of OLTP Collector? + +[Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) provide a way to export data from an OLTP Collector to a supported destination. + +We configure OLTP collector to export data to Prometheus like this: + +```yaml +exporters: + # Data sources: metrics + prometheus: + endpoint: "prometheus:8889" + namespace: "default" +``` + +We must additionally enable this exporter via configuration like this: + +```yaml +service: + pipelines: + metrics: + exporters: [prometheus] +``` + +OLTP collector support for exporting to InfluxDB is still in [beta](https://github.com/open-telemetry/opentelemetry-collector#beta=). See [InfluxDB Exporter](https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector-contrib/exporter/influxdbexporter#section-readme). + +### How to query data in Prometheus from Grafana? + +In order to query Prometheus data from Grafana, we must add a Prometheus datasource. Steps outlined [here](https://prometheus.io/docs/visualization/grafana/#creating-a-prometheus-data-source). + +### How to query data in Prometheus from incentivized testnet leaderboard? + +Prometheus server exposes an HTTP API for querying metrics (see [docs](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-exemplars)). + +Implementation details for the incentivized testnet leaderboard are not yet known (likely built by an external vendor). Two possible implementations are: + +1. If the incentivized testnet has a dedicated backend, it can query the HTTP API above +1. If the incentivized testnet has **no** dedicated backend and the frontend queries Prometheus directly, then there exists a TypeScript library: [prometheus-query-js](https://github.com/samber/prometheus-query-js) which may be helpful. + +### How can a node operator monitor their own node? + +Node operators have the option of running their own instance of OTEL Collector to collect metrics from their nodes. Rough steps: + +1. [Install celestia-node](https://docs.celestia.org/developers/celestia-node) +1. Start a Grafana instance. If you'd like to use a cloud-hosted Grafana, sign up for an account on +1. [Install OTEL Collector](https://opentelemetry.io/docs/collector/getting-started/). If on a Linux machine follow [these steps](https://opentelemetry.io/docs/collector/getting-started/#linux-packaging=). OTEL Collector should start automatically immediately after installation. +1. Configure OTEL Collector to receive metrics from celestia-node by confirming your `/etc/otelcol/config.yaml` has the default config: + + ```yaml + receivers: + otlp: + protocols: + grpc: + http: + ``` + + This starts the [OTLP receiver](https://github.com/open-telemetry/opentelemetry-collector/blob/main/receiver/otlpreceiver/README.md) on port 4317 for gRPC and 4318 for HTTP. Celestia-node will by default emit HTTP metrics to `localhost:4318` so if you deployed OTEL Collector on the same machine as celestia-node, you can preserve the default config. +1. Configure OTEL Collector to send metrics to Prometheus. If you are using cloud-hosted Grafana, add something like the following to your `/etc/otelcol/config.yaml`: + + ```yaml + exporters: + prometheusremotewrite: + endpoint: https://361398:eyJrIjoiYTNlZTFiOTc2NjA2ODJlOGY1ZGRlNGJkNWMwODRkMDY2M2U2MTE3NiIsIm4iOiJtZXRyaWNzLWtleSIsImlkIjo2MTU4ODJ9@prometheus-prod-01-eu-west-0.grafana.net/api/prom/push + ``` + +1. Configure OTEL Collector to enable the `otlp` receiver and the `prometheusremotewrite` exporter. In `/etc/otelcol/config.yaml`: + + ```yaml + service: + pipelines: + metrics: + receivers: [otlp] + exporters: [prometheusremotewrite] + ``` + + See [this article](https://grafana.com/blog/2022/05/10/how-to-collect-prometheus-metrics-with-the-opentelemetry-collector-and-grafana/) for more details. You may need to specify port 443 in the endpoint like this: `endpoint: "https://USER:PASSWORD@prometheus-blocks-prod-us-central1.grafana.net:443/api/prom/push"` + +1. Restart OTEL Collector with `sudo systemctl restart otelcol` +1. Monitor that OTEL Collector started correctly with `systemctl status otelcol.service` and confirming no errors in `journalctl | grep otelcol | grep Error` +1. Start celestia-node +1. Verify that metrics are being displayed in Grafana +1. [Optional] Import a [OpenTelemetry Collector Dashboard](https://grafana.com/grafana/dashboards/12553-opentelemetry-collector/) into Grafana to monitor your OTEL Collector. + +### Should we host a Prometheus instance ourselves or use a hosted provider? + +We already host a Prometheus instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development. Grafana offers a hosted service, see [Grafana pricing](https://grafana.com/pricing/) for details. + +### Should we host a Grafana instance ourselves or use a hosted provider? + +We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development. Grafana offers a hosted service, see [Grafana pricing](https://grafana.com/pricing/) for details. + +### Should we host separate Prometheus instances per use case? I.e. one for internal dashboards and one for public leaderboard? + +The Prometheus docs state the following with regard to [Denial of Service](https://prometheus.io/docs/operating/security/#denial-of-service): + +> There are some mitigations in place for excess load or expensive queries. However, if too many or too expensive queries/metrics are provided components will fall over. It is more likely that a component will be accidentally taken out by a trusted user than by malicious action. + +So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance for now and if we observe scenarios where the Prometheus instance falls over, we can explore a hosted option or running separate instances per use case. + +## Status + +Proposed + +## References + +- +- +- +- +- diff --git a/docs/adr/adr-010-monitoring.md b/docs/adr/adr-010-monitoring.md deleted file mode 100644 index f61532ffbf..0000000000 --- a/docs/adr/adr-010-monitoring.md +++ /dev/null @@ -1,88 +0,0 @@ -# ADR #010: Monitoring - -## Changelog - -- 2022-7-19: Started - -## Context - -We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). These metrics will be pushed via [OLTP Explorter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTLP Collector](https://opentelemetry.io/docs/collector/) instance. - -We would like to make the metrics collected in OLTP Collector actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard front-end. - -This document proposes a strategy for making data in an OTLP Collector available for use in internal Grafana dashboards and a public leaderboard. - -![Monitoring Diagram](./img/monitoring-diagram.svg) - -// Comment on diagram # - -### Where to export data to? - -Grafana can query data from [multiple data sources](https://grafana.com/docs/grafana/latest/datasources/#supported-data-sources). This document explores two of these data sources: - -1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. -1. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. - -If alternative data sources should be evaluated, please share them with us. - -### How to export data out of OLTP Collector? - -[Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) provide a way to export data from an OLTP Collector to a supported destination. - -We configure OLTP collector to export data to Prometheus like this: - -```yaml -exporters: - # Data sources: metrics - prometheus: - endpoint: "prometheus:8889" - namespace: "default" -``` - -We must additionally enable this exporter via configuration like this: - -```yaml -service: - pipelines: - metrics: - exporters: [prometheus] -``` - -OLTP collector support for exporting to InfluxDB is still in [beta](https://github.com/open-telemetry/opentelemetry-collector#beta=). See [InfluxDB Exporter](https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector-contrib/exporter/influxdbexporter#section-readme). - -### How to query data in Prometheus from Grafana? - -In order to query Prometheus data from Grafana, we must add a Prometheus datasource. Steps outlined [here](https://prometheus.io/docs/visualization/grafana/#creating-a-prometheus-data-source). - -### How to query data in Prometheus from incentivized testnet leaderboard? - -Prometheus server exposes an HTTP API for querying metrics (see [docs](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-exemplars)). - -Implementation details for the incentivized testnet leaderboard are not yet known (likely built by an external vendor). Two possible implementations are: - -1. If the incentivized testnet has a dedicated backend, it can query the HTTP API above -1. If the incentivized testnet has **no** dedicated backend and the frontend queries Prometheus directly, then there exists a TypeScript library: [prometheus-query-js](https://github.com/samber/prometheus-query-js) which may be helpful. - -## Status - -Proposed - -## References - -- -- -- -- - -## Open Questions - -**Q:** Should we host a Prometheus instance ourselves or use a hosted provider? -**A:** We already host a Prometheus instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development and we can punt the decision of using a hosted provider to a later date. - -**Q:** Should we host a Grafana instance ourselves or use a hosted provider? -**A:** We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development and we can punt the decision of using a hosted provider to a later date. - -**Q:** Should we host separate Prometheus instances per use case? I.e. one for internal dashboards and one for public leaderboard? -**A:** The Prometheus docs state the following with regard to [Denial of Service](https://prometheus.io/docs/operating/security/#denial-of-service): - > There are some mitigations in place for excess load or expensive queries. However, if too many or too expensive queries/metrics are provided components will fall over. It is more likely that a component will be accidentally taken out by a trusted user than by malicious action. - So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance for now and if we observe scenarios where the Prometheus instance falls over, we can explore a hosted option or running separate instances per use case. diff --git a/docs/adr/img/incentivized-testnet-monitoring-diagram.svg b/docs/adr/img/incentivized-testnet-monitoring-diagram.svg new file mode 100644 index 0000000000..d0ab68867b --- /dev/null +++ b/docs/adr/img/incentivized-testnet-monitoring-diagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/adr/img/monitoring-diagram.svg b/docs/adr/img/monitoring-diagram.svg deleted file mode 100644 index 7acb29384a..0000000000 --- a/docs/adr/img/monitoring-diagram.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 4cdd324fa2fdda6152da9daef6b5584fcc6e23fe Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Tue, 26 Jul 2022 10:36:15 -0400 Subject: [PATCH 0003/1008] Prefix ordered list with ordered numbers Motivated by https://github.com/celestiaorg/celestia-node/pull/922#discussion_r930044696 --- ...adr-010-incentivized-testnet-monitoring.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index ed13b5d930..ecde18dfe9 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -24,7 +24,7 @@ This document proposes a strategy for making data in the Celestia team managed O Grafana can query data from [multiple data sources](https://grafana.com/docs/grafana/latest/datasources/#supported-data-sources). This document explores two of these data sources: 1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. -1. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. +2. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. If alternative data sources should be evaluated, please share them with us. @@ -64,16 +64,16 @@ Prometheus server exposes an HTTP API for querying metrics (see [docs](https://p Implementation details for the incentivized testnet leaderboard are not yet known (likely built by an external vendor). Two possible implementations are: 1. If the incentivized testnet has a dedicated backend, it can query the HTTP API above -1. If the incentivized testnet has **no** dedicated backend and the frontend queries Prometheus directly, then there exists a TypeScript library: [prometheus-query-js](https://github.com/samber/prometheus-query-js) which may be helpful. +2. If the incentivized testnet has **no** dedicated backend and the frontend queries Prometheus directly, then there exists a TypeScript library: [prometheus-query-js](https://github.com/samber/prometheus-query-js) which may be helpful. ### How can a node operator monitor their own node? Node operators have the option of running their own instance of OTEL Collector to collect metrics from their nodes. Rough steps: 1. [Install celestia-node](https://docs.celestia.org/developers/celestia-node) -1. Start a Grafana instance. If you'd like to use a cloud-hosted Grafana, sign up for an account on -1. [Install OTEL Collector](https://opentelemetry.io/docs/collector/getting-started/). If on a Linux machine follow [these steps](https://opentelemetry.io/docs/collector/getting-started/#linux-packaging=). OTEL Collector should start automatically immediately after installation. -1. Configure OTEL Collector to receive metrics from celestia-node by confirming your `/etc/otelcol/config.yaml` has the default config: +2. Start a Grafana instance. If you'd like to use a cloud-hosted Grafana, sign up for an account on +3. [Install OTEL Collector](https://opentelemetry.io/docs/collector/getting-started/). If on a Linux machine follow [these steps](https://opentelemetry.io/docs/collector/getting-started/#linux-packaging=). OTEL Collector should start automatically immediately after installation. +4. Configure OTEL Collector to receive metrics from celestia-node by confirming your `/etc/otelcol/config.yaml` has the default config: ```yaml receivers: @@ -84,7 +84,7 @@ Node operators have the option of running their own instance of OTEL Collector t ``` This starts the [OTLP receiver](https://github.com/open-telemetry/opentelemetry-collector/blob/main/receiver/otlpreceiver/README.md) on port 4317 for gRPC and 4318 for HTTP. Celestia-node will by default emit HTTP metrics to `localhost:4318` so if you deployed OTEL Collector on the same machine as celestia-node, you can preserve the default config. -1. Configure OTEL Collector to send metrics to Prometheus. If you are using cloud-hosted Grafana, add something like the following to your `/etc/otelcol/config.yaml`: +5. Configure OTEL Collector to send metrics to Prometheus. If you are using cloud-hosted Grafana, add something like the following to your `/etc/otelcol/config.yaml`: ```yaml exporters: @@ -92,7 +92,7 @@ Node operators have the option of running their own instance of OTEL Collector t endpoint: https://361398:eyJrIjoiYTNlZTFiOTc2NjA2ODJlOGY1ZGRlNGJkNWMwODRkMDY2M2U2MTE3NiIsIm4iOiJtZXRyaWNzLWtleSIsImlkIjo2MTU4ODJ9@prometheus-prod-01-eu-west-0.grafana.net/api/prom/push ``` -1. Configure OTEL Collector to enable the `otlp` receiver and the `prometheusremotewrite` exporter. In `/etc/otelcol/config.yaml`: +6. Configure OTEL Collector to enable the `otlp` receiver and the `prometheusremotewrite` exporter. In `/etc/otelcol/config.yaml`: ```yaml service: @@ -104,11 +104,11 @@ Node operators have the option of running their own instance of OTEL Collector t See [this article](https://grafana.com/blog/2022/05/10/how-to-collect-prometheus-metrics-with-the-opentelemetry-collector-and-grafana/) for more details. You may need to specify port 443 in the endpoint like this: `endpoint: "https://USER:PASSWORD@prometheus-blocks-prod-us-central1.grafana.net:443/api/prom/push"` -1. Restart OTEL Collector with `sudo systemctl restart otelcol` -1. Monitor that OTEL Collector started correctly with `systemctl status otelcol.service` and confirming no errors in `journalctl | grep otelcol | grep Error` -1. Start celestia-node -1. Verify that metrics are being displayed in Grafana -1. [Optional] Import a [OpenTelemetry Collector Dashboard](https://grafana.com/grafana/dashboards/12553-opentelemetry-collector/) into Grafana to monitor your OTEL Collector. +7. Restart OTEL Collector with `sudo systemctl restart otelcol` +8. Monitor that OTEL Collector started correctly with `systemctl status otelcol.service` and confirming no errors in `journalctl | grep otelcol | grep Error` +9. Start celestia-node +10. Verify that metrics are being displayed in Grafana +11. [Optional] Import a [OpenTelemetry Collector Dashboard](https://grafana.com/grafana/dashboards/12553-opentelemetry-collector/) into Grafana to monitor your OTEL Collector. ### Should we host a Prometheus instance ourselves or use a hosted provider? From 457908ca6a519b309e1d63457782fb928dcd44b9 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Tue, 26 Jul 2022 17:03:02 -0400 Subject: [PATCH 0004/1008] Add section on Uptrace --- ...adr-010-incentivized-testnet-monitoring.md | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index ecde18dfe9..5a013e49ad 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -3,17 +3,18 @@ ## Changelog - 2022-7-19: Started -- 2022-7-22: Add section on "How can node operators monitor their node?" +- 2022-7-22: Add section on "How to monitor celestia-node with Grafana Cloud" +- 2022-7-26: Add section on "How to monitor celestia-node with Uptrace" ## Context -We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OLTP Explorter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. The Celestia team will manage an OLTP Collector instance during the incentivized testnet (and likely beyond) to collect metrics from nodes on the network. Celestia-node operators have the option of running their own OTEL Collector instance in order to collect metrics from their nodes. +We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OLTP Exporter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. The Celestia team will manage an OTEL Collector instance during the incentivized testnet (and likely beyond) to collect metrics from nodes on the network. Celestia-node operators have the option of running their own OTEL Collector instance in order to collect metrics from their nodes. -We would like to make the metrics collected in the Celestia team managed OLTP Collector actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. +We would like to make the metrics collected in the Celestia team managed OTEL Collector actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. -We would like to make it possible for node operators to monitor their own nodes by deploying their own OLTP collector instance. +We would like to make it possible for node operators to monitor their own nodes with the telemetry tools Grafana and Uptrace. They can achieve this by deploying their own OTEL Collector instance and configuring it to export metrics to Grafana or Uptrace. -This document proposes a strategy for making data in the Celestia team managed OTEL Collector available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can monitor their own node by deploying their own OLTP collector instance. +This document proposes a strategy for making data in the Celestia team managed OTEL Collector available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can monitor their own node by deploying their own OTEL Collector instance. ![Incentivized Testnet Monitoring Diagram](./img/incentivized-testnet-monitoring-diagram.svg) @@ -26,13 +27,13 @@ Grafana can query data from [multiple data sources](https://grafana.com/docs/gra 1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. 2. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. -If alternative data sources should be evaluated, please share them with us. +If alternative data sources should be evaluated, please share them with us. We propose using Prometheus at this time. -### How to export data out of OLTP Collector? +### How to export data out of OTEL Collector? -[Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) provide a way to export data from an OLTP Collector to a supported destination. +[Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) provide a way to export data from an OTEL Collector to a supported destination. -We configure OLTP collector to export data to Prometheus like this: +We configure OTEL Collector to export data to Prometheus like this: ```yaml exporters: @@ -51,7 +52,7 @@ service: exporters: [prometheus] ``` -OLTP collector support for exporting to InfluxDB is still in [beta](https://github.com/open-telemetry/opentelemetry-collector#beta=). See [InfluxDB Exporter](https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector-contrib/exporter/influxdbexporter#section-readme). +OTEL Collector support for exporting to InfluxDB is still in [beta](https://github.com/open-telemetry/opentelemetry-collector#beta=). See [InfluxDB Exporter](https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector-contrib/exporter/influxdbexporter#section-readme). ### How to query data in Prometheus from Grafana? @@ -68,11 +69,13 @@ Implementation details for the incentivized testnet leaderboard are not yet know ### How can a node operator monitor their own node? -Node operators have the option of running their own instance of OTEL Collector to collect metrics from their nodes. Rough steps: +Node operators have the option of running their own instance of OTEL Collector to collect metrics from their nodes. A minimal guide for node operators to collect telemetry from their nodes follows: + +#### How to monitor celestia-node with Grafana Cloud 1. [Install celestia-node](https://docs.celestia.org/developers/celestia-node) -2. Start a Grafana instance. If you'd like to use a cloud-hosted Grafana, sign up for an account on -3. [Install OTEL Collector](https://opentelemetry.io/docs/collector/getting-started/). If on a Linux machine follow [these steps](https://opentelemetry.io/docs/collector/getting-started/#linux-packaging=). OTEL Collector should start automatically immediately after installation. +2. Sign up for an account on [Grafana](https://grafana.com/) +3. [Install OTEL Collector](https://opentelemetry.io/docs/collector/getting-started/) on the same machine as celestia-node. If on a Linux machine follow [these steps](https://opentelemetry.io/docs/collector/getting-started/#linux-packaging=). OTEL Collector should start automatically immediately after installation. 4. Configure OTEL Collector to receive metrics from celestia-node by confirming your `/etc/otelcol/config.yaml` has the default config: ```yaml @@ -106,10 +109,31 @@ Node operators have the option of running their own instance of OTEL Collector t 7. Restart OTEL Collector with `sudo systemctl restart otelcol` 8. Monitor that OTEL Collector started correctly with `systemctl status otelcol.service` and confirming no errors in `journalctl | grep otelcol | grep Error` -9. Start celestia-node -10. Verify that metrics are being displayed in Grafana +9. Start celestia-node with metrics enabled `celestia light start --core.ip https://rpc-mamaki.pops.one --metrics` +10. Verify that metrics are being displayed in Grafana. 11. [Optional] Import a [OpenTelemetry Collector Dashboard](https://grafana.com/grafana/dashboards/12553-opentelemetry-collector/) into Grafana to monitor your OTEL Collector. +#### How to monitor celestia-node with Uptrace + +1. [Install celestia-node](https://docs.celestia.org/developers/celestia-node). +2. Create an account on [Uptrace](https://app.uptrace.dev/). +3. Create a project on Uptrace. +4. Follow [these steps](https://uptrace.dev/opentelemetry/collector.html#when-to-use-opentelemetry-collector=) to install OTEL Collector Contrib on the same host as celestia-node. +5. Configure OTEL Collector Contrib based on the [configuration](https://uptrace.dev/opentelemetry/collector.html#configuration=) section in the Uptrace docs. Ensure you selected your newly created project in the dropdown. If you'd like to collect traces and metrics, you need to add the `metrics` section under `services.pipelines`: + + ```yaml + service: + pipelines: + metrics: + receivers: [otlp] + processors: [batch] + exporters: [otlp] + ``` + +6. Restart OTEL Collector contrib with `sudo systemctl restart otelcol-contrib`. Check that OTEL Collector Contrib is running with `sudo systemctl status otelcol-contrib` and confirm there are no errors in `sudo journalctl -u otelcol-contrib -f`. If you encounter `No journal files were found.` then reference this [StackOverflow post](https://stackoverflow.com/questions/30783134/systemd-user-journals-not-being-created/47930381#47930381). +7. Start celestia-node with metrics and traces enabled: `celestia light start --core.ip https://rpc-mamaki.pops.one --tracing --metrics`. +8. Navigate to Uptrace and create a dashboard. Confirm you can see a metric. + ### Should we host a Prometheus instance ourselves or use a hosted provider? We already host a Prometheus instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development. Grafana offers a hosted service, see [Grafana pricing](https://grafana.com/pricing/) for details. @@ -126,6 +150,10 @@ The Prometheus docs state the following with regard to [Denial of Service](https So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance for now and if we observe scenarios where the Prometheus instance falls over, we can explore a hosted option or running separate instances per use case. +### Should node operators be able to emit metrics and traces to multiple OTEL collectors? + +During the incentivized testnet, if we require node operators to emit metrics (and traces) to a Celestia managed OTEL Collector, then they won't be able to also emit metrics to an OTEL Collector they manage (based on our current implementation). + ## Status Proposed From 9d46feafaffab8cfefcfa2423b55f6541250d2ba Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Fri, 29 Jul 2022 16:34:06 -0400 Subject: [PATCH 0005/1008] Add section on "How to send data over HTTPS" --- ...adr-010-incentivized-testnet-monitoring.md | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 5a013e49ad..737eae0957 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -5,6 +5,7 @@ - 2022-7-19: Started - 2022-7-22: Add section on "How to monitor celestia-node with Grafana Cloud" - 2022-7-26: Add section on "How to monitor celestia-node with Uptrace" +- 2022-7-29: Add section on "How to send data over HTTPS" ## Context @@ -154,6 +155,33 @@ So if we are concerned about the public leaderboard crashing the Prometheus inst During the incentivized testnet, if we require node operators to emit metrics (and traces) to a Celestia managed OTEL Collector, then they won't be able to also emit metrics to an OTEL Collector they manage (based on our current implementation). +### How to send data over HTTPS + +#### OTEL Collector -> Prometheus + +Uses HTTPS by default. No additional configuration needed besides copying remote endpoint from Grafana Cloud. + +#### OTEL Collector -> Uptrace + +Uses HTTPS by default. No additional configuration needed besides copying the data source name from Uptrace. + +#### celestia-node -> OTEL Collector + +1. Ensure that celestia-node doesn't use [`WithInsecure`](https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/otlp/otlpmetric/otlpmetrichttp/options.go#L161) when constructing otlptracehttp client +1. Configure the OTEL Collector receiver to run with a TLS certificate and key. A TLS certificate can be generated with [LetsEncrypt](https://letsencrypt.org/). Example: + +```yaml +receivers: + otlp: + protocols: + grpc: + http: + endpoint: otel.collector.celestia.observer:4318 + tls: + cert_file: /home/fullchain.pem + key_file: /home/privkey.pem +``` + ## Status Proposed @@ -162,6 +190,7 @@ Proposed - - +- - - -- +- From b52c184cc92707a559527b6b7280cd0ca4ef7e1d Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Fri, 29 Jul 2022 18:27:02 -0400 Subject: [PATCH 0006/1008] Add steps for self-signed certs --- ...adr-010-incentivized-testnet-monitoring.md | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 737eae0957..f76a774c87 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -165,7 +165,9 @@ Uses HTTPS by default. No additional configuration needed besides copying remote Uses HTTPS by default. No additional configuration needed besides copying the data source name from Uptrace. -#### celestia-node -> OTEL Collector +#### celestia-node -> OTEL Collector with public certificate + +In the case where an OTEL Collector is running on a different host than celestia-node (e.g. Celestia managed OTEL Collector during the incentivized testnet), then the OTEL Collector must be configured with a public certificate so that celestia-nodes can send data to it over HTTPS. 1. Ensure that celestia-node doesn't use [`WithInsecure`](https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/otlp/otlpmetric/otlpmetrichttp/options.go#L161) when constructing otlptracehttp client 1. Configure the OTEL Collector receiver to run with a TLS certificate and key. A TLS certificate can be generated with [LetsEncrypt](https://letsencrypt.org/). Example: @@ -182,6 +184,26 @@ receivers: key_file: /home/privkey.pem ``` +#### celestia-node -> OTEL Collector without public certificate + +In the case where a node operator wants to send data from celestia-node to an OTEL Collector without a public certificate (e.g. node-operator managed OTEL Collector), they can issue self-signed certificate in order send data over HTTPS. Alternatively they can send data over HTTP. + +1. Follow the steps at [setting up certificates](https://opentelemetry.io/docs/collector/configuration/#setting-up-certificates) +1. Configure the OTEL Collector receiver to run with this self-signed certificate. Example: + + ```yaml + receivers: + otlp: + protocols: + grpc: + http: + tls: + cert_file: /home/cert.pem + key_file: /home/cert-key.pem + ``` + +1. Ensure that celestia-node runs with a TLS config that contains the Root CA created in step 1. See [sample code](https://github.com/celestiaorg/celestia-node/blob/rp/tracing-with-tls/cmd/flags_misc.go#L173-L199) + ## Status Proposed From fb47e0f4b9ca199b0d7cb85910ca7a802e6fb709 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Sat, 30 Jul 2022 12:03:02 -0400 Subject: [PATCH 0007/1008] Add decision header for Prometheus vs. InfluxDB --- docs/adr/adr-010-incentivized-testnet-monitoring.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index f76a774c87..f7d6b61600 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -28,7 +28,11 @@ Grafana can query data from [multiple data sources](https://grafana.com/docs/gra 1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. 2. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. -If alternative data sources should be evaluated, please share them with us. We propose using Prometheus at this time. +If alternative data sources should be evaluated, please share them with us. + +#### Decision + +We agreed to using Prometheus at this time. ### How to export data out of OTEL Collector? From 8aa82fc650fbebf3098e9f57a06ce9068dd1c3a6 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Sat, 30 Jul 2022 12:09:33 -0400 Subject: [PATCH 0008/1008] Add detailed design section and toggles for steps --- docs/adr/adr-010-incentivized-testnet-monitoring.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index f7d6b61600..34d5e084d8 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -34,6 +34,8 @@ If alternative data sources should be evaluated, please share them with us. We agreed to using Prometheus at this time. +## Detailed Design + ### How to export data out of OTEL Collector? [Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) provide a way to export data from an OTEL Collector to a supported destination. @@ -76,7 +78,8 @@ Implementation details for the incentivized testnet leaderboard are not yet know Node operators have the option of running their own instance of OTEL Collector to collect metrics from their nodes. A minimal guide for node operators to collect telemetry from their nodes follows: -#### How to monitor celestia-node with Grafana Cloud +
+ How to monitor celestia-node with Grafana Cloud 1. [Install celestia-node](https://docs.celestia.org/developers/celestia-node) 2. Sign up for an account on [Grafana](https://grafana.com/) @@ -118,7 +121,10 @@ Node operators have the option of running their own instance of OTEL Collector t 10. Verify that metrics are being displayed in Grafana. 11. [Optional] Import a [OpenTelemetry Collector Dashboard](https://grafana.com/grafana/dashboards/12553-opentelemetry-collector/) into Grafana to monitor your OTEL Collector. -#### How to monitor celestia-node with Uptrace +
+ +
+ How to monitor celestia-node with Uptrace 1. [Install celestia-node](https://docs.celestia.org/developers/celestia-node). 2. Create an account on [Uptrace](https://app.uptrace.dev/). @@ -138,6 +144,7 @@ Node operators have the option of running their own instance of OTEL Collector t 6. Restart OTEL Collector contrib with `sudo systemctl restart otelcol-contrib`. Check that OTEL Collector Contrib is running with `sudo systemctl status otelcol-contrib` and confirm there are no errors in `sudo journalctl -u otelcol-contrib -f`. If you encounter `No journal files were found.` then reference this [StackOverflow post](https://stackoverflow.com/questions/30783134/systemd-user-journals-not-being-created/47930381#47930381). 7. Start celestia-node with metrics and traces enabled: `celestia light start --core.ip https://rpc-mamaki.pops.one --tracing --metrics`. 8. Navigate to Uptrace and create a dashboard. Confirm you can see a metric. +
### Should we host a Prometheus instance ourselves or use a hosted provider? From 4f5d2e9638e44f7a8a2983595db1907cc4d7bb50 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Sat, 30 Jul 2022 12:10:16 -0400 Subject: [PATCH 0009/1008] fix: markdownlint --- docs/adr/adr-010-incentivized-testnet-monitoring.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 34d5e084d8..ce2a9ec486 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -144,6 +144,7 @@ Node operators have the option of running their own instance of OTEL Collector t 6. Restart OTEL Collector contrib with `sudo systemctl restart otelcol-contrib`. Check that OTEL Collector Contrib is running with `sudo systemctl status otelcol-contrib` and confirm there are no errors in `sudo journalctl -u otelcol-contrib -f`. If you encounter `No journal files were found.` then reference this [StackOverflow post](https://stackoverflow.com/questions/30783134/systemd-user-journals-not-being-created/47930381#47930381). 7. Start celestia-node with metrics and traces enabled: `celestia light start --core.ip https://rpc-mamaki.pops.one --tracing --metrics`. 8. Navigate to Uptrace and create a dashboard. Confirm you can see a metric. + ### Should we host a Prometheus instance ourselves or use a hosted provider? From d69600e8f770d36d881a79f1e4ae2cb58137f486 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Mon, 1 Aug 2022 11:58:26 -0400 Subject: [PATCH 0010/1008] Remove link to Lucidchart --- docs/adr/adr-010-incentivized-testnet-monitoring.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index ce2a9ec486..1f762ebc62 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -19,8 +19,6 @@ This document proposes a strategy for making data in the Celestia team managed O ![Incentivized Testnet Monitoring Diagram](./img/incentivized-testnet-monitoring-diagram.svg) -// Comment on diagram # - ### Where to export data to? Grafana can query data from [multiple data sources](https://grafana.com/docs/grafana/latest/datasources/#supported-data-sources). This document explores two of these data sources: From 1e7d508f33e919dfbd1fb07d6a7b2ef5afb761ae Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Mon, 1 Aug 2022 17:26:56 -0400 Subject: [PATCH 0011/1008] Minimize Celestia team managed components --- ...adr-010-incentivized-testnet-monitoring.md | 39 ++++++++++++------- ...ncentivized-testnet-monitoring-diagram.svg | 2 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 1f762ebc62..3378047716 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -6,33 +6,38 @@ - 2022-7-22: Add section on "How to monitor celestia-node with Grafana Cloud" - 2022-7-26: Add section on "How to monitor celestia-node with Uptrace" - 2022-7-29: Add section on "How to send data over HTTPS" +- 2022-8-1: Revise architecture to minimize Celestia managed components ## Context -We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OLTP Exporter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. The Celestia team will manage an OTEL Collector instance during the incentivized testnet (and likely beyond) to collect metrics from nodes on the network. Celestia-node operators have the option of running their own OTEL Collector instance in order to collect metrics from their nodes. +We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OLTP Exporter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. -We would like to make the metrics collected in the Celestia team managed OTEL Collector actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. +We would like to make the metrics exported by celestia-node actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. -We would like to make it possible for node operators to monitor their own nodes with the telemetry tools Grafana and Uptrace. They can achieve this by deploying their own OTEL Collector instance and configuring it to export metrics to Grafana or Uptrace. +We would like to make it possible for node operators to monitor their own nodes with existing telemetry tools (e.g. Grafana and Uptrace). They can achieve this by deploying and configuring an OTEL Collector instance. -This document proposes a strategy for making data in the Celestia team managed OTEL Collector available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can monitor their own node by deploying their own OTEL Collector instance. +This document proposes a strategy for making data available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can configure their OTEL Collector instance. + +## Proposed Architecture + +We expect celestia-node operators to deploy an OTEL Collector agent alongside celestia-node during the incentivized testnet and export metrics to a Prometheus instance hosted in Grafana Cloud. We will share a Prometheus endpoint and API keys when this infrastructure is available. ![Incentivized Testnet Monitoring Diagram](./img/incentivized-testnet-monitoring-diagram.svg) +## Detailed Design + ### Where to export data to? Grafana can query data from [multiple data sources](https://grafana.com/docs/grafana/latest/datasources/#supported-data-sources). This document explores two of these data sources: 1. [Prometheus](https://github.com/prometheus/prometheus) is an open-source time series database written in Go. Prometheus uses the [PromQL](https://prometheus.io/docs/prometheus/latest/querying/basics/) query language. We can deploy Prometheus ourselves or use a hosted Prometheus provider (ex. [Google](https://cloud.google.com/stackdriver/docs/managed-prometheus), [AWS](https://aws.amazon.com/prometheus/), [Grafana](https://grafana.com/go/hosted-prometheus-monitoring/), etc.). Prometheus is pull-based which means services that would like to expose Prometheus metrics must provide an HTTP endpoint (ex. `/metrics`) that a Prometheus instance can poll (see [instrumenting a Go application for Prometheus](https://prometheus.io/docs/guides/go-application/)). Prometheus is used by [Cosmos SDK telemetry](https://docs.cosmos.network/main/core/telemetry.html) and [Tendermint telemetry](https://docs.tendermint.com/v0.35/nodes/metrics.html) so one major benefit to using Prometheus is that metrics emitted by celestia-core, celestia-app, and celestia-node can share the same database. -2. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy the InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries [ref](https://www.robustperception.io/translating-between-monitoring-languages/). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. +2. [InfluxDB](https://github.com/influxdata/influxdb) is another open-source time series database written in Go. It is free to deploy InfluxDB but there is a commercial offering from [influxdata](https://www.influxdata.com/get-influxdb/) that provides clustering and on-prem deployments. InfluxDB uses the [InfluxQL](https://docs.influxdata.com/influxdb/v1.8/query_language/) query language which appears less capable at advanced queries than PromQL ([article](https://www.robustperception.io/translating-between-monitoring-languages/)). InfluxDB is push-based which means services can push metrics directly to an InfluxDB instance ([ref](https://logz.io/blog/prometheus-influxdb/#:~:text=InfluxDB%20is%20a%20push%2Dbased,and%20Prometheus%20fetches%20them%20periodically.)). See [Prometheus vs. InfluxDB](https://prometheus.io/docs/introduction/comparison/#prometheus-vs-influxdb) for a more detailed comparison. If alternative data sources should be evaluated, please share them with us. #### Decision -We agreed to using Prometheus at this time. - -## Detailed Design +We agreed on using Prometheus at this time. ### How to export data out of OTEL Collector? @@ -74,7 +79,7 @@ Implementation details for the incentivized testnet leaderboard are not yet know ### How can a node operator monitor their own node? -Node operators have the option of running their own instance of OTEL Collector to collect metrics from their nodes. A minimal guide for node operators to collect telemetry from their nodes follows: +Node operators have the option of adding an additional exporter to their OTEL Collector configuration in order to export to multiple backends. This may be useful for node operators who want to configure alerting on metrics emitted by their node. A minimal guide for node operators to collect telemetry from their nodes follows:
How to monitor celestia-node with Grafana Cloud @@ -147,11 +152,11 @@ Node operators have the option of running their own instance of OTEL Collector t ### Should we host a Prometheus instance ourselves or use a hosted provider? -We already host a Prometheus instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development. Grafana offers a hosted service, see [Grafana pricing](https://grafana.com/pricing/) for details. +We currently host a Prometheus instance on DigitalOcean (host mamaki-prometheus) for development. However, cloud hosted Prometheus providers take on the responsibility of running, upgrading, and scaling a Prometheus instance for us (see [oss-vs-cloud](https://grafana.com/oss-vs-cloud/). Although multiple hosted providers exist, we propose using Grafana Cloud's hosted Prometheus for the incentivized testnet. ### Should we host a Grafana instance ourselves or use a hosted provider? -We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). This seems like a good option during development. Grafana offers a hosted service, see [Grafana pricing](https://grafana.com/pricing/) for details. +We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). We propose using Grafana Cloud's hosted Grafana for the incentivized testnet due to it's tight integration with Grafana Cloud Prometheus. ### Should we host separate Prometheus instances per use case? I.e. one for internal dashboards and one for public leaderboard? @@ -159,11 +164,11 @@ The Prometheus docs state the following with regard to [Denial of Service](https > There are some mitigations in place for excess load or expensive queries. However, if too many or too expensive queries/metrics are provided components will fall over. It is more likely that a component will be accidentally taken out by a trusted user than by malicious action. -So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance for now and if we observe scenarios where the Prometheus instance falls over, we can explore a hosted option or running separate instances per use case. +So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance because Grafana Cloud guarantees 99.5% uptime. ### Should node operators be able to emit metrics and traces to multiple OTEL collectors? -During the incentivized testnet, if we require node operators to emit metrics (and traces) to a Celestia managed OTEL Collector, then they won't be able to also emit metrics to an OTEL Collector they manage (based on our current implementation). +~~During the incentivized testnet, if we require node operators to emit metrics (and traces) to a Celestia managed OTEL Collector, then they won't be able to also emit metrics to an OTEL Collector they manage (based on our current implementation).~~ This is no longer a concern because we expect celestia-node operators to run their own OTEL Collector agent alongside celestia-node. Under this architecture, node operators are at liberty to configure multiple exporters and can therefore export to multiple OTEL collectors by routing traffic through their agent collector. ### How to send data over HTTPS @@ -177,7 +182,7 @@ Uses HTTPS by default. No additional configuration needed besides copying the da #### celestia-node -> OTEL Collector with public certificate -In the case where an OTEL Collector is running on a different host than celestia-node (e.g. Celestia managed OTEL Collector during the incentivized testnet), then the OTEL Collector must be configured with a public certificate so that celestia-nodes can send data to it over HTTPS. +In the case where an OTEL Collector is running on a different host than celestia-node, then the OTEL Collector must be configured with a public certificate so that celestia-node can send data to it over HTTPS. 1. Ensure that celestia-node doesn't use [`WithInsecure`](https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/otlp/otlpmetric/otlpmetrichttp/options.go#L161) when constructing otlptracehttp client 1. Configure the OTEL Collector receiver to run with a TLS certificate and key. A TLS certificate can be generated with [LetsEncrypt](https://letsencrypt.org/). Example: @@ -214,6 +219,12 @@ In the case where a node operator wants to send data from celestia-node to an OT 1. Ensure that celestia-node runs with a TLS config that contains the Root CA created in step 1. See [sample code](https://github.com/celestiaorg/celestia-node/blob/rp/tracing-with-tls/cmd/flags_misc.go#L173-L199) +#### What are the resource requirements of OTEL collector? + +Official resource requirements are not stated in the OTEL Collector docs. However, [performance benchmarks](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/performance.md#results-without-tail-based-sampling) indicate that OTEL Collector is able to handle up to 10K traces ([units unclear](https://github.com/open-telemetry/opentelemetry-collector/issues/5780)) on 1 CPU and 2GB RAM. Given [light node](https://docs.celestia.org/nodes/light-node#hardware-requirements) runs on 1 CPU and 2GB RAM, it seems feasible to run an OTEL Collector agent on the most resource constrained target hardware. + +During mainnet, we won't require nodes to share telemetry data so resource constrained devices won't be obligated to run an OTEL Collector agent indefinitely. + ## Status Proposed diff --git a/docs/adr/img/incentivized-testnet-monitoring-diagram.svg b/docs/adr/img/incentivized-testnet-monitoring-diagram.svg index d0ab68867b..e6b6109e8e 100644 --- a/docs/adr/img/incentivized-testnet-monitoring-diagram.svg +++ b/docs/adr/img/incentivized-testnet-monitoring-diagram.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 6757cec12ae31946beecc5fb95f5bf6259cddb57 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Thu, 4 Aug 2022 10:10:41 -0400 Subject: [PATCH 0012/1008] Why doesn't the Celestia team host OTEL Collectors --- .../adr/adr-010-incentivized-testnet-monitoring.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 3378047716..0df1a706ce 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -7,6 +7,7 @@ - 2022-7-26: Add section on "How to monitor celestia-node with Uptrace" - 2022-7-29: Add section on "How to send data over HTTPS" - 2022-8-1: Revise architecture to minimize Celestia managed components +- 2022-8-4: Add section on "Why doesn't the Celestia team host OTEL Collectors for node operators?" ## Context @@ -166,9 +167,18 @@ The Prometheus docs state the following with regard to [Denial of Service](https So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance because Grafana Cloud guarantees 99.5% uptime. -### Should node operators be able to emit metrics and traces to multiple OTEL collectors? +### Why doesn't the Celestia team host OTEL Collectors for node operators? -~~During the incentivized testnet, if we require node operators to emit metrics (and traces) to a Celestia managed OTEL Collector, then they won't be able to also emit metrics to an OTEL Collector they manage (based on our current implementation).~~ This is no longer a concern because we expect celestia-node operators to run their own OTEL Collector agent alongside celestia-node. Under this architecture, node operators are at liberty to configure multiple exporters and can therefore export to multiple OTEL collectors by routing traffic through their agent collector. +1. Node operators will lose the ability to monitor their own celestia-node. Since opentelemetry-go only supports configuring one exporter ( [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055)), if node operators were obligated to export metrics to a Celestia team managed OTEL Collector endpoint, they wouldn't be able to export to their own OTEL Collector (and by proxy any telemetry platforms they wish to use). This violates the desideratum: + + > We would like to make it possible for node operators to monitor their own nodes with existing telemetry tools (e.g. Grafana and Uptrace) + +1. Node operators will have an "incentive" to maintain high uptime for their OTEL Collector. If the Celestia team took on this responsibility and failed to provide a highly available solution, then node operators would be penalized for downtime of a component they have no control over. +1. We expect 1500+ node operators during the incentivized testnet and there is minimal documentation on the scale of workload an individual OTEL Collector can handle. We'd have to design and operate a highly available OTEL Collector fleet to maintain high uptime for node operators. At this time no cloud managed offerings for OTEL Collector exist. + +### Should node operators be able to configure celestia-node to export to multiple OTEL collectors? + +This is not supported by [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055) and is no longer a concern because we expect celestia-node operators to run their own OTEL Collector agent alongside celestia-node. Under this architecture, node operators are at liberty to configure multiple exporters in OTEL Collector and can therefore export to multiple OTEL collectors by routing traffic through their agent OTEL Collector. ### How to send data over HTTPS From 35678c77c9b9520f0129d6cfa55f7e0711cef7ef Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Mon, 8 Aug 2022 13:55:04 -0400 Subject: [PATCH 0013/1008] Rename section to "Which actor should run OTEL Collector(s) during the incentivized testnet?" --- ...adr-010-incentivized-testnet-monitoring.md | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 0df1a706ce..3d29e85faf 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -8,6 +8,7 @@ - 2022-7-29: Add section on "How to send data over HTTPS" - 2022-8-1: Revise architecture to minimize Celestia managed components - 2022-8-4: Add section on "Why doesn't the Celestia team host OTEL Collectors for node operators?" +- 2022-8-8: Rename section to "Which actor should run OTEL Collector(s) during the incentivized testnet?" ## Context @@ -167,14 +168,44 @@ The Prometheus docs state the following with regard to [Denial of Service](https So if we are concerned about the public leaderboard crashing the Prometheus instance that we use for internal dashboards, we may want to host two separate instances. This seems feasible by configuring OTEL Collector to export to two different Prometheus instances. This is a one way door, I suggest sticking with one instance because Grafana Cloud guarantees 99.5% uptime. -### Why doesn't the Celestia team host OTEL Collectors for node operators? +### Which actor should run OTEL Collector(s) during the incentivized testnet? -1. Node operators will lose the ability to monitor their own celestia-node. Since opentelemetry-go only supports configuring one exporter ( [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055)), if node operators were obligated to export metrics to a Celestia team managed OTEL Collector endpoint, they wouldn't be able to export to their own OTEL Collector (and by proxy any telemetry platforms they wish to use). This violates the desideratum: +#### Scenario A: Node operators + +Pros + +- This deployment architecture is more representative of mainnet where node operators will run their own telemetry stack to monitor their node. Exposing node operators to OTEL Collector during incentivized testnet allows them to practice this deployment architecture prior to mainnet. +- Node operators will have an "incentive" to maintain high uptime for their OTEL Collector. + +Cons + +- Additional operational burden for incentivized testnet participants. We can mitigate this concern by providing easy install steps and scripts. + +#### Scenario B: Celestia team + +Pros + +- It will be easier for nodes to participate if they only have to deploy one piece of software (celestia-node) and not two (celestia-node and OTEL Collector). + +Cons + +- Node operators will lose the ability to monitor their own celestia-node. Since opentelemetry-go supports configuring only one exporter ( [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055)), if node operators were obligated to export metrics to a Celestia team managed OTEL Collector endpoint, they wouldn't be able to export to their own OTEL Collector (and by proxy any telemetry platforms they wish to use). This violates the desideratum: > We would like to make it possible for node operators to monitor their own nodes with existing telemetry tools (e.g. Grafana and Uptrace) -1. Node operators will have an "incentive" to maintain high uptime for their OTEL Collector. If the Celestia team took on this responsibility and failed to provide a highly available solution, then node operators would be penalized for downtime of a component they have no control over. -1. We expect 1500+ node operators during the incentivized testnet and there is minimal documentation on the scale of workload an individual OTEL Collector can handle. We'd have to design and operate a highly available OTEL Collector fleet to maintain high uptime for node operators. At this time no cloud managed offerings for OTEL Collector exist. +- If the Celestia team took on this responsibility and failed to provide a highly available solution, then node operators would be penalized for downtime of a component they have no control over. +- We expect 1500+ node operators during the incentivized testnet and there is minimal documentation on the scale of workload an individual OTEL Collector can handle. We'd have to design and operate a best-effort highly available OTEL Collector fleet to maintain high uptime for node operators. At this time no cloud managed offerings for OTEL Collector exist. + +#### Scenario C: Node operators by default. Celestia team as a best-effort fallback + +Pros + +- Optionality for node operators who don't want to deploy an OTEL Collector to rely on a best-effort OTEL Collector provided by Celestia team. + +Cons: + +- This option increases the cognitive load on node operators who now have an additional decision at deployment time. +- Increased operational burden on Celestia team during incentivized testnet (and beyond). ### Should node operators be able to configure celestia-node to export to multiple OTEL collectors? From e437c24418785bdbab955331d22c5005ba25005c Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Mon, 8 Aug 2022 15:03:41 -0400 Subject: [PATCH 0014/1008] Add diagrams from FigJam --- .../adr-010-incentivized-testnet-monitoring.md | 12 +++++++++--- ...incentivized-testnet-monitoring-diagram.png | Bin 0 -> 225268 bytes ...incentivized-testnet-monitoring-diagram.svg | 1 - ...entivized-testnet-monitoring-scenario-a.png | Bin 0 -> 228160 bytes ...entivized-testnet-monitoring-scenario-b.png | Bin 0 -> 151627 bytes ...entivized-testnet-monitoring-scenario-c.png | Bin 0 -> 246633 bytes 6 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 docs/adr/img/incentivized-testnet-monitoring-diagram.png delete mode 100644 docs/adr/img/incentivized-testnet-monitoring-diagram.svg create mode 100644 docs/adr/img/incentivized-testnet-monitoring-scenario-a.png create mode 100644 docs/adr/img/incentivized-testnet-monitoring-scenario-b.png create mode 100644 docs/adr/img/incentivized-testnet-monitoring-scenario-c.png diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 3d29e85faf..0c80b39afa 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -12,7 +12,7 @@ ## Context -We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OLTP Exporter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. +We're adding telemetry to celestia-node by instrumenting our codebase with metrics (see [ADR-009-telemetry](./adr-009-telemetry.md)). If the option to report metrics is enabled on celestia-node, then celestia-node will push metrics via [OTLP Exporter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) to an [OTEL Collector](https://opentelemetry.io/docs/collector/) instance. We would like to make the metrics exported by celestia-node actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. @@ -24,7 +24,7 @@ This document proposes a strategy for making data available for use in internal We expect celestia-node operators to deploy an OTEL Collector agent alongside celestia-node during the incentivized testnet and export metrics to a Prometheus instance hosted in Grafana Cloud. We will share a Prometheus endpoint and API keys when this infrastructure is available. -![Incentivized Testnet Monitoring Diagram](./img/incentivized-testnet-monitoring-diagram.svg) +![incentivized testnet monitoring diagram](./img/incentivized-testnet-monitoring-diagram.png) ## Detailed Design @@ -172,6 +172,8 @@ So if we are concerned about the public leaderboard crashing the Prometheus inst #### Scenario A: Node operators +![scenario a](./img/incentivized-testnet-monitoring-scenario-a.png) + Pros - This deployment architecture is more representative of mainnet where node operators will run their own telemetry stack to monitor their node. Exposing node operators to OTEL Collector during incentivized testnet allows them to practice this deployment architecture prior to mainnet. @@ -183,6 +185,8 @@ Cons #### Scenario B: Celestia team +![scenario b](./img/incentivized-testnet-monitoring-scenario-b.png) + Pros - It will be easier for nodes to participate if they only have to deploy one piece of software (celestia-node) and not two (celestia-node and OTEL Collector). @@ -196,7 +200,9 @@ Cons - If the Celestia team took on this responsibility and failed to provide a highly available solution, then node operators would be penalized for downtime of a component they have no control over. - We expect 1500+ node operators during the incentivized testnet and there is minimal documentation on the scale of workload an individual OTEL Collector can handle. We'd have to design and operate a best-effort highly available OTEL Collector fleet to maintain high uptime for node operators. At this time no cloud managed offerings for OTEL Collector exist. -#### Scenario C: Node operators by default. Celestia team as a best-effort fallback +#### Scenario C: Both. Node operators by default and Celestia team as a best-effort fallback + +![scenario c](./img/incentivized-testnet-monitoring-scenario-c.png) Pros diff --git a/docs/adr/img/incentivized-testnet-monitoring-diagram.png b/docs/adr/img/incentivized-testnet-monitoring-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..6565a52c5fa6f5866cf74d792de9b541ef49ba2c GIT binary patch literal 225268 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 #0Ix2 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 \ No newline at end of file diff --git a/docs/adr/img/incentivized-testnet-monitoring-scenario-a.png b/docs/adr/img/incentivized-testnet-monitoring-scenario-a.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e3fe9e47e3c86a319c89ab7ea26788e2193558 GIT binary patch literal 228160 zcmeFZi96Ko8#b;zD)p!kqI#0-BBU^tBxK2&v1f}x7_yE@3l&1PWErxIbqIs8M~K15 zHrbQR*au?_GiK)ZnI6ygJ>K_U_&ptm!}uJzXYT91?(qaJL*j2s~-P+6@8UIKA#!Ksh*gMcH3_w2dyV0uT2Vz zN|ZG38Y(hB^6xK5J>P9^>CO`oaN!1?x$&tf5eCdk|M9!D{8zXs==GZ;|;-7 ze;7+dJCn7!JhuDUI5-G;nIi19o_*1M3=)%T3fV4ST3LC=4DLVSl6aF?rrwztoXUJO?Cp`W^72$#927rZ&}Yz)z^G!pILN`l`I98xxW`^p{*3JT`R;xg1Eg}Cu3Ye_ntzI)jJ=O8d$ z^IgfZwle;tN$a33Et*MXH|T1x(j3AFIpvMo~s*x;cLHMt+kzxt0k{a zx9C~b_5bu{pQ&hb$ROh{^;NnnDJyGb)d2^Wzw{hk&`wcek?QRnD?bmnq zxgp?8j8u{PomlF*!Q*B8i;Ig+4i4GAi8oumXKNktMc36?(l&RtpEe2{Zn2j++ieCkUl(`5^dSIa`$>TFa{=xsiWs zy-Qfcm-5X?*fV)?EZ`$Wwd)9R7ib(8QJ4B>?;JwHyZz;>ru$aeYb*qhPEE;jbmP-+ z_;;<%bYLqRch+^0N95xH2k!6M+Wml5W`_2YSREkdgWmKT=61X{ZrsRadveP(`*M5_ z;Q43OXG7H8Gr7AmQHO^Zif46GC=~XM^`ULdKX?qX4Ne7){2gk#Es}4RPHK_`tdlFd ztqg5h_=6|pcqS-|SyEE6&b~9ivniNC#&JFwD}KuN66HX9iifAC(nRKE*5=B}%HxpM z>Db^5LpV)-*DhwMyEvqGajUAK!I~Cp{GrsgX;S=m(kk4B;z)Q&hk5{B8SKE}3+8<9 z9KXl@!Lw~$J5XtDAaGBZbBi4@A|e#VArIQ#_|cIO@e-%;i+%8#A43122**#at$!GS zXBnaDZFhk)8^KELk;0*86o5%fgabDWl#p%i6WC*koBOaOLj8aefGOSgjlo0an8V38}{dBOG>T+E0$m{j9*LOOl#cp==P^-kOQgDg~}; zbmLRNDSHJ~&pHFpBj_==Yno=azy1NxOl8h2Kb1Bm8JqftGFCN)ywcNkL4_-o86da~ z$Fm`}5&GKztK5izgsW!&Ah&k;Yt!e?R|Mz{GPYym<5DiYY2wF)RWIL{Nj;|;vQ@VG z24&!X4^R&;2WOl3=q=!KnN~*v@fJ9;aNy+hk==+J{wN`}Am6Kb4=pVv2f6~>@{yHd znQ-dLgz~}dsozWn{Win6>k>~v@b1FJ++m@AUIvlZXR-a7EuJ0k⪻;owK(WkOO9i@uYpA-I5 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
<#vBq=Gtp6j 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
m=4cP8d(LV{m_DNX+EZKvAXF>zT&zcl62!>x}nt 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 F{V%!b9VY+) literal 0 HcmV?d00001 diff --git a/docs/adr/img/incentivized-testnet-monitoring-scenario-b.png b/docs/adr/img/incentivized-testnet-monitoring-scenario-b.png new file mode 100644 index 0000000000000000000000000000000000000000..ab8ab3416497ebf4a9fbf84351d3afc816bb17bc GIT binary patch literal 151627 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
-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 literal 0 HcmV?d00001 diff --git a/docs/adr/img/incentivized-testnet-monitoring-scenario-c.png b/docs/adr/img/incentivized-testnet-monitoring-scenario-c.png new file mode 100644 index 0000000000000000000000000000000000000000..7dafa60c32a43c583c3f6b95e3fae936afbbd8c0 GIT binary patch literal 246633 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�{>|@7Iwbr`Gm?}uI zRSAzZrdRE)F$0aVR{-B&0;}oN8sJK>Bz_8*x~g?Hd|r<(bdTBvB?-Y<&3Z+WLVNHN!rH8;9z4zDN+beQjECYz!iSa!I= zS>_nXoXwgXAb7X>AfuV}%_+%46>=U$7?3Aq{ zK*&>Xm!^v!+VSY2RuB{15ywmTopX9@OlA-}-V$t-9k~O(Oas65N*kQjJ}5 z%?`MRKSY&ty;prN>4#q#f!ShjrqCPl+{GOjC}@I$ z=(o6iHgwPj|-uK zBM_#-8Qyisav0v9;jg5XW%Z)=g~%|o@7|xoNx~enyg>o{@-vAApvs-R;F@{C-XvZ~ z%|vfa_vSr9tf2nuevyocxhW39N!AQy^Vo9tw@uVlDGOIg#)QCZT4Hq)I2qnD8>e&k z$4^=`hjsZ@h|@J4Fxu@W?J;ssp2)SR2*m&jk_)ZVdkk(q5%*MXZ@H$x0r@arlOed> zIl*DDF1~sa22U+&li9#<>1CHsuF|Pdgp>q!h^heH;9A=cP=S>kST%|6pftki2o~9! zG-zguA5!-P6<=u_eM;?`LxUY*^=I@W!r@)ni~W`e|3J?oOaceQ#+PO^`OBJQ4ips# z|I6MDy5D5$oiv3$XjRZ2!OGMMKRp|2rS`l^_+#05H@{9$-(?3*l;KES&J&@SWWPpP zU2+>TN~f;XpooGDOdp=Rdd(_!zrddcH(h8fhYP;^0ldVY;ob|CFW2k?MvQNgQI+i; ztgi!$qjtc7j;*i@SID(L#j+WaRh`erOWrc2XnMIU22O=OsjL5-?Hru(OiZ-RM zYjh&n5uGsK2qxnn!-LWd7;kG@Z?GIZnnc7XsTC}GD8v?Pp<4G4(rEudHYUu1SUy=| z#%k(>1JG=e7$~b0)4D|XLaYj0wbzE7q{K|MbjR+*4TjOi$6$76UvOo<&I6()cRB`y zc>>~jW3+atZ-S8m@D9!o+q;^lyp|CAW`r*Y`yhgL^ zGR9=sH>2SVv#Uq|DfDekBz%#0fJBDKz_1Qur{tj-H9j4@ls>(zN9CNL?1Iex@tM?9 z2QnGsupuR(w*U@@@S#yDI7p={awgmxIP=`Umns;jMilpnJj^YWw?^c*L@eQ`KV&On zYm~Gahk`=)YUh7KtEGGX0rcqvQzgXji z{{z(GNdQbe4xaGP=`XZa3NOCczV$jG6|Jb-yD0RdF7v+tM6+^aiGX4hI%7) zu~Jv9U|IU(#Rw`9?Ru>!ey}s9M^-RJc!ba{Li%kFae-6SI6hKo8?KNjX7E;-N(7N_ zrIZj25_QC3YR|=N)HN-EiLNfb%#6}|$U9Ei$V0>YtXD75K+b4n`-rxXHY})$4di-; zgBYe?zkr=@PqdLc56>mz{h_Q~qA%HMw+4xvv(ISXTta!4d*w;;dtKkwX5G1%gEjmS zUrhZz9&~KJ@+}W3B&f%mEr2BBFmRP$)+?gVVcOW&=^zAnL7agCsuKm>-EcqS(31rW z7NCzQ!hr*M#EK7U8gL)$yMJ{NoSWK7;1nk3`GYXSRHHrB#MV@lvu$utbz1M zJ>`xyb)RQx7{9dTgb79E-4OhZAMzF9&DnL_{E7Xod!AU=VdA9uA-DEBqY9&EDVIw9-M;Y}Lw1$>dsR_%CU4rFyOL5|jG6Cw?)>h_-xe8^q1inyzVu(N()qgcdx zwac~Jsf}}hTRg1Yk!f|xX!j|(byg*>5MV1>ju>&U-?R4lH-jh%bEm#2n8~C>uKAcH z+IVb;n@3smdRgyTXoZ>;y2Ey-mvdHEaJUQeoaXqpdnK|OqldWEY=+qE?E z;H^bah5W1=M}y6yIG~`HY<#gD5g#$#{JiA@51Mb>>W86XiNHs*d%cm6UVq<{+35S# z5^hk5uZ}mR$71n}pdp|qbPOpOAoq61=3Q!)^x=Ev96SCJQ|4jwq7x`k%PXxRU4b&rg_ur1p@zm6L zx(kYTg(wb&TDE%k2tEUT_RO$1+8=QH919t?7m`&o*L!$ebpY~D!{nD6Wu+>;f9&Cj z-`R%c_Y0<%zl1`xi+fJo3JSuh>SqhdrdT1I3f@^xbDdjw?D=@4N1>&>&IOQdV>kVt z8FPK)*!iwRRl1O{{y#g&i<0X=?U#4$`iuz@rGrIMo$b+EK4|7lvt{z02!=s~k z#2!$WI~iv6%wFmfAH<%H)0+*z8R@iXZG$;os?<&NO4V}G+fLdgOiY=RK3=h&p36wL z|7hN+^}KN)@KXmLUh0iEyWDEI_i}gb{PzbxHK5A9(R=bnYb967Rj$q#_Z$y$d!?;6F@LEm>vT5HL0aOC}!(1I$0&lnpSPnaN<;d(yFGj(v>Ksp(Yu|`C zk9glY^9c)fmL2nAKrodWj7;^efXeC96c37bSK1m)w1Zkc$`DSKXaoX2;(6vqpG>+9 zKbh!#^)aKTcR3_JbmL zB2=l@s?Wc6$GCb;w+4MUZ5Z!obbtmh2>K+osHA%-QjX|V)x|&C& z7a`@cu4acOd7Zf!q_giut>5imK>Cuom=@f`n3b>qb*>5QvTtsf$>-P`#@*NCo~{-$ zyNqDu7cwfOCKTuN04LkHi4qgE1l(k|MV_6_)gW2|H76-oj!H&z8 z&omd&vF^j;bxg}^2mPomy;F6|V&X!~4Jh52~@ zv_WwKyWe%Nc+C{`(Adji8{%Cc8c@et+dg*vL-g14*+|kRY+LvkEySF4l$lKRDA?Ge}2GJGq`+K@U1U={M-=Y#>YQ@wc_qSwOFjJ zlNJRJ^oHGTW?OQ+a*b5v>^UrsA(4lyukvcmJDF_?0?+NOQzAxg z+9IEYWmuuuZTn_*`ojffU1v?Szg|GE?1EQ@o6I83P-HvXSxiXYITh8ouRh32xWt?? zNvFgCRqo^o2H<&IWP{zgJ`@T(h!9M-8GPu3JXuhBcM($+`Y_fs77@qY7vT@4n0Q_X zREYcWh9En+Ga`dtbsWTrgq#fD;isElxURi&wQyd7;yeK2(SD0JEYC-SvpV)pfqsbB zt4WB-Pdvzfckc9)%PGTWtLvggW|A(Y%HO+?_ltjKR~R|WN6~IqyHc*|^gB!?z3Y#$aE=1otKA7XcCr5a zOBU*V_RxWD8izmVJ4~4;Ik@B~Hg@ks(}*C-MRl6)=Fkq)W6rs*G)HF=!yB8H;Ek+r zM$692rU%ys@LJ2qnfnK{?Nnzxu=S2d#{tz z@B4OxER;8-5?zkGENBFOBxpB&g2L}_get|gFT1gyunkrqo|91`pKQU=OR(qq&k_k? zfET}nZ>|p)D+|D~iDw$p3`jzX59~2)4xfrfjjvUp7?=^6r30QIR32KrsQLQ}>r6Rq z|Ge0sN+{7{0b2I*(4phMy(Qf>ONSU{=a}M&aRhZu<*eGd+Dzo-pIV6(V?%Y?nPa5{ z4av)e5vMtpMvR}LgkYH+e7?{f$i{9zym|`Z)LhMwU!iuWz(+FWuX-?R26>;25KkgM zKNnR_(KEl+DA%U|WL+O664aMXPJil3A+MLT5hV%QA@2r_4<(#LI5``Gs<0i{g6X6R z$O>|rXG?PLzXHQ>3k-uiIMnXBFVOg>xit}LlN>xM$yJWR7kIXF4UlmL@ckD-4c-q< zXMyvRNdQfI#aOT8Rht0TX&N9S|IVvSbmtc+{I7HH%x$(|*up1g4YVdM!~QURn7+<9 zm3Jn*w6Zt{5(3c2@v8!n6~ncj<}U|Tg^cn+*-|Syq!P@Jmq0|9gp7#=U4X^1Tm}m?%xPm#YA3G2{h!skWxM6&CtMH4X*6WXe z!+8vrd_0Qj7Xqvg=1{?xClwN(mHW^#GceucyudbG1lV`UcsNTrc7zHcQOKZd;)m)0 zu-#5_fx~dL{Yt{BfEy@1K)2m6We*h7RKS1?&O=8ZUehA4|{stZ&Mr7R%8oowvWXpngKL&eI$>B<9=H8~655P*^~jV})#Bmd#j077~rpY?TVJr?XTlxj_!5qB~d(8xy5{o5}!0G@)Kq~CsrUXcjlJoQi z_%FWG1Pn#(^`5p*5JJ`9S)fxoHU_Hu0}w;>1zJYQZze&wj#%#^_zdScCL8dq7`NC! z&IJ!RT9Hv}opn!`4}1qbC@(i+-x88H1Lgm+QU>-lHnohJaRHOrpd%`Ep8w>bpeGs1k8I8(Jkgc#d9HH&{D2 z7TyBf4*i=JiCs_$>90*nqb_syBgQjE=geB?km!1s)`I1j`5{efOti*I{Z*ed>y)O8 z8=wnKM+DgrXi7)n^M#V@4g`e~sKa2lLqtS(>EK*j(UNZyW_q>7W$q{T%HUEI5}6v@ z)z;JRP3ovIZqQGhewGzdY;39N(sEqo01+%aa#{yNe^A-ewdrQL`T?rPaKKPrsj(y> z>akz*0F)NeA;hW9(m*^=#1*7nV`u+gi8qi%1q87xWi{|Yq2DuU-5_as7)%*<1^#*) zd#-}uLJYqv24jLy1*k}EAK8G)Xk{&RzsH^)u;&2}qt(40qEiLcaXAT-Gh;DBvbn{E zh@YZ>cOWYIovFezP{b$o&TOxl)iYpsIxhzM!v9n}n7JH6;2M|Hfa?4W`@8zjYZn5) zfd08yW&UE#hxQT78N6RkeS^DmW1X|BzRCfNx@wS-rEISa%_mgD`fNZ&hQb7ZVY~yx zrIA570}PN^wGoxIU}d!yYzosPQ1NZP6Ab&Wpb>#vA#AE`3=xr z12u4KYVTgF%!5KK`x|Xu=s7yJlWMX7J@XxwjEgDitp5|r2Dwi|h7<%3i^?*JSf2V- zivgDUuq@B=8XM#+RWnsdYB<-^EU01!O=N0sJ6R>nwrN~;C{!(`3710^r-X(AYKSV* z#kC0KfVfmyZ>hSRlV|)`?p{fz@j>+17;$bzU{=zck~SUe|1MlTtv#ku^GT>HVd4l> z6gd&vS!Y~jI}-GXy`Pi5D1K+(+|}%>c_Au@YZeo2wt`%S{?9kjME${CpDY#d##v3Z zixAi=pE%Kmo&D!#U|nURuviFJ#W$P_>2>B_TAJ#zNW8O>0ro=($n{LC=bp6Yd* zPVWq>ZME+}rXuCPrSI~I7go~*y4j#H8Tx|pbGyEwYB3Y3R@Z-meAHR9|53@r287AcqC%y zPO!dSr30g^ubLc)cSe5;cGQ|Rjw5gJmMO1*4b`0k%V@B9-Is~g9}t-G!6qxOFo1&O z%~v5gACAU7NeH&N=yh$ex7+A(x0vHpQW?BC>(n>a7rn$Z3RUIgFVCGEX=xJLIpF{B^Lq$g{DG#HwW}{w|{A zChWT*oyx3Q8{3=zhR%J*_m-bS zRufr=s293&kC`JM*o9ND6rYae@p5m=UT%wIX#s88se)pdN}c1Xq?j9etuDu48gr1Q z6LqF!77pk}D@}j5BIHbA4>jiMjo*6IihdXzFt$Br?>o&V0e+ijl5y64EH=yPrk5U3 zy;K}#%BL-&3*2NhenGd#Ffup43TA?hSU=nUZ_#k$v1~ zV+>euwf(y?npT_*J%(4@OZWuJiE7SlrCnDieHafC>%aMjb2=jN<~Yxs>K0p;qRIj8 z&nl|i3(mG8Z&w?^uHK;YAMrO6+i|+Tw1w>B`o=6=&IB6JQ>#$9Mi($g2)_nP_d)K( z)QzqvkhLSnDeV{9HZ`hAb?+@vvuQy5L{dZh2Lg0)u)hH@;yXL*URw8dTVtJNp~>za zlx}Tg3L<3x4}0$&)nxj%i$(_v<0y(< zstPs`1V*HzVxy~!0zv>mK!kvR2~t9^j94g&R4EaaCN-4M6Ko(wg0w&agb^W-kVHxn zNJz4ufX?{)&N*xCwbwaoe|w#I{~+YeTb};h<+`t%yg;+X*GyJ`7}2`e_#|l^zi+>AqL?us5PV*F+>Ks~q1ecUJMuL`Wh( z-cY3jtx%j@z{I^OxJxm!&vR>`-6Q9it}a*?6hu}$?{WOLvLbqPqYDKOFx&2C!>XvR zqQR1ow8h|2UnsqQpbX#7s!3BtA9WeL=f(9M4jXdQ_x2bFzM{|O@JWaGae*)Hib}0W zxOFV$VOxbqObtSHe8}GJi|tK!$F@q&67+`LW#%1XP9_pG3S%$4HD!msV1*HM0Z!Sv zyiAMuMr5Sxl;yNvh0M%g=`z_<2QIf}9@9VX;vO)9jBr0hn27&I~#oG?PSQbXq3pkMW}ee$=}AUS6fWPmRbdUip+}>}G7KMN3|iB{abBF;L(t%qT~Vy&i?62G{F1{J}Xm`l_yzsxxSX>Gy4%2rFg_GA>KqkGHUzDbe3N!NLL0l1_Lb^WY?tb4cv z9cTb=3wB6aAp>36Gbc`GKQEi6{ZSTj2wL=H#_+2+s-@VSoezN=eZu+`HJ^m4{vz7_ zP;6R6ehLEl_UzJ^pb5#D>r|RLP#t5&aQb@0+q%K^h$n}L1sxZix7GTQmD8M;J1DCf zegYi+UHXTPwHSxZeRF5qosjcrHCk73jFhu3CWk?52s?83B4LX|KDkb!i4Lb}x8c|; zGG`p?DTCURD<^a0$OibU*Gr0xfG^FLoCsFoUF5u2w;uwTLUfBGX{5D9K@?lbVdA}Zu7T~_$0jwb58YlQ9I?rJ+#o-Rm2cbrpeD`!mvyQy zLfaAKbyZm9ct?Uy#!(dU0{~_?Xd_R&{e)~yaL-xs((UkNVb5!0bPl^>kXZA}Y+G6S zv`M|Dq6MYGB)BklmCycCr=seIcPCmkC#WrOLODIF2!!$#Sq~Z~-U_dwuRG{$j<%Ot zA3yUdwCKjDM()GQE{AL9J4W)}90Gze$mWyl zyaG_8f5d~tD32oz0D9}^dHF|Vo7=wol(pa5p9P2Zs*b08t^j$er7UI?TH!Zgz{1gL zMMk8Yt1p%>d$qQvtirkr_ALqtgUFFX#`T~oGWF_Iatf5^l(s3fu|klJp^qzd%t^%oV2 z?SHYeP{~)z;a6iosAZaa;#x;JP4nP$9>(g@@70!3C1&Z{KiB>x3K;~U2z59o5Rbry ziI6$>zL!At`rhfrWoWGU(3*zn9Uv7z(v_Hul8Y6ni1P zNG0I{yof6@s`9zL&}y`G2*lbo3;cjJ(WaG9Z6|f@Gg9hC60~?`i`bG9@rf!HnuTA~ zg)g`jV!fyyC`OOI@1M$fCXRy-!AeOflQa%WgB2lP}Uzkwx#OCCgY~k35g4l!srh^$Ufke(Jgh?`KbP1|xH5<-OMuG#yr(rPU3fc^^AZ8RP(IAM!0|V37P} z#J!i5{@ZUQAN|psp_IeJZ+6{LDF9k`N5WAN5GBd{`8Lb@__jo+NKTofoQKu7U?849 zZ5njXu_v6pF-Xi}wHWmZZrBQf(+F*0SnD-!i8SFpYWLbWj8@22o-a?k-uK9A}WNz1uyh&}eI*8bNQJt^&{&`i9hh zv71e)EL=2ARUtr}N+9gI2#6$L^GI?E$rFb^fUqnVjqm%$?Uj*@6N6l~ER>29~a9FL%cJXkiAtGQ-kPD<`P3GEuw0 z4Jyxgm-(^t1$Qf1FV(&OPvwDg*>xmP&J z-__0&eTTy{xCB7racJ%ma=->H29c%^Jyy3`ufZ%QX5UQ-R5>Dfj@eF`WzJr;V>Uf%a?#v)s56FnQF11|0hi z1kkcrvrZ~y??;?dS#Z#&GUL`<^0k)nl_lp-c@!O_K~RZ>3dGif5{_ufs~9wtT# zA@=Za5eo))_9z{_33&5|9*4D9$jBSqELporO>_7d0~kPHSB)~80Nk1QTYHIxElp6i zAsM=Dd|Ndn&@i9E&Ay>t+7(A|Ui?D6VVUnqQIrt-RBDjf zLB;N4{mjyp`!2=bT7=$1syf3#B zG*{VmS8C%PcbnZ3K_{_^U*7(Ca!cdYoHwGskSOjaEhED~f-cvJ<)@d>vdD%k2t%V3ST9z#-#g3(mq zh#a`?T!(uAxBp6y;#9ml`&fbc8AF9R=MPseod+QHg( zU7U&UUQ>I9p@SNjuQCgub7UlW;`zF*C;B$;_!mUY>gU&P4tc8bV&{9jzHnZgdX64F zzI;P?rH5Q*MY>uq%17n~+WS((=3|Jqmn*MrczpW0{O&ilNDQ=g?QY1xq_z+7aw3vE z7(T-T4yc>sw|Ya7 zk9&v0RJdUxoywS6v)D!sf8{$1u8FaVd#1u|2Sk9PWh+>#v}=Ke1%X73HeJ=EXzqF24h-&ZO#>B9W1LuV+ZAq z3At*}2@~el#77VXDQ;gcKPNzg1}nn#U4^(a#;y~*V2duRty8mr+ol!KnX#MpCmj2w zdJCJEUw_?7Vjk9@K$sHqAZO1<9kfovlf6hYCkK3;yJG6qXzkwlu@A_;wJ-rkj1mWk zN^m$F6gmt3id4wp^#LdasP-ZKW5ApXfxP^ZR1RC;vsIt?GqfP>J&0!oa#H(rfn zNjbTS2~baLa+%LNaohw_2D>!}In z1F*~}uszD82|*soeJUy{Ae`7~d-|f70r6nZ6Hwta!IX#x9L@)UrOQV`Aa2~YufW1Z zujC(py*i7eqmrGvCGCScV)w$1HSc#hdB4jCj#AjRyxND<2ji+n;LPcp=shpWyq*N! zb)8}t;y$zZCk&iE=$$${RM`eo-SV=Q`)3PA?%EA`axt_Kabvz{bw5cl+()L-D4$$X ziy|>PDxJ!hWDUkt@77(7fhRZVC}I7=-iI?*{#nSxbOhbH>qSCxfIrPTeNRTET=dOn zRS_p^i(OM(Whym7C((A)zC9-ALb;(Dz4hv}ScVNA^@W02BjuR4ZFB8IMrWn>nTK zQMj29ZiNZgGb9XJo2F45QsnK-eXm>3VxPCA(4%xv6@<-%u9d(Xm4CC{4@q~>`a}mE zs|wSK}0*sS7VV(v2ER@!T^LJQ97R1?o4oD*Zt zLqC%NN@3IDEMHs%+7w-yo2$A1>It1hGqwiG3;rLj7P3y+LY-K7w?K)l+ z;(u#BT?BF-TJ;`r{i^pmthQ}KdNfo=5f@JSu&L%{Yfukto5$e2DaXNi1Q%B8l-ghE zM!$qN;+U)YIqut;s5_>>J^9Km_8smV(oNI@FWw5#0lE@zH3y?M2#;l_c}mPB<4&P4 zmS2LuY|CxkJX1t=ziK~%(b+?va-tIDcIv#Jk|tlddUwo@i?}ixQdh~(uVrdb{K7_L zlx+-9q5ijYhD3#6n|9)gjPq{m=vxENZrfT+s(FZf8^$=?=`~oGU ziP)ssJ>#j03X|s&*ECw_9|`|!jq|=or2t{nYJ>tTA-%EHVuE~nWWx$WVURGs{}p!$-d@B96W0G2{V+i^0-X-@ezZ@n>vuf{xoXI{sz z&70&Msl%F05B;*xV~A=jhkY|XSi;I+?PiKoE`~a9>{%oI!8;Z=1G^u{EGOo%F?~2T z+uY|J4*m$Xm0SmnRoUERcY+Hra5Ns0ebW$NHQcwx-zV&z7JZHlFAsMPgYqi|gPSl* zY&(x;ftM)WGb7mJ9atn80HC!GfFD%ALRkU@IxYm_vHl56rjv8>P3EcQ4N%LCGY^_P z*GBHfi9mipl0JU@U$zpL%G!@Cuf}~WTzP#%DePi&-fj!}nhBcb=_>73&8FGGrgd?nxn_`krdqu+)6opYNNpy19IhBH>hi{ zTfJs`G}v#Rp2gF}8E>ys0F>su6Uce@W<*Er_=c_b#PPC~3%9{i2ad_GDbbkNnZoN+ zN6oi_%|D8dmJ(V6G0??#@H5|t-%r@$qANDIeVa-*2)Pk;<Sm(Y|XSKCoheavM zynNPs&U5#}YwvRP-uk3d`xd_Z%F@8E#(hCpP|~TtYaH;YUwusC+Tq^V;P(Rf{H}Nz za^%!u#y)zPMB|h!rVWGBKpI%9&h+sR6?{dpdGT4OeCpsm=pc|B5i?9OhMM)n# zA_9q2A~P0zpLxBz*AFr1Y2TY1G`I(zvZccExa=zUjlcT#ObK*6w3_}hXyVsVWY~65 zDEjn`BxNVJp5R6sM_E|Bh0Tiwgtpd!P(J)PND7FALUR}g6==p z&j1_5P;!(9pij$a79cgkz;Z^QYKS;>W&5UN{XZwv2;HcJpT8#dGsaF5f_HeErNey;DGGs&Ag1 z^1JWFWl@Z)_q~4{9BVR+Pn;JRLxSr>t6tBE1icB4$+0R_t_X5y60q|;jV&S|nSrB! zZgtuRX}B>Y`lW}tD6VNe6j{Wav+*vML51yjc~9!WnV@~N^q_Kv*UN6_2g#H^Jq!M` zV3X>T2Lq!vIh^dPOL6f#N51}onZayi+-F>C)yn(}svYm~=WvA|@_OoVu zD8cw6jWHkQerefZ-l)g?nyg?~5d4J(+G~AC#4@zoli7ezpa|u3dX=a5iGhJr14X^HWhbhH0A8B_~8o*NugT4*F$>TP$D%2gT&Vmz{Z!ZRgk4c|{$&Jp4Bm2JIG1vfW z(iXmv3O_4`fwB2+3O+r#bw1HER6NvYT`!CDa`oD>q%lLHz`*f9p1|Kkb?&Y4GRoE?C{>7U%qTej8k-Geso8PluRMqSn zM{i*{S6$Bpjr8vO)^zag$bDEdSmXU*RqASK-vCU3`7t6@6gy)Q%`>?xnEJfA>9~am zG-qP^nfJh(EGiI0K%wy*rQS+n(TI&|>!4X>oUmTdJa>qS?*g;=RA@)}H%r?=X-CtF6@c2ior} zzOA9>GnelrRve->&I0`sQnIP&!@0KIq!_70>l1wcHm65T2O+ARASvadW(B#<2JU@7 znn2uN+bZ)HAM)eR?EN)e)`1h9SzyTjw)D6kKS2U(0W~TZLHCww^W%Jk6WBIVLqsIf zrT^=X;3CCaiwK1umy188S1+o-Kb`vbqJP`z-=P8~#J>~a-zNBX6#P38#CzkvYr(%w z@b4)2-!>6k-UvZHNU;j<;yEJ(V!Zf>uwRm0$3Vo*H&k97&{9@F#xPi{_S{@w^(sij z2XJY`$sgAzKBX+));0kQyb56pAcx1Db3910eVSG{h|QF;(n2snX!jJ*MO~hNKZ~yz zRUQ}rliURqWSzO2Q5_(ILGW(&vO@<>jfqJTTEzrgZzdLi%-_(v_`0d@#Y-eF>Z%Qn zjl$=~?-gc5fK~6H0$ z59Q3=5zZyMOW@{AKG}=xHbSziM-v@d;6y0UzLsuD_)*k!??s)`#TPyI9(XW({)`R# zx_bpABL5v91;IW9O11bX30zVcw0E8||^4IT0XlUQF zSoOYFV%D2nQ^>0afl)KR0YEhsXzVF}ztt~DtjW4`(G)HTfi>y?q-39hVEl9|Aa?>f z4*3pv@0D*q+T_ln{xSUP2L(1Ip!MLWIF&*$put-M@#z(}@VuK45TW8gG3#%Nq#9m; zKZ~VqJ-L{Ey97dn20`aR+^_EZ9e})M7motV=IMD5S?dIGu}CxVt(nN47W>|&R{;lu zkF&TVshPjanYvg`#V5wyz(5y~ zP5>>qbnqq*eE;vqN5k9y)WN{{|L4sj;zm=ff;w;@`^|$qsRq>mu>zSi_|fSNr*zgTJ%E5(zJ))nX};`74lI|KH%gWy6f^-}k2cbFW?H$7KB9w-o$z%ZXDzRpit^6;V9=Q=7>A(45#G`NG(ln;urjdR9JbHcx}6hsOfNmG0kO1dh@Taqym6TT1dM7ApbonV z5h=VELJ(r_nBcy2X@tts+PZXUc}>kFz|mSO43-eus*Dw1>lTS^;)rS|KsWuPpf3i$ zo4mvY6Dt5iNx0#Z6^NNE?VrPwP7G~QVm4&khELBU!1!0?|~MfjD_Br2LSWn zY4NHB4r1v)l?Pwg&#S)tk2xSdXQ%Ve618{@1fulQ;=6R}=<@%D7u{Z3 zBNRWkw6t{dZEbCTSgt{tC$B8cA;lg*Se-MZ{?iP0$su3(UeO;P5r6&{t1CeK{&%eL z|EFd8vF(6x(`<3W=%;=cBq4{=mo|el6#%CzUZE#_{uX5ZUB3P&_lN%%){|jWe=XY1 z{59Wcb~O8qMAuQ-G{o+yjKH;o7)q^m@?P^`X*;v{V@J2&bhVb#io{H`rRpl#Y_z`o z<=7Q#)zN0fdXO7>>Bu@^Js{NA zH&SCi4L_fk17dG%pmJ`yF)>S$Dx@jl?xOs7O4)@6ICWPQ6Vh~d-`o+CEC%+915au} zgfWgk>1j+h%rC)bk7F2i=M0&f7|YP0S$iO4enA~D}NkTLe!GWq73sITzUs_?_+(?QPZU*CAfJr8;r zwQ-c1myWj z**pL{BA36=4aflba9Wprl`{Aiarf&oUDVSOai&6fC2mg9C5Engrs!*+5CTbaoWL6c zf9Wc2m{G4q{>!ycxo`K`JLqaNbh zkN&x>6kqT08tvZ3$oBba_oz-GR+mWa`&!jQy%;5z#Mzd}O2%<%ZWsoX5M% zY%hwSH{;ZMjx$=)a)DlJn?^Qv zHd5Dpb}*X#uBq3iaVK`dCdtUlt0~LE-Qapl+)Oo-3agc3Y&++`R35PmthD8!pD^6h zQ)Aq^`OdvZF0xDn%^ZUrWJ0_lyTX)G+1JxIpzUT-%Zuq_SF!i7x0{Rv1`>`f4hNSb zN{c#dq}r|fVr&=~40DIx+>vw*1Blq-)!Pofc_De;03}>-aT)m&gffdV;7Vu9pth+c zXyV0(P3u$}&T2(swh#^GSTxOeSuWe(I=C^; zhOFJW6_N)3YPU9F@94*HnDpc`Y#Me;)JAa5=MJRzf+7~=s=56<@s#A)gj}_eqBWXP34;_H@Z-i)T(JhHKWx} z+APnbD@`jCD=^wdH)QFfV1P`T8yO=CO`*QeuAV2)R*iBUty`%y#DEzxiIosgc#woh zVpu}+%fcw{E?o<3|I^1?wYlC9J9?7{PZ&urot#oTb0lHUxm7-NEF7bK%82x%8x@wUg_6{)b5k~W>4(XYvr4a9$>|;_~?-$ac@s< zppQs9A)+%=V)RVMOrOzLR-On08MM13BA-!2q^b^@=}ND6+WW!xn(7?>jN#rh!9JoH zr$kf!Gq(bxvKZ;RX#&|xMRmqX)T1%1ZlSw_y{~Ok5ChTEE|HS*iC4~tdMKs=ZN8Sp zyw_G;#JEKBqUj_CWEJ`z;Kys%P57(4p`IjNpkQF0x#*3vM)1ORJ)X2032u?TCaZ{A z-dsU?yMtj+=9&GXFLl0bT15%0CX&1ocejd|9PJ9YaFIJlXrlvTj^Ij!M}~m@U`awv$sAePxc*)lyp=*_CVQ1>sW^&EEVo4Npz{&NJo=P_n!HESpmER;pAD3NM%oU0Hcr00HLiR4SgPvEzxkueUjut*_V2D z^+lqOA&I>r?b1TMGuor3^&XA0-4|M(g{ZPKI5iUm-seb1`!A;SOp4h~Us%9*`Iug^ zN_k;ud_E_**spQ!h3`ufm^xXNe;DgXy2yE%zTqIf*wZ|=U^l(RC>{&F{lNT%iQI_{ zck|^|a$nP6J!Ee>>M7#z5k=e_z_Wz}@A;7VoP}Fu^wp2?+ilnj5r~HBTALCGmC?M> zPBgV*=sL%kKVf`L`#7ILRG!0^<2!w|Q$8Q0BIF!#&1^;TC;vRcXKjtlA))f(yb=$fnEoBf{WZO4St(ADW=TMKbDLdAv~^wrxCvIKSVt z+Pqva-MLCl3!3z*3(#l$IY3Da+0X5FH*N`lreTwJpkdddnUtPasrH>ztg8 zlHlwWHG7U?);$CStCFiNBitnKsUGXMDNC-PA6SNmLy#%%c~CVxm8IOFx$HfxBS^05 z8PdGLOVX#XH3LG>-00~1I25{-LCubTK3;%#GU7j;PJBLhX1#xC0ZnmeJYN(r*-D|x za$()g)%`iztF>X7N~-Z3QqprnTO%~!RjX28hA*$&)uhu@<4aJ^#jTtEq_N$69Nl#) zvb`xFjE-k3&sN?Vmz2kW{jJz;54|NID>}Q7I+w_Cy3K6mnT75!e>+qTWh-k$nXV6$ zj8T`IXTY=7KjZzdA9#NKRE*mZI8;XKMtf`pr98jJ2l<9EA4a;aiwY><#qDf-wT>Pk zz>HihI*26R@zHYMPaH2eVw`0~rVGOM1D~opW5j+ zu;=p)+tJtjwk(CSDv~6&;3f?(SG-NDXO)^@df;hd=s&5; zO>OP-duLcYoxR7xO_4pcRXywk71NsjVp()t$Iua{F3#n#*~rFn)2Vrj`dDRC-BHr0 z>O_AePP;os;}qrH?_{B?e$ly>^iiJtc;$)oWI5z$J4OYG!PpPJv>H>;i*4cY0B1p1 zN}&8nTAA+=hQAeGMQYbUIORx2XUwMxSV2LKe5!Y>b9iyFz{Cy5*0I}u$Gh0uJ7ou@ zIh?S)vWT85_saubn0&2M5A{URpZilIzb#7p4Ix%=@m>~sQJTb8DyeK+t^j`yM@yeD z+7xEDYLjQZ3E2|jCRgz6c?YJUALOfsvQ;@x0@w$W2%a3)i?tqVw_cy*`0wk}rx)MR z5euTeNu@#@ggfa8mZgTz*mlz0FPS{Q*iW(HvoC!QG74;wW)aBc&@!piJu_Q%!%fI$ z>wV>Jq%zhiGkcdcDSNqow38`}rFH};-+FPCd^H7L=;W8$=3EXdU#CZTpUrrkk?t46 zS+)}PEnLz}oqT{t@)Pu9t_YKBiWuhyg|4c%_A!d%_w(z^jpz_T-pIwh@3=^*a;=rw z16htBq_<5;&wbqBK30IGNFk}mQ`?Noi(vQ>ZA>pb*asN+%}0gRIBPjt5?1i4;$StZ zh&9r3=S!o{(>8o-Ko1h1i8D8S{HZ>Osh+eaXK&{30!F-hu2dSn2icY-h=s-9?RPar z_HxNco_ufBi9D{)5n~fDpvvIneKdiLjJh}f5R$}6iZq{PxY72HQsB-(xoay-yJ0fd zTGNGYl&Vv~xdYhIBJ!!;!+$U_Bj-{><$FeA$r-10vHiX=ZnP`82P@fq*f%xShCx0> ztYf+CQWN+^`AhZ|(eupnF{5^9oV@9ExX?17kn1*Scd|ZR>2>jDM+ahPs?zyZT!Q@v?4VQXHmCeMzCesaJmJ!{4{x~- zUFB1$*=4WMdx}0v8u{!SdZ4ea=#DJK(z9bA8`>i|7x=Z0cxx1vLWj^Com)!xyA2kR zzH%{~^)~!+b2winHC>Z*B0{-rBNl!LZkb<9G@<+gKN}VoBYoeAutjG}2j$tNqV!Lt zLtM_~tu{@?gO!*W9nWK&c?7r&?5nHinaQX@r{rDFQ2m4@vnX@>A4y_G)nBBsqj zIrY>KJhJ3#$2;LJKE8DZJ~>y87+qv?bo^e(ZQbU*#s=;*Ibv{N#L=7= z0sJAfdoAV7eU7{whKX}yIg*mM%5Soz&wn!X`TJyp-;|_|e6+3~Ql=zfidSWcCna$7 zjxSnFN<3|D)k^+qdENEN@Y_t6_n7D1+VhA553myV6Co7L-4Tq^I5hXp!;MkFz_9Xp zl-@9iS*56Yc=N12C){Do8cDNx%0Fkmeqo2{c6(1r#3xMqq^moY1&iBouiJDANplhT zpxQBN=ZQFV6F1cPe= z!l~GLtrit@&K%WoN9vR7NxgMy)=)Zd4d${c$7b$t zC5XFf+$(e?W2!l6Y1dxrV(0f$4yJa~_cW6J2$i>Dyz8(t5ez1U&!=G$U7K$%_7%@mQZOojVpW#A@#ppXCrqu1wzQuLhL;XCq8&?UeSz8oqV?= z?n=7Lm~>q#<11HsI8FPpV!y17No03FkVp-0f_=$#N?HcFWX{592ix`S!B(T3W^jH-Z`|SEX4L-DXE*gEw!U2OVE|J-OXu@AFwKQDL=wE0Sb_9b zD5=V;VFnr`&d#u>7PlrB=+Y|X=DGD&ZmLoTQ!95WR|4ZVvTxO&d$#X1mNfFE>%DpJ zzOqcB+X$GJ@(pGGQj(=6I~co3LIULw=@n&F%4dvjQQ5y;1vV`18$4T;$@aO!E;Wy9 zlX_>mddMmrTZSn*M=%9)Q1M82_6qthkfsl&6Do42C%RrKq!|TL+CXr1yEU(?niW2t z8M%d<66hTA*V{j12b(HKKZcOs59UML#rS>eUTFB(fQTTcE<-1hk8-xJ%x#F82PotFx)`n5fqdtx5bzysy-2wxPjRWOR1t*6QmiFW3y^8{&4g#^^ zk6P2mUyVFJTe+adOi1>vM&cDwk}n3}Z>AGM ztH)j$j)FX+liro|EL!B|-#&#Mxspn6D~fSwaf-1lRNDF}VdQXyF6Ah?j$37EUFoU%CL8;GEan`~_qnS8C zC@`uPk1LFo?GJ1;E@iZ}rSm5Xf+&)L(+r(T$pQG_C_JxLHdR&MeXC%8Ha2GD-8-7w zVd{?3Pj}Xku1wFW=QN^dMpx0uw9_z66gGvCRcz;{#n$yy)gmS@ysB5*o=U!AaqYE{ z6nO{np=_i$qt}v#7_)NGsM+A8{4wBwf9YDy!QY?M2pzGydoP!*vob!=+zC?m{)PFi z(TA>g>_g;}h7zTtl@j{IH}me^c5kJg%`O2#9qXslzpxl55jH;ChCvKk?78E0`q zI=K*U6j)6R7n6qhsf#?eFsivj44(nhZp-7Wb8$lR{cg;p*cN}QL84TWd3p{raj2^j ziJ7F^iF{}aEuO76N!* z5ORBGWI@~ZRC5vDAik)Okf$vRM`GPJ@y{j&wcnh7%-ebA^hC&#NO5Cu(emm-pbd}c zvix>j=?_&_N;=VA6yr7oCU#~O#APGQ z*z2`*-7A7NJXQx0`$rO8%!V2Chioqr8NAQQqoru0b&^k+)k7{^=d>vc5tr|s{ak%lhYlak2U0}~sGP1mkLtF5A;`7|S+ zb`fUgs;yR6WfMo7VPnBT~p=r4n(*q|jKO%>nR{flKx&3BvEGu8UQIO3k~AB*v4t9jU3VbEY`mD_=Q(KP!zgGr zwUB$NXQ>!;n-**4a>F5VKRH<<-9tgM%Q@zxepxUqcWss+s^`LxwXXE0G#4rcGLpHy zDyT5ymTHslf$KW(`0dT-S4AFP=^Y5HEMJg!(uYl6M5e+5e#NyHA>3IVUlw`qELPPd;& zt}09gZ5mAds3GSq1LwoI%Aop}Ut9Ll&Fq_`CAH!4?Fv9a>*nTFui7qi`l{z^X4jsp zQU}remzdCu8pmpdj2cusuSGD^ZSo3>$>;Ox+UuQ%WX#w5^3RwvH`3k7k=%;ju&SZt z8l$d@*^Tz==sLvg#{G>gX+~!R^U8uuBgQWpIgRcyW}W`}fJn+US3Y1Z2|FOpysD0G zuW6KimqQDCP(ceVSljDXTL_K%l%)>k!g5I04P!Pkl3Bnyn0U2Gx)Dd+MI7t452wx4 z%6mWK;i8a^#f$wld`)>tpRbiD;3(Vg8UF?d4RE`zmS(_$H<~ijNWMzQhx?q>K%!oFLXM1wBM0{v3j?@G#~C+Fo_ zQsKr1es2Q4f{~NJzB1^3kDUpbD0sQ9VCaVSysq?32ZAs|X*3gg6R?V}<7fe;Jq+Dk z0h6&z=g3jwr=FaaPk}oTn`KQ0wvq7AihO0-dx73xS*$k-qkj8Q{j7K4XU^23O{ifl zev>9n(TH4?X>H7%9>iOA%Fe7~s7Vq;29VR3mT4r{{;Ien6xWIo^N z{93^yaU=zq5}2Mc?TO-e+Ad_!Pdg7{-Qtl5qGIXlI_3gL`k%VGX7+;d3VcTm1b?}0JfUgSi>S__?=ixXpj5Gy`f)0pB9 z&c0*!QrP>J-Ie@i@i=_>>y5t;9Z}(9wJuR-+#SyO9l7;f8B(+EMmr)X(Z+un|B^;bPM!{%I4{#a4x>|s|6tT9rzgUn05rA<$onI2U${kY#G2HBE=Y~{~8 z3~p%&FVZOEO2cG6X_)Tv_eeKSrRTR|IEjdJz_h4{EZUlPM=w{GaiBHv1OrCuaois$ zBMT_g?=IQP1F5;oci?nqGBI=Blr`dSC9$C9LdOKIeuhSW8(ueEgyaiXHApz zZwOBeB7%aUDqG|Rzs?_kgkbg(UATAY(e!DCOo@nW1%AJxFH3OXPS@vZ@2qy48fyNg?pe z`vXzWM(nv>h)g*dQ_8FIR4n?&rr1C-!F)SC{3ItU-s?4!WMnacX0s!wD`z(}_9oT5 zroV{NPnZD##qKI}2k+jwtn*T%%eI`8_vzisHk=)jg`unDMf>4S@`+Ed@%XhpHiq)Q z-;j5A+$Cz)jiu3@?48MIRjC82?k(Y0`ZcXmVD57B+sPh7bQzg_qiB6?A_oRMBAJ1| zQw|nF!%wT8BRNy5;52;N5u1@){W)Q3tyyVaIrhZvZPRhyN5muTAJxOm)pt2^;}xCX z-E40E+8WhLZsADZaJaoi%Da|q2F+4s7o26hTSXexadguC*;A(X@vyY2p?23L8ot;nX#cYEepblJ+R1l) z1vKQckCBDN(b-0k(q7pugL^i8rSRZL+0LSX0W&!msc7ulFT@eCBLa#wxSzSsz<5$| zLK&%#&6Z#A4ntVi+y^%Lcd3>6inLRUa|4I-lpG^6+1E9qyulN zC6(kZc_!|^RdRZNG6uL6v@dcb(mks&+sQV?Nci<_$GY7;K45Ar4+X?@^{I9Q8}mOO zX!5iA78YF9V3*!IYP$T*<&WVxj&54jNs80)-ago=cYlzs# z`B;4zQK$kdHuQxDXLIyfmQaUYgUD8+Swn*teiSMGVz)6(8kzPjhAi1?qUDz>l$rp_ z)6hJO5u9;|g-+@_S#9=$+O0PhW%66IKZBmXY4m2nXO`R!nY@ibk6zwPYa;o_*QDAc z=DF2hfoAzkY>ZAXt_?5gSBszeQ*$g{&^|e?D|C;V1}Pj_h@Iq?JCu98=+R@jf%3oV za1DSdq3vsp8LG7SV0l?hj?ukFooQj(v~`b3y_B0mOrSM4CV&{Xe8{63Q%Wc-!?CaMm8YtN3!MQy zY)wxEX(eY)rq7XEICIWU_qLE}KiW=mp)vkdbiCd{HpVL*JL(YP9+urD@TS=pd zBrk?6CUh#JkDWCf&4jSqLgAgJE%Dn|Ke8l$&ZfS_QvChxsDw_s@u5jExpvWK68k*}XGQ`Qwy$bYzlsWWs<_laeTJsx^o^*3J%1ykRtj4QuvTve>Tmh zS%cy($idN9-H$S?b7|IVg%`rC%{hVOnOyBkV|!kEmO_g6L^jOB)-4c8eIMuTRPQ%h zr7DQ&BUZ65(U&2tMWU;pMXMr$akE+KY@t9w_?LnnWO!s&bnCbnG}Ul#A=1dlR~3kC z>&SM;jT?;Sqy#Tdg6g5OfVx-_cgAQBF`-SudHnS95=ntb{1asC3BZT9jsr&jf3POQ z2NLbu#N2;OI>O*~IW4{ka%k+2rT2rc*EgGg36Gn5`pQ`eBLY|z-8rwP^Z12P^&oNbupm$p z#E+X6?TyzzF2Y~{$VC6%KxSP2f6l4fU5krC!^3toAc^7v zRMz)pPF$huaep_2IP)@>_4C@GGqL31{Hf**?ccBe=Ul|*Ge05G7tjBD>PPeZ+YB&T+{FnI_7)f;blxaMLuy*D8w$f=N3~(N_H22PWV>L;PIqC*R-Z1{iz0kRV0s zpy_$h+2g`)>X_1u=o4kZGN{Y)$j{xpX$krV35kCtRBD$peT$T41zBnWVe-eZ_tw%w zT{?{7c*-;Xl=k}h?@P=nIgj)BE8oK*#kW~WFD=9kuil-ZCH>SM4W>)*AV{+Nr&r<4 z3qRfgaMmY&v|;amy=BP)39-A9WGaZ8U-T+1-lx<2eIHqq@Mf_bN-_mo!=KvZSB2zsQv#@s7>DsRXC}c=lmbfeDJ+dylek! zalhBF?*Z(;G?(=E_aHo;{+GJ{@%=UFm*TqBQV~u`(?C{yS z*f4NbXMR=$dT98oSnP>#|6kszmR6;{6se)m+E8`^`i38V%8bkLb7BsmL=0aUbRFjg z7ZoNPm>hK$eR3ES-TW)T*Vz}(;MO?!)?DUH6GX8|wP1GC>X8~z3tt#zNl5-6w+6-Q z|KRH!F48&@*ErzdwMQl1A&f$+q z1-8Od5lj>L2qDFGE60^iivHYa?rRb?87tsl!0XpWwI7H|B}>-nCksyFP3$Mlz*!Dz z=2Qg6S2m}HAdu(A(@S1#pDHWlWTKflr5%{&rnsnLs(>NyIWk5>^XE83=>-AXii4mw zk3xnbxykG<92T}99q*K2=|y-sSrbf;jen&&rzncIUHIS+NgV~X7t@MCnCz}xj6RMa z9M2$tP5@^;L8pWNbLJp(8m0 zB$AV5OHqPcyIu=Eh2Dvgh24}u0>IXJSS@&1fDX3xg(wI_M^_|=O#KcB`s5<^RPv^q zW6N-tQ}YPxisio`*;AZwNXf(Kl<2uZWQ`+SVx6EPDA{ZfmlJAKPfu-^vjhpG zOLOjEd>vX*5kv>GENM!0Yv*(mIcou;ZZFr5**gI`G%POKY=%w6A?dmL@L9Znu?2v5 zG2-KDkc=>a z62GbMlACbSj2tPSFT*0pBD^s+GkV;Kf+S;D?YfX{k2&${iplKX$wWyjrA9snm%9q? z%Vlz)Af}oWy)0v&p#fxlOK9*&2{M%c79Mv1Fn@8-;?1s#6iibH6c3r502Hqvb#nf@#jSlh=xMA#n~yo%41`zLgKHAMJ>)}q!Bc$i36(?q^LTrDXt#RP>+Lk5FpO^Yq!6?hC56opANwD# zRqT^CW_GeZs=@^>v=dB(A%QQWldYIK7n%mfAQf8y(mNuRXIMeLasgh4>5pIF zgR$@(UrQAu%mtvQ1wccFX8`C@vJ^b6y8kZf%cVsA94vY&*seBu_nutA&9$M~|rJyU|bHrejAB(uOy+AI-J6_%lcDwFB zvutXk{J9)FR&~Qm%5{$fwhNx~;?W{TEe-Ho)esn)_el^;m^Q1IK-!*i9w>Th=Cs!G zp1$!09|*XkZij-X4=sAL+vWjPCy+3$C(N%yO5Und64Y*;KnZ}y7UiTQRgBeEgV>C9y;d#{{|@*4Z>}q6XwAWNf?Es1I;&j1g-h&zQd4Up~I=)sp=z4I`#Rh zSl%g@ssUlRmnWTjSN6AAf2}R^TUE@Tdjno$vKFQ4KTbi6A1(Sx|2X%5PyM+W|EKQ6 zVtHmsb9EV+s#I4jL@RXIw*#2Z-%R$pgZM0B?*FzNIM-=XyGtZv2FZc-Vt)cfjvinB zKVw0hZ2I9Rfx)~=$-R7zHe<)1+XT5L`QMlOB#wNUSzmEki8e~Da^mG>;giXkC*FSe zlUt1}*8d#Rv+A|Gl}NLn-tp*?haLBO27_`7oco9LYY_K!b+@|z zH%id>!=*V$Hfw<+4yzhlf> zEJ%Dgo_Xnk@B!0%S;D?KdBmSv9%HfM|AiR--&5ar=l{dz^!F$}!!$XN6LCZZQf9^$ z&MAgCeLrYHW_$kp{rhWO8}#p%REy{8bQZ_>{^Onhi<}C$s<@}r7CA35ZuHXBS0W8L&pd(ooY-gl^qE2 zyfB(=5`5vGy!GJwCKRIuYMTULR&R->;ysbR-(}J6S>U8rax%<=gU+q(l`cFkB)4 znCjtW`F}k}@EOC!B@tYog3QkSq8Tt9Dwbn~WCtj2GHU9=d_>17*K_Bt+-L_034<;# zaN(RGI&DX2Rx-;MOzWLyh3{%7DnqX2Y5q&gRW04z9R2m<^ZKo`z;XXiq5JcBxaiH% z<{F#{0Yw5`h%y=kWhG!TPNk$Z#l+kyeCkU0^kDBy`fXwB4e!jLy!~=8X&}A81y_j; zlhWdE%Iw%HAu(2I-?sbDb=SFX^y1W>v-1DMgdWB23;6@Ast@}=A-lzA`R8TqpTO9b zE5#nfL9~aPTR>$XwK704?LiHT4`vJn4An-=5ay_oAXZxJ3*!lY@3FvnO|mN8f^)OF z`^G>An?U$OLJ&**LJuFXE?ieYcSS_Gbd%|#ls6sz=Pbt3GsZoXAXQHPR&6u`%_$pD z2_?3;$1l3ED2!(Aj5d_9$cjJQ#5B^v-L-*c511I8fnF zX%2P3+R{Pe%DI;@*Ir1b6V)>un`w?%*ZZ`2pN}<*WV|bT zRCFmb2`>0hGHr1L{tPBf9tAtpy!VN8)X?TAoaes#arpR!0We;AWgLJYZ$DM zVvG}edOon@6LRQ5aEH~^xjQ{D?=QVb#;+|Apm-*@hI7|FEaw}s^!|&#AeNdmy;V07 z0vzk2&z!VY8co`{tqtB{5&Gq5a7^`42-x}OpO4z;MNPeV%-jkdYwu+7oGsf8I!)0` zhx_L;?%omRsPY6;1G}%+o_631?g;ODdC4nI7E`?_M=rzT*ElzIz4roBeNkzlp^|O6 z>WT@!yw^s*ecj5_H@kP(y~BEPIpmtrz(0f}=u>v&RxNb9(e>=Ji((UTx0LmJ8n$ig!qIi$>PqB(`45^2wh?36shGl~}L@&1+@$9P>J{VzZ z{oc(amv1*Nq1>E0E!T04%Pnh(vS0vG-JPFFP!)2sL*t54Jt&(3^?OMq?gJ~^`Z>!g zDVSTgoystW9(48v#36nr2cdJ=E8q9#(&*v3n;9KEFm8IAu~7j%uW;AP+|2n9e$g+J zNwda*#)&H~N4e*PcKi`VAD2KYQ$}kjfs_4Nna^<#(_%J^KAHp4tFJFF)2K-ohOV^$ zlOn}LJzV6*`vEwYa{ch4%Y~sf$x&BkxBi|5^H&5$O{nr4DD!Ya%C}aGr{r-Jk4AqQH9Ox3HN;{&;e8YG&?$DTpt=Ej@ zzRZ{~DM2-nbq%5XjvjNPCBJbTy29u<3dkm(ion(o1hplq!zC_BY`8J?mkyVdMAL}D z{p2llb5~Ix&zJLO&VNFtdhhnVHRa z(Lp~c+;c3|rVE33Mh@V#R5-ygD13$+u@lssu%OGPJp8v*;UtVws3CEwPHoKa(`dM(Uu;0!`u%4jzd zMM>29vJjMt=_iTS0?>c&zm+`ga%Z;9A%VdPx3~m?(s(3dPBka)hg&&?w>&%$)t%Pd zw&5^11amIyG}h9SzW255=%ATD;))G|Z3U-e&npp4UJ*~9_am*l$ayW^h1$L23S1C; zaIV5~7(05n!n;UCih#i+GmV>APgyTCCMop`L8h3ViqzTT7G=x0O|Sf|ncHe8u7jr> z-_L>pn&vrEpvTxuL_cpSL z=p&i&^Y*6@psnhe4_XxogU2`BoVnddNZfW@Q_{gJ zTXj?BX2RPW2Bwqp_qKEV<1}=yD#W9=*72mdmS28Io7$MT_QBF&Ty70+cTjgcVz4@U zPz6$=7x1@*1PC?8)v%tuv7m-BG6Uuq&gV=R@C&7OgWL_k#9Y9+*>QaLzybI zEZ-}A>`U%B)Qv(QIANnzA$98F`#lGjEqlCZtQPy}N$9=tg(m zgnINWT3K4byY`TRdK5zHR`kcHSpP40w(fXuL8Q@tqeb%*&zpR5=NjXdFZXC{v0IaK z>kAaaq2h5C;ZyznmwW@CB#$@QJ*v^@$WuqW0{U7%*m;w;!AJPiU`2uYyd%(;u?q+b z)M>$kUy;f;Up18%9)q=kQq)gvgRfYBSmhrq&Og=l9(0qgoGwZ@krAfr1NUe=zALTa zLM!52oT%v`!f@ng0ey4g`ob|36jI{xZx7$p33}LM#|{mW(SK>$w2ny@Z|fL+cmZ@X zR%2+XI$WzVxtxW9IxuyhE=F$Fox|7Mn^JqXjw7DcW~**cV)fo8cl%BxZoKrJn)p83 zCTr9)UTi>}dT+%^D|zmW-?{7FAx7#r9west`8?Odohs1^D7<#bVV41ts^{*dGInX!0D}|beJ5?cDn4pqOuh2(P6BV93WBE0c(H6$z z_Q04XBVSZ2u@U2G{7gT_O%=8D9V6JE>R=ER}6Y2k_y}AU0b$}aFPUHa}qR)A0{P+I9R0JSdXe1TyewvXRPdJ ziequ|oCQxYMRB-hJAw)f%Hf(5zY*DS&9c*nFY9RCGP!gE45J+f`lp>gO{H=sNwj z8?Nx#p~O_3*YerX^7|MnGywi1Y=m?BZtK<&=xOjE1A4i)aM~%D}O~SSa)H&i|4azy!Y}WLt}cv&h?XY80If0ut}%jOHq`=qJ0MzdY6d0 zG1WUJ{a+@fJ&T}|g_)w{dn+&YS2)Kr6h>+w0-lW4L$n*VVMoPZM2_SnTNBE;7gL%3 zdM$6482p9}dm2^ZC@QoHIY`8jTqbw@AX17U)c?)Eo`)yT&O3_}rhykzB{ld(3KFIt zE_>k%Z)md!PgNx>*SEFaW2!TdVy85BVQKCV^b$q$8P;qY+&+d7l}$P+DS8x{X^D8P zn{evDT;WzZn7S6PgHwI)zUySNxH$O1?>)v%2ppBYa}DP_L$UYwDbf@Jg>&zu z2+Njo60GNrs?c<^rG(M%FjSDE32AF-UKI&~N*c zs2edU>v)1{zxeGj7cJB#YXSkmQH^5bBh4L8V%>PPfCMH!p;X3Xv?p{sF z6Gl0_-!R7PzjNmfe<+PFS4j%}!}Wbt<%t3bCbc%N@OE31`%(+pV1@s5aA?5$8|iXi zr;aqUr@KOjl6Ibe=kCaTz?Pe*zr;Fs<>g~6Fq5@iZ+49RBJikoS`fodD*i9xd@+4O zGOE%ddVaT-E@7cEDkF{|=8w-NSa$_PP&@a~)+V68FqYZs+b)dwFyn?OVCU$jFReLq+$JkTA)%27>W+ z-|WD3E7{q5-jdU~ouO`dI}7fa98NBKB3ozReZq-z-VNWK9cF+?`*z>m4i`;1p}_Do zS~THGlwHW;#_=|$>^W*$+PrD{{=hy$Po4`&9LwkSA%`ea3w5yy(xBZdeIfQS)hT~HAOp=i z6BLo#S6Q|w(MwY~n1HA-Dwa1$-T?$89cVVZm+a@~GN(KT`t_-zGmh5R6jL$2C-Bu3 zEaJ{}XDVx_hkQ6k^!zkXlE&6btds`(hDhhQRfl??PlY{~6#8KIW8nKS07ePC0pt97 zP@48%Pn&!dDa%g*WGIfRUbDT5*W+XK<+9c#7b!S0&O#~|9}E-fle^I{0lu^Hfc*2E zIc^6rXv(#gGvq^!8v2AL0q#l_k+c0-sJqTs#eB=L)cgpOh<>n%M$Fh zvYi=jj&v8YmuK9Eql0Ep3fdx(7rGKM%H*vngL4;IoeWAq6o;Cz-F~dErYSIjMQrE8 zQNu4s4{t$`B?&GWckW<=TAQmYNns%YS@2r^s5i9}&9;x_P%*rEAFfUCYNUuSQ9QIT z9szcx6INK1b6IlwFkQYS=fUW!Fu1hiA*rAVMSHV!Y4B=`$~7rmjF>mx)x~=%DlB&| zgab6q#2nCj)-Y(qdu|o_o2)HCV^zN-=-CWfgV~y~48xCDJIGZ9sM9EWQ&qL^XLBl; zdWoMGeDH7YD{@slKS8!ple+@P*}iRCIrGGt#z~j>he__2yKaF=sv0CkFWUB}+g*c1 zsKL`6X_3rKiya7^ULzqL7+z`&1uoQA5kLlcvaZCcQ7u2C1}NWc z)YV76k+A16`DdOij?pja9)Jjm=0AwE z<~lf4C}+8)la-=s62z)cx_#E8n^R~%#*Y|x<*dZ~tFC|KjxmnNA6H+YE< zkD9{`hG}F(KPwiGI7Wx(Yrq2T_DbJ=VpMO587ksFtIf52B_4U38WU{@*%vy$`;a*3 ziP?i~qurxo`4r$^3-07*)inYw>}n5&sNZcV>ZU9t@OO88(Yx{4TuUgQG4$RBu5h`B zJ7z%B-DS2$n@RqZ;(O?ny2x7#z^w@&rMM|R4`P**MBs1CqDCQBQc))u}VIUQ+S9?ub-xa&R`Yx+8C1;86r zVxj6a-dX!XE?!;_J0J^!BXW0u;-e~%%l7SXn#FzVfa1B*%;W)lJL%{%k_J~`mumZ5 zbgH;giPLGOO`dZL`#K!FcXaa}+rqZfeNP=})Xeji#U^dASMoe;5S1WqwD|(kf7Du)WuJ-F&%*akrIGTXW_;oVI87K+*ibSTH&-YnfNJ=D0rP zWplp`cZ`|ggq27EjVHnr!Mzn0-G0Za6Lw@-W%z9#_K+KHke`azhuQ9WrU!kKVh6cv zmt}vAylxWfe2c5VVRyhuvBPf^uv0Je{O5 zMJSJSPh@yF36W2C4!2MyDuN8-^w=Tk8XAsQY%eACA(-&$AUzGak1ibGV0JBX)|bs#qN9cuRbh2mJ0LEb+F(o za9I7M&{EiQX_9-ZstSU>DRI?{!guf9@daQGv+lf8+LdRniu53GEZel`8WmidScm)J zot8w;uzpVUZGzr_&5v)8y0W(bXV&@+7BZg`_?6hq(foxWn02fe5g&aO-Gs9>EpIzI zW98k;vj^3dNhN3~7bg6`fQNVJq}VM91zr3KFT$}V2NqQ6_jUDelvvEq0(NR+E=-d} z!==6BT7|ILd+WwFn_?^9rO9V<>vy^i@n6>SLr&v*1a+Jp*x_e5bzhhuH{;eqlUyQ) z{m2#YFx+gP{2kx4!FrIZ}@Y zbdx78a8GORu0mOEbhVl2Ds=EUpzwNbgXz2wz+i;oa>#$J95Vt?C7Xz)w%W%F3UuB3n zdy8XaJNt<*tzg(vA3UjxU|d$AC)1FU$yg$DO3%em>*pP4q3{4*>$annSb1!Vc%}(v zy^viZ0%q#7wB$^}x^a`PyEUDfGTc}cJ;D5r&{&+(ajIo&qDot1uLENlAv~0>3#_v7 z9<1DhJMSwq@&a+y7(}7R4R%c62rQX{FIyEMMR9`jxL$Uvw%$5R2VF zV=g*<3op&TR^eBu4wh*~5+frUzChn5l={Ct#$^_mMzpL&1OO)uOX5&^E|9xTcTE`#WMS zYklo2`ezR-&l)4fT9Wya^LJ!GKA0C>g)O)69n5g)FmJbDp|gA#KTl9=yVUZ#!33A@ znC&^};*!qYd55P{?+WoGF3D$aF`851Dc!NBSfANDemeCBWxk z=4HFC9=sFc5j|TGo++fk%5l75K&rGU@dA9ANH_=c2#l{CZ1Usu!K-isvof^|AP(xb zufc%(e(4m~YUGYWZ{KWlc3m#J1xuxXaM4qc7FFncb*!`G^pWwN@Hr$ zVwi2&n|+tx3EGziUvW9g0G4rvu08Jj>PnF`Qt;Ux?@J`~`C9?Y?@h#9Cx&bROpNwB zCNOdUd@?gILRb6+zE@$lkJkYL0pwaIsz9*%>Y1g=l9H8Qz+`!bwQOP3JWyd=wJFai z0;38y;ckDNophD9w(qL>&T0+d@|LRSI`(ZngPHsCfxq|I`=ITH-!qF>|X{Wgje>$Qc_)UO;kn(rGN@E*|Hv}AJbLBzcVa2{_GWzIFjqY?b#~*AM z=UrA6lDLOB{z2vfc>~H`iq3H~H<8>&4BVZc1}YvS{@qPQn+%L>pwabvqzd<}NL(qX z%~1$zsohcQo3o{J5>Rk`cZo>+W#IzyR*aup?TrwHdL1H8`)4c0HqJh<%H+BF<{6wdq&cxL z_tNhQ`VK%bQSC)f!r%*Y+-au0ePI6p4P7adTnu9e2haP7y>1h8#{lR2kIevUk?UI zntEp8T5`H+G*N_VS+jDS94!ppy%coOTpK+E+|Ona?$;O+J9c06!nNR3m?9#zxSqOW zizs!BYmh?iE@bwy6^_fHg!Yyx-`CRRWI^9uJTiK?zMk1525zG1+yY8pekN>YCVzb1 zmt@NC*TzZV?HBs_LwFaaBqWT@A=%C?Bx2Dufro|&8>WtzoV;9k_fEMZhQGAEm!q*< zlBdyRD|DHTh}V&}eH`7jM&ab)6K)I2EK!dip$|$&FKfmC@K}Sy0bpoaveyRw(Grrz zXWU^B1-z}=sBj#85`TPfI^oc^ZQGXF@PXC+J;S2#_9DX;s+>PX)JPWm@NSdREHGs& z0;r?<5ihEffr=ETD;ODOv<%z(IqnBuf{rvGY^1f*oGD`um=zA~!4W1>PdmR@{{D{4 z_c>J_XcMP8yW1=xS3QC}mo@fWQj@t)a<6lTf8%` z-Ew;dj<1(edexN?0sqGE$t#4weNG9qe(3E*^WVd#tcIHmX}b@j zN&biWyA)4J{BrA$ciXi>$@2*ezYduR{Hn^xyCDUHlJ9(*yz(^ukQ$7pV;w!JU+LQ` zJOY~wPFJ`(;k<>@f-vB6dK-6fL?6YGtZaU^nCeUI%+;@?A%n%jx1SQsGB^>3b}x_9 zkU4YvIa17BKBh8D+(4m1p})V&73JyDMZQsf{u2qBS5K)xI}YQsGGn*t`DuIAcoP@oSL919n`p79&tk{x&i;|Q}qsB6wU3e!HW zN3<}{XN4tYUINHMtfjYg*U&T-j~CBbB%X^Phm{8(y*<6`2DU zvl8u~;7+P+HaBNKx^eIe;-ob;uE+zsatvTUE;aCc)d8otUHSVPSWWS|EF0fj0^S!D z;|c&1hIifSB4u&`QJL$eK$q`II}o3RKNP32HvtTf@m$vO_9s-6uJw(Q#ihph1%3-i z#UM64ml??=g5V20;!{%zz=6tw8YI6?a(nUwc-}e%TyZ6K>YL3N#@PwDC3X&l`l$Y{ z^MX}=!=o_meB@x{WFnd2pln*0`Ct^lUC3_IIxQuMLm6->gWnJESe%Skj!nIafl>|q_eMeCsE8}Bf z^1Sc+^(b22@jo7s6i=!T4^m0WwV>n4oq%LKYT4v%e!#{wNtmJ#*@*VqqTYy@57j0V z9N?85Vp;Phmhl;ATW1*t8T~~TQKZ))Au1ppAg;K>;-`4&@v^lA+fB&inK5R10i*iO zh3H%C<-p3*N)7|LPN`pOc_A6v=*TP0=&n%@D_LCoSg?n?v*0#W>pJ$Z=vh)C!(>RX zyS2mS1o5SAf{5nl*f4irK&MuExrzOS)4jQoq#M&sfol-@$DQ)J@+ig9s_p){s1^|d z=-G!fd7T>HypBFHk%#r*+ZYdovH%cDBW9!c1CPaJH5P4yAlgR71Z4M4T=MdIuAc6= z_H*Y}u6M35)lh=Pe9h)E1w#5aw@tcvtWSQ_wq8(^mE z@g~>S!nWxJhZThyG&_i;`R7>A)TyXnL(T}~ymtzq8DS)q${l1@tyg6vt6*7iRcM(G zMdHr-@*&Y&nq{2$Q}Ni_n{9nna#(q`@Ndj^{PL=i#+nDDx%boh;Zs-R)6Q)1Pv7Mm zGIeaQq$}L9OWyr}S}4M-nm*sYlNZwMk&+V+w}YLq+`$vLS}bX7xh8iEFBCJR%OQmx z2M4=VE12#cXae9kGvl2TUzEyh*%hOltPfXbCt|~&JP_l@@Jzr=>Wrg)Kc=SW%HPWIDs7&?a}B=(nofD+k#OM#^#5^fQ)MNK;xNu zCcs}9r@Y>PjxzYbt%^Jy1Q%Jp=}7#tTLeP*^i>UA%O2CT?mS2Q21)Y`A2*lJz>sgR zk5!8jsEg$qG(7-m3}u1=8PZI7Fyiyud!UXo+~DK#!scTK8|Z=EOE!toPHH+$z20uG zN_Ctt9`|Z?jD^9;^I4brEySX$=g1D48q?Wij(OsP5V&p)Z{6OY9NXoDa4@K;aHxAp zypc^Mj>g`*P_w~19Ag!*?J(U6=X$_K%oijN2*h@W2G|$@29L&t$rB{g+cMVjW=FzNnXrJE6%pb3;Yw2 z#00AO>q5WxK3%q}&*}^0>TJ0T-s_nJns@9;F@Xocmo8lzxFQlLGXEiFAAxy8u)MWJ7d z1xTO^0|C!*5Q=FBv3}S0@w!&&$;Yy4c1p>g zqsyp!Ne3(Z7m?xE@osVm@aHNMxE_KKkDSRKB)e1!Xg~1$mQARxUh`t({3z@$hx{W9 z{!uc8^6~2X%5np#3*2doFD>;c`u8qoE<4dWOYCyy{C} z08DF(Yx}}enF+;va1!(M*AeTySUrNp|c&02=Ns%}jb+Dgr@vMNY!Yov&+;_Ae-s^{B{i*?yT; zW7oz0w6_qTJpEapjBd`(x0ZZF>xh-_>A0N1M>7}{TUF#FDEI0?Sxt66k`(vk8>0p( zfR^wCb@jRx=O^%J4!6Y^S1#siKnjW0afHrEyX=ua=>|e`*LN&KY#EAC5Zn3VlxZaD@fH7^vo@ zZFcYT=Y!*Q4G(ZK{m-Pry0k|p&uoUkifd2fyvD|&w=kX4F9(Dj7kSevJVYjfj?IVf z2rtq0?s;g(p>*9XkI3vY$zf9!6T7aQ&vh7;1*Dz@JX@%pu{K5z3UMgy3XcEJD%BWQ zqr}Oc&QZ}C+YeGQMJ#>6}iVYa{)xIb58KUvroq~>i0+%sKWM$EKgI8jv8G#&&VX*ouIp(~^X9s$wN zo|>Dy(Bp%dBBo$X?5;NlNO?p>n$O)2)%GiF#=i8r$RkT{*T_NuU@#6kg83zEp*sIj zxEd?!VHUVNabzraGW_rAy_L`*fjeugzdV;szD-lK{CZ3|Q>}6Oa7)XZEyjr^JsadS z^Xj{&$<5NUgU*f@9@j9>CXKSfHT(}<$K5R7KykrmUWnh~Jw~%Iqo4;*7Pt18P~fs{ zUyo(Agq#xnK{|XouRLrojTeCBmt+?^FChO6B$u zw&mvRnJJl6oN^~ztK-Cstrz`vpjf$jz!{gG6jjFkJh=Amjj!uR; zB}C&I3Hgr7?TLlaAQBctH1vbJ#80payiRxCPIU6oopktGU7o9&)}1im5%;+}z6K+1 znXKH@#kZ8Wx8NEjblHitF`B{0to{5~l~3!UZjAx_kG*dh>wam2_=WMHz%J^(+APY8zs3-4ve!SiPB^k7tjj<-;dG+7wgo(%=*1jfkJel32U6{+k5d`8!*6Sw3da zNk0+2-MeU9q56VBWYT7G1}z*yb;@poR92``OhvNq%=?Gy)DowA5;=cTDP;dGq5&lH z5wB!Lm~tcUC#Jz8*DvOdFzNMmbti@eAZLeT0|$weqr9uMQ@Ag>TMB{=*qOm+_SJm7 zI~-TE3s#JB&kK*+r-4sCY^mvIOxkz{4wP)^rHch$ejn0<1 z#$khxHpd1j+T#h4I>&Sk^W{Y+ixa&rxh9;A*IjkZ@fGnld($P$$@Jg7nC%udBZA&n zvD|!SiP@LISJaQhr!}XYb;nJ16@yfS^OZ$WHi%ZXLx5=^0PR2q5!&-r;R28IebGIpQAhp^*5?>%Y{Im%V!tZx3zMv$1-!2 zXLH_H&|KeD-BUXJA(%D0T00)csi)!SQQ`e>+yP<^=i8vp7vQ?x>~o4|dm_@s%ORdb zkg#}W@+^uaxXsTAFjkU_kdV5;*4@>|OPxF-c2Km~J)QHfV1F&}ns33O3TSiuPprcS zD-Awcv->wTjhRP)UX%IeohPTr7B{)HrhdXJl=R@#8eHT`fnA6BG2L7G=FmgdAotQK3-k1_dl=pSqMK`I z*5%M!U9wY`cCypc&&Cj4<3LKqJyb!TIb65Z|~H^(`YlO5$e?Rw8nSxT==+K)kI8g5~M^kcZQgN?<0 zq7)QAOy4m0V$*qTep?gIdGhXvk)Qqhi-UHiwoY{;rvflMx5Wjqzz_XKb(jI>RfE6l^Xi=)C#^-xergV9*Yl%72EQ?c8K)(-~fz zYu6wN$ty=@_jMD0f?P8O`*f)>n!dtwhbVb5|1%@WE!lHF_2!^ld?GyJ<# z&^5sTkacf2jJ)!8x%P)R|q=5=O86`@6Zbr63c$I#+u zL-oCmr=4ANx56UkzLbJ%ycIPQeSAmTyy^VT@&df$=WvZ+iZs<9t(mBYEYPuyzLDOT z5vLdVDy@P|H2bjkq9ygpdoa);_W zKr1$`xz<6MV|b_KfD)N?@GlBZ`Lc%E+CJUP|aKuf&++9>(nTG6Cg z+vY?IaG$*sogg`XZebotPhf5M2#4Fh(KB@XI#e)P{8+N4)WZn$W;q9ix_LhH_=H#G zZMs>fIb?F69)##&CI|LIKy&C}w2g(MjnHA>d$bbxk@P%|H?paLvQ3Tr0p!eZ{kWob z2t)+p^jy`##4W4$hU|C8zH&+A9D9bOZC2&IRnF(WIw^et7%9`-TO*N#Q)!`pL`ZD{ z^?KP!XXBbd!Ggtn|EWvKp+33A@+wj@>AZuXsl97u!1=zXz=wHQ`?3a%qG4I97q~QV z5jKbEmmd2Ef^6w_Kc!m2C=6|{3>6`wA2@Vu>8D*)$^g-QT|X% zZHt#CAeB+y;J~P5K<57I@C7S`^|j&syB+W-sNr&d2B=txs|;SHnaTFIza>!^*fl&h z+GP4S;64i~b6lO3#k?0w=n#3yN3>`??)--r$_Qzy5IL7I!Flgi9% z%R)F=Tj*AK09sNg9iN_hlI zR3yGD93`ssn-$1F?AxaVN)$5d!l46(UuuCj#82KeM*!*9*KG6JP8CGw+2$X5Tw{>w zc1yN}d^@=;u;@Gm-Jy}mkB~@Pb?);pYk?JQ_v=q zs3AonYGns#&jiw+dWd?I<1c0uDc9VmmY1z_XmMi~UK>RoT^G7M1!{QgD5-qIUAi{+ z3F}$0Q}z4Sy{Bb5TPQx24{HQU-xAa6_M5+jlPO+xwJ8t`95i1!X9b-n%Xm0=<#r`G zn@d3ZP_*X%k|)=g_P?2V4U`^3LJ`XoO~cH89#Xf+dq6tN4s%m}pVQL0a|>C9g3s6- zVDu0*Gvk2LDrKejxk<#9y=Lz4nD;o zgFx7qqq~jiA-}0I2=&rk&+8-A=Z}b9stPty&OrY_f+uJy-DdRLYC}}!o8zcs#1>{@ zIGpmF)`*BfpIf(a^a(30`*a?#|F;wL;@meG&=f^O{U0HF%;N-cfhHU3BBL6l^yHhs ze0%IW0~#NJkaY~ECvp0^5i9?Y?5}0gbTuELCiC-_7&i$%yzYn641XRnq8|BSM9a(; z)Z*;Er<1Ybdc(&?Q1x*H+r#R~cZ%&D7GH+9WWJ>Nz37-Algu8ns287vw2;aX=N2rd zfmv@9#}9-)OR#J@mMa6QR&PL!>-GcM`836?wQ=xt{1yuR!fj!z#qMyFmIa6}&Atqr z;G7El%}s4fSke1jiN&tTwI7J8U_B9Z%;V2WTM-{|9`*QjKW#f8n-YI;M0cTOm}usx zI?(jRffjuMgXQ_s+K|!r^<@O~%CRVx;)wwqsT@D&DOmcisGqj|3ny>vk6&BRosWJ=VxxJ>0OB-#F7;~zhujiII9Vc z{CpbW=z23;NRuy*Ra%OScl%xcopNzh#rA55N(`!?go&=QtDb5j%@~Cueqi zP2p*VUF%inPb5F_n;>6Q^aQpmTQ$Wc6wH{;p_-2-o$UZX=yHVn;fjdCkg1jkd-|ol zl%rU`melY7!ZFv=4jqXgU$l6B?HXjj16E>>0iq>1bgGI)p!o_<&QB39^PP6u z!6=kwP|y_>*fF=Z`m98%|FhyhND@5M^IX;(*#_N zR1Ajv-a9{i>=viuRO0Hw$|=9rbAhnp>$eiXpx{94@hH^s-F`;|*4%w&?mPZ&GSthX5T3tnNeN*Vs>E!py({m&)E2G{o75Wu?6il3EecnUeB%NfRhd zeXevR-P@QS?jk6Pl|X-Zx*h{vp$x%y0B4Wt0-dEK&LlCTbZI77mYt$C-d{(I5VQoC z|9J!Xwjf7Ul}o&>!WxD_uK8aM&9Zy}&+Z=ZATOQyOFw|i>ITco%}h_Kaak+T{uyS0 z7buAf~52KAu&Ui>I3Ksf|#p6*!t;UR2lIFY`q|YPbFlEyB#wptrU!)7X`7c99 zarHS}s9RNQ?@h90GsS%ZZ(BK}uaI4|wTN@%pA)$~O0wg1PT5T5f3&9rRxn0EFTn;3 z(la<8=9VU*#rZ|B_>u<9`I1^t*$vIKp%b=7jRDDBP? z$jiN;Z^V)WZN&$EJF2FEK~0=IiIIAM$dJGa2%@=d4p+#k3-+&I9H?-4lmqgR6mpy? zluPeC7J~|%m(`}Y zrmgSpl4o7&^S$-t3(69nF(AX?zE{|8U{>qjmJmi{k;>W8Cb*#Q5q!lZfa(Ll0$Y<- zMzt2mJorY-Qe!-AgMpX@nzU%nEq+cCf67ZLihz^;UJtmc3_$$lEk6+rRL|KW4NOy~ zp*)JXXmTk8e>fuOh;9eYXmm?WXHaMGx9|A=Bh>_0ZD-ZwErQ6i3^qw~%0#R=1CZW_ z)+yZAM$8Xo7Pz1*m~(aiqM}qA;IpS$esRI^U7cr%fW`?$8qlYK3ZFJir105~7ds8L zUbgYp%#Pfc&HlyqpI=!Q4OXXOPzP5NwhA;M+Y034Hj|Im12GT)Y{UU>l{KN2Ip=Xu z`mO{7)BZlb?Mjj5TZZNsN9X_wToZZJYun*F~CN zOb(DWCkyZ?93B&Z4Bi8?;64kC`akTBXuP{lo(6|?5}GK~6K((6P3{JTCj?wJtsY#m zrzhjhCmTJ;xpiabzo|N|Md#RDwKE2HNb*05AJ9tX0%AXmL$8Aa2`DbxK2jfP;0f^z zO!nm~%wC*oCqxUVW{T?*WRWBbXqKvEff zzNg9m7jyN!f4StD-_swYlYs_0sb?{9(j7Ms0O~4e6dBaQw!Ss_`Y)NmHMp~s_JYt5 ze0~f+#kwmm69s!f>WV(0f`HwXB(356&anQivO795SvJTGSL?u`%7`I52mdQkB zf8hOM?d{5VVNHJq`{$0Ex@N2$E>1B8{-X(X5;Z0^#?|gx$aWEG)DY8ud>|L23RlPGxaV6?%H=i-#b@i-Ti4weyY zjs7FJi33)8IBd>@Z2RfW{b1B3?hIjwW-X)2E7l9QKY7HqB;l@JVpx$tET?JX%+%yxmJ-F4+oi&H zS-9JLc*Iz;&*=Myt=XRjoa4l-8MNTGFf}h_NW;SmYlC z3~}Cgn}9^mj1W<$w^Gherw=s0{mF!M{nEzYZpY(^u1B#IkN+RW-aH=4^?w7G98{uo zO3GH9Qb(33vW$rqofMTqS%!p4*0GN-H0B-x`4hS$7>-` zhv8I`FOjA;4^gXL4gVml^Fs132=s=)drSIby#~5AUD%>gvr9RI#7RQH2SsRJGAghh zGkb|fxp+L7$0K`6(hxir8;*X&*N>q+^7g-h-=Smxj&h{b0qD}3^xA?#^#%Pn-73Rn z_%nKht=upt#`_N8rp+1Cu^56NfN~vSFZ_b4E58wOkPBlk1a5uM`T}pR$!h}a1E|0_ zqU{4dz^hmbV>Pa=A5n@6Qqzn{089~AwZ z)jJbqJs#8l#2jpd06CCNda|$Wp|Iv_*ays6>Gn-L=?3JWFShDvFLD`nXl*``_j*E7 zk^&DJV?X3GDQ(ep9@Q^eZi-y>@QV+*IJP0rjsqL}35fIpduj`=$_Xz-f_whNvW`W0 zvq;H%O^ny+82Ysv$%5}#K{WFlcv61Fox8}%d_Mr6@goHSKE?mW7h2GV3P2Q^ayU*o ze)=K`0OsiG&1DfOEy9V^40mwrl}?wymtRy2E4+VMd1{MIei(`CnehG^{}M1}+Slnk zXGDLe0DCP+!KS_sdebgU4r<;-^n4C<;JgpSM%>RsxxsXQ%`+R?aqvFm9&T>agYHQt zs&?ywWJuTR#9awe@GZS~Hn6r(gEOp=6P*h&!&KmJ@xyw;lmDvHr@ZZ|SR=h{PG5PN zhjMo+Znd=)oxG$Oj?)YKb4YvLxSAWXqf>tuX|;21psPL^-5;dBSuyuX&ejb4ZlEno7SYVNOtLR!Z$f0kJCGG16wA3j$CLU?>R(24JlzY0;x zIaPRG;#-s$ohy52UNY#5ImY$xgkL+4wKVR^i{zkA!2K89P2BD}EY?OnDzEfyH4NC4 zptQH87BE!Tvl8E;Fw0-7S8NNw1o28Sb>YpbJFQ~}V$JWs=}Dg3xLYdX1vT_9YOu13 zxybAi_zlS@G2su;_#ay{TYmysW zR1eGI{%SlO8Bt2A1lw1G;1ScW8F$aA@+jRhoc*Y7fC>kICw0;nX8Kd4IqwCqr{8E9 z`mFQ=VD3Y9eFHKwX7JLEg4l2RHc8$GW`?8dmMxu#m5k&z$#3y#ubuL#p*6USYDhsWehhr3jx55+CUxKb z^{$}N5Qkp-epS=1d0b?W=E^Zi5ri{)zal+5I^P@L3weH6kobt7DkN^P^S^Sob)%6|@~T|FbtqRPY>+E2@)ce!xtq{8 zIUU1C`E!U2fBcsW0bualop$k+f!=_d;}iQE2ept=d?N_VGpM4q1Otz!&m#G&)n*6p zV0}yNo}%3KDVY@|MyQHVY-P`7#JH1%klt{7@B;XvSykH%LeQH`I+{%GR}xQ5@6pBL zPee}JV=IBIPW2Of1zqlZt-(}_xLw-29w(^0*e?XX1?UhG(N4CDK3T`4nr>wt7M4P8qH4Xk-v-ScH3SA1It&F92B$GUY99`LU}w1 z^9?K*NHP}TMNe9;92)6nHn!e!evZA^hLSk?vfT&3R?~ZXgDGUw%$rsG1hQ zQBHiCZzij2#UAb*5HH?xoeN?k)<8JNRQt)Vs7=0^)8W44SU53A)srwTO!JF1Dnro$I5V7*GJ-;WAVRoL)=#1D@<#lr(8h8e1}5 z=2hv8rT#_AXnqOpbKoB&c<~z<`Do`$!tIlmM`vF=+1dlje4_jnMZT#^V109eIPaRr zBh}%EbHiP0z&Vw<*{y#IqfGP$z>D}a`ghIjrPr7Y!$U0bvoQWloj7{->&Z!?o%B@* z&^o)ZP>}(_#-ibe2zY9=b-pstb&dM3MgqL6wx!7?fcih%yg^*#KG`tkZXJJc6rtVo z`?osaWt?2)%6KIGVuuIXY*%XqK9Im?i1*F;;8w&kXog_++m=2^j6S@QR;qH4rlzT-^7f zYVW~)YnUgBUrSi5Ylj}c%@YW867Z^nuO%V@tb?&B%-$%<3K`vtJm?G|jJ&Bd{+0K-CzAxas58gS`-NIrVvLR6u3{0`!MH>4%QRz|dyp zUE11KexWgNw7%7sOuD&gmDFD@a0d#GDcYu#d`o6>`@P{81p&Y|94G10*K8zD$V|Yy zkYX91)87QX%M#7C^J;o{Z#|fmT9GEdwUGTqR#U;{?qG*s#=CxbXKT>G9wTL1S*X^8%P`J_Es;6W1&B#`y!Mv#&gL9z++j&M$mROcA zbku`v{rmkK!(*3ylNc^#erRdIQ%P;iQN0*e9d%)**e_zp`Q=WMuQ&4pHHmH;2PbKY z!;Jc$fcIFcethd!`#I32&a|0BuS49N{J1+?Z6E4xziD-AF}UMg-9F0ih*&r3GsFwn zy$SdU+$Jmf?M+llwyQNi%mW*Hjsi8U2sa0J{fKTnK)_m&QR+0Wi)_*1Wej+Xg}0y< zTY#|lDVkNjjK4SZ`nQXg7tj}Y^%2-6Rqgmy<;Rx>PPUJD#uI~QriVXAQLp!>3Dg=w zBKSoWf_Klhs1bB}BizayIqt5Ds9@I3xBX#ZiT-bkHCW%}HdR2l<6Hc-#NuMhi+7^k z4J)F&SO4_0Wl+T8o&t?D=cSA13`!^DSW)$ouirk;8qF<~q^Go7Q(oQ0MG=-~oy`jtLyJDx($LSL`DTLTCy16P&o#t3J0sImx?$ZiQcZSxy6Ap*uviq- zqO(tAfuwrGM%nq20Ah~l4^*!#rb~gQN3;v6X%uLS7=-~Q(0{JBJb(h+eb^JDyj?^!@AU&6B*M41h>I@zeXJ-sJ$;e3}qG9`A@ zf?3DF`~UTziZ%ak1QX>cJOP&w8E(Pw7EW(;lLju*mAkm5ub6_UJv95G3e5RbPBYcl z&x3;2&K{~fDc}W8e)U_myZk{&&UQOAU3yx?!dO4o^dK9Pm_P#yAAewjM)AKjM&sl< z3j-8ylML1c3>h@jw)&4O9^mx!wDi{-CqRXQJWXMf@p<%3og2lNy=VF_jF;zK161`5 zOLyI1cf+vA^t+Boh^={Slr3{e8}a&|mE`nDolM1!wtgM9n4LiLyXl)Rc7zHYAgZd< z{pcXtxcx{gau+ECKkDUZ^x|x6j-aVlr;zO#g77}t+>cnf0KH!-S)6q|koHHe5Qj=t zOTG9#XuYuUImps9d2+TQzHaj{1^+Pt(HzqL^yU9lY>B79L1|{KnmktwVPCtl5QkYWry9!F*KR-MzmT6y4{friV zTPp9neE8-<3Jx^Cdv?5@MdQ?mjj~!Kv3(mwghS2;#XTIy2HBaB;_!^1m_s%vkAEA* z!0HsI0^P}V&o9FPf5Xsh6^j;lZx<=r9+sbkpc?fZ4F!;aN8I|cq%D$+@@m{PM9a%Ux8A8kFO*QOE)WI(RUQK+5H2=d-T<(8LW~U@B z|3eJxcT5x(AnydGg_dl9QS@s`J7O^0I)+J?n4+&#ktb8b0p9)&Ih?(?1*K%^ zyQCp$D>-v_j#o9atCjIdMKmV1Gg5gl=pB!dYco9!yHD+tSlA|&I-ZoKC^{;}Mk z$O6!XO&{&8VwLt8OP$Po0oaBPxwIQ@YrfKkH@1;)lM9(p{krq;Sh+&;VEmey5UrfuT4+h>#tNhNE+IcQ6=U?$Hq?64oOx%qP1fHIc?2{c z)G5CY{em9h0EnmIc(0}khJOGcI?A z%emp+fQk(i>5VNwyaq3u#(&!apyKZWPpD5_t1ueJf`XiOd{W%Fw!oKLi+ zMEL~edm3X`N;2S($l34lODd9uIP@ui)J9HTl;(w6|+{Rwjy`?-edNI7sm~H>N4Hkfdx@UI+)EQ1&oYmG$^3#`I4^+ z+N4GYvZ~wEj>S}qa+83GlcB45)qO_)GiI)j!}{|(zi}UKm~uq%UD#nxBJb9BweG-K zIc<@U=e(YB&)Himx`D1W+g1#2Shc(h%8wIQ8icadB}17x>*aGIZ$~11%Nn75Rjp-L zd62I+ADETEmW!EU;E_J!dsa$aEFp`}93m{5MK9QY)3m!DesQ2@mtaBjC{NZx#VssL zonxFoncGdxEUO?*9N{Ws;`kwPiB-Wf&qv-3``ah_c+01$c zM>O_}k|a22;t7~v=i{yzvj*~)_#WiTJkk_RBCyvoKh$Mm{XP}O&VA!JeK*Pch z6+nqp9a66^I@G>YT?9Yk2XL^!7*wwkbpz2QZX%3*r=^wn^U9{@C6+{pTe~GWl)C=i zau%aMuN+j1ohc^;qMFtxZ^jF#kBe$U^Cc!=N>HxU_tzISl;^E|%dgi1dfCx52uW!@ zTn&-t6v@5e;BWbyi|>N}ZW>5&OX&uUs_eIiLqVu|MN@lKe63{MR??)-Si(gaX>1xH zv`dCR^OOtp{mr=Al2RH4;GG7?V&|Bod(>LTS?p}4N~Rk}H4T&<U#X$D+yP6YdVszZ92j?8h^Cj_@kTkha3AWP?nq0klfiE?cH}eW+byHg+soFdw%=nQ@n%#%VGL!VO4J; zFB)D3Oc;_k;3U>vX}lma1$nLkwd19|pvBM;NH8C&yEo32<}y~9DAo-~OBZYAc!-<# zhsU$FqDi+>jCpw7+D!Zr0X*xpCM1Rwygj10?CnXRm!bM! z`DGvflBZsEe0w;~_R`o!=l#V=d#C@BrAsyJJfhToz2mR=hf$TG+ICVdfBn)(GcR)g zE_?Bp_X}%USJw0JgF{Ui8%~~W}Epo-H#>Qd>5ki!jAB4S~!bXebgBYzG(Y?GER|3KQRtd z5Whe`?*sxHL9QdzMWGK_HQ}P@hIUx_=%j0!!Nj)Y1MRYAOJPS*XE^1icjAK;E zxS#U%eO`m37CYwdPi_wpPvonE@J64*N`O5Qc+!z=Q;guJy08{zZ?2`&B>rPc^6)zr zOC-uDf0r3c-_k$HAN3iLROCGO?n2vcxTeTLcYwHs%n`upLBdTIpx_sqrH zg{wR-W4P71zs4Hp=vxjh$2y0+&{hA>F8K-pPG5oD})Ci@Q*v)g%teJks z-&^ZX5)=26?HC541EZ2U?l|d4dC$ zvTY|xPR%h8T6=A!pgm)7TpOebHIb1CZ9~-U4&NPFMR^*uXf0iq;Mzc9D&0(ZlmQ=L zDug6kA#K!L9%Xa>KD)51QFHBvuD6FN5<9!J4C*mEKPHP#4ZwSjl8F2!6{74ees44= zHMYOyc{JVF<1Fyxt7?4U$rnN;3Aw%?odiDF+3moA15Ezf1Ts_9>;Z23&?@c(hfN}w zmymW+25;R(xC%tx|Dq%AI*`fUhHhRWq;^}it8L58%k!sil*)jpPNa%Yj!@yzO{k@s zN4yQY=qf6Dhop8Ji~c?6;2r3|CapT96@sroH?_OHtraZ&K&ovOS17Eis>%?XT8q6Q zZgfAB$Eod6|GOjbiwF0&p!lf8b`c-bs++_;Aaxj!baf8!r{x)t*2t=Zm5@X>&H%w{ zy4unX|M7!yp8%VOSy_@a)k@?NiTLqPJ-mTRhxyWoJ$K6E z;RYs~AN^3y*VlK4R3*@4g#PT=vsxJeZgvs*qY5aMk#PzC_7#B@3Ahx{VHn#t@2HDqkfY3TNtp>=^kk6wCZB&Hc!=EGEy1# ziu0KX)bExc2EZ9S1da#%_wracKmEbXDUllziB%Ag$!eo`&-PZV!X{}c9k0;P+A&DP zW=_N+3g`9Xr{ZRm=aa%JG`^L~tpOtx6%&gW=(#n&l!_ypn47z>I=m6;A;^3=YWL4h z6*90lqxPX7WFDm;IeXqfjtxConM-TX&|3eJYaLAxJpq-kW%2LV190?e+J{GB6$(N! z>#{5#2WMr+o|f74=$8#1-e(>ofRx*}Av^?`D_Bxn)#rGV3w~Dnmy-{AxcRBf;c=`= zgs4$o@!a{K@Nh@1ZBmZs*SdiIt?&3tU%q^qV@GR}0;KYzxj$JAc2~TRF(`3*i_+kx z8ejq2;JbGdU>jtv-1j35HqXH*0wi1f6GY7Dbe29`yJh2?)0E17wUfY5yw9xJuu%%9 z!=IUd4GqF7P%;ZeS4*T2C00Mrs7I=QNI@5CSU%q84N#fLCmx7aU@#+qSD9gH!5N%< zz+=V341d@r2aM5fJ-|Nkm+$vJR0u3~_;f6J<(T>UJn4yxl@St4H#fv%d>gcxk_7GJm=U8%qISU8{U}N$^x9BHTz4G|_%rca6P86+1Tl$2D z*WXO3M1Xe7OC3dpT+W{`N#bjPyb*3nteS zq~7I*4CKutp>0@&r3P3!^50|9fM?18N@W9kRfVGs=|rZ^gM@wSOUO}_MVptBTRddsZYpHRrC)@d3b-H76Xw<+z@F7|CfFM z{!3?|t;Q)Qnh(9tN-~<6kYF8u^w9zbuU#;l4OJ;qx&1{BSe}hfi~dPsjA~63hsX~G zKKH-N+01K`huuS7&oo3v8v0rP!Go&&4q;Wy%C}GVV5-UwJs)dHUfW~-dSe&!yfY%; zVv>Z*yvwQZzl%OBXE<7Y_cZIKeTG2ndW5!<^byOTl6Hr@Sf3_O^Gm97Fg@)gq=;fL!o>u!# z@ppQPv#wiQT$&g?HlS(rH!_CudadWl;N4qYoonPU?D6fxTpw8m{2)>26o z7G~3$j`C+#uQPI(9u_n{bmQs|gDchc6*#cscT=uY|AdQ^5|1+Nan$DWw#0)UdXzL@ zT|8ATzeWm-+j4Ia9nuPG6OUnc29jBH6lu6nbMsQ($vis@^UAAO zNsJkcK6@MhCF5v6zgH7AuU{R(q^Y>)2dE(mZ;D-KN1x>F9n9|JSIshv`#GLG`3_g~hY9FdE*Xo&l}gwD~Z(s-z;DEn?IR__`d|-4|sV3Op3uY?BS#{j`Rdw$TE2ljD5j7VyKxla_0mf0e-YBE`ERVP8P&qt64pbL$? z~v=QO%$EwX+(vTHN}3fO1?gg9yIOROHPKW z=Wf}a*~1JqL&Vf8tPxO?N4`&#TfTO+!9>Bj_!ewo%fkFboAmY`%Md1QnD_0v3f>~$ zzU0)JmiM30i`O_E+X%5mSEgQnD%eEp`BEC-?r$+GRJetZ;m^7T_eXJ9u8e5K$?@Am z{tfKLZQCY9opF|JqGgkr-=h?0mPhyqN5KU~gx$|ElLd(8I)#WuNO_75Qw zM8(6q=2PRSr%B>qf&s!(??DCnj8?tITp;BO!|rz8Yaw0-uo3p>>T9wU%I{=RkoO*GYcF_qb3btJ~?uoT|?HXK>%|T@3 zoAM!Ffp9*7+PQ(1wY@c`M7)~3IQkZKI3izF_$kxprgdvDvI>>U6TUc|mPsuQzCU#R zarH-qg)us{q1bAOY6R|bt$2%9aerm!+YhNoihNTQ7=lkpq2!9YdB<7&y~9w8fyS=A zd%J8v=dxN_yMV;;)_R!*Jxlz3BMv?8sJ53@?2^5!$g=TFtoTaTv8OPLr0U#b<$^B^ zkzE(_top}`98c2J%DHTPF!60tb;|6k;HmJMN&@aB^-zOkBIR3CfJS8>p-!Oq(dcnV z7pq9^3SGVmygFHm>32#Tdkvp3B8;p?EPjSc23oO=Nu#m&_`Ns#chFvo_XbHAQ$uf` zURqdcO5IE9MjGRH5dI{^1>lP_Pde!Z)=~~T;9l=LG%*0#ciW#J2xeWF^dpB z%7A(gLXBK~w?|ea<;g34J@u>6|OA40{Q_J zi0uad9Wzi^y;DuOFJ42`6=fw~rPq| zZs0X|@B7B2(Lp-YBi52&2+QxRDK7e+iTv4^65Iy;W|7^5V4C?f7E`MNhlQ zbcBGz*YEw`ChmV;igW9*QL%C5cL3BP>l!pR_wN5S3*udgI1xGbq@~Owq}LG6wYMZ0 z$7xy(89vu29CtT+#Opu~f1u<^*RSxhc;-63R~R-jlQ<85nKkoAYqR1A2;yKX>>bxt zJ2=&4ythK#>U;HssTSs%_}#7#^lLz{$fqb>t&o$@bp4UD?dGU*>*X;Z2K&tq$!_`V zU+$1y7Oy~WUA=g#;GyTAH|A3%`f}x!kuP66xICTR|1pnsZ+5ts<}WQ_8SnL^E$qLj znPQ13Yf_8nqKtcnD*}pAs5RsIA9X$%(b@2)G_sMu_F{3&Ihri&bD7{(`QW{9!FWWO zLUqLEg{~$|Nt*sO+Tr|JYapRw1bK9{*k!vnBlLYV2)+FEB2Mgu#}ZsqDC4wiTdXBQn%5ltW~&zatdR((|oJK z_Qfz`DBl$qg*zpDZ^D=#OWAcm_I}op{63H*l}^!-J8(za2Z=uvI5ov=(s&CmY0hhK z$g_#G9RG5Q*d1?{0LF}^laY4QUXe$U;xjMvDM~(1F+aVQ#m2D z02}Mt(jNWa7+?PxHv=#ycVq8>zmLUGsjpwZzV2Z#;&o{HAAaj$`3;#`e7>Y{^FY^X zP1h7%P&4WCC54%1ia6NV;>}E~Xy+MVpk78V)!K=%z6*K@^kdFe4IciDUW_wl>%fBL zWS<)l^9EjN@}YO~4EJPDb_bE>Q>mF44WFutXr=h>f0pZe0BQEc^!B9ADYWRFDn*O5(d(=(B+d$fvHT>FiuQY|F`wjg$OlkH?E(J4@F(t2*+`28`l9MEs#wL($D zj&@!^PbD89$UxYZ9p(t~g&X%c8VUO?SQC5uLZ$@MN@Y?)U>z!FP%Gu4cay40XE7m_ z@plL!;URBwRYI&Llkrh&WBLGgJ`D<12LQdsqP~FLigE)g;U6w%3t-w@RbnKy=5y9J ziQhzFDPE2_S>5J7FYrduoL7Z0h5fmbMsE?2xDIc<^*kicG=h8+{Xs}eSqOh0E4wpm zw5e1z3vx%RB{Le{c>;E%4phIpV!S0P2Ylc-`X{RL5(-T(DyamgSsqWod&R?+L+O@RBHtGGl zHG2Jt*Q?q)Wl4N!qkiDiQy!1wJXGf({x$s0rLXGTe1G6sLY6yB+DZh**WLOAYX}yb4qh#Wwjd2X#m0ZdQuh1p%su2lg0+>wg65G+GUh6RCt2Q88kTNjv>5m#oQq5 zTjBqLiM>n0Kwn9z>tUdtZ*g%@r@)S~QC5f_L6$PQK(c z4Qx=3exd~*tEXcHZx-t&{*&SH%opHS@1%)3#o|_XP;m1XI~0|l{YfDl0IF9X@>}4f zXg6x-IaoJ74ELrg)C0k(=IBVT3`=cDcG|T;Vw0bH&s~2C31YMVs(lSasu4x+C>OJs zuT?u;7i~i7~Z4bXDh@n&pwt#xn2|B*2kh19*v?V06 zGy;yft`HF9mAWjkMzi|U{XU{2ws_jaMVG$BPfsP7gl=_0f?7h!pBM3dK-uq>ntEA~ z&D^3-pi^CtfhdNH$lQPta~2jgL(Z;!`RxV>%B{%Y-|U^1ot$uvA^6%MU4n9QIq;>~ zh$1!agP9|zd8Un;sNHeeA7L8LN-Qd3ey;KCfmeCY#4pehIcbt4%?yzdI_2o*R4g=0 z=2s#HE0@e%RXj|649<(!s;85 zx;b6V8iEhXii?-WujYWzp-WuY)6M-#MH+uH*sUobiTll^x}LZw;OTklceF7oH*!k| zHL$W3P~vAn>!4l=`(v@+pgQ?`=P`5L?z!t2~ce7fH+2NyhLgN!NmFEFVGt>tMt6^_aeSVGrm z6@^R9C)xQMO^y>%H4v!g%y2@~H$?1vvWC)9iymwBbDD!c^=QQv#W;Wq@FHf<;9yS- zL-%SG0Q-LB;cR_(X`>8+yu-96~Xn0e1 z-qw60Kycp7g;;Rx@a0n}yB6~koG=@&5bP~P9402PY6@TTINYL%a^bDyOJ_uZLrd~% zmyWFNV-67XZb!Nnpu@m0o)tuvovsA2FXc5VXORE^Q~PTf>CdA4B8xn0Q84z{ZY!bQ zr{J!8__sp%7NuAUBV&#C)lHF}xsmn~9^uHC-LQKi8wzIdrQ;qAVE>mm+QyACq~?Zv z>B*dEc?0POye#Mb_;$mSktc99S1H-u2b^PqBw>9w8V_9DyKi28D5Fc)@!a*LEMB;YCbL552A>{4mXC&c;lv5&}XOXA;EcBXD#H?%T?>_b1Qlbs-Rv(>X}pG z>AXn*t2?Lic>qa=WS>|J26)6_(_6-L!#K4Ri*Q&M^rZ%upZY3HlN?|of66>qbGvft z^Wev@WM5U5DtR-{)Mv%QJl$_MeboHRQG3PrB;s2_BgHw@@LZPZVo;WUx!yQ*tiMQ2 zZV$1jzs%}acS!+Hm|3k>99i+jr^sU8;{Ne3?IOaPmAssr<5v`#pT{22y*H3>2Y-dY zQ#)4qNhcXCZ`MIM@5oS8Y@@P;H(sEMelem*`Jt48`qvE?8>eXXa|k~T_7$4wHrS6 z_ka1)a^N6dpkCiFvQg|k3as979#Or+de&tut;n6Ru@$UFwNBdcr)=v9u*!oz$~);P z2G;3Ovbj3;xk2E>?2EuD_g|WzmrblER_Vx$5%w#O!Ip%feAbtPULPKx&t9R67IIOJ3C-g=F08RY&Sxo@OP)~glBKou=;(ASie%)dF z2?(mbZXNZOet#ZrA*dHn@&&iX3D!8)#aYu@7<(o}Ui@_9*2RHKKoX_&a9Xi#Lb@BV zIbZ)H{FCStyxu$z7z)?=J2e7!N=fl9@94Q@w_#2_kRz(kWw*dndniP@MQ}0h+~mQTEu>Z<-}#P-eqsFP2(dA0^- zM;(4|?4WH|lE#R+*0-k7J|drJ6J+wSKGK^NM-M%g5c%-N?1{%*Ay6P1`*x<_5drv= zgV`_4=|&YhI6DYO7&R_D%vw6bgrI49JX^8%Q8si8-#=^*rckd{l+gh)Y1Z7NZfK@} z?@9}LJ@-MfrANu3vf&T4Q)M~qCH{a(lT3AK%$-pso zS2<&apTzAvB5#cQn&yAh}Uh+PD-5g8bQWB zaVsi@JR1V&*G~%Wm8iDEez~Nww>1^7e>M`Ex2VDSyHM3}tfjBha-OGh^Np4Crv$Z@ zzt)GPl}*c=Z^WiqC5$AzjKaB|Z*|B~3;*2WMBH7Afu&<1TTE?G)q3?b1!}`BQ-J%E z``%3qA>|3UwwZd@tt63nQG>_N0Q&t4nRhVnER>s+fzMi>V3j@U*uUn2+_YQD*)qx_ zFGMQ8Ir{Mr4Sv=`Dg)RsKcFB=XYh8eUc`_UuvPk4ino*pj~pM~6CbfSBQl&ua>z8= z2g&-M|MzMO0;FSIBi5W#lY3c`XhU3jy4)~e9-#RZSwDqH99W7 zjsFho&9&bRsGN2(ywj@R^aU$+3Cp{4f5J=|Q_j*^4zhxfO2qBke>b^mq@5OS|Mgi! zVOyOq?#$x?N7m|gwBnB#q z(uue>u%C?#U(+(wPue5wvnX_pMD{TEt^@=vmeqeB@SmS?#mD}V8b5rR&*E6r1e$JK z7cfKOz(WZFRuDq<`_JDP7|6@rR@m^K=N^i3bF*Ge7P3;#?#Bk~vhwoYPvMqhmvH;U z;Tu_@)SoxfeFCiJzmuO~R(c9WA+XQwZIP+)?Haw*b(er>8Bm>S5CJ9wc4&J>?f=^_ z{XgGvx5hv9lJwHAfFD>*sKiqTZnynAzO%Elj1zqd>-HF{AZqx@B?%-9i%qTtM?S;? zx~G!#$VP!FSH);UmyVqLhB)$xb#)x~MUz6F?_$E-`yT=gYW}@Ec;~03FPTnfCLp#e zJmaXzKVv=bcyR1MP}EUiREq2soA8P&?wZfFfp(}rTp3+}t;j)EIIe4R*g#H*+lI&& zsQ{vGlV(b6lN-V{Q3wS}!Jkz1|ERiZ%Q__UUp7Ls04cj89vFCt9D)sDuhoe(R0)HI1wpG9@a`WW|ar9&Ghj`Q7 z&PrPVYlSxdyTvy9*35*}hlZn?IDWv7H*(hS+T={=kwO5SI>~V!_mS&zl_qgja5Ia% zUVsj+OVs2^LZGdSvHy9g$~|Ww7RJUSOR3b2%MT;<1u;Ndh#xLz4QWwj|1i)4BwAIv z7Z}m}j=1f6o)sWkQRna)Fmup4A7p?C|6Zm4rW<6gY^$?~ekT9~#2r!zPb?cXrtt8H zsmul&ATJ8k7Ezp^lhpFMzr|<8Uu78}L}WJF*w~ay0Zxln{}WHN5Ut~?q^cuTYpZda z_k9o|m}t@;QVM!XtqzK{z);=S7jQ=16Zr}MhlW%Kg3dc*%5ca@R*ET>-!wPiVJZlm zs=s*=i->j4LWJPii=YQ&pFfjkvZFRP=A6GH)EKpCipG~E-|UgIm0Sh#6K z3r6#Yq4eQkF$q%@J0B{1J(7)fb8zax8Y&d?aQXxKe zcLiV%Sp;!Dt>gpqC`2+DF#S_`<*@)f|0VCkVC&fJvhR{O&`Pj=lDu-~(YEsLH320D zx7}dZoSa7;Kr>V$Dv~(1EHP#ZwQ&GR1)~paXscX+{O{{|(14lpD&#pzfuCL|4uKt| zu+-R)Oa1vR&!s$cA6tc;J9n;ZLwHeO3R!I^l`PCp$5{beEIearPA1w?yXyf=dXg{()hq$9{#Ux2%iy|D1WX186Y>e5e0WiRe|*4#)E!~@t{xC05<7YI#j&;K zkgR(XvqMzPzq30*0aKxu!t~Q%;m0|g1O36Pd8u=g6ijEB>~;?48*XsneP`2ZR7ljW z{r#D^42FKsP73w9B;ik3YBhmc;N&(3>tDprJ<9Y+bT64rhB2*E*T+2*A%e^*A?gSghGduU82ZVS zk{4)~%fv-U=86(TA`9xNx3OY;qOv#nR;{HdXK4Q6i}B=T!E@XqpUN{yU* zJ0>fLc#bX|?N$J;fN{aC8Fu;nJ5!iDr_qbF;d4>Uup-y(^9)L?=ZT(uVqoQ(%FGl1 zU3C68`2agflssT30TLUv5F@{wX(b+@H>&M+E#E&aYBPz=^4Y_fJTt(5lvy$e6hl)+ z*DOV8%mtu$FD^{43dYFmNBll}apzTJne5*A;|F41N&tGLIAAr(9WDwbXoXG?zfefD zMu3gslzBv86I3kLXqqOA;cuwNg8QB9Vg{T<5z0pGT{ZMyv+(n_(COa2$VNC?3my*1j- zx$jm`-cb&fwH#|2WKSplpJ)BYI^!hET`a@^?U*(OJzn-u6rp4R^FxM)>G2!7ns71t zp6v3r4c`GzPH~iL@0BPN?m(km zSB_e1;VV;6O0rf|RE2y{AP_*dXz%T>^nkX-uGkd3{aT}+N&vtX6!94z1DgH`LLLQ@zQKjwANmfX{;D z_)ez}8%VbTvG1h2&c(elJBENwB0-#Uwp!wu(`nZ~x7@YA7sDRU7{eY2Qo~L9Y)y>| zmZ`?_B^d9+Mz6Z!q=V7?69>Kv+X#qJwIHnu*y>Oc(|R`b*LIqGzGl?Butd&D%f-X} zVXH%AkEn+YUIq_VUAIf?cZ|~d>jD{Pj4VigoxI(5v?|i-TDls=AUVsy!^G6D*-Vvj zS)5=yzFDDGGivGSd)9HzJFmES*EcxYd?<07I330<*!oPrO(5dnYZ6+h_S*eGTWsa&7R4wZfgS7G2<+ja$oB3WxyTb6ff=1J6 zYxES8^Lu`T>O6R$*fpvkexOu$>dV`fB#da*$K{!^3bAMQ!iwwc&W)Qww}?CL?eY|F z6RiRT{y3W)mRJ8H7?J8Q`YSFBrLaU{9SzjyWEzP(b?`E+BWL8Ba;-ogzT`=N-F_=2 z8(U+jFli@~zFxW)KIAcZX1~;|LwQ{E!vWjvwR&>8kD0Yw&e_~J;Ho}DpYw&hx@$T& zyyHD3c9}HwmF-%ki=Ue6tFe((HK~P>7Bsv%Sy`XZye176#0fEyiS!jbk6qfUPcKz# zD@f~-aJTp)!_X5eYPaIzz8ZCjZJ|vr>K~=HdpnDqgTel(jF5_LGaMkq$0F?--T-ww zFU6C$F~Xf^cjiuP^BvoSKijwT)Q`RwS%~ZOYh4?Vf}L}|@BiE;0U&0b8a&cnPe6&4 z7XOgzq}rC(y>poxV`k8Oc#Z_myt*R9yO@wbmGvnN`-Gns;SNxe8L<^5`q zDI1h=Bcpx=gBvclJgsmt$U{>Xh@6^p*;4CwdbZ#uGPfa<5g zD+D+y<;RY=z_b42#LJ`p`t4}}M26L(p2j&FS?LfLbOJ`rQvz_w&HVZ8mRK(aP^JUq zM7Tvgp-Fa_8neUPjDaXWOi6C3FLv+oS~RN8oM8qeC>&2+P#&$oU(>{ERSYle^gbdd zKkq1ERCv@Rwn!1C#4D7-&#V&A#Wp_ZL^|BcNy98d+jS; z)*uD%`yi7W-Gt9)sQrfIUSmWz zuJHZ)3}7W2l#49aIhqoyTvJK#&3D}YhpzXIYwG&{fWr__nYBfb5l1arkRg=~5f`ls zC>2mfqEu1YBWw~71r-JRRRLuLqzcLi%AP?{q67%a2oNAL0|XKvgph>fJ}3Tu-(R86 z^V~oBM_-0}&$;KG^Z9(<Ro# zarptApp3jrWcCw(&R>eNe(}LtK;ZF9%c{x(>0+~-WFKJ52hXK8qdZMWtjn3E0arKg z#mk&JH-fp*>t=#hB;%~ZjSi&Gbh{i`b~8~JIiwh}kXU|HZd8dhT7WzSLil=yk;*IF z`5SL_`96pe;-iF9%~2q}R$L@TGc!XX2%3)y+zmi>v+fd;C@Z9S!G;8z2!cCrq!n zTedk;#b~3Nv%uQ>qHxGQJvOr5fnbPe{@R%(h?I=G*iHDq#|Kb?ct6%2Q35<;KQ5gE zqzgAynB;Ov{9aS$V4r)6A*bgKK+mvfedV0vzs4NO(7W;m3ox@B<9yJE`|*=8@o8Qk z?y-4<76C*qA)KYYmz{Z#oHwy}VW7x*7HiZNeOsL#-D}%;U{0S|&jbOSlc|ckLBh=C z^ETDq(G-UMV?In3*~m)UtHe%vBh<7M`IE@fa{I6Ts~ zd(i0iz~UX^-f^e`6P0vxr;tkOu!T~CwkG* zx0X(Ep0Mhz88v6mr`6E?ZI=KYZFE@MuxvFSq^4lg%O@w-iR0(xT>zySBe5jzs$1;QY*=1exCT+PQ9aG_!is z>iVw*#RFb`XUr>*$j)8OOV)(lt8imU{Be~pP4uq{9UQqU)m#6tTUZ6baOKTzG0;sD!D_$(&_F2sDyChDz-vkB_q2z@uJd^t7( zfNb$sd{VuXRP#h;cJ*)N#s-yQ=|m|61MHEn+FZADoD?4@CrI_k-l(A;W|^pstr-;_ zD@_2jzm5urdVIivmd|vm(vtFJ#NcJv?cx|!$E0i-v;J`Eu^k37N)Z)~0btPwjs$V4 z)TFKsIm+LJG0rT15ZAxcLTFmx4QfsqNI{?y?h~|;%v`hM-rhL#Y(VH2B^#pJ7c@qN z!D+O9PXy+`9YeHqT1cFwY}uYQt@1SR;n?-+ja_cGT+wP^*Ht{V0{elOv$hX#Wb(GS z6PKDA$HfQQIWua1Lhd(pbJ{xo&pz$-V zrK_jib&~o4?r6~XW!1O+5_a5uq0wnThSPgOt08zSlzo2cJTqUOcb`arJq$2wj3+7p&XZYUA&&} z&%F9*Z%&YJ>I3W}giPS=uSuFK$&&(4kODvteD3Egq-_l|C(fVEAWvvgzi;J&tUYF=E zO5;T_?pX#`vXva6$lC73t?CYW!{z=iOuf=Y6bS!w5=`4WPC~YacG~Iy#`>JvN$hG@ z$$o3z$@0>=%D2jsca_&JX^O_lE*JTFZJq|$n>@HE->v%_jW^^2OyV;zPETW?uajjD z#EV=pt4ccE+w2K_;co7JQdgVHjuSM$RZc_9`HUeK;);XP0tNVj#d*wlMKM1a6jQ)fxKQOkcHXX#;HGtt)$h8gbG#QXhH+Bp?UT%~j;l^V zDHz7ickJXJ=0^jXxRzS@?HuL#ST4uS39)3HN=E`!L)6G?l`gom+fB}HrP6J}U_~~C zOZSUJ<@g@pj;?!pSlJiM4D!c_ICCC7S6+DQ;OcgPzGp+f6$XSHQK?=wJOYIMa$Uvv z1yMJ1`n&&>Cc}c;A86xJbJ5aI_pA+(mWPU2yKJiazd}3dx~yuP53$xhhhJvy6srl< zT3gDzD$t|2h&lFCob5}RfY*QVsZA_q-(|*6ZI2nK2i{s}?sI$$p;kMOZ6y_3N+}QF z4h2wO>N0k=RRS2uXPNC@Zab@6YKk69(B+i*_^@+SgGa7zbD0fwqXZjN9MGS#R4nSS z!||aPKnHi-Tv8Yrm?O7B&&3uJ(g*$W5$lzQrckv`9TV4Z*#(@kXBt<|QiM1mi}3XS zUxhdT!Mb1%&YU@e9)Dwj#|DgQIJ^ zjtCZC$%uc;4Eo+&#~#de1c4d`9D%0S_CkAK*T_gF9!bGi{< z$|{l-t-o2E7>&3vVTe>;(v`xP?^MeZS+QBu(}m(+J{VH6gJ-9YgCDPH58mRk7uT;z z@Ja1O4U={?2R9BI?K%>Fd4V+MXVGFr+E9 z_~=rj3t>aPEuRFSl>IF+oCnC94pThci!D!3(bhvqG0sb`euHgX^;N#6V$5W@?5~!b zogih8VAB3AKms555$g!=l5tyO4?t#GosjO>vK%R$4wA*dek7i%|~<@~e~< z5lnt+n?`upkm)QGD1nl=;%)HU$K)P*h*rXq0Gg|qv2D2NItT~xa2;P{57VBJ%|Meb zidPf|z~}Nf0wkgK6Il7M?A7g4V9%dUo(!%UaqXn;Zb!;&y00h$(=Cvn07sRfGt11U4ev3y2KbY|+1* zRR7z*g%PmkbYZ_+wf{+6&Z-OOr!101oMGWa`$`_Ip1Dm+#FYFw>_4)0CsbG(m2*7GrRGT31;kRXg3py&tY(IBRQCP zq~F^#_D=)xUCXVl+rJ@O1Wm{brN~Whi^JA`Z2}=b%EVuI|K}d8 zr~rBt=1+V2lMN6DFujctynv24Z|Othl@8FGINUVS-8G^n^tHZX1pae)$)Az3m36Z# z|EZgIB0x#k!yJcK{958Jav%FwtCYcyNi&liN5t#m3^dX53bEPfaC-n##94l*gt@@k z?-tJdO~d9h6bE1mdAqd)Q3S_W3c2QCG)+^5ISkgJeZ*$_;vtJy#N-6{He3m+HrASD zQxkE>*p-oBtfHi6UFbDWV7m=4L}Vw+XiF%Qyr+j&3L4mdRcd~BAEJp^y?O&iMd9jG zsXMVZ4@#hvS=a@~5W@`R*i+JLSVlSSEEPMxbu%pPv5}2U4h&`POKY;004U~~P^mds zybgIcfYOXRwh*%6>pnn;<_pBBwRcdmQ;VFESDl^l- z@E*>jL%k!BRC{6fj5I9aX?9~zl@jlH2jQ_v%-BQm-BgVC#?Ct7PQuC_jltQO*`WW` zq5G+Pch6-+n^az2er0u2+z zuamGB=6Y}X)H8eDYI=YQKr3qQt`gH=dbhGI!)F0fRJt3iKGc3NAebD$2)V&1#Kc#O zu+i!#JkTSPx5$H|XBszt2q+8Z@FYMj5Jrhk*D5xToy!xQ*>-Ji8$?)MBtnm1s60$$ zH@?7_&|naIsU<}k*4(>S^mY<-h3Rf@KWzcpWdhFIU`>!0CzXd-ykq|(xHmZ3Wl^r+ z{lOlIw`*g58jCX@N-&GI5U#BWHp8Os*!)Q%)a~*URcd@`wHKX`gngZ@yx5W$I$ud~hH|0& zRGX88)ei<=$XF4W{l;VPH=sFa1?bZ`3kOY3136;HgRPU|4SriZ#+6>TiQg8hqTFr^ z>UmwLk?qu5l0Yav5swB+wc|9+XJlq|s_K%gT8`YMw2SRq39HvG6yrR)A?T)Y)&A#X z-}(&awyXINZ=nBI8Zi}`dR`Gy%DS*Qp~vrTUADV5THs^qev;~ZYWPL(xoWpku(^m@ z+j{4H^Zl_V&5OF{t1mvN3hXdIp2Q8*2BR}&e76!7-IDQCInd1;Q1KD(^&q3EZ`(!a zcTff#7S{{dF);ae;6H~=zrnN#(dOT~;s#9n*ujbmbg$iqBNoTnL@j|ZS9~xwOF*Aa zCvjp<-xM4j08)OQL|Uieji(7G+)USkFE}PV3jcc%^5Vd=wQSJ!p*{0o9IH?HJXk6X z9hL-hwV-qe#3878@u3KVZLRzQnS58%HsOF$hqZ%0?wJ~hl9r)vl9Gsfo-P<03I_^k zXM9v3VXnn;BYYy3|I0qL=;+G?S{p=iGfH}M$L!WeFK7r{+~i+^DLGD3Fe>MFUlMTw z^hwyj3TO_z5AsO!eu{n#64W_h#ORy6Jv~ZA-(=S=|D%iEd>Mw>OM2}Z1Q+vkuf13V z?i*efAXn->HlF>s+IdioeH^DL3`zr*7!KGGZB|f>`mCwphWO^2mZd~jFw zD*6nP7tl!?TYVu1h*W<#jNJTRAg>b8H7jStjK$t*P>yr~<#X0+5fD%Q=Wz1JofrBuf=lra&X7qm}MJ zP!R>TVAS5V8Jy@EpFt2Yu17{iS%{fbGXxO@dfLP(3E2DJdjQ@E2}rNAbz6An6$O(I zFzO-_k=~5DDc7;I)rXdBK>i!(BTCYAK&cjJq|2L#dJ9f?+}ZkYze8KC^b*wEb#w(2 zpak;D0M8xg`@&GQk1vXT9}#zp`|s6!;ioPohP!pocEG{8NYET10~ROH#zfcKO2l!l ziAh61=Vc>763>(R5Lp<4QMybRDW!=i9%;W90OPa`f6eqiur202E{V%t=CYeY77Y4; zKL+TJu*3tMo-(DDCNSnc+rP3v*+mg1?}j(TsuJJ7{_nGZC{p#?QYywJ2+oU%dBP&} z2^uICob@Jq6sCnE2?s#cvGPKWMUaJ9JMlCUa^FF#-G2s@BY_OTh{fiNkFfqi$pz6_^5SQnkIw>dH z+1dG`cWfX(z$I@i5E?dpz6f;X;u1lKTX)EQASln#2Ds1CYs7Fa7>crA~VHG?*bQ%$(V^xFRzF6I29bmW{uxzZPY&Sj)Hcy%btB{ zWv}m&JkC0Y9w3{`s9c5>$B_S?$RcE zjf9*`r^fUQByflSQ#a$R^I1j_TBH}nYg#;xR366220t0k(H6D1QAOJCn>RbYP$p{C z@>Ws^3a<~$P6J5)3Wb*DNjSrWGEYR;ge*i~=G27F{Juk@ehJB7B(NXZ3eB7&K-hIX zA(3aiUU=R2Ow2n}0TrY%H>^P_iCFr>@Vi>EcwL*{nOfBBdo#yU%hb4<;AGgeyT-v6W47={_UAUl6_wUTYMz=5B;$T_PmWd1>k-vYKoJS)0wa#nRp zDX7t`wEK`(bQ8g!K3#=X4k|4y@CVx=C3&U7!O0Fm7`+RVf>}w3OC7c}<0l$)2Yw|)SVjI;Z zU+!1CuV}#3>Ag2$^f2OP5HjML1t>0>qcj*Qh4;tB9$Etb%)%6abfvx;s0vZD7}=BM zhT{nP7r7VS^#W+(MYPh0L$>Z|T$5%KnSKYx0+Kk1i&Mf6yx@MsyI!%5L44$NT2i(hk4-FOXXG{Lxn$|g zii=gDc)|1vt2Wme|f(!_z?TPJS7_6*?kpf)b;L?%XY!1S`t^R#BM>s74%f1wK7h=34^` z#_?e)5yREtyRAi)WYg;7Fj0;a?l-Q@YD|dR%}aY41PkspyrrO*TP{N*F4!*lCb*@D z+0f;8v$$ODB!d0km5rvO0PRrr!3hRp5X7*O&H3Ye6gVxV< z`$Or}UR&@tQd%oZ+n;Gm*~;&j2CpwF+xqiQ_%lke0%9+0%Ptv{+tDRT?;jkG4gWdh zHhHpY+b;IYZR~1;6BT#PQVeh*X23RrJ=lUfw303%h;mCA&(9gRnyghF>uoL9C73R{ zIVh$nPo^>+Po`KTuh2^UTiRzx9BBJ}+RdemFoJEmtkYGYUu2a@oSjfq9_8_7rDE$$ z_15`n4bV9f<(l}z6#3>lhLEeSGyTzE^MCfYV9ez+sy_$-pYuYB$JX*S45!I$%CBDY z4Q=GjJjw6I8IPnz2^?snUTR^^}>I*w7U}9 znz=;|B4K=Uf=;KJDRr!LDu zB3k3i@i-aBk)?r#Zz|`Ey^YJOaBQAmHqkgmow;2%XT~^*Rh+|0fL5w46RSyYm3L2D z-{$X>&z4*jbI43^ZilF`sZ~Am?xZ}Y`j-g0mD&+iu5;BBIZF6-5+~%qG5NFw)BCL8 zn1HeR@UoUT8BV<7*{1GBQNHgy6EWx=c|Xi=>WNo?Y5KV7#r-T?5mNrm!`>D?s8-2B zlp}COn7dZgEgnKJK-RyC%rfp=M=UOS0@k^6fgwuut;pzcz2*RTAiix&hyt_ttGo7PjJicbLCobGvA#^3HDZzXYIYwMOHPw8*V{aO*wPm z?PQoqK;4(v53hlx#P2R#hete6$J(1pzK6Z;W#QzGb#0?-knCh5n$Q8+bq7h&R)P^u z$RrPE)f<_(dPhrl`;^jT^R~twDWQ%JxtQ7v8%;SOumo7-wf;8N8R!| zB=~un#q3I3;&rY~w8ehNmCKenaaAxNfn!{E>Gfr!W#s}D~ZI>lHTxvh!u zrbD6HqqK5wH}3}4-)^p0^K3B%!{0GUyiHo;SW_Jus+upDboF9WZx__zduqRqzNC*# z^BbQGl>;D6ElsQf{s=f3hS+5aClE7$NEgfs8^IDZynXTCLDtWJc>F~lmXRQcG@(TR zsZXTaM^-x*&>#E1n@9=dicMR9)aX^$nm8CJPqK4}0H}&l!dkxl*-GDJ<={tjAZ_lR zI-~LQvc>9S7Wapr;8VWKM%}B8vi@q*#*v5P7qv1kRN>6)moMX!cqDrL^H-#j`}BtW zKzDmOYT}d2mm`mii&#`k$7}*J6aSyLs)>GGf(vsYDTWY;o_z+KF#${9%*HUF1;UHx zCa=r;`ZT!VmGZ@J+X`jbq)977dmDWgYxz7Z&OdE;J8ttjTedP)0An9$8;D{6qN`l{ zuI2_m;DskwmH-&QKAYJzaiZ(9*P4?a@JqVb#wK;`^PhkkxJTiY{1f1))sjOD*C^iE z`S?RS190-vRi9~J%`uN{qIrC(yCi1W%gZWwED@QuhKB)kA2Ij^#u#lIJPM4=Bh-Tt)3moya2-Qt>c)s z?;kP?%zYjd)=pZ_xyVQ!y(!5s7A7|6f8RecAaP@+@g4nC9`)Xokznl!JDY_4rqfkv z#sUtpg|@)kW960{5I#mc*R$uCiNum;F>3O zwM~8h1Md6IGaZvPCkk?7e<(&&E}&u?OD%_;nw*CyTMfv6kOCUn7k4Cfe2Ck$crV@O zC%(USjldLeaq4qHoWi8WIz7l6I)ocHY)&FSUlS9>$@Xd8^Uz z7#m;`Xxw{`=J=vN%vb;W;iA(@@Vd&n0!>w$sI|t~bEZIkUB{Wy@{n5Q{xZ9Fg&uNT z&b*I1pS`awtZvIIZ^tGBTC5W(@>%D&d*tCbGg^AnlCU+hRfE%1VnGHahSN?uHib5D zYE(xA?UUC1kTmpiS2a{x*9pRbqQ3E3(5@?#eIYrrNogefRMb}n;#H^PNcW$B%o?c8 zeT;v6yb+$IOS<&o@aD;#TZ4q3GOJxEv1_RW2kZRFFu-R#m-%u2=h?~6It5?yuZd9K z16IbLhXPxV922&Nn^63ETEqebOOV2?d_a#!cl&(3dofvn;IF;BQv0m&19!0x&}V^r zR&V(y+T_)(-Wn{@qDIS7GQb?k+Q41cKY6l0En%%TfczF(J?%*r)&?SA4&3bX;~ z({ako4)enMl}{(UXXotKc$%I;^hIwD%WliCaTMk8Oe zcfZ#b8%r!|CK?Ae=W5&h@}yP6FP9pwGZ-90P~9<2h{+bo;P+8mf(z4&cKkRY>37NZ!+EEB29UpWYDoPQ z`kgK7Ed3K#EkQ%iIV&GWw`^j&e7SVI5W%ePF5emykkA)R(1w8Gxo^NtOUA=hKZtt3JjKlzVSY24%GQ_$|WQauxgQHpX?eqtVK6j?B4 z?fE*EVJ{+9?0By?ySPQgKh*_4ex%^AFM)XL<+g$%_0jQFGiC_T#-y^)IWx8DHPMeP z6#`R(Iv*C;6t_J}tp_$i>{Sf8J2k`Bn*2*7A|m@6UUd?Tl|+{ush z&72VVr8R!-SzgSZ2{@&#&D)jGnTysUww5Og?oOIDVH$yj#+>m1d~75v>C6H84A+J_ zkDPujtl?l+k(nehjZ?Nx{$AG^Ve~esQ*%uiw{pe0SQpvG0)O=R?ezO55n;@=;X&FZ z0@>XI!>0-D&k?LmOq@$nQ&OB+pzz*sX=lz5nI5i6?||tfjYa$ke;gcaA zqT1K(jv;pc4bW}FlonHdS#aNUBttZd7Xgdw3qfnEi)kDwS;ZfnQvhZTs@GdYB0`-Q z*#f>U0IUP{)j$C(!w0rQ-L7yI>2|=9C<;*I;8le(=aXXHZwLAnG^6os`R}eUt4{}Y zMxe9M4FFy~9BFENJR^eL>hn9|Ro&o2)e>rCqPj10CFW?gx7k(ms&<9bhF691?d53N%hL%t$-U1#3=siE0jZ)3x8bm9invzsvlYVH z+M@(wV1@YZhxRtLmoAFR03fwY2b_c%ny?<5F5=^%y7Q&`bpM;-z+mq8!LgoZh&A^L zXxP1)4$9Rz!=^Wcyc#PFL&m9g84-{K{q@2gXaCI(vN^C_UUYK2e-4C_+w`0(ia>OB zMy%5lJHv*88Z6q%BxS#tMU6V}DRu?ODthhgfH0DEu$iXcPV+wuCU8NF7R&@dD*>7D zqC0o)gdV!>fR$b)tW@HkmwS^v4^2w`-{ZxBKBge(Xs3a|S87dVnh-d8gl&E7_5;r3 zgeCk1ICS%;_~K|HYNBhRL49`5G6al1u1yKq9e%RR29vj!toakV_g6T!m1}_P`3Fm^ z)jC)v_6K?uYnMu(4uzG4$rp!3=4Q(++F`ROB#G!tZu^1xIq+6m9>1>8j$L|)EB5@q z2Z9A266XqHk@ZId$r~!)%bWKj6J<8>DL7Eac`i1|1suxC>(-J2l?56gzIf64uDiA8 zddl#Qw*T#^By2fi6*Jv;ednE_*4;I5y!iBCs7wLtAiD!Nphq+Uww;;Z74J1K!wF`^ z;bkI^s?gtk4XnI>uCfR*WD{&cJ1DP44Y`eArN*Id3eFRDXTftoJx;6uc{M5LD3+yA z$Gu>2YwE7WKqdE08};3+QuZKiB4@sLCv4H&^;EDjfY3q;3EEvO)&A`pHE5&XhPrB? zOBuIL;(0}@PZ=Ou_}e6ez;fts7yFn8V(Xx%D%MGYYTuy;AohF;xb}`()j;kj-SYkD znpjq{{0^wBz%F5O((e$c~y z-6fwYR#gKbEK}E8aDOLOeI=Qr$s{x0#WCD&zS0Tv%mluxCT~ynSBF1~dK>je;?kaW zxNr$9yi+v@SrJLpw9$(vt@T&QUrUSyiBV+;7HL%2fPilWxU}XNIborPQvQZe$`!1; zJz!<2or2Arj3da;axL^F696LL@f86K(sx%NOu~4CKDr^cA>Bfu$;&|-piyT5v0EsYVW@KETa=EaEtn1=<+lOJxCQnz|Lh#-vF+#K%+p zw@-^=V8=M8_kz_V%;kM-_4@~6z9|9 zAx*`591!V$Gx)W$iy#aGbf3?R0_-&2hQS7kbFkg*VrH2<^g)-*)W{Pa9D-YP$jQU> zaMPLjP$`#u8R4nzmdLa8TTf_%sREUAU+ux$$j(8HQ3Bt$asCRcgL>tgoBY9G?2!H= zq~d!gZMV`5zY+_#!LNXkai<%y6I7oAG(de4waw$o(nYWB~wg`s9X9Jc(dRLv+j+Gvl* zbL%%ORkbW}fmnXJdMkrirOAY=tH1PohZT#;X_|wZ1dLr!%;|a&6}G|2#LyiK=2Wi1 zjTrNG7g~{RjkbP2vr$1g6xQ1@nIb3&u+dbDFBpdTvqs@~8Mx3F0=P`hV04$nJ z(t)^~8nuaIxk|~U*Oh_}X*Bgj+uN`pJ*5wm{^FcKq801E6g3^!MW2hJo_L|A!BG$* zz|lnh^YMa5{AK~5BfAE8cDPB?fK-={tYUqY&eUenRs;KjJ6amu>8z$}BSt{O_`CoZ zdevf35Zc8f7ZRPQ_1?iX0&MdNU=e;@3)Oh1$wH=2Yy+5;BZr6IUCNbkgOIfGm%=Qg47a5{h_fqbX}<9Yj_i8BPPZKJ+>6`D&5>>JLMDS2hR7n`E$_oH(C=G zh#IJFO0+LvrPe0rK=Y+za1X_@hl1(gz??S$27=)AGw|(0^_v8d_+q-T;0mzLc!Fgc z6@4hTqm|wm85PwBoCG!|FT{Gusj3(B-v>c!9BU9ET!~owtb=@lbw8Rl{t*=5cMPUD z@Bw7~SB~{c7}4v$4&QSTIW2%Ya%Jkq3J98g*XSuJm)p48@nM%E0VX}g|BGm1B5SY@ z???zc-x(G!Qv2iULN}K7+RD*hB`Uz>Mc5=ilQmy>V{x+bXpf`S!zbPJDJ|gMn`#aD zTUcx_CSOhosf?ESALXn#x*qnO=`ZOAzmx7YU5+C(Xs!|ERIU*2&Tl>th~MPLYyFA9^-qR(C5K7|yQxb#+z zM^FDa*dm359I1xbnju9@50@>amN=b25}7CaO*UIiHVx6%p9`>4K6(<23yQ0~Ef{bt z9AKD(W(CkC2m}!P4+faOoQvf0?Bt@wcy9ZmQN2LCKL6K`S}}_lb(23{^9ckppx@D} z@sTKC><_}7j)A#xYsu01D}ZkwZO(|zqJPb%oHOpTD-(i1=&A81`df({pfn))ve5w1 z{ehSQ^a9yFmzeI6(W(-BsSs{oYDS*OcibundwJEmiXH+41wBhoFG zG&SGrePskXZy}aY0|;eRCr2a@w@au`+l$1KS`}uRv(s6-N#gybcx78&cF6p zu{KqF6eRK6)MLkgn6+&-`)Op)F~9`-P_g6qY7}kcj<}-(ZAf*L8?6}g6YvWJ!MI9a zN;3qsXvyRHNqtyW5#nkh-tA{OBLdh3Ja_vCnyfTY~O;S8tTM#Y9LWh-_6 z{si=~Smb|gs|0p}eTLY)p936A4<4hP?I9YzTN~Je{beA`;YJ~TDKeeAZLD@GUM{%S z!kV~`#RPry5Y4~eX)=2h8@k*9{Ey7rk<1OtTHK`vv?}52(%3~FWr!q!OD0G)!n4p| z<>(q$5RI}^>Vqg%J-^)?_P+Z75<*iidOAQj?x$~;0et|KXKfARci}%f-CQFgDqovX zK2-eS8FB5!W#M6?eYV$~N~0A=y4-P5!i3TURUoFbQaI&qIa~a2_(Mo>`pm@3J_(gJ zdQT_xVPyEjg|Hm%MPxZw+v0`Ue=={sxdL+uJKBbxQ2p_$h63tU+U!;*AV-JIym0v` zzQ1u_xP;BO7*C)||lG}uP`qp~HT?@4_cjy`c-?Sq$d2i*9;W1tUFaNGs;3ROD`P!IVgwg0Vo2SPgD ze5P+V5205YMktVITRJflQo5<;r+12+fi&0Gsc)gU7j0dE##~ij|F`<@bts-u7zz#! zr3rg|iqS8Df*FIF!k3!zeI-Zn}-WF@t9vdUnpv}Un_A`PM zW{FkBy_KFt)D3sB2IOy49AffUg1TaHKJRQncWtaYE8c#{x>`iWvO1m<2}`}z^`~f! z?l#aU^9pwn&g=5!Jw~jtA7A8xHlVo9l4`$D<5vWuwUy)mJ?kdneOTuAdBR7dLWdS% z@)VE`H&9~*TPggN``)R7eD}iV|$t zZ*D`UpLNHV!)wdyBPRJR0q`MWh)Y$aC&afLW?^RRE#my{SXezd&3~FTD$1%p)TFRh zO;-WbNR!Fu>7KLy%+J>!cc5Xii6V{wq(b550_@uvQug~$Me6~^$X+eVII(pJfM( zi=j-iD4B^9j${s#*LrH3oNgdQU@DXMu4#{P748RO%kK)*xce#J zQ6x%23Nwcdpl~G96Kb^-M%{et^$T~G$`n+BUHIfr*?k*21x==MM%R|x;GXlLU)!_8 z+c)v|WI&mOC0r<$4i~}89k802#A;Dr3wN%e<%Y&0!``YZJhW_j}Jgg(Ozh+WCCnbRiHJ*oS?8)2h*(6f;mao> z(H&X-Ss2V@lnNKx#pWV_@Q@MdyR~vpPFUw+!|GUa2m{CODX;)voLQ+s?98bUh7w{e zkjPy9#*EMWN`p+d1L+YU%Wqo~z<+(Hh>FhxnjX#FJy>0iLU6`|)t^#|_>Mz}&sqbH zN2ZEO8HMqj{Cie#TK#=m&FpIaP-csErq=g*Y(9nd#0?SM4tR}ecXnu2Mp$W+d&hSy z1f-IqJU6edKa`sJ_buZ5F%=^_`tSl{HL*3LSDQ-QXc&i2A@mBrZ8``D4^?BD+5-ry ztwa2QdPJ9m@5#Qo#>Pf}i|o$XrDvUC*E{g*AZ0+4#ynKa{kVXX;V^>hb9Ra9l>A)? zyg9a!m=B;Kw{z`5tF!U~|wNFGg;V(e>vEni)Z#E$9IAw|(hrap#UFN~7rdPRLD zB8KoF_Tk_O#@r|6;j{taJaYB5BI@CwA*O^jz8Imuu%X=JODU8xdk{}>FWtkNYLaIs z$!%^8_9c}ehDycH%d8fObn44prh0X;t8`&cA`ia$KB=C@m&MH?WXpa#Uj%F(io4I} zC#B#=F}52*&i<_5WV5c4PRx}^yfxf$|4O_F%pu0DSbUZazMa>9$0G<3kUN)*VRbNZ z5N>-t*n6tM2@!PYBX+8@d)Om9n~53Q24`xEmWg$;HbLRCWeSeTteWwZ+Rlh)-Pw=O zO?_)st?S<~EEAyMIztX+1}$;titPfiSHJ%%$JH24e>mZdnRQ1<49V|qfLBHwtmWfY z&KCh9d2k>1LOI(NtqJR|0Mr^Ke{pqw-Sk%*1Img^g7DBXg_+!ix+kqE@pdNk`rk-c z|LbCMrK(*?XY4tJZ^~HeGLMYK1lAu${BzJ$*E`A@=4$nt9y2pM0qdNUmuw9>5)UiW za4axx#y!)oMq5>H7yuQxjJDs&MJ5UlYN|2R7s#=-2U4!tL7QMzI}1ALT@<@8If#fmplu(um`XHD_C zk`Y{&Gpx3A4GajtD=ik77|OwXUHT@AiLY}it*R-Ulfw0F{r)GwylzHF>2pgbjxVC~ zn#2A;!BEj}e)6VU)@Hr&S3>tYAvP?sVjOrj9jUlOLzI*Sx1%>yG~53I+TZ!)x~xhw zAI`HuO?0&5G<^MVToL=-b15>sK@>J+T0`P(#_Y-tMQK=-?$I@KCC7B?pr=pxj(>k9 zaPgaJiQC^7a#DSSJpbBCI~6FP`kM+r9BM~=KSUnCaAR%#P0G%L5b3l6XEIoUM_;=7 z%Tuj~wxYZTKtdMz@YA1$@8Kl0H6uLQ{gu}0`k%oVMl*5SN)+Y_H0MpL>5l}nQH8re zT{Zf8hd=!&8siv<-8Zpe0yI;l^|J!~zT#yhe85~Q+!F5oRSq_HBJ9n>N=sn(HZxSE zthG>)x8eIiiDOz%%cy{1=MSP!e+GN(bSQ+w!uBwN`xfLEiB-Ajw^!k7n2;5%g+Kv%#=|j+NuKGpuJ(m6$5m_5ZJ-YTeZaRKYA0^ z?Ej}n=^=}RHIl`UBVqjwP;Br>=IYGkzhcwxk7-a3>Sd6`|NM+%C5Eyi=TLnK1Nf+} zyTm&qs`*(QMh3QL0lu=b!vBoj#Fs#sJ~N@jVfv;Gd-jvx%)?-rOJ7bItn&^H{0>yE zLxPt_!Wy+|O{Lb%Qvsc7$MNHeU3ZTIB~kzVLXuH$XXG;zVo&1A_Dvh= znQmLdw1aG|-g;Zb|Y?<*^Gko#+YR{RU9)fM)e&L5*76UKig&D*uhohpG+8gFoJQzc# z1Pr=Xt2W#sdxd)GZtmBSkrBOb$AQ8UF}QEKDkP^WXTyLpK_ac=uEF%NoAFDCGujfE zC=-J8mx5Ip_$)H^IX473PRbS3^sn(mV&Oe$l`FHOQ_Dwj(vKH z_#R~7>#ja==VJ98AFBQFZwW(bAX5js%yx85-{OUQYBSc2f$rS!V421wU^5n6MH zLWax9HdF(I=>H+>&Et~H+W&D)Qzy5a${nqonVM-)Ggnk9E1RcHr$uu^Go#!UcSUSd z%fxz`vPCg-CM)+;ToAD=sZh~eP*HG6F%b|IP!aeYn&0P1=X<~AkNHEo?{m&|uJ!%C zu7$B7|15{zh%<^rc-+Vh<4}@5W-64sqd7{)}Zh=Eu*EED)HU=Mh<8RQFyGlnAB= zCD!z~g2d6A+m^;SM5%=H_J-=VS)}-?nyg7Xk zAK`XDdp-{2Hfi=_e?nBtdf{&l;`y#hjGhmQ-_v+1HI9DraUnM0l~lZC z06Or!)RFQeZ66GHEaheDOw)CWI1!+!_|_aQaD|msJK)rB2rEOWD7itS0hfeg)mQLM zeq8;DteBgwL&zxMZ+3^%$L^ib3RUNHPP|Xl;yTwdv5ED{#!2PhPu15J*iY+?6DKPi z4vR}zAs^tNg+`1?;C!4thY6go-p&&{Nw|EwTpD0thr+ZjVYJ%v(2_+7ciB`jY8vCk zpCy}89{B$$L}24DhE@ruK$jNTF+n}ETq!r+hdG-6h&mQQ95s6X?&6`R(jGTBJG+1Y ze2@Rh;aQnEzR*;rBt7qmnK9TiXsV_nXPs93QExcwS5QTMk;{9*kgF-vJ?{9+1VO~X zSB?WYiuHS#=jlAyJiz;&^pYFAeHhYoC0{GaDYR(6RMyrVpKV#!`*lmd7R7~7mZzLT z1JTS-&-a(h1vmetU}Ls%&FxMU(3r+qq#>Yey*gw*L|5TSA-|p-jCfIM%4*mq*fpgh zH+=TTf^y>WEP069D5y?eobIn2LJ5bcLujd3EUBR<6eaR|+z=$#e1{VAt$<`m9V}d5*N{c?lWC=3o4*EK_BPk9-!>{&5YTiM~ zB$p#FOeSJWnfd$ZWL^e5r-a1xuM!61`{3^d_R?2Ygib*cXdbXcsbXj`$*fnx#TVMu zLj)n6CKVK5w+&bh{N$L&jy)X#iD*p1%snUZ2=T z&0lfGg&S_#!0pTUi|I)EktbaY!}CV8=bR%m zikcl2Sg*7)uy-4%@nJ^jW!{@0{a4Y>A(Zt*=??hfr)ac}hDhW4@ubNjstf65O5%ZV zbTLqxb(HtK17cuyvQ6O}C=Ed9dZ%8HEeDNh{)E)!AcVu!`S*M>E*W_+RqjwjxJD89Hcm& z#;9#$t?TUNp(rrvvM-()kBy&rWT0W379g}Dj#=a_9uxNYXSIc7H3cP=-rq7CC0SmS z#2!wp0)$Xn*E1`J(8R;(LRI*}+iB?8A!hR})=dE=W#*2vQT$&-TtNO@0MWG6l3rGD zJtcdnE9v4{+fSmLH6O!!g<*ZH*J9WJwtVLwV*tCUtCdAlp)F$+9Ee32xvS$N4 zjX;X!!G8e8`y+LcJ>wmA074Gk5^nb-ohVx1w%RTKH00Y&c}y?K0f4y)oV%h?nKlJP7x?%3O`511p|(E-M8VUPRW)K6U## z#AG7_C=+qyb#0Sy$Q)={5Ka59JSV~1d?3QdEfrxArALl$-O7XRnb9-xV_xcW))Oat zQ#XxF=-Xrk-Mn+j>!_DMX3~gk9Df-F?ZBdN0I4G$+2*vGAMiy7>F-O-wQ_i*-2*iZM+ZQ59FW#J(eBiFAp- zmN~qi+bEh#F2p%F8R~?%mO=AAj=iEB`+$JuU8&^+lLa205%4i#4Hzi2y_Ap)7a6^` zhaNm9FzSi*pRBuy*g@MNAn_4$Fd&)i0iryxyC-2wNv+L6Wdy6( zqVPAGMcL2EVasiHpP;dZi`KR*DzLmu31`sG2A|=4gVcda0w5QajY<${N)e5F5 ze%*4>NxMs{eVmec`V;kJE69fgqRhbPfYc*=pMR-__?gw(XHA)<*fQ#naI(3 zpJY4xOwV0f8W8__9)47E;iTbVF{bz>NuLodcqq2T1LjTU9;oJJF1(mZ z6gyGEV|?!jZK0!=q@^MM@rtEW@`_{Ygj!FK;i>}?Xob9A3@@?PHZmtb)%*L9G#K(Ec}{3QcBj)Tw;5jTPNnMR?_;ut zCx4O!r*FhD@KEx4w4$8MH3tXl9ET1I3)@`|cM@5)!eMUe(Hw>F9haQLeD-=orLX_; zs)<+My*?m``C3lmy5_6aEkdUmI?^p*J418 zUV7aMS`gXQG`*8L?cuoi`!*L1-n`TA#e@q0>mu6-4)r*vJ@1H| zI95~2y0wq_inY|F>}a;ozur&E%tJTAF;;D^WD9rv?-4^<^_cQdFDmXpO-TWr8NfaV z5jvXQ3(?`8sDeWl@tZ_N0T6}jXM_aFME(rFV=+)=(dKOQo$}%~ny6vQi>L7gA91>K zqT1=onGmB8T{QK9!^w>4QHNXHjE-a_cD4gL-%)|rzYQDrjG^r}-yF5^bK|`tY-eqa zc|b_r-57!v8T(S;;Ker>)r0&I#OSX?xJd}FLhw`W|FbzV4|>?n^2m>5+nx|Djbgty zx|=}yq;WcF8!Pg<5a|GAejFR1$Pa1+ewZ=4B00356kSB+zg0Dz@ABKF|1}xX8}F?( zES=3J+N(CUEcYYwa@koOm#box!m>=RzR;S=y!+YWdXh!$IWF>kNOnHEASukj>Mfz@ zZC8BjGwutT+l|p?9G!Y*D%yE{!u@8$$`7x#rgU!N?2pYP>2O_>oEvm_L(X7&?rk$; zi24_92GODdo2&Vb@gVNRv$1RW?V1B-%YInA4ML2^1@ZMYkm8JPT$YFjyb7bGN^2+R z{}$U+atwrwr-1wLDl9$RcgRghdeH>ZL<%5gmpM0jG z51VVXK&7KEUEr$RZrSKZR~c|z{BqA(qu_A*Jcz=OCPy{BopWl`IoK#(DClgM96);? z^{|}dcXd2|@R{(ndJ*H622Fz%`LJCcbUdt#l9tj7KqwPhdM|5>78c&4w?D^3+FnHo zOtO03r$aSH-_sW7+Dm&WZ;G{|T+F#eTAs@#|k| z-iSTQ=4$KdMylaOw^A-D*=ue{7(8Sa+4-R}BY1~d1BP`=kktYD^}y(9fhJ~#1?U?2 zA>B>(xI<0C?L_X)ZPD%M9}ZqgQeka5;sK{%L2~tsjEJXe}dYw z)Wtnb#Qz82VuCZ6-xmYzG}*RGtdh*CVkIiger zZdy4V#*m$B9y%FkI3fnoaNtb8ZQ!;YAtb#Bm=6f^`{#_985qT91$R4e%vT7TZo%!? zBngZd1DTZ@JQ3Kp=jyGZxA}0%Y&O2xW77#A?IWdBzA}17d8qA~(lyNuFB)BK*Tmia z)B9SX^J2UHEzG`|*Xw6po!+p1l<$vv_*97N#$^c5vmdwVD5YdqdmCIV%~e!8*Q3Nw zipVVD&|YzA4?yf1hRex4vlE%a!py`F+=|*ap$i{~OAY`MHd0CJreQ7P8g+&Lp-}Qw6t)4jG1phYb%Usuv|YO=n>Y&%Y89vCMxh z9%K@AIG~!YEXmx9pty7F)gzY8YrsvK8IZ8_4GQDt&Q z^RmJ~1}5w2((BEEgi@9C(sd_W7SA92`j9F&QQyP5732}^t@URc>(v&Xd-BrPLJ^9* z=B?JT(k+~V>#?kg=}}V!x%cTy3)BVQ+YhrvHWc zfIze49$qKb`sk#MUEvG%dhYvYG3mtpBm(DT^6b6JaU1Gu--TCJuv^Egg|87avf%42 zil_Dt|05fMMm%sx$)^yc3=uV`xWrS?D2A7?k5v^Ur3yS@I41;xH{&p`wER(-`x zmoLv;La+ZkmZa5id$IoTN5|IaL&L~(n=S43e*n#$(O*CR-I(hS56<#WtIE}@8tDs| zBBXQOqjFC=@Vh@@^f9m4`<|*VKF=34=y8y!Z4cItjXgelqt+NT-&u!zpV+N*-(PGd z%nvDxOHIY*+MH}iw9ZgDRl}YE5iFnUj5{<)S=g1f2(A1?5f4(@N=S^Xd|X>>)T3yk zv!%NcWwrw`HK(IZfbphxsop2WzQ_MaG%rQD(poILg{3iDe;oDc@z=>M456qr)er-` zEk|Djl78iRj)k?|5jgg)M^_=dDLBZFG!~;MAa_MOt)k<0!v_u4%`qbl1Yubj!@k!Q z3%g$K+blPIq%0RUBiyrMkW~dQ$zRZ0jaLN(9X9m#!VnLo z7ef*5<-l?AF%PsTe*}AWpRi16oOHO=glg5CGwLBG#y1*PJ2I#?Cke4U=xAS@l0Q7j zEyv_}07Fj{2>nu-P0iac8K^k$Vni5BxrAR%{F8Z;b(2v!3CSGz?19L%xs@cD)n|pJ z3n;?p>T@-a_3y%NQQk*HI=r_V*lnDJT@Q9=ubg1Tx$bKLshMdR;)3 zg5syY(SAuWt)zHt$~L?x#QRT%XfKuqUv^DGPu&BVvi$rcitR(sJk}4|HJ1k z2kvNvmlOmc(xW?h=C9|Tm$2$b=c1#gRBr6nXWP<>A?$>n=w4C%r3-AE-D?_Ix_S=p z`G>dHVlDCtWBz14;W^e&BCbcJaZY+2U2Z?aJ?Iripqd-oxjAki9~jXp4OQSeaf2GD zymtD|E0RlTwG{JRiS1)xq_!>mCk|N_iwbKHlZj)F=1n8}XKij`?;vPg zdi-wZZ3=pu%OKugsx`gQhHlUcm#f+3lb|>!$cp(?o`6MkhPB1++;U9>BwG#I#WRD{+OUj)pSdKL8A0vnBfW&@f#KFew?QO;GVh2>^co9bxG^hugrBj`#9k=IBBkIOt*$LEMUP>?(0ea=Wc)Olft2 zzL(Sz)AVe`<=jpXpP$9TyS##vVFMT{N7-`912ARwl0+i$>OlWfvKlvDm?(>q zYl=aM3ZySI=cvBu5AjMu+IsX(gtzfJA=1XQaba|DsnC)CQlpQca)07O^OiLQdLFm6 zp0PfB$~X)N1$2ySu0L?ZuE(AV#)IB*M8o}kXYyxl9NTrM+T@5N#h*>o8m24xi3bR& zXGmeQ_1d{6&li%CtlpNe*3n3)Os%Fd{OGa})FFy@X>=WS4#^haD?>DpL|jB(*0T`& z!@!OukgZOW&=KEOPFBP4c4jLvh&>ne zCnJ7P_yS}AMfaGug7kkB+B8~VvVt;*e_JoLUVwhVM;D+Gsq~u#%WXx(U0kwj&^uSg z{d|Utbb>w^=1z@I;fp?JH}0*G%rtApPGe-PbLGl^kciPp9_Z}~pj>Z=(knlKqU`9& zHy!DXPWh6-JU@xrQeNFcEEL%8tsTaeNqZj6v-GCI6vWYl-dI1$7*E=f=1Fl?5X+rW z`j~x_xWoL%BoF7O#wlRtl#_H~lpzKGa`08Ol;H(XUZ4badHoL!p*W}2?~$gYA92WQ+7pc)lhcI zNG_%M=054SjIh8z0r(<#L>>x<7t#|&Vo;ko8w*$jb(Ey}0%Nu;J^kG$PPrG-Ex$J~ zoETa}L*6n&r*{C50ZNaqc`m#1FxA6bXgXB|daCkiBm<`$IOJ%M z5TiuP3S0mBVvX*mWF>JbuEz|Wy4Dol2x?gPyU~mDU%M&?a=KnqR)9g|T zZn;QRO&dB;Ag@2|le{?20bR_eKt-X9FWOp*2Dw2_qlm+jW$Yb0RzO(xKQ(rLrL=fT zajiuk%qR;F(9j!I)e|gba(0S=>cBK!qA@nHQu8F<-0PZTTDdJ$^W;>igSCs^7x@Q zbz$q5>{CP9TMZwYNH3-nF;mcPP%|_1wweI;+2-#b z4FHPYa5x4NLCs5+^KMz0ZvzN0o{3z zLHIgyeOTN@#Bof#`ZTqEX!pv`lzU?SRZd%1zuHs&kw_2QYan9xGYZ_%Z+*G@~sQ zZ2+L~l)3=ajk7K7S&{6NLM4$-&vgD%*ot4sg@dUuO*#4d;9x$pkzbRVYD-dQIv7$R zIJgT7@Hdf4%N89BBkS&StIn@H8PH41)Y)nIU~B@&EJfa4BZHKWA2N}E$W<%a_Fyh% z902ZdX=+Q%$lO(D`3>QbdU&!e91L0PNX})x1_>&F@YoJpv)uJHs<;0{@7MgMWW#%W zPsnOH2hgpH17>jbB{0WjBAsZdt4qELcHQ7ywn>ArpX3hE$JVFE(hjT!xFpDMQi16( z0NjXTxVN(bG{`|o2KgE~M+!&+(g)*EJ# zD%o;PZTY?2n8r)r>p9?8b^C9f+f!qQ8Rj2e1U_}9)@mxja;)3Y|3p4j?0NW!t zs0=-qrxV?Bk7d~nXMIm0$=0Ix4euwfkvZs@ZLzGg{|(R}6rkWU0LG&>ru5zxVDFd- zo@4*IYchB*UVFKyVBvWHD`J^W$C}B)i|c{{Z7>@JgoCd)%=Hlqao8JLs28|+_rf7tszy6)Q;;Pr? z%1(H{xZda5QZ(FPCQF760M!bEsngqnqCV&oiA097*dNRHEC;pY2eLLG-vu(|-lcB& zZ9eLA>TBiv4>AU`X|&E1nkK;ODm&o0%#D#hG3?%k zO_SrnO{ZmpBf!KC%p|KXFdGLQH_DLP-=A-Q4uhB0h4ntwejw1Vd3`FE7nk$`69X!) zo~%`8%?D>aabdk#GfGb-*jZdM0`mi~v|N?hH2u-C=p@%|_W#1AwShYQ#lUjYYhqw! zb{8|0L>9}Y!1~}_gThNbRty5v2p)<5f49B$LanJnveuelr>nqs2JQ~NE0rIFDjgBy zFw~}1dZOG12*y+WHNi{(M5-)D%aA?*U-GfKEBB72ov_CPgazMM3xrID{jvKatSD8{ z!tf!96e+uNrIVMo9V_fg_GuPXfI&P?5dO|0>55;;HSxgS66~IZ9n%{qqd6%l_#26~ z+COB0e=h(6WTA4&zPs~;XP3_Eo&>$XpLz(8y%|^0wgEG+F94`qsxn(B6J1)0#3h}W zDoI-*FtRsdWFWbl+9?!eFoeFp$BthTMn<$sCvoC6*7Q;tJpIbJJd zbK6}RtW6;~2!{tZF}j_Na| zMGHlD;h_{*jk9n0kdh2}HBwAIcP}U;lr`UBzUqLNzyVX&O3uG3;P>+D9oAg8{VjYa zcwHwj%gwo7n9JK|;Q!VwK=R(2!BhDb%Wh6r>xi;9z?Bv%jhs=T3d0NByu|9A@ko=J5>A$Q zj(zqE!r9)uEO++0{&ZGA^(vi@1D5tgtyOo;&UY}7u_RGDp1J}n7W-No;n1!TJB52l zJ(0L_;VJC(GFV@#c%U;O?LZ0}v1ji?B#0TFDIr4If5inbp)Y$yIE7WR=4&A5Mf1Dx80NdVKE zQXxhss!QLS19UXd388!S`H<;@A((Ns1yW@DDQ^uYfb~v^hN30Q!vc_Jjr#u1a467s zN&yR|Zr-M-Kz^LRWulUV305rg?nGx0b)mNHaoaHbxzC$v%wR=n! zj3IVWFmaLS4@#_O7)6V?Na^GVzK7{AJkALMkdjs+M!;|S2 zvihuL8W40fOn6nW_#9yLC?;mQr<7!H+l=*68Cb3$l^Iz=A=|C8G;&tpXD4da7SwMn z_`k?5VyF6F6;CP%h|&<0qKxTvbi>4(K%t8uErmFL(raSWssJvWXPT@kBEb}c1c zSN?`s4|InCa_Ko=V#$c*4E$e{_%7bIOWCvsu-O001z#i(3S~cW2K8ANShL9W39GS~ zrZ>N8RXEwI)(V_bC6S5@{pBF%g*LvL765g-La8_Fy;xBn%F#4i02*1j?ZJQ%-I2C5 z;0sTc4E)+GHGhAESh4HC^))g$lj}Wjje;GT#nsK&{KbFcNM&x{OB3Nmm2z)Qy=ls3 zw7Md2X8}V50byUqIbqAU59R^5_lk!Fl6GD?-I~DehNDZF5|#Cb%VEFBoCin5aS&TO z(V|w9Z)gD2-MW^Kv~5P$l>iPl%t<0U)~W5^eC}`lPGrefTT1<|rd7TiO_m+JaEa*M zzTOvj9&e<&TA-8(0LzJqiQ>p!PT$#Vi&nUTwA#Yx-`I1RZOFL#&&xd<+>IY1#6-$%z6@Lpx`vBmwWTYu3(Lq~g!M(OY`#z2r)9L^% z*au<1@(1PdGUpNM5mrn!Lql%>)l>tb8+c6VhG<(m#${Hd`i%1jBFF9jjU8<*yN&1c z*R}G20RXl7P^etF_qlr}FW}8fA%*tQoIvdD+z&TAhkOxvk=Ue2Y>asm<2BX{;vzx)JpT0A5+YuFNf_zU;jet-o?9 zF+ji90>}V0$i~cB3<31!4^6-c$vj+8r=QD*2F<G3>0+(hW&x@C;#m1Yn8x}(rZy~nE-B@4W7S;>a$ zxd6{RL~BhedyR~m)md?(dn_QjE~cd`l*|rXg|9DWyimAw@TsW+nth4L7N}@VB!deP zSb_mJHX2It*8xNHRxBii5BT_{8}f;|Be8c?#6TfCuTcU3_TG^fWY@w8$kA1F_x2F2iL(7~0!DyeT^VQw3ykWW_8`+bl7lIzYWk}6ut0gKRqci( z@JP9mWa>ksmQl{JUm(CHy5-pVw++3ZAgEzG!Lz z?ipZS&BfVccOf!0&M|2TAP)KdT_8~1<~iZys|(R^%)9|D()L=4-cP{Pn?id(E*Um1nSJrEHU(f8T4hX1vEFh})=k+As>~@>;5=8`9uR{T6sO+f z?0<0*vsc}ZZvuaDU^v-em?E}a7e1X~MqCe~RV^cR1#bJFq#$5Yb-*zKe6S)zO`XH#0*MtI zdI*>vLUKqP?3&0Dq#RZ}!4%Mzph|!FsGfj)Cs|!O&XYM-`Tw}J3Rst*$m`1m4{D;l z+Q97`0XgDpG=TN(_>9cPOQXrSQt@1Z@FHaPg7*n0 zdC4JB&#K-_-?Ayc09SXxw3l`lg?!=)(vJX(UnKpV(K}Vl(i4=Ca3U6S#cF{xf++jJ ztC2>lraZExA#ho#piGiK6>S8KTIJuC>-|yN6#Rv*BB(ClPc#^1lYpto9$C&AsHdlj zX!>Q(R|;&?1PFYQfQxiP8aNXMoLbS;r6%(%kUA1nZviQaY}P6t(H9C_eA}X{$$IN< zgAQa%{tis=m+C#7d=JNe=iFpe%$%6hjQU*@TJ$oK{*FehT3?M9TNJ0t4K{*}m+8N4d_CjI%0m!Ny)df#aot7&Q$ICnr+mQJx#w!N1O z_O2YJ19tsh)7k{RRtlh(iS~TR^Lu0eJXNYFLqNYiiy(vw$4{&Oi`pW)mmLP{%VmsgZs0eKxm+C`c(mB|Q-ZxF`6EY5$3BPpR z3{&Es)$vr8d98W9I}LyUSb`gBe5yuLV4JdB-=8vgK2lzixBM}0S~gj-`n$XUqK2%0 ziQRA9XIcp~e=w4|U=6%6ssBTxT)7EwB2Y}A&xJeg$=l%Un_P-RWf%Fy4@^mHAQ&B*}#IL&Q-;$^Mi69--ihRe=AJnWM zV3Z6&RI4l_q>a7k>gSyRN^~0DEWYfz4cO!F&p#O{wK;11CffPa41Mj}@xf#i_`04G zF|UoNClaP2KiR>mR%ufAyx)~O+Oqnf8)q|8hA-^-S_6Eueu=KU4bZU`9%-J`U-7k7KId6ntQMz zkDV|dIT6jEK;UEI{fz#%i+@;v#}9EM!lrCESqA7gRj|N6a*XYWX{

bU;}XwVda@oWTK|atmqq3#vjEh>|=QE9Pfx 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 R_E&*?`}O`^MLT`Z{~w`kIhz0g literal 0 HcmV?d00001 From 7ffa115337aa21e62177483e68c64763041cf615 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Tue, 9 Aug 2022 21:47:48 -0400 Subject: [PATCH 0015/1008] update diagrams, add scenario D, address hlib feedback --- ...adr-010-incentivized-testnet-monitoring.md | 51 ++++++++++-------- ...ncentivized-testnet-monitoring-diagram.png | Bin 225268 -> 0 bytes ...ntivized-testnet-monitoring-scenario-a.png | Bin 228160 -> 229633 bytes ...ntivized-testnet-monitoring-scenario-b.png | Bin 151627 -> 152126 bytes ...ntivized-testnet-monitoring-scenario-c.png | Bin 246633 -> 266205 bytes 5 files changed, 29 insertions(+), 22 deletions(-) delete mode 100644 docs/adr/img/incentivized-testnet-monitoring-diagram.png diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 0c80b39afa..96a9c6b852 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -16,15 +16,9 @@ We're adding telemetry to celestia-node by instrumenting our codebase with metri We would like to make the metrics exported by celestia-node actionable by making them queryable in internal Grafana dashboards. We additionally want a subset of metrics to be queryable by a public incentivized testnet leaderboard frontend. -We would like to make it possible for node operators to monitor their own nodes with existing telemetry tools (e.g. Grafana and Uptrace). They can achieve this by deploying and configuring an OTEL Collector instance. +We would like to make it possible for node operators to monitor their own nodes with existing telemetry tools (e.g. Grafana and Uptrace). -This document proposes a strategy for making data available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can configure their OTEL Collector instance. - -## Proposed Architecture - -We expect celestia-node operators to deploy an OTEL Collector agent alongside celestia-node during the incentivized testnet and export metrics to a Prometheus instance hosted in Grafana Cloud. We will share a Prometheus endpoint and API keys when this infrastructure is available. - -![incentivized testnet monitoring diagram](./img/incentivized-testnet-monitoring-diagram.png) +This document proposes a strategy for making data available for use in internal Grafana dashboards and a public leaderboard. Additionally it describes how a node operator can deploy and configure their own OTEL Collector instance. ## Detailed Design @@ -154,11 +148,11 @@ Node operators have the option of adding an additional exporter to their OTEL Co ### Should we host a Prometheus instance ourselves or use a hosted provider? -We currently host a Prometheus instance on DigitalOcean (host mamaki-prometheus) for development. However, cloud hosted Prometheus providers take on the responsibility of running, upgrading, and scaling a Prometheus instance for us (see [oss-vs-cloud](https://grafana.com/oss-vs-cloud/). Although multiple hosted providers exist, we propose using Grafana Cloud's hosted Prometheus for the incentivized testnet. +We currently host a Prometheus instance on DigitalOcean (host mamaki-prometheus) for development. However, cloud hosted Prometheus providers take on the responsibility of running, upgrading, and scaling a Prometheus instance for us (see [oss-vs-cloud](https://grafana.com/oss-vs-cloud/). Although multiple hosted providers exist, we propose using Grafana Cloud's hosted Prometheus at this time. ### Should we host a Grafana instance ourselves or use a hosted provider? -We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). We propose using Grafana Cloud's hosted Grafana for the incentivized testnet due to it's tight integration with Grafana Cloud Prometheus. +We already host a Grafana instance on DigitalOcean (host mamaki-prometheus). We propose using Grafana Cloud's hosted Grafana at this time due to it's tight integration with Grafana Cloud Prometheus. ### Should we host separate Prometheus instances per use case? I.e. one for internal dashboards and one for public leaderboard? @@ -176,7 +170,7 @@ So if we are concerned about the public leaderboard crashing the Prometheus inst Pros -- This deployment architecture is more representative of mainnet where node operators will run their own telemetry stack to monitor their node. Exposing node operators to OTEL Collector during incentivized testnet allows them to practice this deployment architecture prior to mainnet. +- This deployment architecture is more representative of mainnet where full storage node operators will run their own telemetry stack to monitor their node. Exposing node operators to OTEL Collector during incentivized testnet allows them to practice this deployment architecture during incentivized testnet prior to mainnet. - Node operators will have an "incentive" to maintain high uptime for their OTEL Collector. Cons @@ -193,14 +187,9 @@ Pros Cons -- Node operators will lose the ability to monitor their own celestia-node. Since opentelemetry-go supports configuring only one exporter ( [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055)), if node operators were obligated to export metrics to a Celestia team managed OTEL Collector endpoint, they wouldn't be able to export to their own OTEL Collector (and by proxy any telemetry platforms they wish to use). This violates the desideratum: - - > We would like to make it possible for node operators to monitor their own nodes with existing telemetry tools (e.g. Grafana and Uptrace) +- At this time, there are no cloud managed offerings for OTEL Collector. There is minimal documentation on the scale of workload an individual OTEL Collector can handle. We'd have to design and operate a highly available OTEL Collector fleet to maintain high uptime for node operators. We'd also have to mitigate DDOS attacks against our OTEL Collector endpoint (cursory investigation below). -- If the Celestia team took on this responsibility and failed to provide a highly available solution, then node operators would be penalized for downtime of a component they have no control over. -- We expect 1500+ node operators during the incentivized testnet and there is minimal documentation on the scale of workload an individual OTEL Collector can handle. We'd have to design and operate a best-effort highly available OTEL Collector fleet to maintain high uptime for node operators. At this time no cloud managed offerings for OTEL Collector exist. - -#### Scenario C: Both. Node operators by default and Celestia team as a best-effort fallback +#### Scenario C: Node operators by default and Celestia team as a fallback ![scenario c](./img/incentivized-testnet-monitoring-scenario-c.png) @@ -208,14 +197,33 @@ Pros - Optionality for node operators who don't want to deploy an OTEL Collector to rely on a best-effort OTEL Collector provided by Celestia team. -Cons: +Cons - This option increases the cognitive load on node operators who now have an additional decision at deployment time. - Increased operational burden on Celestia team during incentivized testnet (and beyond). +#### Scenario D: Celestia team by default and node operators if they want + +The diagram, pros, and cons are the same as scenario C. + +This scenario differs from Scenario C in the docs for node deployment. The docs specify a Celestia team managed OTEL Collector endpoint by default. For node operators who want self-managed telemetry, the docs contain steps on how to setup a node operator managed agent OTEL Collector and how to proxy metrics to the Celestia team managed gateway OTEL Collector. + +The docs may also contain steps on connecting the agent OTEL Collector to Prometheus and Grafana. + ### Should node operators be able to configure celestia-node to export to multiple OTEL collectors? -This is not supported by [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055) and is no longer a concern because we expect celestia-node operators to run their own OTEL Collector agent alongside celestia-node. Under this architecture, node operators are at liberty to configure multiple exporters in OTEL Collector and can therefore export to multiple OTEL collectors by routing traffic through their agent OTEL Collector. +This is not supported by [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055). This means node operators can only configure one OTLP backend for their node. If they wish to export metrics to multiple OTEL Collectors then they must route traffic through an agent OTEL Collector that they have deployed. Their agent OTEL Collector can forward metrics to any other OTEL Collector that they have access to. + +### How to mitigate DDOS attacks against OTEL Collector? + +- [https://medium.com/opentelemetry/securing-your-opentelemetry-collector-1a4f9fa5bd6f](https://medium.com/opentelemetry/securing-your-opentelemetry-collector-1a4f9fa5bd6f) + - Uses an authentication server (Keycloak) to handle OAuth2 between service and remote OTEL Collector. +- [https://medium.com/@michaelericksen_12434/securing-your-opentelemetry-collector-updated-3f9884e37a09](https://medium.com/@michaelericksen_12434/securing-your-opentelemetry-collector-updated-3f9884e37a09) + - Also uses authentication server (Keycloak). Runs services in Docker. + +### How to mitigate DDOS attacks against Prometheus? + +It’s possible to create an API key with the `MetricsPublisher` role on cloud hosted Prometheus. These API keys can be distributed to participants if they are expected to remote write to Prometheus. ### How to send data over HTTPS @@ -270,8 +278,6 @@ In the case where a node operator wants to send data from celestia-node to an OT Official resource requirements are not stated in the OTEL Collector docs. However, [performance benchmarks](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/performance.md#results-without-tail-based-sampling) indicate that OTEL Collector is able to handle up to 10K traces ([units unclear](https://github.com/open-telemetry/opentelemetry-collector/issues/5780)) on 1 CPU and 2GB RAM. Given [light node](https://docs.celestia.org/nodes/light-node#hardware-requirements) runs on 1 CPU and 2GB RAM, it seems feasible to run an OTEL Collector agent on the most resource constrained target hardware. -During mainnet, we won't require nodes to share telemetry data so resource constrained devices won't be obligated to run an OTEL Collector agent indefinitely. - ## Status Proposed @@ -284,3 +290,4 @@ Proposed - - - +- diff --git a/docs/adr/img/incentivized-testnet-monitoring-diagram.png b/docs/adr/img/incentivized-testnet-monitoring-diagram.png deleted file mode 100644 index 6565a52c5fa6f5866cf74d792de9b541ef49ba2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225268 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 #0Ix2 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 IPr^_C2W73wv;Y7A diff --git a/docs/adr/img/incentivized-testnet-monitoring-scenario-b.png b/docs/adr/img/incentivized-testnet-monitoring-scenario-b.png index ab8ab3416497ebf4a9fbf84351d3afc816bb17bc..6d59799c0964f0fa5147cc097f36fbb6d782159b 100644 GIT binary patch delta 76622 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

  • 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 delta 75665 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%0A9ijl z0GwS#RoS>fVlT(W8~{%N@c2=f^K&K;u)2|0VI)o{ib26Of>^zsXbJ}TWg9?%wW2#- zA&&~uCbc+JVP+c*_@D!&{xTDK=qx76g$aO7dFXwq$XWeL@i^zX}N9xR({X9PO$%Zg76dL_*5D zvH1hiN;7I7N$a@kbaV0P^HhKj@;`oe%O*yqk;y0Nd~z6^CEmb3y&S0d%=6~FLHF@u zn2h2ADi?NqZ;Uu1=@PSN(^czU4d}IV-(1tP8BeMd6=heBF!K{T=fJalSy?moW zW5@n()xC6?U#-_1PE|lh*8xxU2!y?}#l~%$KZ73rYcBzR3yS`4{Jzot^-unHKJZVQ z{>S(K>-+zu7XE)%;NRc>zfl1QXn_Be|FMhyx%^+KAoSmuRR7fYf7>AFzcQ=;|5pI| z-`4QITS4gm{Fruao~H=eA;6O(x!D(dr_OEcUFHN8>P*q*KK5&i3rE>m8WM+uOt?4h zjO>R^KUC5!EftwxE(w^~+1VPON`FdWg<_*CHxFv#t`4EiFm>l_UJ$lD1Ps;7RFDdU z6$s>0^$daAzQ2Ed`2~!qH7nUZ!2Wni!~R)*UjM*=;@6~!BeOunp!tInIbdCKYK-8L z+vAB@{;)@UZ`u9P5p?2vpRVS}f7;M6VhDWKuVQCxL`va)M{Zu+YEjPjhJ=|Y` z?OzE_tU)4v3|7!>2WZyi^B(}grj6Y3HcIV101_=iBQufbW%5jUHtH?U&8)Wr+nD92 z3;;UZk6HP6DwGsJ^4rQ-1hFNv0T`WPfQ~LhduJk1TS1-xbY&(|WfuruZUm84p=d@X z(qkLI?2ypjSxCXHzT_$Nt1P4*1e%Y&n+d9?hl9`9v;bcsOw zK|uk;re~Q_U{te0Zi}%E1agpx5NjNXa1OCZEABf}eWd}_YYW&$;BT%ZEwh|ms4QSt zkpV~d06IDwd19+cRVlhW8|k(Uj9ETfJO?Sc?LJ5;j<(D}I&F0$cA$H5klI`QDGTT` zxk&e|V1UrCa*>(afUjb?OfwI;8;R^IGss8o+_KSr%R;2!HV~2DkB%rria|_y$a0l+ z{8VYe3nF*(&Pi(dX~{23OG`f%8GzL=E{|t$VBl!4OzyT_ZiLm}=_r$0S+;Z)ps_7e z+d#gW%tyzroFQlm#FPsV6mzdyI`n`%9GIF$Ug8%Cjq=SNH8Ynwx!`j4E~vloVvP`S8`ZxBE;;EUJ2_in_AlwU^K>*^ex4 z5zT%m-y@zgO^oeXsQP#d8Zg$9RT}}&X+zivJ|6>Yus7@oO^k*<-x)+L?24QAZW>~}G=sv}4H)EC#7;{x~ z;{*dB6(Q36MYaSI`xEZVA0A&kZSXkrl5~2*J(Z3cSuas3L#sNP+rhN9PpY#DPnW(h z0uv68^cL(MQv6NOESI)PAc{zpZ0S3mkZyJU{fqL*!YE^o{^d-fndSDp5SLlnxatdx z5hQ~4Q?J6^w&;}~F`P(hm8DV#)TW!A>eK|2dVC7hQ{p^$}JNP zWUAc%;-L_k)$!VjXaSkTJ?@{-ZBu$b{S%-Hsjt7bN@bNPqVf?yY zQ`-ucF=@sRejQs&Uf+g-7MZ%hmD7LzxrZ%|@{F$L{|q&+Qyb@D`~a)i6idL}=<4m3Uv%*$tl?$P%c1$3zJ^mn+V>xO-IsHJh)UOLUa8Id&FqVO- z@*kZLYd(ofE}GeW^%hRO&y8L0=2TRV)bg{&5KqF&8R6LWN4IF#{9{44Hq9F>CD5#5 z3j;9;xNKM1Q_J!GmDSDJZSetmZlyC7a&nmF+0%1}74{p*zyM32CGl+A zjK**dS$$=dU^`a+9UXGm(TqYb_V292;_7WN@y7on?KrLKP z>6(5tV*ISyjqaxSt3QgliERPrlTW&(R2a!va$ZsP$-5=_)wntaUn=k7)RSdZ>Sero z+QIH{!Gr{XmoLphxS%uUz2B%BWO4=U2t!oWf_@-Mx4PN%oc8ASE!ANkq@>IW`l}z| z%1UYcg0sn;f6*HyN5J_hUyX_L3si!%x@j9w_#>fvHVy`7%T@VfeA$?%obU{`FmNjv zxP7iWY8rMTI~)Gy$4VhP|13Ei*Cd>hQ;1Xju2wH~xE)IR*y<^%%FuioI48vb8=LWw zY>oC|`P#{}($=B**)p_b2TxfAjRy?^T4w@Azqg(VQK zWpZWOkYeu-0zDh~#KLR$`^`?MSZ%MPkM#0kT&h9ebNZn$70V*7@pXS>9f`7-Q~5C; z5(aP^QBYlSw|m`&ezPfSJHLT(YNPY%T!fc>R%vn_KOZw3np!Zofd5D5Dkqjt9oV38W#LcO5l>e6(ej(bZj! zbKG-!PETFxOLIY6TaK(QpCx;Vx!&(c8qcb$r5?sj&`q1vhVs_uAY^`vsn6~b+&ih) zs}`;ukfEA-PBjK)Q{B}DPT8gHJ1CuW08_9|i`up|p!^x@y!ex=3*YDt;;HB8CU~KX z*l^(oA8wy}Zkrd8#mDIA=$KO`7Obh_a>5B!E^U%;QSANAuU$p}8eUe!xJTZb><y zGMMyl^2>PWd`j(J&c`PXa+AB`@hhPsNMb9|KCFY>aT?H^A{f zT%$T*{KI>qM#K%1um~9I?MHDH&~3ND+%IRQYa^SoigfZg^E^v`u70kNJN&1WS%vNb zuk_L-w?xU`_+jtyTJ9`*#nV#h6x=o=!g3+pJdb#je%X znemK2Nm%cpdI{qVDJSTJ2cVi5K9{Cu{EW4|tn;ner11Q?x@f$??)Xl zjhS{nYY~VNyw*Z`ryJE8&!+l=Lb-PCnR#xG^^4JQasVP`cIiRA_Rr>?kQn-OU4`s&q4YUTL>T9odbKI0^TZ!FN=KVgdS19H50>Kv@6MJbTz!`;mk<^x zrb%#_TOMwTmn;dGs413gZ#U_PYIA#P=&G?;IddZGPr=I${JMeY8&-CH30Z++u5-n+ zmpanIa~w&<*$?AcHPYXeDT<(=-eI%N7jJS1`K>VFm_d~(J%Gz!`r6p|X zpylr@j*_2uzAP5Gc4!;n%NJGy$(W0KpXzqsbCWf_ZtIjpb1^A))lNYlv*HoflHt66 zJT5SC*8gZ8blm-D)Z&V==Ixt2m~cUo+uX|*Z|dD-D9f?L?T*l$YkSY`FV38n!F%NP zceA~+;&n}0l<`O*((+tW#G?rdAaFcjImNC{p#wM?mOl8j`iuMVSalHR>7noWwKaZhOx^s}bazSwLw$ACEcmyQ*t-wVa@ zbm=+a#&-7}vatztJ@KhW5^z6*Qhx`Sn&2|lB6#!a$NZlc79O9>WF_daaf}DkdrH~d zel)HSbgbCbe#H^Bay(;?z1n`fnat1d2+w-N;x^bq=x-`?>&3dXyBM7npY^_-+F~&d zCB5%>uX^?#m`*8*QEBbNQg;)Sdb1p|B1FHneI+X%7EF%eZVVSj-frsCeSn?E;S@>% zwemv#{(e2(fmVfE?YDSw^Cs3-nO1peIXOA~jwM0w6*-!+tgTcBT0QUj_$SQc+AlD8 zU5(k�E5%tHlR2Zk5OKiC) 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 diff --git a/docs/adr/img/incentivized-testnet-monitoring-scenario-c.png b/docs/adr/img/incentivized-testnet-monitoring-scenario-c.png index 7dafa60c32a43c583c3f6b95e3fae936afbbd8c0..14c8dc5b4bfb47fbc3d863ccf4e8d4cf3f44fc3c 100644 GIT binary patch literal 266205 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

    -eEeqGE#0TR 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

    MfF>BfMlbrk{rBhW~P%o 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 literal 246633 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�{>|@7Iwbr`Gm?}uI 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�p#=cQQkkD^B$uv$eK`3S!Y?8%f7B*m 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 literal 0 HcmV?d00001 From 98e8311c7d7a68ea40a725c1f2551dd38bb4fff5 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 30 Sep 2022 18:42:23 +0300 Subject: [PATCH 0116/1008] Update docs/adr/adr-012-daser-parallelization.md Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- docs/adr/adr-012-daser-parallelization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/adr-012-daser-parallelization.md b/docs/adr/adr-012-daser-parallelization.md index 9882351c0b..4882ae2027 100644 --- a/docs/adr/adr-012-daser-parallelization.md +++ b/docs/adr/adr-012-daser-parallelization.md @@ -11,7 +11,7 @@ ## Context DAS is the process of verifying the availability of -block data by sampling chunks or shares of those blocks. The `das` package implements an engine to continuously ensure the availability of the chain's block data via the `Availability` interface. +block data by sampling chunks or shares of those blocks. The `das` package implements an engine to ensure the availability of the chain's block data via the `Availability` interface. Verifying the availability of block data is a priority functionality for celestia-node. Its performance could benefit significantly from parallelization optimisation to make it able to fully utilise network bandwidth. ## Previous approach From 2adf45d18ef0286d0278acb24014f56053f0e9cb Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 5 Oct 2022 18:01:01 +0300 Subject: [PATCH 0117/1008] Apply suggestions from code review Co-authored-by: CHAMI Rachid Co-authored-by: Ismail Khoffi --- docs/adr/adr-012-daser-parallelization.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/adr/adr-012-daser-parallelization.md b/docs/adr/adr-012-daser-parallelization.md index 4882ae2027..877dcae278 100644 --- a/docs/adr/adr-012-daser-parallelization.md +++ b/docs/adr/adr-012-daser-parallelization.md @@ -10,8 +10,7 @@ ## Context -DAS is the process of verifying the availability of -block data by sampling chunks or shares of those blocks. The `das` package implements an engine to ensure the availability of the chain's block data via the `Availability` interface. +DAS is the process of verifying the availability of block data by sampling chunks or shares of those blocks. The `das` package implements an engine to ensure the availability of the chain's block data via the `Availability` interface. Verifying the availability of block data is a priority functionality for celestia-node. Its performance could benefit significantly from parallelization optimisation to make it able to fully utilise network bandwidth. ## Previous approach @@ -50,7 +49,7 @@ Sampling flow: ## Concurrency limit The maximum amount of concurrently running workers is defined by the const `concurrencyLimit` = 16. This value is an approximation that came from the first basic performance tests. -During the test, samples/sec rate was observed with moving average over 30 sec window for period of 5min. The metric was triggered only by sampled header with width > 2. +During the test, samples/sec rate was observed with moving average over 30 sec window for a period of 5min. The metric was triggered only by a sampled header with width > 2. ```text amount of workers: 8, speed: 8.66 @@ -63,7 +62,7 @@ amount of workers: 64, speed: 11.83 ``` Based on basic experiment results, values higher than 16 don’t bring much benefit. At the same time, increased parallelization comes with a cost of higher memory consumption. -Future improvements discussed later. +Future improvements will discussed later and are out of scope of this ADR. ## Status @@ -71,7 +70,7 @@ Implemented ## Future plans -Several params values that come hardcoded in DASer (`samplingRange`, `concurrencyLimit`, `priorityQueueSize`, `genesisHeight`, `backgroundStoreInterval`) should become configurable so the node runner can define them based on the specific node setup. Default values should be optimized by performance testing for most common setups and could potentially vary for different node types. +Several params values that come hardcoded in DASer (`samplingRange`, `concurrencyLimit`, `priorityQueueSize`, `genesisHeight`, `backgroundStoreInterval`) should become configurable, so the node runner can define them based on the specific node setup. Default values should be optimized by performance testing for most common setups, and could potentially vary for different node types. ## References From 76a5aef000200270c0b1a95b51c2c6502bd704ae Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:01:44 +0300 Subject: [PATCH 0118/1008] Update docs/adr/adr-012-daser-parallelization.md Co-authored-by: Hlib Kanunnikov --- docs/adr/adr-012-daser-parallelization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/adr-012-daser-parallelization.md b/docs/adr/adr-012-daser-parallelization.md index 877dcae278..a1f3af885b 100644 --- a/docs/adr/adr-012-daser-parallelization.md +++ b/docs/adr/adr-012-daser-parallelization.md @@ -62,7 +62,7 @@ amount of workers: 64, speed: 11.83 ``` Based on basic experiment results, values higher than 16 don’t bring much benefit. At the same time, increased parallelization comes with a cost of higher memory consumption. -Future improvements will discussed later and are out of scope of this ADR. +Future improvements will be discussed later and are out of the scope of this ADR. ## Status From 23c77e8684a7bfd488568256037198f585910683 Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Fri, 7 Oct 2022 17:17:31 +0300 Subject: [PATCH 0119/1008] feat(nodebuilder): add AccountAddress to state's Module (#1209) Getting address from node's account This is a handy feature that testground's tests are already utilising in upcoming PFD test-plans Ref: #1056 #506 External Ref: https://github.com/celestiaorg/test-infra/issues/33 --- nodebuilder/state/service.go | 2 ++ state/core_access.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/nodebuilder/state/service.go b/nodebuilder/state/service.go index 5da7b872a1..63c10bf87b 100644 --- a/nodebuilder/state/service.go +++ b/nodebuilder/state/service.go @@ -18,6 +18,8 @@ type Module interface { // IsStopped checks if the Module's context has been stopped IsStopped() bool + // AccountAddress retrieves the address of the node's account/signer + AccountAddress(ctx context.Context) (state.Address, error) // Balance retrieves the Celestia coin balance for the node's account/signer // and verifies it against the corresponding block's AppHash. Balance(ctx context.Context) (*state.Balance, error) diff --git a/state/core_access.go b/state/core_access.go index af1e624105..617d8c3081 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -139,6 +139,14 @@ func (ca *CoreAccessor) SubmitPayForData( return payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim) } +func (ca *CoreAccessor) AccountAddress(ctx context.Context) (Address, error) { + addr, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + return addr, nil +} + func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { addr, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { From 661e61bff2861591a2642f21dff66bb8a14a13f0 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 10 Oct 2022 11:31:19 +0200 Subject: [PATCH 0120/1008] chore: disable debug log in Makefile (#1210) --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index b9e1f50cf2..ea3c594f58 100644 --- a/Makefile +++ b/Makefile @@ -82,25 +82,25 @@ lint: ## test-unit: Running unit tests test-unit: @echo "--> Running unit tests" - @go test -v `go list ./... | grep -v nodebuilder/tests` -covermode=atomic -coverprofile=coverage.out + @go test `go list ./... | grep -v nodebuilder/tests` -covermode=atomic -coverprofile=coverage.out .PHONY: test-unit ## test-unit-race: Running unit tests with data race detector test-unit-race: @echo "--> Running unit tests with data race detector" - @go test -v -race `go list ./... | grep -v nodebuilder/tests` + @go test -race `go list ./... | grep -v nodebuilder/tests` .PHONY: test-unit-race ## test-swamp: Running swamp tests located in nodebuilder/tests test-swamp: @echo "--> Running swamp tests" - @go test -v ./nodebuilder/tests + @go test ./nodebuilder/tests .PHONY: test-swamp ## test-swamp: Running swamp tests with data race detector located in node/tests test-swamp-race: @echo "--> Running swamp tests with data race detector" - @go test -v -race ./nodebuilder/tests + @go test -race ./nodebuilder/tests .PHONY: test-swamp-race ## test-all: Running both unit and swamp tests From 32864cd35a8be4a1182a7d7b676410c97a2dc922 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 10 Oct 2022 12:48:15 +0200 Subject: [PATCH 0121/1008] fix(nodebuilder/header): Only provide `p2p.Subscriber` as `p2p.Broadcaster` for `bridge` node (#1203) Fixing a remnant from #997 -- only **bridge** node requires a `p2p.Broadcaster` as it's the only node type that can broadcast to `header-sub`. --- nodebuilder/header/module.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index c726ef0aca..06b3ccea89 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -41,9 +41,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(func(subscriber *p2p.Subscriber) header.Subscriber { return subscriber }), - fx.Provide(func(subscriber *p2p.Subscriber) header.Broadcaster { - return subscriber - }), fx.Provide(fx.Annotate( sync.NewSyncer, fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, syncer *sync.Syncer) error { @@ -75,7 +72,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return sub.Stop(ctx) }), )), - fx.Provide(fx.Annotate( p2p.NewExchangeServer, fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer) error { @@ -98,6 +94,9 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "header", baseComponents, + fx.Provide(func(subscriber *p2p.Subscriber) header.Broadcaster { + return subscriber + }), fx.Supply(header.MakeExtendedHeader), ) default: From 18860dfd9a10bc214ed40ae08ada4ed63bf47aa3 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 10 Oct 2022 13:49:33 +0300 Subject: [PATCH 0122/1008] feat!(header/p2p): add deadlines on stream and rework proto (#1038) Resolves: * #635 * #197 * #366 (n/a) --- header/p2p/exchange.go | 29 +++- header/p2p/exchange_test.go | 5 +- header/p2p/pb/extended_header_request.pb.go | 147 +++++++++++++++----- header/p2p/pb/extended_header_request.proto | 6 +- header/p2p/server.go | 33 ++++- 5 files changed, 169 insertions(+), 51 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index c0559802fe..55abceaec4 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "math/rand" + "time" logging "github.com/ipfs/go-log/v2" @@ -23,11 +24,18 @@ import ( var log = logging.Logger("header/p2p") +const ( + // writeDeadline sets timeout for sending messages to the stream + writeDeadline = time.Second * 5 + // readDeadline sets timeout for reading messages from the stream + readDeadline = time.Minute +) + // PubSubTopic hardcodes the name of the ExtendedHeader // gossipsub topic. const PubSubTopic = "header-sub" -var exchangeProtocolID = protocol.ID(fmt.Sprintf("/header-ex/v0.0.2/%s", params.DefaultNetwork())) +var exchangeProtocolID = protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", params.DefaultNetwork())) // Exchange enables sending outbound ExtendedHeaderRequests to the network as well as // handling inbound ExtendedHeaderRequests from the network. @@ -50,7 +58,7 @@ func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { log.Debug("requesting head") // create request req := &p2p_pb.ExtendedHeaderRequest{ - Origin: uint64(0), + Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: uint64(0)}, Amount: 1, } headers, err := ex.performRequest(ctx, req) @@ -71,7 +79,7 @@ func (ex *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.Ext } // create request req := &p2p_pb.ExtendedHeaderRequest{ - Origin: height, + Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: height}, Amount: 1, } headers, err := ex.performRequest(ctx, req) @@ -87,7 +95,7 @@ func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( log.Debugw("requesting headers", "from", from, "to", from+amount) // create request req := &p2p_pb.ExtendedHeaderRequest{ - Origin: from, + Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: from}, Amount: amount, } return ex.performRequest(ctx, req) @@ -99,7 +107,7 @@ func (ex *Exchange) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.Ext log.Debugw("requesting header", "hash", hash.String()) // create request req := &p2p_pb.ExtendedHeaderRequest{ - Hash: hash.Bytes(), + Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: hash.Bytes()}, Amount: 1, } headers, err := ex.performRequest(ctx, req) @@ -131,22 +139,31 @@ func (ex *Exchange) performRequest( if err != nil { return nil, err } + if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + log.Debugf("error setting deadline: %s", err) + } // send request _, err = serde.Write(stream, req) if err != nil { stream.Reset() //nolint:errcheck return nil, err } + err = stream.CloseWrite() + if err != nil { + log.Error(err) + } // read responses headers := make([]*header.ExtendedHeader, req.Amount) for i := 0; i < int(req.Amount); i++ { resp := new(header_pb.ExtendedHeader) + if err = stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { + log.Debugf("error setting deadline: %s", err) + } _, err := serde.Read(stream, resp) if err != nil { stream.Reset() //nolint:errcheck return nil, err } - header, err := header.ProtoToExtendedHeader(resp) if err != nil { stream.Reset() //nolint:errcheck diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index c97cf5f4f2..a2c28d6194 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -13,10 +13,11 @@ import ( tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" header_pb "github.com/celestiaorg/celestia-node/header/pb" - "github.com/celestiaorg/go-libp2p-messenger/serde" ) func TestExchange_RequestHead(t *testing.T) { @@ -77,7 +78,7 @@ func TestExchange_RequestByHash(t *testing.T) { // create request for a header at a random height reqHeight := store.headHeight - 2 req := &p2p_pb.ExtendedHeaderRequest{ - Hash: store.headers[reqHeight].Hash(), + Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: store.headers[reqHeight].Hash()}, Amount: 1, } // send request diff --git a/header/p2p/pb/extended_header_request.pb.go b/header/p2p/pb/extended_header_request.pb.go index 0a427b2eb3..7941114de8 100644 --- a/header/p2p/pb/extended_header_request.pb.go +++ b/header/p2p/pb/extended_header_request.pb.go @@ -23,9 +23,11 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type ExtendedHeaderRequest struct { - Origin uint64 `protobuf:"varint,1,opt,name=origin,proto3" json:"origin,omitempty"` - Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` - Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + // Types that are valid to be assigned to Data: + // *ExtendedHeaderRequest_Origin + // *ExtendedHeaderRequest_Hash + Data isExtendedHeaderRequest_Data `protobuf_oneof:"data"` + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` } func (m *ExtendedHeaderRequest) Reset() { *m = ExtendedHeaderRequest{} } @@ -61,16 +63,39 @@ func (m *ExtendedHeaderRequest) XXX_DiscardUnknown() { var xxx_messageInfo_ExtendedHeaderRequest proto.InternalMessageInfo -func (m *ExtendedHeaderRequest) GetOrigin() uint64 { +type isExtendedHeaderRequest_Data interface { + isExtendedHeaderRequest_Data() + MarshalTo([]byte) (int, error) + Size() int +} + +type ExtendedHeaderRequest_Origin struct { + Origin uint64 `protobuf:"varint,1,opt,name=origin,proto3,oneof" json:"origin,omitempty"` +} +type ExtendedHeaderRequest_Hash struct { + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3,oneof" json:"hash,omitempty"` +} + +func (*ExtendedHeaderRequest_Origin) isExtendedHeaderRequest_Data() {} +func (*ExtendedHeaderRequest_Hash) isExtendedHeaderRequest_Data() {} + +func (m *ExtendedHeaderRequest) GetData() isExtendedHeaderRequest_Data { if m != nil { - return m.Origin + return m.Data + } + return nil +} + +func (m *ExtendedHeaderRequest) GetOrigin() uint64 { + if x, ok := m.GetData().(*ExtendedHeaderRequest_Origin); ok { + return x.Origin } return 0 } func (m *ExtendedHeaderRequest) GetHash() []byte { - if m != nil { - return m.Hash + if x, ok := m.GetData().(*ExtendedHeaderRequest_Hash); ok { + return x.Hash } return nil } @@ -82,6 +107,14 @@ func (m *ExtendedHeaderRequest) GetAmount() uint64 { return 0 } +// XXX_OneofWrappers is for the internal use of the proto package. +func (*ExtendedHeaderRequest) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*ExtendedHeaderRequest_Origin)(nil), + (*ExtendedHeaderRequest_Hash)(nil), + } +} + func init() { proto.RegisterType((*ExtendedHeaderRequest)(nil), "p2p.pb.ExtendedHeaderRequest") } @@ -91,18 +124,19 @@ func init() { } var fileDescriptor_ea2a1467b965216e = []byte{ - // 168 bytes of a gzipped FileDescriptorProto + // 183 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xce, 0x48, 0x4d, 0x4c, 0x49, 0x2d, 0xd2, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x87, 0x08, 0xc7, 0x17, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0xe8, 0x15, 0x14, - 0xe5, 0x97, 0xe4, 0x0b, 0xb1, 0x15, 0x18, 0x15, 0xe8, 0x15, 0x24, 0x29, 0x45, 0x73, 0x89, 0xba, - 0x42, 0x15, 0x7a, 0x80, 0xd5, 0x05, 0x41, 0x94, 0x09, 0x89, 0x71, 0xb1, 0xe5, 0x17, 0x65, 0xa6, - 0x67, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x04, 0x41, 0x79, 0x42, 0x42, 0x5c, 0x2c, 0x19, - 0x89, 0xc5, 0x19, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x60, 0x36, 0x48, 0x6d, 0x62, 0x6e, - 0x7e, 0x69, 0x5e, 0x89, 0x04, 0x33, 0x44, 0x2d, 0x84, 0xe7, 0x24, 0x71, 0xe2, 0x91, 0x1c, 0xe3, - 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, - 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x57, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, - 0x58, 0x4e, 0x0f, 0x06, 0xb4, 0x00, 0x00, 0x00, + 0xe5, 0x97, 0xe4, 0x0b, 0xb1, 0x15, 0x18, 0x15, 0xe8, 0x15, 0x24, 0x29, 0xa5, 0x73, 0x89, 0xba, + 0x42, 0x15, 0x7a, 0x80, 0xd5, 0x05, 0x41, 0x94, 0x09, 0x49, 0x70, 0xb1, 0xe5, 0x17, 0x65, 0xa6, + 0x67, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x78, 0x30, 0x04, 0x41, 0xf9, 0x42, 0x22, 0x5c, + 0x2c, 0x19, 0x89, 0xc5, 0x19, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x1e, 0x0c, 0x41, 0x60, 0x9e, + 0x90, 0x18, 0x17, 0x5b, 0x62, 0x6e, 0x7e, 0x69, 0x5e, 0x89, 0x04, 0x33, 0x48, 0x7d, 0x10, 0x94, + 0xe7, 0xc4, 0xc6, 0xc5, 0x92, 0x92, 0x58, 0x92, 0xe8, 0x24, 0x71, 0xe2, 0x91, 0x1c, 0xe3, 0x85, + 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, + 0x8d, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x17, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x45, + 0xbf, 0x56, 0x8c, 0xc0, 0x00, 0x00, 0x00, } func (m *ExtendedHeaderRequest) Marshal() (dAtA []byte, err error) { @@ -130,21 +164,46 @@ func (m *ExtendedHeaderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - if len(m.Hash) > 0 { + if m.Data != nil { + { + size := m.Data.Size() + i -= size + if _, err := m.Data.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *ExtendedHeaderRequest_Origin) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExtendedHeaderRequest_Origin) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(m.Origin)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} +func (m *ExtendedHeaderRequest_Hash) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExtendedHeaderRequest_Hash) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Hash != nil { i -= len(m.Hash) copy(dAtA[i:], m.Hash) i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0x12 } - if m.Origin != 0 { - i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(m.Origin)) - i-- - dAtA[i] = 0x8 - } return len(dAtA) - i, nil } - func encodeVarintExtendedHeaderRequest(dAtA []byte, offset int, v uint64) int { offset -= sovExtendedHeaderRequest(v) base := offset @@ -162,12 +221,8 @@ func (m *ExtendedHeaderRequest) Size() (n int) { } var l int _ = l - if m.Origin != 0 { - n += 1 + sovExtendedHeaderRequest(uint64(m.Origin)) - } - l = len(m.Hash) - if l > 0 { - n += 1 + l + sovExtendedHeaderRequest(uint64(l)) + if m.Data != nil { + n += m.Data.Size() } if m.Amount != 0 { n += 1 + sovExtendedHeaderRequest(uint64(m.Amount)) @@ -175,6 +230,28 @@ func (m *ExtendedHeaderRequest) Size() (n int) { return n } +func (m *ExtendedHeaderRequest_Origin) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovExtendedHeaderRequest(uint64(m.Origin)) + return n +} +func (m *ExtendedHeaderRequest_Hash) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Hash != nil { + l = len(m.Hash) + n += 1 + l + sovExtendedHeaderRequest(uint64(l)) + } + return n +} + func sovExtendedHeaderRequest(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -214,7 +291,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Origin", wireType) } - m.Origin = 0 + var v uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExtendedHeaderRequest @@ -224,11 +301,12 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Origin |= uint64(b&0x7F) << shift + v |= uint64(b&0x7F) << shift if b < 0x80 { break } } + m.Data = &ExtendedHeaderRequest_Origin{v} case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) @@ -258,10 +336,9 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) - if m.Hash == nil { - m.Hash = []byte{} - } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Data = &ExtendedHeaderRequest_Hash{v} iNdEx = postIndex case 3: if wireType != 0 { diff --git a/header/p2p/pb/extended_header_request.proto b/header/p2p/pb/extended_header_request.proto index 8ae8800c58..c91a86dc00 100644 --- a/header/p2p/pb/extended_header_request.proto +++ b/header/p2p/pb/extended_header_request.proto @@ -3,7 +3,9 @@ syntax = "proto3"; package p2p.pb; message ExtendedHeaderRequest { - uint64 origin = 1; - bytes hash = 2; + oneof data { + uint64 origin = 1; + bytes hash = 2; + } uint64 amount = 3; } diff --git a/header/p2p/server.go b/header/p2p/server.go index 98747fd228..44bde05b35 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -2,15 +2,17 @@ package p2p import ( "context" + "time" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" - "github.com/celestiaorg/go-libp2p-messenger/serde" ) // ExchangeServer represents the server-side component for @@ -52,19 +54,31 @@ func (serv *ExchangeServer) Stop(context.Context) error { // requestHandler handles inbound ExtendedHeaderRequests. func (serv *ExchangeServer) requestHandler(stream network.Stream) { + err := stream.SetReadDeadline(time.Now().Add(readDeadline)) + if err != nil { + log.Debugf("error setting deadline: %s", err) + } // unmarshal request pbreq := new(p2p_pb.ExtendedHeaderRequest) - _, err := serde.Read(stream, pbreq) + _, err = serde.Read(stream, pbreq) if err != nil { log.Errorw("server: reading header request from stream", "err", err) stream.Reset() //nolint:errcheck return } + if err = stream.CloseRead(); err != nil { + log.Error(err) + } // retrieve and write ExtendedHeaders - if pbreq.Hash != nil { - serv.handleRequestByHash(pbreq.Hash, stream) - } else { - serv.handleRequest(pbreq.Origin, pbreq.Origin+pbreq.Amount, stream) + switch pbreq.Data.(type) { + case *p2p_pb.ExtendedHeaderRequest_Hash: + serv.handleRequestByHash(pbreq.GetHash(), stream) + case *p2p_pb.ExtendedHeaderRequest_Origin: + serv.handleRequest(pbreq.GetOrigin(), pbreq.GetOrigin()+pbreq.Amount, stream) + default: + log.Error("server: invalid data type received") + stream.Reset() //nolint:errcheck + return } err = stream.Close() @@ -90,6 +104,9 @@ func (serv *ExchangeServer) handleRequestByHash(hash []byte, stream network.Stre stream.Reset() //nolint:errcheck return } + if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + log.Debugf("error setting deadline: %s", err) + } _, err = serde.Write(stream, resp) if err != nil { log.Errorw("server: writing header to stream", "hash", tmbytes.HexBytes(hash).String(), "err", err) @@ -124,8 +141,12 @@ func (serv *ExchangeServer) handleRequest(from, to uint64, stream network.Stream } headers = headersByRange } + // write all headers to stream for _, h := range headers { + if err := stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + log.Debugf("error setting deadline: %s", err) + } resp, err := header.ExtendedHeaderToProto(h) if err != nil { log.Errorw("server: marshaling header to proto", "height", h.Height, "err", err) From 8892d3c1da44aef87e2711317976eee8ba0b4d4f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 11 Oct 2022 14:58:46 +0200 Subject: [PATCH 0123/1008] chore(nodebuilder/header): remove dead metrics code (#1217) --- nodebuilder/header/opts.go | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 nodebuilder/header/opts.go diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go deleted file mode 100644 index 95524056e8..0000000000 --- a/nodebuilder/header/opts.go +++ /dev/null @@ -1,16 +0,0 @@ -package header - -import ( - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/header" -) - -// TODO: Eventually we should have a per-module metrics option. -// WithMetrics enables metrics exporting for the node. -func WithMetrics(enable bool) fx.Option { - if !enable { - return fx.Options() - } - return fx.Options(fx.Invoke(header.WithMetrics)) -} From cf3637cd5c09973cac0e146ea8ad2d5e4eac6bc1 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 11 Oct 2022 16:02:34 +0200 Subject: [PATCH 0124/1008] fix(nodebuider/header): initialize Store during start lifecycle instead of fx.Invoke (#1218) Here we solve two issues: * Tests and red CI without downgrading FX while keeping node modules construction order independent * The recent FX update(to 1.18.2) changed the order of fx.Invoke executions and uncovered an issue with order between connection to a trusted peer and joining the header-sub topic. Connection to the trusted peer happens implicitly during store initialization, and initialization happens during Invoke, hence it's ordering was changed after the update. * Node constructor free from network calls * store.Init does a network call, and it was invoked during node construction. Now, we do this only during the Start, where network calls are acceptable. Store initialization during the Start lifecycle goes after the Syncer is started, causing Syncer to error, as it requires initialized Store. To overcome this a private `initStore` proxy type is introduced, which explicitly chains the order of fx provides and, subsequently, Starts. --- nodebuilder/header/header.go | 54 ++++++++++++++++++++++++++---------- nodebuilder/header/module.go | 6 ++-- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 5c6f307ed2..caeb2486d8 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -2,19 +2,22 @@ package header import ( "context" + "time" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" + "go.uber.org/fx" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/params" ) -// P2PExchange constructs new Exchange for headers. -func P2PExchange(cfg Config) func(params.Bootstrappers, host.Host) (header.Exchange, error) { +// newP2PExchange constructs new Exchange for headers. +func newP2PExchange(cfg Config) func(params.Bootstrappers, host.Host) (header.Exchange, error) { return func(bpeers params.Bootstrappers, host host.Host) (header.Exchange, error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { @@ -29,22 +32,43 @@ func P2PExchange(cfg Config) func(params.Bootstrappers, host.Host) (header.Excha } } -// InitStore initializes the store. -func InitStore(ctx context.Context, cfg Config, net params.Network, s header.Store, ex header.Exchange) error { +// newSyncer constructs new Syncer for headers. +func newSyncer(ex header.Exchange, store initStore, sub header.Subscriber, duration time.Duration) *sync.Syncer { + return sync.NewSyncer(ex, store, sub, duration) +} + +// initStore is a type representing initialized header store. +// NOTE: It is needed to ensure that Store is always initialized before Syncer is started. +type initStore header.Store + +// newInitStore constructs an initialized store +func newInitStore( + lc fx.Lifecycle, + cfg Config, + net params.Network, + s header.Store, + ex header.Exchange, +) (initStore, error) { trustedHash, err := cfg.trustedHash(net) if err != nil { - return err + return nil, err } - err = store.Init(ctx, s, ex, trustedHash) - if err != nil { - // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. - // This is due to requesting step of initialization, which fetches initial Header by trusted hash from - // the network. The step can't be done during unit tests and fixing it would require either - // * Having some test/dev/offline mode for Node that mocks out all the networking - // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step - log.Errorf("initializing store failed: %s", err) - } + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + err = store.Init(ctx, s, ex, trustedHash) + if err != nil { + // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. + // This is due to requesting step of initialization, which fetches initial Header by trusted hash from + // the network. The step can't be done during unit tests and fixing it would require either + // * Having some test/dev/offline mode for Node that mocks out all the networking + // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step + // * Or removing explicit initialization in favor of automated initialization by Syncer + log.Errorf("initializing store failed: %s", err) + } + return nil + }, + }) - return nil + return s, nil } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 06b3ccea89..f9d71f5ed1 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -37,12 +37,12 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return store.Stop(ctx) }), )), - fx.Invoke(InitStore), + fx.Provide(newInitStore), fx.Provide(func(subscriber *p2p.Subscriber) header.Subscriber { return subscriber }), fx.Provide(fx.Annotate( - sync.NewSyncer, + newSyncer, fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, syncer *sync.Syncer) error { syncerStartFunc := func(ctx context.Context) error { err := syncer.Start(ctx) @@ -88,7 +88,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "header", baseComponents, - fx.Provide(P2PExchange(*cfg)), + fx.Provide(newP2PExchange(*cfg)), ) case node.Bridge: return fx.Module( From e89b628319cb33330488e42b29d7dfde94166d60 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 11 Oct 2022 17:15:45 +0300 Subject: [PATCH 0125/1008] header/p2p: request head from multiple peers (#1046) Resolves: * https://github.com/celestiaorg/celestia-node/issues/980 * https://github.com/celestiaorg/celestia-node/issues/1019 --- header/p2p/exchange.go | 82 +++++++++++++++++++++++++++++++++---- header/p2p/exchange_test.go | 65 +++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 7 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 55abceaec4..a488fc6f97 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -5,10 +5,10 @@ import ( "context" "fmt" "math/rand" + "sort" "time" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" @@ -29,6 +29,8 @@ const ( writeDeadline = time.Second * 5 // readDeadline sets timeout for reading messages from the stream readDeadline = time.Minute + // the target minimum amount of responses with the same chain head + minResponses = 2 ) // PubSubTopic hardcodes the name of the ExtendedHeader @@ -61,11 +63,35 @@ func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: uint64(0)}, Amount: 1, } - headers, err := ex.performRequest(ctx, req) - if err != nil { - return nil, err + + headerCh := make(chan *header.ExtendedHeader) + // request head from each trusted peer + for _, from := range ex.trustedPeers { + go func(from peer.ID) { + headers, err := request(ctx, from, ex.host, req) + if err != nil { + log.Errorw("head request to trusted peer failed", "trustedPeer", from, "err", err) + headerCh <- nil + return + } + // doRequest ensures that the result slice will have at least one ExtendedHeader + headerCh <- headers[0] + }(from) + } + + result := make([]*header.ExtendedHeader, 0, len(ex.trustedPeers)) + for range ex.trustedPeers { + select { + case h := <-headerCh: + if h != nil { + result = append(result, h) + } + case <-ctx.Done(): + return nil, ctx.Err() + } } - return headers[0], nil + + return bestHead(result) } // GetByHeight performs a request for the ExtendedHeader at the given @@ -135,7 +161,17 @@ func (ex *Exchange) performRequest( //nolint:gosec // G404: Use of weak random number generator index := rand.Intn(len(ex.trustedPeers)) - stream, err := ex.host.NewStream(ctx, ex.trustedPeers[index], exchangeProtocolID) + return request(ctx, ex.trustedPeers[index], ex.host, req) +} + +// request sends the ExtendedHeaderRequest to a remote peer. +func request( + ctx context.Context, + to peer.ID, + host host.Host, + req *p2p_pb.ExtendedHeaderRequest, +) ([]*header.ExtendedHeader, error) { + stream, err := host.NewStream(ctx, to, exchangeProtocolID) if err != nil { return nil, err } @@ -172,9 +208,41 @@ func (ex *Exchange) performRequest( headers[i] = header } + if err = stream.Close(); err != nil { + log.Errorw("closing stream", "err", err) + } // ensure at least one header was retrieved if len(headers) == 0 { return nil, header.ErrNotFound } - return headers, stream.Close() + return headers, nil +} + +// bestHead chooses ExtendedHeader that matches the conditions: +// * should have max height among received; +// * should be received at least from 2 peers; +// If neither condition is met, then latest ExtendedHeader will be returned (header of the highest height). +func bestHead(result []*header.ExtendedHeader) (*header.ExtendedHeader, error) { + if len(result) == 0 { + return nil, header.ErrNotFound + } + counter := make(map[string]int) + // go through all of ExtendedHeaders and count the number of headers with a specific hash + for _, res := range result { + counter[res.Hash().String()]++ + } + // sort results in a decreasing order + sort.Slice(result, func(i, j int) bool { + return result[i].Height > result[j].Height + }) + + // try to find ExtendedHeader with the maximum height that was received at least from 2 peers + for _, res := range result { + if counter[res.Hash().String()] >= minResponses { + return res, nil + } + } + log.Debug("could not find latest header received from at least two peers, returning header with the max height") + // otherwise return header with the max height + return result[0], nil } diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index a2c28d6194..af07e6ac12 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -96,6 +96,71 @@ func TestExchange_RequestByHash(t *testing.T) { assert.Equal(t, store.headers[reqHeight].Hash(), eh.Hash()) } +func Test_bestHead(t *testing.T) { + gen := func() []*header.ExtendedHeader { + suite := header.NewTestSuite(t, 3) + res := make([]*header.ExtendedHeader, 0) + for i := 0; i < 3; i++ { + res = append(res, suite.GenExtendedHeader()) + } + return res + } + testCases := []struct { + precondition func() []*header.ExtendedHeader + expectedHeight int64 + }{ + /* + Height -> Amount + headerHeight[0]=1 -> 1 + headerHeight[1]=2 -> 1 + headerHeight[2]=3 -> 1 + result -> headerHeight[2] + */ + { + precondition: gen, + expectedHeight: 3, + }, + /* + Height -> Amount + headerHeight[0]=1 -> 2 + headerHeight[1]=2 -> 1 + headerHeight[2]=3 -> 1 + result -> headerHeight[0] + */ + { + precondition: func() []*header.ExtendedHeader { + res := gen() + res = append(res, res[0]) + return res + }, + expectedHeight: 1, + }, + /* + Height -> Amount + headerHeight[0]=1 -> 3 + headerHeight[1]=2 -> 2 + headerHeight[2]=3 -> 1 + result -> headerHeight[1] + */ + { + precondition: func() []*header.ExtendedHeader { + res := gen() + res = append(res, res[0]) + res = append(res, res[0]) + res = append(res, res[1]) + return res + }, + expectedHeight: 2, + }, + } + for _, tt := range testCases { + res := tt.precondition() + header, err := bestHead(res) + require.NoError(t, err) + require.True(t, header.Height == tt.expectedHeight) + } +} + func createMocknet(t *testing.T) (libhost.Host, libhost.Host) { net, err := mocknet.FullMeshConnected(2) require.NoError(t, err) From d0eaef632566e8aa6f60c0941ee1f5008ff132d9 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 11 Oct 2022 11:34:08 -0500 Subject: [PATCH 0126/1008] refactor(swamp): use celestia-app instead of the kvstore (#1160) Co-authored-by: Wondertan --- go.mod | 15 +- go.sum | 273 +++++++++++++++++++++++++- nodebuilder/tests/fraud_test.go | 25 +-- nodebuilder/tests/p2p_test.go | 8 +- nodebuilder/tests/reconstruct_test.go | 23 +-- nodebuilder/tests/swamp/config.go | 12 +- nodebuilder/tests/swamp/swamp.go | 111 +++++------ nodebuilder/tests/swamp/swamp_tx.go | 45 ++--- nodebuilder/tests/sync_test.go | 14 +- 9 files changed, 373 insertions(+), 153 deletions(-) diff --git a/go.mod b/go.mod index 27f736b41c..b18b981ea4 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/math v1.0.0-beta.3 + github.com/99designs/keyring v1.2.1 // indirect github.com/BurntSushi/toml v1.2.0 github.com/celestiaorg/celestia-app v0.7.0-rc-1 github.com/celestiaorg/go-libp2p-messenger v0.1.0 @@ -71,7 +72,6 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/99designs/keyring v1.2.1 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.0 // indirect @@ -88,6 +88,8 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/confio/ics23/go v0.7.0 // indirect github.com/containerd/cgroups v1.0.4 // indirect github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect @@ -104,6 +106,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.4.0 // indirect @@ -151,6 +154,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/huin/goupnp v1.0.3 // indirect + github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect @@ -196,6 +200,7 @@ require ( github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect + github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/miekg/dns v1.1.50 // indirect @@ -231,7 +236,9 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rs/cors v1.8.2 // indirect + github.com/rs/zerolog v1.27.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -271,18 +278,20 @@ require ( google.golang.org/api v0.81.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect + nhooyr.io/websocket v1.8.6 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) replace ( + github.com/celestiaorg/celestia-app => github.com/celestiaorg/celestia-app v0.7.0-rc-1.0.20220930201625-f8540b2aec19 github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20-celestia-node-patch ) diff --git a/go.sum b/go.sum index 82c3adacb9..83d2201f8a 100644 --- a/go.sum +++ b/go.sum @@ -3,12 +3,14 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= @@ -38,6 +40,7 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= @@ -59,6 +62,7 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= cosmossdk.io/math v1.0.0-beta.3 h1:TbZxSopz2LqjJ7aXYfn7nJSb8vNaBklW6BLpcei1qwM= @@ -71,6 +75,8 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= +git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= @@ -78,6 +84,9 @@ github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwR github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= @@ -85,8 +94,10 @@ github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= @@ -95,20 +106,27 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= +github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= github.com/adlio/schema v1.1.13 h1:LeNMVg5Z1FX+Qgz8tJUijBLRdcpbFUElz+d1489On98= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -124,6 +142,15 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.40.45 h1:QN1nsY27ssD/JmW4s83qmSb+uL6DG4GmCDzjmJB4xUI= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= @@ -136,15 +163,20 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= @@ -155,6 +187,7 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -165,11 +198,13 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.7.0-rc-1 h1:5d6z/qmzM93yYLSXWule29tfwQ2cY7HGigt6PhZm2GI= -github.com/celestiaorg/celestia-app v0.7.0-rc-1/go.mod h1:Ic44p6PGAdd/H5MS4Ga85wcKIQr+MvgsQNmxhnMG0Co= -github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20 h1:XspZtkoIfJ68TCVOOGWkI4W7taQFJLLqSu6GME5RbqU= -github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= +github.com/celestiaorg/celestia-app v0.7.0-rc-1.0.20220930201625-f8540b2aec19 h1:X4HE3OJQ70LYz6Si4PpHA++DNqHOHtPNdPVxymjYsY4= +github.com/celestiaorg/celestia-app v0.7.0-rc-1.0.20220930201625-f8540b2aec19/go.mod h1:jFaHWNtgYHD9zyZYtGxLuHKZ0DrYz9yoZMATrFr0wsE= +github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20-celestia-node-patch h1:lL8AH+b6a8/8L+ZB2G4ysEhT0j/8RSVMJShbo8Ysy18= +github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20-celestia-node-patch/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 h1:A1F7L/09uGClsU+kmugMy47Ezv4ll0uxNRNdaGa37T8= github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0/go.mod h1:OXRC0p460CFKl77uQZWY/8p5uZmDrNum7BmVZDupq0Q= github.com/celestiaorg/go-leopard v0.1.0 h1:28z2EkvKJIez5J9CEaiiUEC+OxalRLtTGJJ1oScfE1g= @@ -192,6 +227,7 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -208,6 +244,7 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -218,11 +255,18 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= +github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= +github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= @@ -265,8 +309,10 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -274,12 +320,16 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -290,16 +340,25 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -308,6 +367,7 @@ github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= @@ -323,16 +383,20 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= @@ -349,9 +413,19 @@ github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -375,19 +449,41 @@ github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -432,10 +528,12 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -455,6 +553,7 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= @@ -487,6 +586,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= @@ -514,8 +614,10 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -540,6 +642,7 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -585,19 +688,34 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= +github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= @@ -713,9 +831,11 @@ github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsj github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.12.1-0.20220417024638-438db461d753 h1:uFlcJKZPLQd7rmOY/RrvBuUaYmAFnlFHKLivhO6cOy8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -733,18 +853,25 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= @@ -752,10 +879,13 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -774,6 +904,13 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= @@ -1015,6 +1152,7 @@ github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= github.com/lucas-clemente/quic-go v0.28.0 h1:9eXVRgIkMQQyiyorz/dAaOYIx3TFzXsIFkNFz4cxuJM= github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= +github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -1022,6 +1160,8 @@ github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamh github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= @@ -1041,17 +1181,30 @@ github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32B github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -1091,19 +1244,26 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= @@ -1175,7 +1335,11 @@ github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -1185,13 +1349,16 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1220,6 +1387,7 @@ github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.m github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= @@ -1233,6 +1401,7 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -1242,8 +1411,11 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -1253,6 +1425,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= @@ -1281,8 +1454,10 @@ github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= @@ -1300,9 +1475,11 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= @@ -1313,16 +1490,22 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= +github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= +github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1331,7 +1514,11 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -1397,6 +1584,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1404,6 +1592,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1428,20 +1617,40 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/warpfork/go-testmark v0.3.0 h1:Q81c4u7hT+BR5kNfNQhEF0VT2pmL7+Kk0wD+ORYl7iA= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= @@ -1458,9 +1667,12 @@ github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1536,6 +1748,7 @@ go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95a go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= @@ -1563,6 +1776,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1572,16 +1786,22 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -1591,6 +1811,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1653,6 +1875,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1663,12 +1886,14 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -1676,9 +1901,12 @@ golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1754,6 +1982,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1766,6 +1995,7 @@ golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1776,6 +2006,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1786,6 +2017,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1802,9 +2034,12 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1817,10 +2052,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1855,11 +2093,15 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1886,6 +2128,7 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1931,6 +2174,12 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -1994,6 +2243,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -2001,6 +2251,7 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= @@ -2027,6 +2278,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -2091,6 +2343,7 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= @@ -2126,8 +2379,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2143,11 +2397,14 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -2163,6 +2420,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2172,14 +2430,19 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index c34308bb23..7b1f6cc899 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -35,7 +35,7 @@ func TestFraudProofBroadcasting(t *testing.T) { bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(header.FraudMaker(t, 20))) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) err := bridge.Start(ctx) @@ -56,17 +56,18 @@ func TestFraudProofBroadcasting(t *testing.T) { subscr, err := full.FraudServ.Subscribe(fraud.BadEncoding) require.NoError(t, err) - _, err = subscr.Proof(ctx) + p, err := subscr.Proof(ctx) require.NoError(t, err) - - // Since GetByHeight is a blocking operation for headers that is not received, we - // should set a timeout because all daser/syncer are stopped at this point - newCtx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) - // rework this after https://github.com/celestiaorg/celestia-node/issues/427 - t.Cleanup(cancel) - - _, err = full.HeaderServ.GetByHeight(newCtx, 25) + require.Equal(t, 20, int(p.Height())) + + // This is an obscure way to check if the Syncer was stopped. + // If we cannot get a height header within a timeframe it means the syncer was stopped + // FIXME: Eventually, this should be a check on service registry managing and keeping + // lifecycles of each Module. + syncCtx, syncCancel := context.WithTimeout(context.Background(), time.Millisecond*100) + _, err = full.HeaderServ.GetByHeight(syncCtx, 100) require.ErrorIs(t, err, context.DeadlineExceeded) + syncCancel() require.NoError(t, full.Stop(ctx)) require.NoError(t, sw.RemoveNode(full, node.Full)) @@ -93,7 +94,7 @@ Steps: 7. Wait until LN will be connected to FN and fetch a fraud proof. */ func TestFraudProofSyncing(t *testing.T) { - sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*300)) const defaultTimeInterval = time.Second * 5 @@ -106,7 +107,7 @@ func TestFraudProofSyncing(t *testing.T) { store := nodebuilder.MockStore(t, cfg) bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(header.FraudMaker(t, 10))) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) t.Cleanup(cancel) err := bridge.Start(ctx) diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 2a973dda83..c913f5281c 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -31,7 +31,7 @@ func TestUseBridgeNodeAsBootstraper(t *testing.T) { bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) err := bridge.Start(ctx) @@ -63,7 +63,7 @@ Steps: func TestAddPeerToBlackList(t *testing.T) { sw := swamp.NewSwamp(t) full := sw.NewFullNode() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) require.NoError(t, full.Start(ctx)) @@ -97,7 +97,7 @@ func TestBootstrapNodesFromBridgeNode(t *testing.T) { bridge := sw.NewNodeWithConfig(node.Bridge, cfg) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) err := bridge.Start(ctx) @@ -179,7 +179,7 @@ func TestRestartNodeDiscovery(t *testing.T) { cfg.Share.PeersLimit = fullNodes bridge := sw.NewNodeWithConfig(node.Bridge, cfg) - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) err := bridge.Start(ctx) diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 7f5c88a0ce..47eb881cb8 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -39,16 +39,13 @@ func TestFullReconstructFromBridge(t *testing.T) { const ( blocks = 20 bsize = 16 - btime = time.Millisecond * 100 + btime = time.Millisecond * 300 ) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - errCh := make(chan error) - go func() { - errCh <- sw.FillBlocks(ctx, bsize, blocks) - }() + fillDn := sw.FillBlocks(ctx, bsize, blocks) bridge := sw.NewBridgeNode() err := bridge.Start(ctx) @@ -72,7 +69,7 @@ func TestFullReconstructFromBridge(t *testing.T) { return full.ShareServ.SharesAvailable(bctx, h.DAH) }) } - require.NoError(t, <-errCh) + require.NoError(t, <-fillDn) require.NoError(t, errg.Wait()) } @@ -97,18 +94,16 @@ func TestFullReconstructFromLights(t *testing.T) { share.DefaultSampleAmount = 20 const ( blocks = 20 - btime = time.Millisecond * 100 + btime = time.Millisecond * 300 bsize = 16 lnodes = 69 ) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - errCh := make(chan error) - go func() { - errCh <- sw.FillBlocks(ctx, bsize, blocks) - }() + fillDn := sw.FillBlocks(ctx, bsize, blocks) const defaultTimeInterval = time.Second * 5 cfg := nodebuilder.DefaultConfig(node.Full) @@ -171,7 +166,7 @@ func TestFullReconstructFromLights(t *testing.T) { return full.ShareServ.SharesAvailable(bctx, h.DAH) }) } - require.NoError(t, <-errCh) + require.NoError(t, <-fillDn) require.NoError(t, errg.Wait()) } diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index 10a6f408c5..f5925d0ff7 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -3,26 +3,20 @@ package swamp import ( "time" - "github.com/tendermint/tendermint/abci/types" tn "github.com/tendermint/tendermint/config" - - "github.com/celestiaorg/celestia-node/core" ) // Components struct represents a set of pre-requisite attributes from the test scenario type Components struct { - App types.Application CoreCfg *tn.Config } // DefaultComponents creates a KvStore with a block retention of 200 // In addition, the empty block interval is set to 200ms func DefaultComponents() *Components { - app := core.CreateKVStore(2000) - tnCfg := tn.ResetTestRoot("swamp_tests") - tnCfg.Consensus.CreateEmptyBlocksInterval = 100 * time.Millisecond + tnCfg := tn.TestConfig() + tnCfg.Consensus.TimeoutCommit = 50 * time.Millisecond return &Components{ - App: app, CoreCfg: tnCfg, } } @@ -37,6 +31,6 @@ func WithBlockTime(t time.Duration) Option { c.CoreCfg.Consensus.CreateEmptyBlocksInterval = t // for filled block c.CoreCfg.Consensus.TimeoutCommit = t - c.CoreCfg.Consensus.SkipTimeoutCommit = true + c.CoreCfg.Consensus.SkipTimeoutCommit = false } } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index af441739cd..df9731c56b 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -6,17 +6,19 @@ import ( "math/rand" "net" "testing" + "time" + "github.com/cosmos/cosmos-sdk/client" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/bytes" - "github.com/tendermint/tendermint/types" + tmrand "github.com/tendermint/tendermint/libs/rand" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-app/testutil/testnode" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" @@ -29,9 +31,9 @@ import ( var blackholeIP6 = net.ParseIP("100::") -const subscriberID string = "NewBlockSwamp/Events" - -var queryEvent string = types.QueryForEvent(types.EventNewBlock).String() +// DefaultTestTimeout should be used as the default timout on all the Swamp tests. +// It's generously set to 5 minutes to give enough time for CI. +const DefaultTestTimeout = time.Minute * 5 // Swamp represents the main functionality that is needed for the test-case: // - Network to link the nodes @@ -41,12 +43,14 @@ var queryEvent string = types.QueryForEvent(types.EventNewBlock).String() type Swamp struct { t *testing.T Network mocknet.Mocknet - CoreClient core.Client BridgeNodes []*nodebuilder.Node FullNodes []*nodebuilder.Node LightNodes []*nodebuilder.Node trustedHash string comps *Components + + ClientContext client.Context + accounts []string } // NewSwamp creates a new instance of Swamp. @@ -63,37 +67,38 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { var err error ctx := context.Background() + // we create an arbitrary number of funded accounts + accounts := make([]string, 100) + for i := 0; i < 10; i++ { + accounts = append(accounts, tmrand.Str(9)) + } + // TODO(@Bidon15): CoreClient(limitation) // Now, we are making an assumption that consensus mechanism is already tested out // so, we are not creating bridge nodes with each one containing its own core client // instead we are assigning all created BNs to 1 Core from the swamp - core.StartTestNode(ctx, t, ic.App, ic.CoreCfg) - endpoint, err := core.GetEndpoint(ic.CoreCfg) + tmNode, app, cctx, err := testnode.New(t, ic.CoreCfg, false, accounts...) require.NoError(t, err) - ip, port, err := net.SplitHostPort(endpoint) - require.NoError(t, err) - remote, err := core.NewRemote(ip, port) + + cctx, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) require.NoError(t, err) - t.Cleanup(func() { - err := remote.Stop() - require.NoError(t, err) - }) - err = remote.Start() + cctx, cleanupGRPCServer, err := testnode.StartGRPCServer(app, testnode.DefaultAppConfig(), cctx) require.NoError(t, err) swp := &Swamp{ - t: t, - Network: mocknet.New(), - CoreClient: remote, - comps: ic, + t: t, + Network: mocknet.New(), + ClientContext: cctx, + comps: ic, + accounts: accounts, } - - swp.trustedHash, err = swp.getTrustedHash(ctx) - require.NoError(t, err) + swp.trustedHash = swp.getTrustedHash(ctx) swp.t.Cleanup(func() { swp.stopAllNodes(ctx, swp.BridgeNodes, swp.FullNodes, swp.LightNodes) + cleanupCoreNode() + cleanupGRPCServer() }) return swp @@ -111,37 +116,34 @@ func (s *Swamp) stopAllNodes(ctx context.Context, allNodes ...[]*nodebuilder.Nod // GetCoreBlockHashByHeight returns a tendermint block's hash by provided height func (s *Swamp) GetCoreBlockHashByHeight(ctx context.Context, height int64) bytes.HexBytes { - b, err := s.CoreClient.Block(ctx, &height) + b, err := s.ClientContext.Client.Block(ctx, &height) require.NoError(s.t, err) return b.BlockID.Hash } // WaitTillHeight holds the test execution until the given amount of blocks // has been produced by the CoreClient. -func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) { +func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) bytes.HexBytes { require.Greater(s.t, height, int64(0)) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - results, err := s.CoreClient.Subscribe(ctx, subscriberID, queryEvent) - require.NoError(s.t, err) - - defer func() { - // TODO(@Wondertan): For some reason, the Unsubscribe does not work and we have to do - // an UnsubscribeAll as a hack. There is somewhere a bug in the Tendermint which should be - // investigated - err = s.CoreClient.UnsubscribeAll(ctx, subscriberID) - require.NoError(s.t, err) - }() - + t := time.NewTicker(time.Millisecond * 50) + defer t.Stop() for { select { case <-ctx.Done(): - return - case block := <-results: - newBlock := block.Data.(types.EventDataNewBlock) - if height <= newBlock.Block.Height { - return + require.NoError(s.t, ctx.Err()) + case <-t.C: + status, err := s.ClientContext.Client.Status(ctx) + require.NoError(s.t, err) + + latest := status.SyncInfo.LatestBlockHeight + switch { + case latest == height: + return status.SyncInfo.LatestBlockHash + case latest > height: + res, err := s.ClientContext.Client.Block(ctx, &height) + require.NoError(s.t, err) + return res.BlockID.Hash } } } @@ -173,25 +175,8 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { // getTrustedHash is needed for celestia nodes to get the trustedhash // from CoreClient. This is required to initialize and start correctly. -func (s *Swamp) getTrustedHash(ctx context.Context) (string, error) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - results, err := s.CoreClient.Subscribe(ctx, subscriberID, queryEvent) - require.NoError(s.t, err) - - defer func() { - err := s.CoreClient.UnsubscribeAll(ctx, subscriberID) - require.NoError(s.t, err) - }() - - select { - case <-ctx.Done(): - return "", fmt.Errorf("can't get trusted hash as the channel is closed") - case block := <-results: - newBlock := block.Data.(types.EventDataNewBlock).Block - return newBlock.Hash().String(), nil - } +func (s *Swamp) getTrustedHash(ctx context.Context) string { + return s.WaitTillHeight(ctx, 1).String() } // NewBridgeNode creates a new instance of a BridgeNode providing a default config @@ -243,7 +228,7 @@ func (s *Swamp) NewNodeWithStore( switch t { case node.Bridge: options = append(options, - coremodule.WithClient(s.CoreClient), + coremodule.WithClient(s.ClientContext.Client), ) n = s.newNode(node.Bridge, store, options...) s.BridgeNodes = append(s.BridgeNodes, n) diff --git a/nodebuilder/tests/swamp/swamp_tx.go b/nodebuilder/tests/swamp/swamp_tx.go index 15a8238b37..1d2b13d014 100644 --- a/nodebuilder/tests/swamp/swamp_tx.go +++ b/nodebuilder/tests/swamp/swamp_tx.go @@ -2,44 +2,21 @@ package swamp import ( "context" - "fmt" - "math/rand" - "time" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-app/testutil/testnode" ) -// SubmitData submits given data in the block. -// TODO(@Wondertan): This must be a real PFD using celestia-app, once we can run App in the Swamp. -func (s *Swamp) SubmitData(ctx context.Context, data []byte) error { - result, err := s.CoreClient.BroadcastTxSync(ctx, append([]byte("key="), data...)) - if err != nil { - return err - } - if result.Code != 0 { - return fmt.Errorf("invalid status code: %d", result.Code) - } - - return nil -} - -func (s *Swamp) FillBlocks(ctx context.Context, bsize, blocks int) error { - btime := s.comps.CoreCfg.Consensus.CreateEmptyBlocksInterval - timer := time.NewTimer(btime) - defer timer.Stop() - - data := make([]byte, bsize*ipld.ShareSize) - for range make([]int, blocks) { - rand.Read(data) //nolint:gosec - if err := s.SubmitData(ctx, data); err != nil { - return err - } - timer.Reset(btime) +// FillBlocks produces the given amount of contiguous blocks with customizable size. +// The returned channel reports when the process is finished. +func (s *Swamp) FillBlocks(ctx context.Context, bsize, blocks int) chan error { + errCh := make(chan error) + go func() { + // TODO: FillBlock must respect the context + _, err := testnode.FillBlock(s.ClientContext, bsize, s.accounts[:blocks+1]) select { - case <-timer.C: + case errCh <- err: case <-ctx.Done(): - return ctx.Err() } - } - return nil + }() + return errCh } diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index d6f5a73928..18e2c1b8be 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -3,7 +3,6 @@ package tests import ( "context" "testing" - "time" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" @@ -15,9 +14,6 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) -// a default timeout for the context that is used in tests -const defaultTimeout = 40 * time.Second - /* Test-Case: Sync a Light Node with a Bridge Node Steps: @@ -33,7 +29,7 @@ func TestSyncLightWithBridge(t *testing.T) { bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw.WaitTillHeight(ctx, 20) @@ -83,7 +79,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw.WaitTillHeight(ctx, 50) @@ -138,7 +134,7 @@ func TestSyncFullWithBridge(t *testing.T) { bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw.WaitTillHeight(ctx, 20) @@ -186,7 +182,7 @@ func TestSyncLightWithFull(t *testing.T) { bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw.WaitTillHeight(ctx, 20) @@ -252,7 +248,7 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw.WaitTillHeight(ctx, 20) From 046fde162bcaf3d5eff98bcba7825e62de97b089 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 12 Oct 2022 13:55:28 +0300 Subject: [PATCH 0127/1008] feat!(header/p2p): extend header response with status code (#1135) Resolves #906 --- header/p2p/exchange.go | 22 +- header/p2p/exchange_test.go | 57 +++- header/p2p/pb/extended_header_request.pb.go | 271 +++++++++++++++++++- header/p2p/pb/extended_header_request.proto | 12 + header/p2p/server.go | 108 ++++---- 5 files changed, 394 insertions(+), 76 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index a488fc6f97..d98753f6fb 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -18,7 +18,6 @@ import ( "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" - header_pb "github.com/celestiaorg/celestia-node/header/pb" "github.com/celestiaorg/celestia-node/params" ) @@ -191,7 +190,7 @@ func request( // read responses headers := make([]*header.ExtendedHeader, req.Amount) for i := 0; i < int(req.Amount); i++ { - resp := new(header_pb.ExtendedHeader) + resp := new(p2p_pb.ExtendedHeaderResponse) if err = stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { log.Debugf("error setting deadline: %s", err) } @@ -200,7 +199,12 @@ func request( stream.Reset() //nolint:errcheck return nil, err } - header, err := header.ProtoToExtendedHeader(resp) + + if err = convertStatusCodeToError(resp.StatusCode); err != nil { + stream.Reset() //nolint:errcheck + return nil, err + } + header, err := header.UnmarshalExtendedHeader(resp.Body) if err != nil { stream.Reset() //nolint:errcheck return nil, err @@ -246,3 +250,15 @@ func bestHead(result []*header.ExtendedHeader) (*header.ExtendedHeader, error) { // otherwise return header with the max height return result[0], nil } + +// convertStatusCodeToError converts passed status code into an error. +func convertStatusCodeToError(code p2p_pb.StatusCode) error { + switch code { + case p2p_pb.StatusCode_OK: + return nil + case p2p_pb.StatusCode_NOT_FOUND: + return header.ErrNotFound + default: + return fmt.Errorf("unknown status code %d", code) + } +} diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index af07e6ac12..e0d60db9a4 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -17,7 +17,6 @@ import ( "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" - header_pb "github.com/celestiaorg/celestia-node/header/pb" ) func TestExchange_RequestHead(t *testing.T) { @@ -53,6 +52,17 @@ func TestExchange_RequestHeaders(t *testing.T) { } } +// TestExchange_RequestHeadersFails tests that the Exchange instance will return +// header.ErrNotFound if it will not have requested header. +func TestExchange_RequestHeadersFails(t *testing.T) { + host, peer := createMocknet(t) + exchg, _ := createP2PExAndServer(t, host, peer) + // perform expected request + _, err := exchg.GetRangeByHeight(context.Background(), 5, 3) + require.Error(t, err) + require.ErrorAs(t, err, &header.ErrNotFound) +} + // TestExchange_RequestByHash tests that the Exchange instance can // respond to an ExtendedHeaderRequest for a hash instead of a height. func TestExchange_RequestByHash(t *testing.T) { @@ -85,11 +95,11 @@ func TestExchange_RequestByHash(t *testing.T) { _, err = serde.Write(stream, req) require.NoError(t, err) // read resp - resp := new(header_pb.ExtendedHeader) + resp := new(p2p_pb.ExtendedHeaderResponse) _, err = serde.Read(stream, resp) require.NoError(t, err) // compare - eh, err := header.ProtoToExtendedHeader(resp) + eh, err := header.UnmarshalExtendedHeader(resp.Body) require.NoError(t, err) assert.Equal(t, store.headers[reqHeight].Height, eh.Height) @@ -161,6 +171,39 @@ func Test_bestHead(t *testing.T) { } } +// TestExchange_RequestByHashFails tests that the Exchange instance can +// respond with a StatusCode_NOT_FOUND if it will not have requested header. +func TestExchange_RequestByHashFails(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + net, err := mocknet.FullMeshConnected(2) + require.NoError(t, err) + // get host and peer + host, peer := net.Hosts()[0], net.Hosts()[1] + serv := NewExchangeServer(host, createStore(t, 0)) + err = serv.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + serv.Stop(context.Background()) //nolint:errcheck + }) + + stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, exchangeProtocolID) + require.NoError(t, err) + req := &p2p_pb.ExtendedHeaderRequest{ + Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: []byte("dummy_hash")}, + Amount: 1, + } + // send request + _, err = serde.Write(stream, req) + require.NoError(t, err) + // read resp + resp := new(p2p_pb.ExtendedHeaderResponse) + _, err = serde.Read(stream, resp) + require.NoError(t, err) + require.Equal(t, resp.StatusCode, p2p_pb.StatusCode_NOT_FOUND) +} + func createMocknet(t *testing.T) (libhost.Host, libhost.Host) { net, err := mocknet.FullMeshConnected(2) require.NoError(t, err) @@ -226,7 +269,7 @@ func (m *mockStore) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.Ext return header, nil } } - return nil, nil + return nil, header.ErrNotFound } func (m *mockStore) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { @@ -235,6 +278,12 @@ func (m *mockStore) GetByHeight(ctx context.Context, height uint64) (*header.Ext func (m *mockStore) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { headers := make([]*header.ExtendedHeader, to-from) + // As the requested range is [from; to), + // check that (to-1) height in request is less than + // the biggest header height in store. + if to-1 > m.Height() { + return nil, header.ErrNotFound + } for i := range headers { headers[i] = m.headers[int64(from)] from++ diff --git a/header/p2p/pb/extended_header_request.pb.go b/header/p2p/pb/extended_header_request.pb.go index 7941114de8..da44b304b8 100644 --- a/header/p2p/pb/extended_header_request.pb.go +++ b/header/p2p/pb/extended_header_request.pb.go @@ -22,6 +22,37 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +type StatusCode int32 + +const ( + StatusCode_INVALID StatusCode = 0 + StatusCode_OK StatusCode = 1 + StatusCode_NOT_FOUND StatusCode = 2 + StatusCode_LIMIT_EXCEEDED StatusCode = 3 +) + +var StatusCode_name = map[int32]string{ + 0: "INVALID", + 1: "OK", + 2: "NOT_FOUND", + 3: "LIMIT_EXCEEDED", +} + +var StatusCode_value = map[string]int32{ + "INVALID": 0, + "OK": 1, + "NOT_FOUND": 2, + "LIMIT_EXCEEDED": 3, +} + +func (x StatusCode) String() string { + return proto.EnumName(StatusCode_name, int32(x)) +} + +func (StatusCode) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ea2a1467b965216e, []int{0} +} + type ExtendedHeaderRequest struct { // Types that are valid to be assigned to Data: // *ExtendedHeaderRequest_Origin @@ -115,8 +146,62 @@ func (*ExtendedHeaderRequest) XXX_OneofWrappers() []interface{} { } } +type ExtendedHeaderResponse struct { + Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + StatusCode StatusCode `protobuf:"varint,2,opt,name=statusCode,proto3,enum=p2p.pb.StatusCode" json:"statusCode,omitempty"` +} + +func (m *ExtendedHeaderResponse) Reset() { *m = ExtendedHeaderResponse{} } +func (m *ExtendedHeaderResponse) String() string { return proto.CompactTextString(m) } +func (*ExtendedHeaderResponse) ProtoMessage() {} +func (*ExtendedHeaderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ea2a1467b965216e, []int{1} +} +func (m *ExtendedHeaderResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExtendedHeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExtendedHeaderResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExtendedHeaderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExtendedHeaderResponse.Merge(m, src) +} +func (m *ExtendedHeaderResponse) XXX_Size() int { + return m.Size() +} +func (m *ExtendedHeaderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ExtendedHeaderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ExtendedHeaderResponse proto.InternalMessageInfo + +func (m *ExtendedHeaderResponse) GetBody() []byte { + if m != nil { + return m.Body + } + return nil +} + +func (m *ExtendedHeaderResponse) GetStatusCode() StatusCode { + if m != nil { + return m.StatusCode + } + return StatusCode_INVALID +} + func init() { + proto.RegisterEnum("p2p.pb.StatusCode", StatusCode_name, StatusCode_value) proto.RegisterType((*ExtendedHeaderRequest)(nil), "p2p.pb.ExtendedHeaderRequest") + proto.RegisterType((*ExtendedHeaderResponse)(nil), "p2p.pb.ExtendedHeaderResponse") } func init() { @@ -124,19 +209,26 @@ func init() { } var fileDescriptor_ea2a1467b965216e = []byte{ - // 183 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xce, 0x48, 0x4d, 0x4c, - 0x49, 0x2d, 0xd2, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, - 0x49, 0x4d, 0x89, 0x87, 0x08, 0xc7, 0x17, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0xe8, 0x15, 0x14, - 0xe5, 0x97, 0xe4, 0x0b, 0xb1, 0x15, 0x18, 0x15, 0xe8, 0x15, 0x24, 0x29, 0xa5, 0x73, 0x89, 0xba, - 0x42, 0x15, 0x7a, 0x80, 0xd5, 0x05, 0x41, 0x94, 0x09, 0x49, 0x70, 0xb1, 0xe5, 0x17, 0x65, 0xa6, - 0x67, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x78, 0x30, 0x04, 0x41, 0xf9, 0x42, 0x22, 0x5c, - 0x2c, 0x19, 0x89, 0xc5, 0x19, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x1e, 0x0c, 0x41, 0x60, 0x9e, - 0x90, 0x18, 0x17, 0x5b, 0x62, 0x6e, 0x7e, 0x69, 0x5e, 0x89, 0x04, 0x33, 0x48, 0x7d, 0x10, 0x94, - 0xe7, 0xc4, 0xc6, 0xc5, 0x92, 0x92, 0x58, 0x92, 0xe8, 0x24, 0x71, 0xe2, 0x91, 0x1c, 0xe3, 0x85, - 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, - 0x8d, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x17, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x45, - 0xbf, 0x56, 0x8c, 0xc0, 0x00, 0x00, 0x00, + // 293 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xc1, 0x4a, 0xc3, 0x40, + 0x10, 0x86, 0xb3, 0x6d, 0x88, 0x38, 0xd6, 0x12, 0x06, 0x2d, 0x39, 0x85, 0xd2, 0x53, 0x51, 0x48, + 0x21, 0x3e, 0x81, 0x6d, 0x22, 0x0d, 0xd6, 0x16, 0xd6, 0x2a, 0xde, 0xe2, 0x86, 0x5d, 0xda, 0x1e, + 0xcc, 0xae, 0xd9, 0x2d, 0xe8, 0x5b, 0xf8, 0x58, 0x1e, 0x7b, 0xf4, 0x28, 0xed, 0x8b, 0x88, 0xdb, + 0xa0, 0xe2, 0x6d, 0xfe, 0xf9, 0x3f, 0xf8, 0x86, 0x81, 0xf3, 0xa5, 0x60, 0x5c, 0x54, 0x03, 0x15, + 0xab, 0x81, 0x2a, 0x06, 0xe2, 0xc5, 0x88, 0x92, 0x0b, 0x9e, 0xef, 0xd7, 0x79, 0x25, 0x9e, 0xd7, + 0x42, 0x9b, 0x48, 0x55, 0xd2, 0x48, 0xf4, 0x54, 0xac, 0x22, 0x55, 0xf4, 0x16, 0x70, 0x9a, 0xd6, + 0xe0, 0xd8, 0x72, 0x74, 0x8f, 0x61, 0x00, 0x9e, 0xac, 0x56, 0x8b, 0x55, 0x19, 0x90, 0x2e, 0xe9, + 0xbb, 0x63, 0x87, 0xd6, 0x19, 0x4f, 0xc0, 0x5d, 0x32, 0xbd, 0x0c, 0x1a, 0x5d, 0xd2, 0x6f, 0x8d, + 0x1d, 0x6a, 0x13, 0x76, 0xc0, 0x63, 0x4f, 0x72, 0x5d, 0x9a, 0xa0, 0xf9, 0xcd, 0xd3, 0x3a, 0x0d, + 0x3d, 0x70, 0x39, 0x33, 0xac, 0xf7, 0x08, 0x9d, 0xff, 0x22, 0xad, 0x64, 0xa9, 0x05, 0x22, 0xb8, + 0x85, 0xe4, 0xaf, 0xd6, 0xd3, 0xa2, 0x76, 0xc6, 0x18, 0x40, 0x1b, 0x66, 0xd6, 0x7a, 0x24, 0xb9, + 0xb0, 0xa6, 0x76, 0x8c, 0xd1, 0xfe, 0xe6, 0xe8, 0xf6, 0xa7, 0xa1, 0x7f, 0xa8, 0xb3, 0x04, 0xe0, + 0xb7, 0xc1, 0x23, 0x38, 0xc8, 0xa6, 0xf7, 0x97, 0x93, 0x2c, 0xf1, 0x1d, 0xf4, 0xa0, 0x31, 0xbb, + 0xf6, 0x09, 0x1e, 0xc3, 0xe1, 0x74, 0x36, 0xcf, 0xaf, 0x66, 0x77, 0xd3, 0xc4, 0x6f, 0x20, 0x42, + 0x7b, 0x92, 0xdd, 0x64, 0xf3, 0x3c, 0x7d, 0x18, 0xa5, 0x69, 0x92, 0x26, 0x7e, 0x73, 0x18, 0xbc, + 0x6f, 0x43, 0xb2, 0xd9, 0x86, 0xe4, 0x73, 0x1b, 0x92, 0xb7, 0x5d, 0xe8, 0x6c, 0x76, 0xa1, 0xf3, + 0xb1, 0x0b, 0x9d, 0xc2, 0xb3, 0x9f, 0xbb, 0xf8, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x41, 0xe0, + 0x70, 0x68, 0x01, 0x00, 0x00, } func (m *ExtendedHeaderRequest) Marshal() (dAtA []byte, err error) { @@ -204,6 +296,41 @@ func (m *ExtendedHeaderRequest_Hash) MarshalToSizedBuffer(dAtA []byte) (int, err } return len(dAtA) - i, nil } +func (m *ExtendedHeaderResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExtendedHeaderResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExtendedHeaderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.StatusCode != 0 { + i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(m.StatusCode)) + i-- + dAtA[i] = 0x10 + } + if len(m.Body) > 0 { + i -= len(m.Body) + copy(dAtA[i:], m.Body) + i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(len(m.Body))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintExtendedHeaderRequest(dAtA []byte, offset int, v uint64) int { offset -= sovExtendedHeaderRequest(v) base := offset @@ -251,6 +378,21 @@ func (m *ExtendedHeaderRequest_Hash) Size() (n int) { } return n } +func (m *ExtendedHeaderResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Body) + if l > 0 { + n += 1 + l + sovExtendedHeaderRequest(uint64(l)) + } + if m.StatusCode != 0 { + n += 1 + sovExtendedHeaderRequest(uint64(m.StatusCode)) + } + return n +} func sovExtendedHeaderRequest(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 @@ -380,6 +522,109 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { } return nil } +func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedHeaderRequest + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExtendedHeaderResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExtendedHeaderResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedHeaderRequest + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthExtendedHeaderRequest + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthExtendedHeaderRequest + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Body = append(m.Body[:0], dAtA[iNdEx:postIndex]...) + if m.Body == nil { + m.Body = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StatusCode", wireType) + } + m.StatusCode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedHeaderRequest + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StatusCode |= StatusCode(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipExtendedHeaderRequest(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthExtendedHeaderRequest + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/header/p2p/pb/extended_header_request.proto b/header/p2p/pb/extended_header_request.proto index c91a86dc00..d508c1cb76 100644 --- a/header/p2p/pb/extended_header_request.proto +++ b/header/p2p/pb/extended_header_request.proto @@ -9,3 +9,15 @@ message ExtendedHeaderRequest { } uint64 amount = 3; } + +enum StatusCode { + INVALID = 0; + OK = 1; + NOT_FOUND = 2; + LIMIT_EXCEEDED = 3; +}; + +message ExtendedHeaderResponse { + bytes body = 1; + StatusCode statusCode = 2; +} diff --git a/header/p2p/server.go b/header/p2p/server.go index 44bde05b35..35c51d89c0 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -6,7 +6,6 @@ import ( "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" - tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -69,17 +68,55 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { if err = stream.CloseRead(); err != nil { log.Error(err) } + + var headers []*header.ExtendedHeader // retrieve and write ExtendedHeaders switch pbreq.Data.(type) { case *p2p_pb.ExtendedHeaderRequest_Hash: - serv.handleRequestByHash(pbreq.GetHash(), stream) + headers, err = serv.handleRequestByHash(pbreq.GetHash()) case *p2p_pb.ExtendedHeaderRequest_Origin: - serv.handleRequest(pbreq.GetOrigin(), pbreq.GetOrigin()+pbreq.Amount, stream) + headers, err = serv.handleRequest(pbreq.GetOrigin(), pbreq.GetOrigin()+pbreq.Amount) default: log.Error("server: invalid data type received") stream.Reset() //nolint:errcheck return } + var code p2p_pb.StatusCode + switch err { + case nil: + code = p2p_pb.StatusCode_OK + case header.ErrNotFound: + // reallocate headers with 1 nil ExtendedHeader + headers = make([]*header.ExtendedHeader, 1) + code = p2p_pb.StatusCode_NOT_FOUND + default: + stream.Reset() //nolint:errcheck + return + } + + // write all headers to stream + for _, h := range headers { + if err := stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + log.Debugf("error setting deadline: %s", err) + } + var bin []byte + // if header is not nil, then marshal it to []byte. + // if header is nil, then error was received,so we will set empty []byte to proto. + if h != nil { + bin, err = h.MarshalBinary() + if err != nil { + log.Errorw("server: marshaling header to proto", "height", h.Height, "err", err) + stream.Reset() //nolint:errcheck + return + } + } + _, err = serde.Write(stream, &p2p_pb.ExtendedHeaderResponse{Body: bin, StatusCode: code}) + if err != nil { + log.Errorw("server: writing header to stream", "height", h.Height, "err", err) + stream.Reset() //nolint:errcheck + return + } + } err = stream.Close() if err != nil { @@ -89,76 +126,35 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { // handleRequestByHash returns the ExtendedHeader at the given hash // if it exists. -func (serv *ExchangeServer) handleRequestByHash(hash []byte, stream network.Stream) { +func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.ExtendedHeader, error) { log.Debugw("server: handling header request", "hash", tmbytes.HexBytes(hash).String()) h, err := serv.store.Get(serv.ctx, hash) if err != nil { log.Errorw("server: getting header by hash", "hash", tmbytes.HexBytes(hash).String(), "err", err) - stream.Reset() //nolint:errcheck - return - } - resp, err := header.ExtendedHeaderToProto(h) - if err != nil { - log.Errorw("server: marshaling header to proto", "hash", tmbytes.HexBytes(hash).String(), "err", err) - stream.Reset() //nolint:errcheck - return - } - if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { - log.Debugf("error setting deadline: %s", err) - } - _, err = serde.Write(stream, resp) - if err != nil { - log.Errorw("server: writing header to stream", "hash", tmbytes.HexBytes(hash).String(), "err", err) - stream.Reset() //nolint:errcheck - return + return nil, err } + return []*header.ExtendedHeader{h}, nil } // handleRequest fetches the ExtendedHeader at the given origin and // writes it to the stream. -func (serv *ExchangeServer) handleRequest(from, to uint64, stream network.Stream) { - var headers []*header.ExtendedHeader +func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHeader, error) { if from == uint64(0) { log.Debug("server: handling head request") - head, err := serv.store.Head(serv.ctx) if err != nil { log.Errorw("server: getting head", "err", err) - stream.Reset() //nolint:errcheck - return + return nil, err } - headers = make([]*header.ExtendedHeader, 1) - headers[0] = head - } else { - log.Debugw("server: handling headers request", "from", from, "to", to) - - headersByRange, err := serv.store.GetRangeByHeight(serv.ctx, from, to) - if err != nil { - log.Errorw("server: getting headers", "from", from, "to", to, "err", err) - stream.Reset() //nolint:errcheck - return - } - headers = headersByRange + return []*header.ExtendedHeader{head}, nil } - // write all headers to stream - for _, h := range headers { - if err := stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { - log.Debugf("error setting deadline: %s", err) - } - resp, err := header.ExtendedHeaderToProto(h) - if err != nil { - log.Errorw("server: marshaling header to proto", "height", h.Height, "err", err) - stream.Reset() //nolint:errcheck - return - } - - _, err = serde.Write(stream, resp) - if err != nil { - log.Errorw("server: writing header to stream", "height", h.Height, "err", err) - stream.Reset() //nolint:errcheck - return - } + log.Debugw("server: handling headers request", "from", from, "to", to) + headersByRange, err := serv.store.GetRangeByHeight(serv.ctx, from, to) + if err != nil { + log.Errorw("server: getting headers", "from", from, "to", to, "err", err) + return nil, err } + return headersByRange, nil } From 0d9358b40a54ca498366838aa3b7ce57a375154f Mon Sep 17 00:00:00 2001 From: Hoyt Ren Date: Wed, 12 Oct 2022 21:53:13 +0800 Subject: [PATCH 0128/1008] Remove watchdog since go 1.19 don't need it anymore. (#1212) --- go.mod | 2 +- logs/logs.go | 1 - nodebuilder/module.go | 1 - nodebuilder/watchdog.go | 49 ----------------------------------------- 4 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 nodebuilder/watchdog.go diff --git a/go.mod b/go.mod index b18b981ea4..849a96f38f 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,6 @@ require ( github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.7.0 github.com/multiformats/go-multihash v0.2.0 - github.com/raulk/go-watchdog v1.3.0 github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 @@ -235,6 +234,7 @@ require ( github.com/prometheus/common v0.35.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/rakyll/statik v0.1.7 // indirect + github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rs/cors v1.8.2 // indirect diff --git a/logs/logs.go b/logs/logs.go index 5932a97a75..bc18fbc411 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -16,7 +16,6 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("dht/RtRefreshManager", "FATAL") _ = logging.SetLogLevel("bitswap_network", "ERROR") _ = logging.SetLogLevel("badger", "INFO") - _ = logging.SetLogLevel("watchdog", "INFO") _ = logging.SetLogLevel("basichost", "INFO") } diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 3dd2efcb17..85909e88c7 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -26,7 +26,6 @@ func ConstructModule(tp node.Type, cfg *Config, store Store) fx.Option { fx.Supply(store.Config), fx.Provide(store.Datastore), fx.Provide(store.Keystore), - fx.Invoke(invokeWatchdog(store.Path())), // modules provided by the node p2p.ConstructModule(tp, &cfg.P2P), state.ConstructModule(tp, &cfg.State), diff --git a/nodebuilder/watchdog.go b/nodebuilder/watchdog.go deleted file mode 100644 index 3931ea2ed9..0000000000 --- a/nodebuilder/watchdog.go +++ /dev/null @@ -1,49 +0,0 @@ -package nodebuilder - -import ( - "context" - "sync" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/raulk/go-watchdog" - "go.uber.org/fx" -) - -var ( - // TODO(@Wondetan): We must start watchdog only once. This is needed for tests where we run multiple instance - // of the Node. Ideally, the Node should have some testing options instead, so we can check for it and run without - // such utilities but it does not hurt to run one instance of watchdog per test. - onceWatchdog = sync.Once{} - logWatchdog = logging.Logger("watchdog") -) - -// invokeWatchdog starts the memory watchdog that helps to prevent some of OOMs by forcing GCing -// It also collects heap profiles in the given directory when heap grows to more than 90% of memory usage -func invokeWatchdog(pprofdir string) func(lc fx.Lifecycle) error { - return func(lc fx.Lifecycle) (errOut error) { - onceWatchdog.Do(func() { - // to get watchdog information logged out - watchdog.Logger = logWatchdog - // these set up heap pprof auto capturing on disk when threshold hit 90% usage - watchdog.HeapProfileDir = pprofdir - watchdog.HeapProfileMaxCaptures = 10 - watchdog.HeapProfileThreshold = 0.9 - - policy := watchdog.NewWatermarkPolicy(0.50, 0.60, 0.70, 0.85, 0.90, 0.925, 0.95) - err, stop := watchdog.SystemDriven(0, time.Second*5, policy) - if err != nil { - errOut = err - return - } - - lc.Append(fx.Hook{ - OnStop: func(context.Context) error { - stop() - return nil - }, - }) - }) - return - } -} From 793e2b019d6d4be98bf03d91c578b3e68da13e55 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:51:53 +0300 Subject: [PATCH 0129/1008] refactor(share): Consolidate share pkg (#1177) Resolves https://github.com/celestiaorg/celestia-node/issues/878 Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- das/daser.go | 4 +- das/daser_test.go | 15 +- fraud/bad_encoding.go | 17 +- fraud/bad_encoding_test.go | 4 +- fraud/pb/proof.pb.go | 6 +- fraud/testing.go | 5 +- header/header.go | 6 +- header/testing.go | 11 +- ipld/get.go | 131 ------ ipld/get_namespaced_shares.go | 224 ---------- ipld/get_shares.go | 136 ------ ipld/helpers.go | 22 - ipld/share.go | 99 ----- nodebuilder/share/module.go | 21 +- nodebuilder/share/service.go | 10 +- nodebuilder/share/share.go | 13 +- nodebuilder/tests/reconstruct_test.go | 9 +- {ipld => share}/add.go | 30 +- share/{interface.go => availability.go} | 8 +- .../cache/availability.go} | 29 +- .../cache/availability_test.go} | 28 +- share/availability/cache/testing.go | 39 ++ share/{ => availability/discovery}/backoff.go | 2 +- .../discovery}/backoff_test.go | 5 +- .../{ => availability/discovery}/discovery.go | 19 +- share/{ => availability/discovery}/set.go | 2 +- .../{ => availability/discovery}/set_test.go | 5 +- share/availability/full/availability.go | 77 ++++ .../full/availability_test.go} | 26 +- .../full/reconstruction_test.go} | 86 ++-- share/availability/full/testing.go | 47 ++ .../light/availability.go} | 51 +-- .../light/availability_test.go} | 79 +++- share/{ => availability/light}/sample.go | 5 +- share/{ => availability/light}/sample_test.go | 2 +- .../light}/testdata/block-825320.json | 0 share/availability/light/testing.go | 55 +++ share/availability/test/testing.go | 198 +++++++++ .../byzantine.go | 6 +- share/doc.go | 9 +- {ipld => share/eds}/retriever.go | 27 +- {ipld => share/eds}/retriever_quadrant.go | 6 +- {ipld => share/eds}/retriever_test.go | 34 +- share/empty.go | 4 +- share/full_availability.go | 72 ---- share/get.go | 105 +++++ {ipld => share}/get_test.go | 40 +- share/ipld/get.go | 401 ++++++++++++++++++ .../plugin => share/ipld}/namespace_hasher.go | 2 +- .../ipld}/namespace_hasher_test.go | 2 +- {ipld/plugin => share/ipld}/nmt.go | 37 +- {ipld => share/ipld}/nmt_adder.go | 35 +- {ipld/plugin => share/ipld}/nmt_test.go | 3 +- {ipld/plugin => share/ipld}/test_helpers.go | 2 +- share/light_availability_test.go | 44 -- {ipld => share}/pb/share.pb.go | 0 {ipld => share}/pb/share.proto | 0 share/service/service.go | 139 ++++++ share/share.go | 202 ++++----- {ipld => share}/test_helpers.go | 7 +- share/testing.go | 295 ------------- 61 files changed, 1543 insertions(+), 1455 deletions(-) delete mode 100644 ipld/get.go delete mode 100644 ipld/get_namespaced_shares.go delete mode 100644 ipld/get_shares.go delete mode 100644 ipld/helpers.go delete mode 100644 ipld/share.go rename {ipld => share}/add.go (76%) rename share/{interface.go => availability.go} (76%) rename share/{cache_availability.go => availability/cache/availability.go} (72%) rename share/{cache_availability_test.go => availability/cache/availability_test.go} (81%) create mode 100644 share/availability/cache/testing.go rename share/{ => availability/discovery}/backoff.go (99%) rename share/{ => availability/discovery}/backoff_test.go (98%) rename share/{ => availability/discovery}/discovery.go (89%) rename share/{ => availability/discovery}/set.go (98%) rename share/{ => availability/discovery}/set_test.go (98%) create mode 100644 share/availability/full/availability.go rename share/{full_availability_test.go => availability/full/availability_test.go} (66%) rename share/{full_reconstruction_test.go => availability/full/reconstruction_test.go} (69%) create mode 100644 share/availability/full/testing.go rename share/{light_availability.go => availability/light/availability.go} (62%) rename share/{share_test.go => availability/light/availability_test.go} (79%) rename share/{ => availability/light}/sample.go (91%) rename share/{ => availability/light}/sample_test.go (97%) rename share/{ => availability/light}/testdata/block-825320.json (100%) create mode 100644 share/availability/light/testing.go create mode 100644 share/availability/test/testing.go rename ipld/retriever_byzantine.go => share/byzantine.go (93%) rename {ipld => share/eds}/retriever.go (95%) rename {ipld => share/eds}/retriever_quadrant.go (96%) rename {ipld => share/eds}/retriever_test.go (76%) delete mode 100644 share/full_availability.go create mode 100644 share/get.go rename {ipld => share}/get_test.go (89%) create mode 100644 share/ipld/get.go rename {ipld/plugin => share/ipld}/namespace_hasher.go (99%) rename {ipld/plugin => share/ipld}/namespace_hasher_test.go (98%) rename {ipld/plugin => share/ipld}/nmt.go (84%) rename {ipld => share/ipld}/nmt_adder.go (58%) rename {ipld/plugin => share/ipld}/nmt_test.go (99%) rename {ipld/plugin => share/ipld}/test_helpers.go (95%) delete mode 100644 share/light_availability_test.go rename {ipld => share}/pb/share.pb.go (100%) rename {ipld => share}/pb/share.proto (100%) create mode 100644 share/service/service.go rename {ipld => share}/test_helpers.go (97%) delete mode 100644 share/testing.go diff --git a/das/daser.go b/das/daser.go index 2ea1bfda2c..8ca54d594e 100644 --- a/das/daser.go +++ b/das/daser.go @@ -7,8 +7,6 @@ import ( "sync/atomic" "time" - "github.com/celestiaorg/celestia-node/ipld" - "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -155,7 +153,7 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { if err == context.Canceled { return err } - var byzantineErr *ipld.ErrByzantine + var byzantineErr *share.ErrByzantine if errors.As(err, &byzantineErr) { log.Warn("Propagating proof...") sendErr := d.bcast.Broadcast(ctx, fraud.CreateBadEncodingProof(h.Hash(), uint64(h.Height), byzantineErr)) diff --git a/das/daser_test.go b/das/daser_test.go index 8a6e9d45b7..c87ce7bfb6 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -5,6 +5,10 @@ import ( "testing" "time" + "github.com/celestiaorg/celestia-node/share/availability/full" + "github.com/celestiaorg/celestia-node/share/availability/light" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/tendermint/tendermint/types" "github.com/ipfs/go-blockservice" @@ -19,7 +23,6 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share" ) var timeout = time.Second * 15 @@ -29,7 +32,7 @@ var timeout = time.Second * 15 func TestDASerLifecycle(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) bServ := mdutils.Bserv() - avail := share.TestLightAvailability(bServ) + avail := light.TestAvailability(bServ) // 15 headers from the past and 15 future headers mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) @@ -63,7 +66,7 @@ func TestDASerLifecycle(t *testing.T) { func TestDASer_Restart(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) bServ := mdutils.Bserv() - avail := share.TestLightAvailability(bServ) + avail := light.TestAvailability(bServ) // 15 headers from the past and 15 future headers mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) @@ -132,7 +135,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { ps, err := pubsub.NewGossipSub(ctx, net.Hosts()[0], pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) - avail := share.TestFullAvailability(bServ) + avail := full.TestAvailability(bServ) // 15 headers from the past and 15 future headers mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) @@ -209,7 +212,7 @@ func (m *mockGetter) fillSubWithHeaders( index := 0 for i := startHeight; i < endHeight; i++ { - dah := share.RandFillBS(t, 16, bServ) + dah := availability_test.RandFillBS(t, 16, bServ) randHeader := header.RandExtendedHeader(t) randHeader.DataHash = dah.Hash() @@ -237,7 +240,7 @@ type mockGetter struct { func (m *mockGetter) generateHeaders(t *testing.T, bServ blockservice.BlockService, startHeight, endHeight int) { for i := startHeight; i < endHeight; i++ { - dah := share.RandFillBS(t, 16, bServ) + dah := availability_test.RandFillBS(t, 16, bServ) randHeader := header.RandExtendedHeader(t) randHeader.DataHash = dah.Hash() diff --git a/fraud/bad_encoding.go b/fraud/bad_encoding.go index 143b4a7189..ad5afe9f14 100644 --- a/fraud/bad_encoding.go +++ b/fraud/bad_encoding.go @@ -8,13 +8,12 @@ import ( "github.com/tendermint/tendermint/pkg/consts" "github.com/tendermint/tendermint/pkg/wrapper" - "github.com/celestiaorg/rsmt2d" - pb "github.com/celestiaorg/celestia-node/fraud/pb" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/ipld" - ipld_pb "github.com/celestiaorg/celestia-node/ipld/pb" - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" + ipld_pb "github.com/celestiaorg/celestia-node/share/pb" + "github.com/celestiaorg/rsmt2d" ) func init() { @@ -27,7 +26,7 @@ type BadEncodingProof struct { // ShareWithProof contains all shares from row or col. // Shares that did not pass verification in rsmt2d will be nil. // For non-nil shares MerkleProofs are computed. - Shares []*ipld.ShareWithProof + Shares []*share.ShareWithProof // Index represents the row/col index where ErrByzantineRow/ErrByzantineColl occurred. Index uint32 // Axis represents the axis that verification failed on. @@ -39,7 +38,7 @@ type BadEncodingProof struct { func CreateBadEncodingProof( hash []byte, height uint64, - errByzantine *ipld.ErrByzantine, + errByzantine *share.ErrByzantine, ) Proof { return &BadEncodingProof{ @@ -92,7 +91,7 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { befp := &BadEncodingProof{ headerHash: in.HeaderHash, BlockHeight: in.Height, - Shares: ipld.ProtoToShare(in.Shares), + Shares: share.ProtoToShare(in.Shares), Index: in.Index, Axis: rsmt2d.Axis(in.Axis), } @@ -141,7 +140,7 @@ func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { continue } shares[index] = share.Share - if ok := share.Validate(plugin.MustCidFromNamespacedSha256(root)); !ok { + if ok := share.Validate(ipld.MustCidFromNamespacedSha256(root)); !ok { return fmt.Errorf("fraud: invalid proof: incorrect share received at index %d", index) } } diff --git a/fraud/bad_encoding_test.go b/fraud/bad_encoding_test.go index ec736d57af..ebb5a95692 100644 --- a/fraud/bad_encoding_test.go +++ b/fraud/bad_encoding_test.go @@ -9,7 +9,7 @@ import ( mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-node/share" ) func TestFraudProofValidation(t *testing.T) { @@ -21,7 +21,7 @@ func TestFraudProofValidation(t *testing.T) { require.NoError(t, err) faultDAH, err := generateByzantineError(ctx, t, h, bServ) - var errByz *ipld.ErrByzantine + var errByz *share.ErrByzantine require.True(t, errors.As(err, &errByz)) p := CreateBadEncodingProof([]byte("hash"), uint64(faultDAH.Height), errByz) err = p.Validate(faultDAH) diff --git a/fraud/pb/proof.pb.go b/fraud/pb/proof.pb.go index ddea926701..8ca49c4410 100644 --- a/fraud/pb/proof.pb.go +++ b/fraud/pb/proof.pb.go @@ -5,11 +5,13 @@ package fraud_pb import ( fmt "fmt" - pb "github.com/celestiaorg/celestia-node/ipld/pb" - proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" + + proto "github.com/gogo/protobuf/proto" + + pb "github.com/celestiaorg/celestia-node/share/pb" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/fraud/testing.go b/fraud/testing.go index f65a366355..c066631585 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -6,10 +6,11 @@ import ( "errors" "testing" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/ipfs/go-blockservice" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/ipld" ) type DummyService struct { @@ -66,7 +67,7 @@ func generateByzantineError( bServ blockservice.BlockService, ) (*header.ExtendedHeader, error) { faultHeader := header.CreateFraudExtHeader(t, h, bServ) - rtrv := ipld.NewRetriever(bServ) + rtrv := eds.NewRetriever(bServ) _, err := rtrv.Retrieve(ctx, faultHeader.DAH) return faultHeader, err } diff --git a/header/header.go b/header/header.go index 6d0a7f1ec6..0e049bfa97 100644 --- a/header/header.go +++ b/header/header.go @@ -5,14 +5,14 @@ import ( "context" "fmt" + "github.com/celestiaorg/celestia-node/share" + "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" bts "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/pkg/da" core "github.com/tendermint/tendermint/types" - - "github.com/celestiaorg/celestia-node/ipld" ) var log = logging.Logger("header") @@ -51,7 +51,7 @@ func MakeExtendedHeader( if err != nil { return nil, err } - extended, err := ipld.AddShares(ctx, namespacedShares.RawShares(), bServ) + extended, err := share.AddShares(ctx, namespacedShares.RawShares(), bServ) if err != nil { return nil, err } diff --git a/header/testing.go b/header/testing.go index 1e76eee828..9dc99fc569 100644 --- a/header/testing.go +++ b/header/testing.go @@ -6,6 +6,8 @@ package header import ( "context" + "github.com/celestiaorg/celestia-node/share" + mrand "math/rand" "testing" "time" @@ -22,7 +24,6 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-node/ipld" ) // TestSuite provides everything you need to test chain of Headers. @@ -225,10 +226,10 @@ func FraudMaker(t *testing.T, faultHeight int64) ConstructFn { } func CreateFraudExtHeader(t *testing.T, eh *ExtendedHeader, dag blockservice.BlockService) *ExtendedHeader { - extended := ipld.RandEDS(t, 2) - shares := ipld.ExtractEDS(extended) - copy(shares[0][ipld.NamespaceSize:], shares[1][ipld.NamespaceSize:]) - extended, err := ipld.ImportShares(context.Background(), shares, dag) + extended := share.RandEDS(t, 2) + shares := share.ExtractEDS(extended) + copy(shares[0][share.NamespaceSize:], shares[1][share.NamespaceSize:]) + extended, err := share.ImportShares(context.Background(), shares, dag) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(extended) eh.DAH = &dah diff --git a/ipld/get.go b/ipld/get.go deleted file mode 100644 index b05f9af385..0000000000 --- a/ipld/get.go +++ /dev/null @@ -1,131 +0,0 @@ -package ipld - -import ( - "context" - - "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - ipld "github.com/ipfs/go-ipld-format" - - "github.com/celestiaorg/celestia-node/ipld/plugin" -) - -// GetShare fetches and returns the data for leaf `leafIndex` of root `rootCid`. -func GetShare( - ctx context.Context, - bGetter blockservice.BlockGetter, - rootCid cid.Cid, - leafIndex int, - totalLeafs int, // this corresponds to the extended square width -) (Share, error) { - nd, err := GetLeaf(ctx, bGetter, rootCid, leafIndex, totalLeafs) - if err != nil { - return nil, err - } - - return leafToShare(nd), nil -} - -// GetLeaf fetches and returns the raw leaf. -// It walks down the IPLD NMT tree until it finds the requested one. -func GetLeaf(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, leaf, total int) (ipld.Node, error) { - // request the node - nd, err := plugin.GetNode(ctx, bGetter, root) - if err != nil { - return nil, err - } - - // look for links - lnks := nd.Links() - if len(lnks) == 1 { - // in case there is only one we reached tree's bottom, so finally request the leaf. - return plugin.GetNode(ctx, bGetter, lnks[0].Cid) - } - - // route walk to appropriate children - total /= 2 // as we are using binary tree, every step decreases total leaves in a half - if leaf < total { - root = lnks[0].Cid // if target leave on the left, go with walk down the first children - } else { - root, leaf = lnks[1].Cid, leaf-total // otherwise go down the second - } - - // recursively walk down through selected children - return GetLeaf(ctx, bGetter, root, leaf, total) -} - -// GetProofsForShares fetches Merkle proofs for the given shares -// and returns the result as an array of ShareWithProof. -func GetProofsForShares( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - shares [][]byte, -) ([]*ShareWithProof, error) { - proofs := make([]*ShareWithProof, len(shares)) - for index, share := range shares { - if share != nil { - proof := make([]cid.Cid, 0) - // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same tree. - // Add options that will control what data will be fetched. - s, err := GetLeaf(ctx, bGetter, root, index, len(shares)) - if err != nil { - return nil, err - } - proof, err = GetProof(ctx, bGetter, root, proof, index, len(shares)) - if err != nil { - return nil, err - } - proofs[index] = NewShareWithProof(index, s.RawData()[1:], proof) - } - } - - return proofs, nil -} - -// GetProof fetches and returns the leaf's Merkle Proof. -// It walks down the IPLD NMT tree until it reaches the leaf and returns collected proof -func GetProof( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - proof []cid.Cid, - leaf, total int, -) ([]cid.Cid, error) { - // request the node - nd, err := plugin.GetNode(ctx, bGetter, root) - if err != nil { - return nil, err - } - // look for links - lnks := nd.Links() - if len(lnks) == 1 { - p := make([]cid.Cid, len(proof)) - copy(p, proof) - return p, nil - } - - // route walk to appropriate children - total /= 2 // as we are using binary tree, every step decreases total leaves in a half - if leaf < total { - root = lnks[0].Cid // if target leave on the left, go with walk down the first children - proof = append(proof, lnks[1].Cid) - } else { - root, leaf = lnks[1].Cid, leaf-total // otherwise go down the second - proof, err = GetProof(ctx, bGetter, root, proof, leaf, total) - if err != nil { - return nil, err - } - return append(proof, lnks[0].Cid), nil - } - - // recursively walk down through selected children - return GetProof(ctx, bGetter, root, proof, leaf, total) -} - -// leafToShare converts an NMT leaf into a Share. -func leafToShare(nd ipld.Node) Share { - // * First byte represents the type of the node, and is unrelated to the actual share data - // * Additional namespace is prepended so that parity data can be identified with a parity namespace, which we cut off - return nd.RawData()[1+NamespaceSize:] // TODO(@Wondertan): Rework NMT/IPLD plugin to avoid the type byte -} diff --git a/ipld/get_namespaced_shares.go b/ipld/get_namespaced_shares.go deleted file mode 100644 index 0a387aa652..0000000000 --- a/ipld/get_namespaced_shares.go +++ /dev/null @@ -1,224 +0,0 @@ -package ipld - -import ( - "context" - "sync" - "sync/atomic" - - "github.com/gammazero/workerpool" - "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - ipld "github.com/ipfs/go-ipld-format" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - - "github.com/celestiaorg/celestia-node/ipld/plugin" - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" -) - -// TODO(@distractedm1nd) Find a better figure than NumWorkersLimit for this pool. Issue #970 -// namespacePool is a worker pool responsible for the goroutines spawned by getLeavesByNamespace -var namespacePool = workerpool.New(NumWorkersLimit) - -// GetSharesByNamespace walks the tree of a given root and returns its shares within the given namespace.ID. -// If a share could not be retrieved, err is not nil, and the returned array -// contains nil shares in place of the shares it was unable to retrieve. -func GetSharesByNamespace( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - nID namespace.ID, - maxShares int, -) ([]Share, error) { - ctx, span := tracer.Start(ctx, "get-shares-by-namespace") - defer span.End() - - leaves, err := getLeavesByNamespace(ctx, bGetter, root, nID, maxShares) - if err != nil && leaves == nil { - return nil, err - } - - shares := make([]Share, len(leaves)) - for i, leaf := range leaves { - if leaf != nil { - shares[i] = leafToShare(leaf) - } - } - - return shares, err -} - -// wrappedWaitGroup is needed because waitgroups do not expose their internal counter, -// and we don't know in advance how many jobs we will have to wait for. -type wrappedWaitGroup struct { - wg sync.WaitGroup - jobs chan *job - counter int64 -} - -func (w *wrappedWaitGroup) add(count int64) { - w.wg.Add(int(count)) - atomic.AddInt64(&w.counter, count) -} - -func (w *wrappedWaitGroup) done() { - w.wg.Done() - numRemaining := atomic.AddInt64(&w.counter, -1) - - // Close channel if this job was the last one - if numRemaining == 0 { - close(w.jobs) - } -} - -type fetchedBounds struct { - lowest int64 - highest int64 -} - -// update checks if the passed index is outside the current bounds, -// and updates the bounds atomically if it extends them. -func (b *fetchedBounds) update(index int64) { - lowest := atomic.LoadInt64(&b.lowest) - // try to write index to the lower bound if appropriate, and retry until the atomic op is successful - // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the comparison here - for index < lowest && !atomic.CompareAndSwapInt64(&b.lowest, lowest, index) { - lowest = atomic.LoadInt64(&b.lowest) - } - // we always run both checks because element can be both the lower and higher bound - // for example, if there is only one share in the namespace - highest := atomic.LoadInt64(&b.highest) - for index > highest && !atomic.CompareAndSwapInt64(&b.highest, highest, index) { - highest = atomic.LoadInt64(&b.highest) - } -} - -// getLeavesByNamespace returns as many leaves from the given root with the given namespace.ID as it can retrieve. -// If no shares are found, it returns both data and error as nil. -// A non-nil error means that only partial data is returned, because at least one share retrieval failed -// The following implementation is based on `GetShares`. -func getLeavesByNamespace( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - nID namespace.ID, - maxShares int, -) ([]ipld.Node, error) { - err := SanityCheckNID(nID) - if err != nil { - return nil, err - } - - ctx, span := tracer.Start(ctx, "get-leaves-by-namespace") - defer span.End() - - span.SetAttributes( - attribute.String("namespace", nID.String()), - attribute.String("root", root.String()), - ) - - // we don't know where in the tree the leaves in the namespace are, - // so we keep track of the bounds to return the correct slice - // maxShares acts as a sentinel to know if we find any leaves - bounds := fetchedBounds{int64(maxShares), 0} - - // buffer the jobs to avoid blocking, we only need as many - // queued as the number of shares in the second-to-last layer - jobs := make(chan *job, (maxShares+1)/2) - jobs <- &job{id: root, ctx: ctx} - - var wg wrappedWaitGroup - wg.jobs = jobs - wg.add(1) - - var ( - singleErr sync.Once - retrievalErr error - ) - - // we overallocate space for leaves since we do not know how many we will find - // on the level above, the length of the Row is passed in as maxShares - leaves := make([]ipld.Node, maxShares) - - for { - select { - case j, ok := <-jobs: - if !ok { - // if there were no leaves under the given root in the given namespace, - // both return values are nil. otherwise, the error will also be non-nil. - if bounds.lowest == int64(maxShares) { - return nil, retrievalErr - } - - return leaves[bounds.lowest : bounds.highest+1], retrievalErr - } - namespacePool.Submit(func() { - ctx, span := tracer.Start(j.ctx, "process-job") - defer span.End() - defer wg.done() - - span.SetAttributes( - attribute.String("cid", j.id.String()), - attribute.Int("pos", j.pos), - ) - - // if an error is likely to be returned or not depends on - // the underlying impl of the blockservice, currently it is not a realistic probability - nd, err := plugin.GetNode(ctx, bGetter, j.id) - if err != nil { - singleErr.Do(func() { - retrievalErr = err - }) - log.Errorw("getSharesByNamespace: could not retrieve node", "nID", nID, "pos", j.pos, "err", err) - span.SetStatus(codes.Error, err.Error()) - // we still need to update the bounds - bounds.update(int64(j.pos)) - return - } - - links := nd.Links() - linkCount := uint64(len(links)) - if linkCount == 1 { - // successfully fetched a leaf belonging to the namespace - span.SetStatus(codes.Ok, "") - leaves[j.pos] = nd - // we found a leaf, so we update the bounds - // the update routine is repeated until the atomic swap is successful - bounds.update(int64(j.pos)) - return - } - - // this node has links in the namespace, so keep walking - for i, lnk := range links { - newJob := &job{ - id: lnk.Cid, - // position represents the index in a flattened binary tree, - // so we can return a slice of leaves in order - pos: j.pos*2 + i, - // we pass the context to job so that spans are tracked in a tree - // structure - ctx: ctx, - } - - // if the link's nID isn't in range we don't need to create a new job for it - jobNid := plugin.NamespacedSha256FromCID(newJob.id) - if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) || !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { - continue - } - - // by passing the previous check, we know we will have one more node to process - // note: it is important to increase the counter before sending to the channel - wg.add(1) - select { - case jobs <- newJob: - case <-ctx.Done(): - return - } - } - }) - case <-ctx.Done(): - return nil, ctx.Err() - } - } -} diff --git a/ipld/get_shares.go b/ipld/get_shares.go deleted file mode 100644 index ed370222dd..0000000000 --- a/ipld/get_shares.go +++ /dev/null @@ -1,136 +0,0 @@ -package ipld - -import ( - "context" - "sync" - - "github.com/gammazero/workerpool" - "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - - "github.com/celestiaorg/celestia-node/ipld/plugin" -) - -// NumWorkersLimit sets global limit for workers spawned by GetShares. -// GetShares could be called MaxSquareSize(128) times per data square each -// spawning up to 128/2 goroutines and altogether this is 8192. Considering -// there can be N blocks fetched at the same time, e.g. during catching up data -// from the past, we multiply this number by the amount of allowed concurrent -// data square fetches(NumConcurrentSquares). -// -// NOTE: This value only limits amount of simultaneously running workers that -// are spawned as the load increases and are killed, once the load declines. -// -// TODO(@Wondertan): This assumes we have parallelized DASer implemented. Sync the values once it is shipped. -// TODO(@Wondertan): Allow configuration of values without global state. -var NumWorkersLimit = MaxSquareSize * MaxSquareSize / 2 * NumConcurrentSquares - -// NumConcurrentSquares limits the amount of squares that are fetched -// concurrently/simultaneously. -var NumConcurrentSquares = 8 - -// Global worker pool that globally controls and limits goroutines spawned by -// GetShares. -// -// TODO(@Wondertan): Idle timeout for workers needs to be configured to around block time, -// so that workers spawned between each reconstruction for every new block are reused. -var pool = workerpool.New(NumWorkersLimit) - -// GetShares gets shares from either local storage, or, if not found, requests -// them from immediate/connected peers. It puts them into the given 'put' func, -// does not return any error, and returns/unblocks only on success -// (got all shares) or on context cancellation. -// -// It works concurrently by spawning workers in the pool which do one basic -// thing - block until data is fetched, s. t. share processing is never -// sequential, and thus we request *all* the shares available without waiting -// for others to finish. It is the required property to maximize data -// availability. As a side effect, we get concurrent tree traversal reducing -// time to data time. -// -// GetShares relies on the fact that the underlying data structure is a binary -// tree, so it's not suitable for anything else besides that. Parts on the -// implementation that rely on this property are explicitly tagged with -// (bin-tree-feat). -func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, shares int, put func(int, Share)) { - ctx, span := tracer.Start(ctx, "get-shares") - defer span.End() - - // this buffer ensures writes to 'jobs' are never blocking (bin-tree-feat) - jobs := make(chan *job, (shares+1)/2) // +1 for the case where 'shares' is 1 - jobs <- &job{id: root, ctx: ctx} - // total is an amount of routines spawned and total amount of nodes we process (bin-tree-feat) - // so we can specify exact amount of loops we do, and wait for this amount - // of routines to finish processing - total := shares*2 - 1 - wg := sync.WaitGroup{} - wg.Add(total) - // all preparations are done, so begin processing jobs - for i := 0; i < total; i++ { - select { - case j := <-jobs: - // work over each job concurrently, s.t. shares do not block - // processing of each other - pool.Submit(func() { - ctx, span := tracer.Start(j.ctx, "process-job") - defer span.End() - defer wg.Done() - - span.SetAttributes( - attribute.String("cid", j.id.String()), - attribute.Int("pos", j.pos), - ) - - nd, err := plugin.GetNode(ctx, bGetter, j.id) - if err != nil { - // we don't really care about errors here - // just fetch as much as possible - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - return - } - // check links to know what we should do with the node - lnks := nd.Links() - if len(lnks) == 1 { // so we are almost there - // the reason why the comment on 'total' is lying, as each - // leaf has its own additional leaf(hack) so get it - nd, err := plugin.GetNode(ctx, bGetter, lnks[0].Cid) - if err != nil { - // again, we don't really care much, just fetch as much as possible - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - return - } - // successfully fetched a share/leaf - // ladies and gentlemen, we got em! - span.SetStatus(codes.Ok, "") - put(j.pos, leafToShare(nd)) - return - } - // ok, we found more links - for i, lnk := range lnks { - // send those to be processed - select { - case jobs <- &job{ - id: lnk.Cid, - // calc position for children nodes (bin-tree-feat), - // s.t. 'if' above knows where to put a share - pos: j.pos*2 + i, - // we pass the context to job so that spans are tracked in a tree - // structure - ctx: ctx, - }: - case <-ctx.Done(): - return - } - } - }) - case <-ctx.Done(): - return - } - } - // "tick-tack, how much more should I wait before you get those shares?" - the goroutine - wg.Wait() -} diff --git a/ipld/helpers.go b/ipld/helpers.go deleted file mode 100644 index 83a21a8947..0000000000 --- a/ipld/helpers.go +++ /dev/null @@ -1,22 +0,0 @@ -package ipld - -import ( - "context" - "fmt" - - "github.com/ipfs/go-cid" -) - -// job represents an encountered node to investigate during the `GetShares` and `GetSharesByNamespace` routines. -type job struct { - id cid.Cid - pos int - ctx context.Context -} - -func SanityCheckNID(nID []byte) error { - if len(nID) != NamespaceSize { - return fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(nID)) - } - return nil -} diff --git a/ipld/share.go b/ipld/share.go deleted file mode 100644 index 1da3908548..0000000000 --- a/ipld/share.go +++ /dev/null @@ -1,99 +0,0 @@ -package ipld - -import ( - "crypto/sha256" - - "github.com/ipfs/go-cid" - "github.com/tendermint/tendermint/pkg/consts" - - "github.com/celestiaorg/celestia-node/ipld/pb" - "github.com/celestiaorg/celestia-node/ipld/plugin" - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" -) - -const ( - // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. - MaxSquareSize = consts.MaxSquareSize - // NamespaceSize is a system-wide size for NMT namespaces. - NamespaceSize = consts.NamespaceSize - // ShareSize is a system-wide size of a share, including both data and namespace ID - ShareSize = consts.ShareSize -) - -// DefaultRSMT2DCodec sets the default rsmt2d.Codec for shares. -var DefaultRSMT2DCodec = consts.DefaultCodec - -// Share contains the raw share data without the corresponding namespace. -// NOTE: Alias for the byte is chosen to keep maximal compatibility, especially with rsmt2d. Ideally, we should define -// reusable type elsewhere and make everyone(Core, rsmt2d, ipld) to rely on it. -type Share = []byte - -// ShareID gets the namespace ID from the share. -func ShareID(s Share) namespace.ID { - return s[:NamespaceSize] -} - -// ShareData gets data from the share. -func ShareData(s Share) []byte { - return s[NamespaceSize:] -} - -// ShareWithProof contains data with corresponding Merkle Proof -type ShareWithProof struct { - // Share is a full data including namespace - Share - // Proof is a Merkle Proof of current share - Proof *nmt.Proof -} - -// NewShareWithProof takes the given leaf and its path, starting from the tree root, -// and computes the nmt.Proof for it. -func NewShareWithProof(index int, share Share, pathToLeaf []cid.Cid) *ShareWithProof { - rangeProofs := make([][]byte, 0, len(pathToLeaf)) - for i := len(pathToLeaf) - 1; i >= 0; i-- { - node := plugin.NamespacedSha256FromCID(pathToLeaf[i]) - rangeProofs = append(rangeProofs, node) - } - - proof := nmt.NewInclusionProof(index, index+1, rangeProofs, true) - return &ShareWithProof{ - share, - &proof, - } -} - -// Validate validates inclusion of the share under the given root CID. -func (s *ShareWithProof) Validate(root cid.Cid) bool { - return s.Proof.VerifyInclusion( - sha256.New(), // TODO(@Wondertan): This should be defined somewhere globally - ShareID(s.Share), - [][]byte{ShareData(s.Share)}, - plugin.NamespacedSha256FromCID(root), - ) -} - -func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { - return &pb.Share{ - Data: s.Share, - Proof: &pb.MerkleProof{ - Start: int64(s.Proof.Start()), - End: int64(s.Proof.End()), - Nodes: s.Proof.Nodes(), - LeafHash: s.Proof.LeafHash(), - }, - } -} - -func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { - shares := make([]*ShareWithProof, len(protoShares)) - for i, share := range protoShares { - proof := ProtoToProof(share.Proof) - shares[i] = &ShareWithProof{share.Data, &proof} - } - return shares -} - -func ProtoToProof(protoProof *pb.MerkleProof) nmt.Proof { - return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, true) -} diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 129f7168cd..53ffa972e9 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -3,10 +3,13 @@ package share import ( "context" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/full" + "github.com/celestiaorg/celestia-node/share/availability/light" + "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/share" ) func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { @@ -28,34 +31,34 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option "share", baseComponents, fx.Provide(fx.Annotate( - share.NewLightAvailability, - fx.OnStart(func(ctx context.Context, avail *share.LightAvailability) error { + light.NewShareAvailability, + fx.OnStart(func(ctx context.Context, avail *light.ShareAvailability) error { return avail.Start(ctx) }), - fx.OnStop(func(ctx context.Context, avail *share.LightAvailability) error { + fx.OnStop(func(ctx context.Context, avail *light.ShareAvailability) error { return avail.Stop(ctx) }), )), // CacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a CacheAvailability but the constructor returns a share.Availability - fx.Provide(CacheAvailability[*share.LightAvailability]), + fx.Provide(CacheAvailability[*light.ShareAvailability]), ) case node.Bridge, node.Full: return fx.Module( "share", baseComponents, fx.Provide(fx.Annotate( - share.NewFullAvailability, - fx.OnStart(func(ctx context.Context, avail *share.FullAvailability) error { + full.NewShareAvailability, + fx.OnStart(func(ctx context.Context, avail *full.ShareAvailability) error { return avail.Start(ctx) }), - fx.OnStop(func(ctx context.Context, avail *share.FullAvailability) error { + fx.OnStop(func(ctx context.Context, avail *full.ShareAvailability) error { return avail.Stop(ctx) }), )), // CacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a CacheAvailability but the constructor returns a share.Availability - fx.Provide(CacheAvailability[*share.FullAvailability]), + fx.Provide(CacheAvailability[*full.ShareAvailability]), ) default: panic("invalid node type") diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go index 4e2be8f26b..284255b8c2 100644 --- a/nodebuilder/share/service.go +++ b/nodebuilder/share/service.go @@ -3,6 +3,8 @@ package share import ( "context" + "github.com/celestiaorg/celestia-node/share/service" + "github.com/ipfs/go-blockservice" "go.uber.org/fx" @@ -30,14 +32,14 @@ type Module interface { } func NewModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Availability) Module { - service := share.NewService(bServ, avail) + serv := service.NewShareService(bServ, avail) lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { - return service.Start(ctx) + return serv.Start(ctx) }, OnStop: func(ctx context.Context) error { - return service.Stop(ctx) + return serv.Stop(ctx) }, }) - return service + return serv } diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index d6ad66898e..2803a29d96 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -1,21 +1,24 @@ package share import ( + "go.uber.org/fx" + "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/routing" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" - "go.uber.org/fx" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/cache" + "github.com/celestiaorg/celestia-node/share/availability/discovery" ) -func Discovery(cfg Config) func(routing.ContentRouting, host.Host) *share.Discovery { +func Discovery(cfg Config) func(routing.ContentRouting, host.Host) *discovery.Discovery { return func( r routing.ContentRouting, h host.Host, - ) *share.Discovery { - return share.NewDiscovery( + ) *discovery.Discovery { + return discovery.NewDiscovery( h, routingdisc.NewRoutingDiscovery(r), cfg.PeersLimit, @@ -27,7 +30,7 @@ func Discovery(cfg Config) func(routing.ContentRouting, host.Host) *share.Discov // CacheAvailability wraps either Full or Light availability with a cache for result sampling. func CacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batching, avail A) share.Availability { - ca := share.NewCacheAvailability(avail, ds) + ca := cache.NewShareAvailability(avail, ds) lc.Append(fx.Hook{ OnStop: ca.Close, }) diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 47eb881cb8..ea803a9802 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -16,11 +16,12 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" - "github.com/celestiaorg/celestia-node/share" ) /* @@ -90,8 +91,8 @@ Steps: 9. Check that the FN can retrieve shares from 1 to 20 blocks */ func TestFullReconstructFromLights(t *testing.T) { - ipld.RetrieveQuadrantTimeout = time.Millisecond * 100 - share.DefaultSampleAmount = 20 + eds.RetrieveQuadrantTimeout = time.Millisecond * 100 + light.DefaultSampleAmount = 20 const ( blocks = 20 btime = time.Millisecond * 300 diff --git a/ipld/add.go b/share/add.go similarity index 76% rename from ipld/add.go rename to share/add.go index 6b768cb3a0..f9e74c6ca9 100644 --- a/ipld/add.go +++ b/share/add.go @@ -1,4 +1,4 @@ -package ipld +package share import ( "context" @@ -6,12 +6,11 @@ import ( "math" "github.com/ipfs/go-blockservice" - ipld "github.com/ipfs/go-ipld-format" + "github.com/tendermint/tendermint/pkg/wrapper" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" - - "github.com/tendermint/tendermint/pkg/wrapper" ) // AddShares erasures and extends shares to blockservice.BlockService using the provided ipld.NodeAdder. @@ -25,8 +24,7 @@ func AddShares( } squareSize := int(math.Sqrt(float64(len(shares)))) // create nmt adder wrapping batch adder with calculated size - bs := batchSize(squareSize * 2) - batchAdder := NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(bs)) + batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) // create the nmt wrapper to generate row and col commitments tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(squareSize), nmt.NodeVisitor(batchAdder.Visit)) // recompute the eds @@ -50,8 +48,7 @@ func ImportShares( } squareSize := int(math.Sqrt(float64(len(shares)))) // create nmt adder wrapping batch adder with calculated size - bs := batchSize(squareSize * 2) - batchAdder := NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(bs)) + batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) // create the nmt wrapper to generate row and col commitments tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(squareSize/2), nmt.NodeVisitor(batchAdder.Visit)) // recompute the eds @@ -93,20 +90,3 @@ func ExtractEDS(eds *rsmt2d.ExtendedDataSquare) []Share { } return out } - -// batchSize calculates the amount of nodes that are generated from block of 'squareSizes' -// to be batched in one write. -func batchSize(squareSize int) int { - // (squareSize*2-1) - amount of nodes in a generated binary tree - // squareSize*2 - the total number of trees, both for rows and cols - // (squareSize*squareSize) - all the shares - // - // Note that while our IPLD tree looks like this: - // ---X - // -X---X - // X-X-X-X - // X-X-X-X - // here we count leaves only once: the CIDs are the same for columns and rows - // and for the last two layers as well: - return (squareSize*2-1)*squareSize*2 - (squareSize * squareSize) -} diff --git a/share/interface.go b/share/availability.go similarity index 76% rename from share/interface.go rename to share/availability.go index 6573f2cfc7..d53d1d752f 100644 --- a/share/interface.go +++ b/share/availability.go @@ -4,16 +4,22 @@ import ( "context" "errors" "time" + + "github.com/tendermint/tendermint/pkg/da" ) // ErrNotAvailable is returned whenever DA sampling fails. -var ErrNotAvailable = errors.New("da: data not available") +var ErrNotAvailable = errors.New("share: data not available") // AvailabilityTimeout specifies timeout for DA validation during which data have to be found on the network, // otherwise ErrNotAvailable is fired. // TODO: https://github.com/celestiaorg/celestia-node/issues/10 const AvailabilityTimeout = 20 * time.Minute +// Root represents root commitment to multiple Shares. +// In practice, it is a commitment to all the Data in a square. +type Root = da.DataAvailabilityHeader + // Availability defines interface for validation of Shares' availability. type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. diff --git a/share/cache_availability.go b/share/availability/cache/availability.go similarity index 72% rename from share/cache_availability.go rename to share/availability/cache/availability.go index 02cdb49905..3d4f94a431 100644 --- a/share/cache_availability.go +++ b/share/availability/cache/availability.go @@ -1,4 +1,4 @@ -package share +package cache import ( "bytes" @@ -8,9 +8,14 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/autobatch" "github.com/ipfs/go-datastore/namespace" + logging "github.com/ipfs/go-log/v2" "github.com/tendermint/tendermint/pkg/da" + + "github.com/celestiaorg/celestia-node/share" ) +var log = logging.Logger("share/cache") + var ( // DefaultWriteBatchSize defines the size of the batched header write. // Headers are written in batches not to thrash the underlying Datastore with writes. @@ -22,11 +27,11 @@ var ( minRoot = da.MinDataAvailabilityHeader() ) -// CacheAvailability wraps a given Availability (whether it's light or full) +// ShareAvailability wraps a given share.Availability (whether it's light or full) // and stores the results of a successful sampling routine over a given Root's hash // to disk. -type CacheAvailability struct { - avail Availability +type ShareAvailability struct { + avail share.Availability // TODO(@Wondertan): Once we come to parallelized DASer, this lock becomes a contention point // Related to #483 @@ -34,20 +39,20 @@ type CacheAvailability struct { ds *autobatch.Datastore } -// NewCacheAvailability wraps the given Availability with an additional datastore +// NewShareAvailability wraps the given share.Availability with an additional datastore // for sampling result caching. -func NewCacheAvailability(avail Availability, ds datastore.Batching) *CacheAvailability { +func NewShareAvailability(avail share.Availability, ds datastore.Batching) *ShareAvailability { ds = namespace.Wrap(ds, cacheAvailabilityPrefix) autoDS := autobatch.NewAutoBatching(ds, DefaultWriteBatchSize) - return &CacheAvailability{ + return &ShareAvailability{ avail: avail, ds: autoDS, } } // SharesAvailable will store, upon success, the hash of the given Root to disk. -func (ca *CacheAvailability) SharesAvailable(ctx context.Context, root *Root) error { +func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { // short-circuit if the given root is minimum DAH of an empty data square if isMinRoot(root) { return nil @@ -76,21 +81,21 @@ func (ca *CacheAvailability) SharesAvailable(ctx context.Context, root *Root) er return err } -func (ca *CacheAvailability) ProbabilityOfAvailability() float64 { +func (ca *ShareAvailability) ProbabilityOfAvailability() float64 { return ca.avail.ProbabilityOfAvailability() } // Close flushes all queued writes to disk. -func (ca *CacheAvailability) Close(ctx context.Context) error { +func (ca *ShareAvailability) Close(ctx context.Context) error { return ca.ds.Flush(ctx) } -func rootKey(root *Root) datastore.Key { +func rootKey(root *share.Root) datastore.Key { return datastore.NewKey(root.String()) } // isMinRoot returns whether the given root is a minimum (empty) // DataAvailabilityHeader (DAH). -func isMinRoot(root *Root) bool { +func isMinRoot(root *share.Root) bool { return bytes.Equal(minRoot.Hash(), root.Hash()) } diff --git a/share/cache_availability_test.go b/share/availability/cache/availability_test.go similarity index 81% rename from share/cache_availability_test.go rename to share/availability/cache/availability_test.go index bdc7eeb5d9..b49fb547e8 100644 --- a/share/cache_availability_test.go +++ b/share/availability/cache/availability_test.go @@ -1,4 +1,4 @@ -package share +package cache import ( "context" @@ -13,6 +13,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/pkg/da" + + "github.com/celestiaorg/celestia-node/share" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/service" ) // TestCacheAvailability tests to ensure that the successful result of a @@ -21,12 +25,12 @@ func TestCacheAvailability(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fullLocalServ, dah0 := randFullLocalServiceWithSquare(t, 16) - lightLocalServ, dah1 := randLightLocalServiceWithSquare(t, 16) + fullLocalServ, dah0 := RandFullLocalServiceWithSquare(t, 16) + lightLocalServ, dah1 := RandLightLocalServiceWithSquare(t, 16) var tests = []struct { - service *Service - root *Root + service *service.ShareService + root *share.Root }{ { service: fullLocalServ, @@ -40,7 +44,7 @@ func TestCacheAvailability(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - ca := tt.service.Availability.(*CacheAvailability) + ca := tt.service.Availability.(*ShareAvailability) // ensure the dah isn't yet in the cache exists, err := ca.ds.Has(ctx, rootKey(tt.root)) require.NoError(t, err) @@ -65,8 +69,8 @@ func TestCacheAvailability_Failed(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - ca := NewCacheAvailability(&dummyAvailability{}, sync.MutexWrap(datastore.NewMapDatastore())) - serv := NewService(mdutils.Bserv(), ca) + ca := NewShareAvailability(&dummyAvailability{}, sync.MutexWrap(datastore.NewMapDatastore())) + serv := service.NewShareService(mdutils.Bserv(), ca) err := serv.SharesAvailable(ctx, &invalidHeader) require.Error(t, err) @@ -83,10 +87,10 @@ func TestCacheAvailability_NoDuplicateSampling(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // create root to cache - root := RandFillBS(t, 16, mdutils.Bserv()) + root := availability_test.RandFillBS(t, 16, mdutils.Bserv()) // wrap dummyAvailability with a datastore ds := sync.MutexWrap(datastore.NewMapDatastore()) - ca := NewCacheAvailability(&dummyAvailability{counter: 0}, ds) + ca := NewShareAvailability(&dummyAvailability{counter: 0}, ds) // sample the root err := ca.SharesAvailable(ctx, root) require.NoError(t, err) @@ -106,7 +110,7 @@ func TestCacheAvailability_MinRoot(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fullLocalServ, _ := randFullLocalServiceWithSquare(t, 16) + fullLocalServ, _ := RandFullLocalServiceWithSquare(t, 16) minDAH := da.MinDataAvailabilityHeader() err := fullLocalServ.SharesAvailable(ctx, &minDAH) @@ -119,7 +123,7 @@ type dummyAvailability struct { // SharesAvailable should only be called once, if called more than once, return // error. -func (da *dummyAvailability) SharesAvailable(_ context.Context, root *Root) error { +func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root) error { if root == &invalidHeader { return fmt.Errorf("invalid header") } diff --git a/share/availability/cache/testing.go b/share/availability/cache/testing.go new file mode 100644 index 0000000000..8ef7a1e743 --- /dev/null +++ b/share/availability/cache/testing.go @@ -0,0 +1,39 @@ +package cache + +import ( + "testing" + + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + mdutils "github.com/ipfs/go-merkledag/test" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/full" + "github.com/celestiaorg/celestia-node/share/availability/light" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/service" +) + +// RandLightLocalServiceWithSquare is the same as light.RandServiceWithSquare, except +// the share.Availability is wrapped with cache availability. +func RandLightLocalServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { + bServ := mdutils.Bserv() + store := dssync.MutexWrap(ds.NewMapDatastore()) + avail := NewShareAvailability( + light.TestAvailability(bServ), + store, + ) + return service.NewShareService(bServ, avail), availability_test.RandFillBS(t, n, bServ) +} + +// RandFullLocalServiceWithSquare is the same as full.RandServiceWithSquare, except +// the share.Availability is wrapped with cache availability. +func RandFullLocalServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { + bServ := mdutils.Bserv() + store := dssync.MutexWrap(ds.NewMapDatastore()) + avail := NewShareAvailability( + full.TestAvailability(bServ), + store, + ) + return service.NewShareService(bServ, avail), availability_test.RandFillBS(t, n, bServ) +} diff --git a/share/backoff.go b/share/availability/discovery/backoff.go similarity index 99% rename from share/backoff.go rename to share/availability/discovery/backoff.go index 6cdb04f8ac..9b0f2f6acc 100644 --- a/share/backoff.go +++ b/share/availability/discovery/backoff.go @@ -1,4 +1,4 @@ -package share +package discovery import ( "context" diff --git a/share/backoff_test.go b/share/availability/discovery/backoff_test.go similarity index 98% rename from share/backoff_test.go rename to share/availability/discovery/backoff_test.go index 48b342d0ee..735bc9ef6d 100644 --- a/share/backoff_test.go +++ b/share/availability/discovery/backoff_test.go @@ -1,15 +1,14 @@ -package share +package discovery import ( "context" "testing" "time" - "github.com/stretchr/testify/require" - "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p/p2p/discovery/backoff" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" ) func TestBackoff_ConnectPeer(t *testing.T) { diff --git a/share/discovery.go b/share/availability/discovery/discovery.go similarity index 89% rename from share/discovery.go rename to share/availability/discovery/discovery.go index 07645c21a7..9733d2d927 100644 --- a/share/discovery.go +++ b/share/availability/discovery/discovery.go @@ -1,9 +1,10 @@ -package share +package discovery import ( "context" "time" + logging "github.com/ipfs/go-log/v2" core "github.com/libp2p/go-libp2p-core/discovery" "github.com/libp2p/go-libp2p-core/event" "github.com/libp2p/go-libp2p-core/host" @@ -11,6 +12,8 @@ import ( "github.com/libp2p/go-libp2p-core/peer" ) +var log = logging.Logger("share/discovery") + const ( // peerWeight is a weight of discovered peers. // peerWeight is a number that will be assigned to all discovered full nodes, @@ -24,7 +27,7 @@ var waitF = func(ttl time.Duration) time.Duration { return 7 * ttl / 8 } -// discovery combines advertise and discover services and allows to store discovered nodes. +// Discovery combines advertise and discover services and allows to store discovered nodes. type Discovery struct { set *limitedSet host host.Host @@ -80,10 +83,10 @@ func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer d.host.ConnManager().TagPeer(peer.ID, topic, peerWeight) } -// ensurePeers ensures we always have 'peerLimit' connected peers. +// EnsurePeers ensures we always have 'peerLimit' connected peers. // It starts peer discovery every 30 seconds until peer cache reaches peersLimit. // Discovery is restarted if any previously connected peers disconnect. -func (d *Discovery) ensurePeers(ctx context.Context) { +func (d *Discovery) EnsurePeers(ctx context.Context) { if d.peersLimit == 0 { log.Warn("peers limit is set to 0. Skipping discovery...") return @@ -118,8 +121,8 @@ func (d *Discovery) ensurePeers(ctx context.Context) { log.Error(err) continue } - for peer := range peers { - go d.handlePeerFound(ctx, topic, peer) + for p := range peers { + go d.handlePeerFound(ctx, topic, p) } case e := <-sub.Out(): // listen to disconnect event to remove peer from set and reset backoff time @@ -137,8 +140,8 @@ func (d *Discovery) ensurePeers(ctx context.Context) { } } -// advertise is a utility function that persistently advertises a service through an Advertiser. -func (d *Discovery) advertise(ctx context.Context) { +// Advertise is a utility function that persistently advertises a service through an Advertiser. +func (d *Discovery) Advertise(ctx context.Context) { timer := time.NewTimer(d.advertiseInterval) defer timer.Stop() for { diff --git a/share/set.go b/share/availability/discovery/set.go similarity index 98% rename from share/set.go rename to share/availability/discovery/set.go index a89b09dabf..9560375586 100644 --- a/share/set.go +++ b/share/availability/discovery/set.go @@ -1,4 +1,4 @@ -package share +package discovery import ( "errors" diff --git a/share/set_test.go b/share/availability/discovery/set_test.go similarity index 98% rename from share/set_test.go rename to share/availability/discovery/set_test.go index 5c95f05904..60e5cdd958 100644 --- a/share/set_test.go +++ b/share/availability/discovery/set_test.go @@ -1,11 +1,10 @@ -package share +package discovery import ( "testing" - "github.com/stretchr/testify/require" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" ) func TestSet_TryAdd(t *testing.T) { diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go new file mode 100644 index 0000000000..1a5c57606c --- /dev/null +++ b/share/availability/full/availability.go @@ -0,0 +1,77 @@ +package full + +import ( + "context" + "errors" + + "github.com/ipfs/go-blockservice" + ipldFormat "github.com/ipfs/go-ipld-format" + logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/eds" +) + +var log = logging.Logger("share/full") + +// ShareAvailability implements share.Availability using the full data square +// recovery technique. It is considered "full" because it is required +// to download enough shares to fully reconstruct the data square. +type ShareAvailability struct { + rtrv *eds.Retriever + disc *discovery.Discovery + + cancel context.CancelFunc +} + +// NewShareAvailability creates a new full ShareAvailability. +func NewShareAvailability(bServ blockservice.BlockService, disc *discovery.Discovery) *ShareAvailability { + return &ShareAvailability{ + rtrv: eds.NewRetriever(bServ), + disc: disc, + } +} + +func (fa *ShareAvailability) Start(context.Context) error { + ctx, cancel := context.WithCancel(context.Background()) + fa.cancel = cancel + + go fa.disc.Advertise(ctx) + go fa.disc.EnsurePeers(ctx) + return nil +} + +func (fa *ShareAvailability) Stop(context.Context) error { + fa.cancel() + return nil +} + +// SharesAvailable reconstructs the data committed to the given Root by requesting +// enough Shares from the network. +func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { + ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) + defer cancel() + // we assume the caller of this method has already performed basic validation on the + // given dah/root. If for some reason this has not happened, the node should panic. + if err := root.ValidateBasic(); err != nil { + log.Errorw("Availability validation cannot be performed on a malformed DataAvailabilityHeader", + "err", err) + panic(err) + } + + _, err := fa.rtrv.Retrieve(ctx, root) + if err != nil { + log.Errorw("availability validation failed", "root", root.Hash(), "err", err) + if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { + return share.ErrNotAvailable + } + + return err + } + return err +} + +func (fa *ShareAvailability) ProbabilityOfAvailability() float64 { + return 1 +} diff --git a/share/full_availability_test.go b/share/availability/full/availability_test.go similarity index 66% rename from share/full_availability_test.go rename to share/availability/full/availability_test.go index 311e0e90b4..ef3e653d9e 100644 --- a/share/full_availability_test.go +++ b/share/availability/full/availability_test.go @@ -1,4 +1,4 @@ -package share +package full import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/stretchr/testify/assert" + + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" ) func init() { @@ -14,25 +16,25 @@ func init() { rand.Seed(time.Now().UnixNano()) } -func TestSharesAvailable_Full(t *testing.T) { +func TestShareAvailableOverMocknet_Full(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // randFullServiceWithSquare creates a NewFullAvailability inside, so we can test it - service, dah := randFullServiceWithSquare(t, 16) - err := service.SharesAvailable(ctx, dah) + net := availability_test.NewTestDAGNet(ctx, t) + _, root := RandNode(net, 32) + nd := Node(net) + net.ConnectAll() + + err := nd.SharesAvailable(ctx, root) assert.NoError(t, err) } -func TestShareAvailableOverMocknet_Full(t *testing.T) { +func TestSharesAvailable_Full(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - net := NewTestDAGNet(ctx, t) - _, root := net.RandFullNode(32) - nd := net.FullNode() - net.ConnectAll() - - err := nd.SharesAvailable(ctx, root) + // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it + service, dah := RandServiceWithSquare(t, 16) + err := service.SharesAvailable(ctx, dah) assert.NoError(t, err) } diff --git a/share/full_reconstruction_test.go b/share/availability/full/reconstruction_test.go similarity index 69% rename from share/full_reconstruction_test.go rename to share/availability/full/reconstruction_test.go index 10d47947dd..74fc67baab 100644 --- a/share/full_reconstruction_test.go +++ b/share/availability/full/reconstruction_test.go @@ -1,6 +1,6 @@ //go:build !race -package share +package full import ( "context" @@ -10,19 +10,22 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/light" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/eds" ) func init() { - ipld.RetrieveQuadrantTimeout = time.Millisecond * 100 // to speed up tests + eds.RetrieveQuadrantTimeout = time.Millisecond * 100 // to speed up tests } -// TestShareAvailable_OneFullNode asserts that a FullAvailability node can ensure +// TestShareAvailable_OneFullNode asserts that a full node can ensure // data is available (reconstruct data square) while being connected to -// LightAvailability nodes only. +// light nodes only. func TestShareAvailable_OneFullNode(t *testing.T) { // NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper - DefaultSampleAmount = 20 // s + light.DefaultSampleAmount = 20 // s const ( origSquareSize = 16 // k lightNodes = 69 // c @@ -31,9 +34,9 @@ func TestShareAvailable_OneFullNode(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() - net := NewTestDAGNet(ctx, t) - source, root := net.RandFullNode(origSquareSize) // make a source node, a.k.a bridge - full := net.FullNode() // make a full availability service which reconstructs data + net := availability_test.NewTestDAGNet(ctx, t) + source, root := RandNode(net, origSquareSize) // make a source node, a.k.a bridge + full := Node(net) // make a full availability service which reconstructs data // ensure there is no connection between source and full nodes // so that full reconstructs from the light nodes only @@ -44,9 +47,9 @@ func TestShareAvailable_OneFullNode(t *testing.T) { return full.SharesAvailable(errCtx, root) }) - lights := make([]*node, lightNodes) + lights := make([]*availability_test.Node, lightNodes) for i := 0; i < len(lights); i++ { - lights[i] = net.LightNode() + lights[i] = light.Node(net) go func(i int) { err := lights[i].SharesAvailable(ctx, root) if err != nil { @@ -67,14 +70,16 @@ func TestShareAvailable_OneFullNode(t *testing.T) { require.NoError(t, err) } -// TestShareAvailable_ConnectedFullNodes asserts that two connected FullAvailability nodes -// can ensure data availability via two isolated LightAvailability node subnetworks. Full nodes -// start their availability process first, then Lights start availability process and connect to -// Fulls and only after Lights connect to the source node which has the data. After Lights connect -// to the source, Full must be able to finish the availability process started in the beginning. +// TestShareAvailable_ConnectedFullNodes asserts that two connected full nodes +// can ensure data availability via two isolated light node subnetworks. Full +// nodes start their availability process first, then light node start +// availability process and connect to full node and only after light node +// connect to the source node which has the data. After light node connect to the +// source, full node must be able to finish the availability process started in +// the beginning. func TestShareAvailable_ConnectedFullNodes(t *testing.T) { // NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper - DefaultSampleAmount = 20 // s + light.DefaultSampleAmount = 20 // s const ( origSquareSize = 16 // k lightNodes = 60 // c @@ -83,12 +88,12 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() - net := NewTestDAGNet(ctx, t) - source, root := net.RandFullNode(origSquareSize) + net := availability_test.NewTestDAGNet(ctx, t) + source, root := RandNode(net, origSquareSize) // create two full nodes and ensure they are disconnected - full1 := net.FullNode() - full2 := net.FullNode() + full1 := Node(net) + full2 := Node(net) // pre-connect fulls net.Connect(full1.ID(), full2.ID()) @@ -107,9 +112,9 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { }) // create light nodes and start sampling for them immediately - lights1, lights2 := make([]*node, lightNodes/2), make([]*node, lightNodes/2) + lights1, lights2 := make([]*availability_test.Node, lightNodes/2), make([]*availability_test.Node, lightNodes/2) for i := 0; i < len(lights1); i++ { - lights1[i] = net.LightNode() + lights1[i] = light.Node(net) go func(i int) { err := lights1[i].SharesAvailable(ctx, root) if err != nil { @@ -117,7 +122,7 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { } }(i) - lights2[i] = net.LightNode() + lights2[i] = light.Node(net) go func(i int) { err := lights2[i].SharesAvailable(ctx, root) if err != nil { @@ -146,10 +151,11 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { require.NoError(t, err) } -// TestShareAvailable_DisconnectedFullNodes asserts that two disconnected FullAvailability nodes -// cannot ensure data is available (reconstruct data square) while being connected to isolated -// LightAvailability nodes subnetworks, which do not have enough nodes to reconstruct the data, -// but once FullAvailability nodes connect, they can collectively reconstruct it. +// TestShareAvailable_DisconnectedFullNodes asserts that two disconnected full +// nodes cannot ensure data is available (reconstruct data square) while being +// connected to isolated light nodes subnetworks, which do not have enough nodes +// to reconstruct the data, but once ShareAvailability nodes connect, they can +// collectively reconstruct it. func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { // S - Source // L - Light Node @@ -169,7 +175,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { // // NOTE: Numbers are taken from the original 'Fraud and Data Availability Proofs' paper - DefaultSampleAmount = 20 // s + light.DefaultSampleAmount = 20 // s const ( origSquareSize = 16 // k lightNodes = 60 // c - total number of nodes on two subnetworks @@ -178,12 +184,12 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() - net := NewTestDAGNet(ctx, t) - source, root := net.RandFullNode(origSquareSize) + net := availability_test.NewTestDAGNet(ctx, t) + source, root := RandNode(net, origSquareSize) // create two full nodes and ensure they are disconnected - full1 := net.FullNode() - full2 := net.FullNode() + full1 := Node(net) + full2 := Node(net) net.Disconnect(full1.ID(), full2.ID()) // ensure fulls and source are not connected @@ -192,7 +198,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { net.Disconnect(full2.ID(), source.ID()) // start reconstruction for fulls that should fail - ctxErr, cancelErr := context.WithTimeout(ctx, ipld.RetrieveQuadrantTimeout*8) + ctxErr, cancelErr := context.WithTimeout(ctx, eds.RetrieveQuadrantTimeout*8) errg, errCtx := errgroup.WithContext(ctxErr) errg.Go(func() error { return full1.SharesAvailable(errCtx, root) @@ -202,9 +208,9 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { }) // create light nodes and start sampling for them immediately - lights1, lights2 := make([]*node, lightNodes/2), make([]*node, lightNodes/2) + lights1, lights2 := make([]*availability_test.Node, lightNodes/2), make([]*availability_test.Node, lightNodes/2) for i := 0; i < len(lights1); i++ { - lights1[i] = net.LightNode() + lights1[i] = light.Node(net) go func(i int) { err := lights1[i].SharesAvailable(ctx, root) if err != nil { @@ -212,7 +218,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { } }(i) - lights2[i] = net.LightNode() + lights2[i] = light.Node(net) go func(i int) { err := lights2[i].SharesAvailable(ctx, root) if err != nil { @@ -235,7 +241,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { // check that any of the fulls cannot reconstruct on their own err := errg.Wait() - require.ErrorIs(t, err, ErrNotAvailable) + require.ErrorIs(t, err, share.ErrNotAvailable) cancelErr() // but after they connect @@ -247,7 +253,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { // they both should be able to reconstruct the block err = full1.SharesAvailable(ctx, root) - require.NoError(t, err, ErrNotAvailable) + require.NoError(t, err, share.ErrNotAvailable) err = full2.SharesAvailable(ctx, root) - require.NoError(t, err, ErrNotAvailable) + require.NoError(t, err, share.ErrNotAvailable) } diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go new file mode 100644 index 0000000000..dad182a8d3 --- /dev/null +++ b/share/availability/full/testing.go @@ -0,0 +1,47 @@ +package full + +import ( + "testing" + "time" + + "github.com/ipfs/go-blockservice" + mdutils "github.com/ipfs/go-merkledag/test" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "github.com/libp2p/go-libp2p/p2p/discovery/routing" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/discovery" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/service" +) + +// RandServiceWithSquare provides a service.ShareService filled with 'n' NMT +// trees of 'n' random shares, essentially storing a whole square. +func RandServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { + bServ := mdutils.Bserv() + return service.NewShareService(bServ, TestAvailability(bServ)), availability_test.RandFillBS(t, n, bServ) +} + +// RandNode creates a Full Node filled with a random block of the given size. +func RandNode(dn *availability_test.DagNet, squareSize int) (*availability_test.Node, *share.Root) { + nd := Node(dn) + return nd, availability_test.RandFillBS(dn.T, squareSize, nd.BlockService) +} + +// Node creates a new empty Full Node. +func Node(dn *availability_test.DagNet) *availability_test.Node { + nd := dn.Node() + nd.ShareService = service.NewShareService(nd.BlockService, TestAvailability(nd.BlockService)) + return nd +} + +func TestAvailability(bServ blockservice.BlockService) *ShareAvailability { + disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + return NewShareAvailability(bServ, disc) +} + +func SubNetNode(sn *availability_test.SubNet) *availability_test.Node { + nd := Node(sn.DagNet) + sn.AddNode(nd) + return nd +} diff --git a/share/light_availability.go b/share/availability/light/availability.go similarity index 62% rename from share/light_availability.go rename to share/availability/light/availability.go index 603c8e407f..eec3141f1e 100644 --- a/share/light_availability.go +++ b/share/availability/light/availability.go @@ -1,59 +1,62 @@ -package share +package light import ( "context" "errors" "math" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/ipfs/go-blockservice" - format "github.com/ipfs/go-ipld-format" + ipldFormat "github.com/ipfs/go-ipld-format" + logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/discovery" ) -// DefaultSampleAmount sets the default amount of samples to be sampled from the network by lightAvailability. -var DefaultSampleAmount = 16 +var log = logging.Logger("share/light") -// LightAvailability implements Availability using Data Availability Sampling technique. +// ShareAvailability implements share.Availability using Data Availability Sampling technique. // It is light because it does not require the downloading of all the data to verify // its availability. It is assumed that there are a lot of lightAvailability instances // on the network doing sampling over the same Root to collectively verify its availability. -type LightAvailability struct { +type ShareAvailability struct { bserv blockservice.BlockService // disc discovers new full nodes in the network. // it is not allowed to call advertise for light nodes (Full nodes only). - disc *Discovery + disc *discovery.Discovery cancel context.CancelFunc } -// NewLightAvailability creates a new light Availability. -func NewLightAvailability( +// NewShareAvailability creates a new light Availability. +func NewShareAvailability( bserv blockservice.BlockService, - disc *Discovery, -) *LightAvailability { - la := &LightAvailability{ + disc *discovery.Discovery, +) *ShareAvailability { + la := &ShareAvailability{ bserv: bserv, disc: disc, } return la } -func (la *LightAvailability) Start(context.Context) error { +func (la *ShareAvailability) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) la.cancel = cancel - go la.disc.ensurePeers(ctx) + go la.disc.EnsurePeers(ctx) return nil } -func (la *LightAvailability) Stop(context.Context) error { +func (la *ShareAvailability) Stop(context.Context) error { la.cancel() return nil } -// SharesAvailable randomly samples DefaultSamples amount of Shares committed to the given Root. +// SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given Root. // This way SharesAvailable subjectively verifies that Shares are available. -func (la *LightAvailability) SharesAvailable(ctx context.Context, dah *Root) error { +func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { log.Debugw("Validate availability", "root", dah.Hash()) // We assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. @@ -67,15 +70,15 @@ func (la *LightAvailability) SharesAvailable(ctx context.Context, dah *Root) err return err } - ctx, cancel := context.WithTimeout(ctx, AvailabilityTimeout) + ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) defer cancel() ses := blockservice.NewSession(ctx, la.bserv) errs := make(chan error, len(samples)) for _, s := range samples { go func(s Sample) { - root, leaf := translate(dah, s.Row, s.Col) - _, err := ipld.GetShare(ctx, ses, root, leaf, len(dah.RowsRoots)) + root, leaf := ipld.Translate(dah, s.Row, s.Col) + _, err := share.GetShare(ctx, ses, root, leaf, len(dah.RowsRoots)) // we don't really care about Share bodies at this point // it also means we now saved the Share in local storage select { @@ -97,8 +100,8 @@ func (la *LightAvailability) SharesAvailable(ctx context.Context, dah *Root) err if !errors.Is(err, context.Canceled) { log.Errorw("availability validation failed", "root", dah.Hash(), "err", err) } - if format.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { - return ErrNotAvailable + if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { + return share.ErrNotAvailable } return err @@ -113,6 +116,6 @@ func (la *LightAvailability) SharesAvailable(ctx context.Context, dah *Root) err // (DefaultSampleAmount). // // Formula: 1 - (0.75 ** amount of samples) -func (la *LightAvailability) ProbabilityOfAvailability() float64 { +func (la *ShareAvailability) ProbabilityOfAvailability() float64 { return 1 - math.Pow(0.75, float64(DefaultSampleAmount)) } diff --git a/share/share_test.go b/share/availability/light/availability_test.go similarity index 79% rename from share/share_test.go rename to share/availability/light/availability_test.go index 1db384c1d9..f7c0a75d44 100644 --- a/share/share_test.go +++ b/share/availability/light/availability_test.go @@ -1,4 +1,4 @@ -package share +package light import ( "bytes" @@ -10,28 +10,71 @@ import ( mrand "math/rand" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/pkg/da" core "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" ) +func init() { + // randomize quadrant fetching, otherwise quadrant sampling is deterministic + rand.Seed(time.Now().UnixNano()) +} + +func TestSharesAvailable(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // RandServiceWithSquare creates a Light ShareAvailability inside, so we can test it + service, dah := RandServiceWithSquare(t, 16) + err := service.SharesAvailable(ctx, dah) + assert.NoError(t, err) +} + +func TestSharesAvailableFailed(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // RandServiceWithSquare creates a Light ShareAvailability inside, so we can test it + s, _ := RandServiceWithSquare(t, 16) + empty := header.EmptyDAH() + err := s.SharesAvailable(ctx, &empty) + assert.Error(t, err) +} + +func TestShareAvailableOverMocknet_Light(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + net := availability_test.NewTestDAGNet(ctx, t) + _, root := RandNode(net, 16) + nd := Node(net) + net.ConnectAll() + + err := nd.SharesAvailable(ctx, root) + assert.NoError(t, err) +} + func TestGetShare(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() n := 16 - serv, dah := randLightServiceWithSquare(t, n) + serv, dah := RandServiceWithSquare(t, n) err := serv.Start(ctx) require.NoError(t, err) for i := range make([]bool, n) { for j := range make([]bool, n) { - share, err := serv.GetShare(ctx, dah, i, j) - assert.NotNil(t, share) + sh, err := serv.GetShare(ctx, dah, i, j) + assert.NotNil(t, sh) assert.NoError(t, err) } } @@ -52,23 +95,23 @@ func TestService_GetSharesByNamespace(t *testing.T) { for _, tt := range tests { t.Run("size: "+strconv.Itoa(tt.squareSize), func(t *testing.T) { - serv, bServ := randLightService() + serv, bServ := RandService() n := tt.squareSize * tt.squareSize - randShares := RandShares(t, n) + randShares := share.RandShares(t, n) idx1 := (n - 1) / 2 idx2 := n / 2 if tt.expectedShareCount > 1 { // make it so that two rows have the same namespace ID copy(randShares[idx2][:8], randShares[idx1][:8]) } - root := FillBS(t, bServ, randShares) + root := availability_test.FillBS(t, bServ, randShares) randNID := randShares[idx1][:8] shares, err := serv.GetSharesByNamespace(context.Background(), root, randNID) require.NoError(t, err) assert.Len(t, shares, tt.expectedShareCount) for _, value := range shares { - assert.Equal(t, randNID, []byte(GetID(value))) + assert.Equal(t, randNID, []byte(share.ID(value))) } if tt.expectedShareCount > 1 { // idx1 is always smaller than idx2 @@ -84,7 +127,7 @@ func TestGetShares(t *testing.T) { defer cancel() n := 16 - serv, dah := randLightServiceWithSquare(t, n) + serv, dah := RandServiceWithSquare(t, n) err := serv.Start(ctx) require.NoError(t, err) @@ -95,7 +138,7 @@ func TestGetShares(t *testing.T) { for _, row := range shares { flattened = append(flattened, row...) } - // generate DAH from shares returned by `GetShares` to compare + // generate DAH from shares returned by `share.GetShares` to compare // calculated DAH to expected DAH squareSize := uint64(math.Sqrt(float64(len(flattened)))) eds, err := da.ExtendShares(squareSize, flattened) @@ -109,7 +152,7 @@ func TestGetShares(t *testing.T) { } func TestService_GetSharesByNamespaceNotFound(t *testing.T) { - serv, root := randLightServiceWithSquare(t, 1) + serv, root := RandServiceWithSquare(t, 1) root.RowsRoots = nil shares, err := serv.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) @@ -129,7 +172,7 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { for _, tt := range tests { b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { t := &testing.T{} - serv, root := randLightServiceWithSquare(t, tt.amountShares) + serv, root := RandServiceWithSquare(t, tt.amountShares) randNID := root.RowsRoots[(len(root.RowsRoots)-1)/2][:8] root.RowsRoots[(len(root.RowsRoots) / 2)] = root.RowsRoots[(len(root.RowsRoots)-1)/2] b.ResetTimer() @@ -142,7 +185,7 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { } func TestSharesRoundTrip(t *testing.T) { - serv, store := randLightService() + serv, store := RandService() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -199,9 +242,9 @@ func TestSharesRoundTrip(t *testing.T) { }, } randBytes := func(n int) []byte { - bytes := make([]byte, n) - mrand.Read(bytes) - return bytes + bs := make([]byte, n) + mrand.Read(bs) + return bs } for i := 128; i < 4192; i += mrand.Intn(256) { l := strconv.Itoa(i) @@ -271,7 +314,7 @@ func TestSharesRoundTrip(t *testing.T) { // test full round trip - with IPLD + decoding shares { - extSquare, err := ipld.AddShares(ctx, namespacedShares.RawShares(), store) + extSquare, err := share.AddShares(ctx, namespacedShares.RawShares(), store) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(extSquare) diff --git a/share/sample.go b/share/availability/light/sample.go similarity index 91% rename from share/sample.go rename to share/availability/light/sample.go index 65da855486..fa2719cd1f 100644 --- a/share/sample.go +++ b/share/availability/light/sample.go @@ -1,11 +1,14 @@ // TODO(@Wondertan): Instead of doing sampling over the coordinates do a random walk over NMT trees. -package share +package light import ( crand "crypto/rand" "math/big" ) +// DefaultSampleAmount sets the default amount of samples to be sampled from the network by ShareAvailability. +var DefaultSampleAmount = 16 + // Sample is a point in 2D space over square. type Sample struct { Row, Col int diff --git a/share/sample_test.go b/share/availability/light/sample_test.go similarity index 97% rename from share/sample_test.go rename to share/availability/light/sample_test.go index c93aa1d28a..7092b99e83 100644 --- a/share/sample_test.go +++ b/share/availability/light/sample_test.go @@ -1,4 +1,4 @@ -package share +package light import ( "testing" diff --git a/share/testdata/block-825320.json b/share/availability/light/testdata/block-825320.json similarity index 100% rename from share/testdata/block-825320.json rename to share/availability/light/testdata/block-825320.json diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go new file mode 100644 index 0000000000..dc01d48234 --- /dev/null +++ b/share/availability/light/testing.go @@ -0,0 +1,55 @@ +package light + +import ( + "testing" + "time" + + "github.com/ipfs/go-blockservice" + mdutils "github.com/ipfs/go-merkledag/test" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "github.com/libp2p/go-libp2p/p2p/discovery/routing" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/discovery" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/service" +) + +// RandServiceWithSquare provides a share.Service filled with 'n' NMT +// trees of 'n' random shares, essentially storing a whole square. +func RandServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { + bServ := mdutils.Bserv() + + return service.NewShareService(bServ, TestAvailability(bServ)), availability_test.RandFillBS(t, n, bServ) +} + +// RandService provides an unfilled share.Service with corresponding +// blockservice.BlockService than can be filled by the test. +func RandService() (*service.ShareService, blockservice.BlockService) { + bServ := mdutils.Bserv() + return service.NewShareService(bServ, TestAvailability(bServ)), bServ +} + +// RandNode creates a Light Node filled with a random block of the given size. +func RandNode(dn *availability_test.DagNet, squareSize int) (*availability_test.Node, *share.Root) { + nd := Node(dn) + return nd, availability_test.RandFillBS(dn.T, squareSize, nd.BlockService) +} + +// Node creates a new empty Light Node. +func Node(dn *availability_test.DagNet) *availability_test.Node { + nd := dn.Node() + nd.ShareService = service.NewShareService(nd.BlockService, TestAvailability(nd.BlockService)) + return nd +} + +func TestAvailability(bServ blockservice.BlockService) *ShareAvailability { + disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + return NewShareAvailability(bServ, disc) +} + +func SubNetNode(sn *availability_test.SubNet) *availability_test.Node { + nd := Node(sn.DagNet) + sn.AddNode(nd) + return nd +} diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go new file mode 100644 index 0000000000..d1f4b29443 --- /dev/null +++ b/share/availability/test/testing.go @@ -0,0 +1,198 @@ +package availability_test + +import ( + "bytes" + "context" + "testing" + + "github.com/ipfs/go-bitswap" + "github.com/ipfs/go-bitswap/network" + "github.com/ipfs/go-blockservice" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-ipfs-routing/offline" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + record "github.com/libp2p/go-libp2p-record" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/pkg/da" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/service" +) + +// RandFillBS fills the given BlockService with a random block of a given size. +func RandFillBS(t *testing.T, n int, bServ blockservice.BlockService) *share.Root { + shares := share.RandShares(t, n*n) + return FillBS(t, bServ, shares) +} + +// FillBS fills the given BlockService with the given shares. +func FillBS(t *testing.T, bServ blockservice.BlockService, shares []share.Share) *share.Root { + eds, err := share.AddShares(context.TODO(), shares, bServ) + require.NoError(t, err) + dah := da.NewDataAvailabilityHeader(eds) + return &dah +} + +type Node struct { + net *DagNet + *service.ShareService + blockservice.BlockService + host.Host +} + +// ClearStorage cleans up the storage of the node. +func (n *Node) ClearStorage() { + keys, err := n.Blockstore().AllKeysChan(n.net.ctx) + require.NoError(n.net.T, err) + + for k := range keys { + err := n.DeleteBlock(n.net.ctx, k) + require.NoError(n.net.T, err) + } +} + +type DagNet struct { + ctx context.Context + T *testing.T + net mocknet.Mocknet + nodes []*Node +} + +// NewTestDAGNet creates a new testing swarm utility to spawn different nodes +// and test how they interact and/or exchange data. +func NewTestDAGNet(ctx context.Context, t *testing.T) *DagNet { + return &DagNet{ + ctx: ctx, + T: t, + net: mocknet.New(), + } +} + +// Node create a plain network node that can serve and request data. +func (dn *DagNet) Node() *Node { + hst, err := dn.net.GenPeer() + require.NoError(dn.T, err) + dstore := dssync.MutexWrap(ds.NewMapDatastore()) + bstore := blockstore.NewBlockstore(dstore) + routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) + bs := bitswap.New( + dn.ctx, + network.NewFromIpfsHost(hst, routing), + bstore, + bitswap.ProvideEnabled(false), // disable routines for DHT content provides, as we don't use them + bitswap.EngineBlockstoreWorkerCount(1), // otherwise it spawns 128 routines which is too much for tests + bitswap.EngineTaskWorkerCount(2), + bitswap.TaskWorkerCount(2), + bitswap.SetSimulateDontHavesOnTimeout(false), + bitswap.SetSendDontHaves(false), + ) + nd := &Node{ + net: dn, + BlockService: blockservice.New(bstore, bs), + Host: hst, + } + dn.nodes = append(dn.nodes, nd) + return nd +} + +// ConnectAll connects all the peers on registered on the DagNet. +func (dn *DagNet) ConnectAll() { + err := dn.net.LinkAll() + require.NoError(dn.T, err) + + err = dn.net.ConnectAllButSelf() + require.NoError(dn.T, err) +} + +// Connect connects two given peer. +func (dn *DagNet) Connect(peerA, peerB peer.ID) { + _, err := dn.net.LinkPeers(peerA, peerB) + require.NoError(dn.T, err) + _, err = dn.net.ConnectPeers(peerA, peerB) + require.NoError(dn.T, err) +} + +// Disconnect disconnects two peers. +// It does a hard disconnect, meaning that disconnected peers won't be able to reconnect on their own +// but only with DagNet.Connect or DagNet.ConnectAll. +func (dn *DagNet) Disconnect(peerA, peerB peer.ID) { + err := dn.net.UnlinkPeers(peerA, peerB) + require.NoError(dn.T, err) + err = dn.net.DisconnectPeers(peerA, peerB) + require.NoError(dn.T, err) +} + +type SubNet struct { + *DagNet + nodes []*Node +} + +func (dn *DagNet) SubNet() *SubNet { + return &SubNet{dn, nil} +} + +func (sn *SubNet) AddNode(nd *Node) { + sn.nodes = append(sn.nodes, nd) +} + +func (sn *SubNet) ConnectAll() { + nodes := sn.nodes + for _, n1 := range nodes { + for _, n2 := range nodes { + if n1 == n2 { + continue + } + _, err := sn.net.LinkPeers(n1.ID(), n2.ID()) + require.NoError(sn.T, err) + + _, err = sn.net.ConnectPeers(n1.ID(), n2.ID()) + require.NoError(sn.T, err) + } + } +} + +type TestBrokenAvailability struct { + Root *share.Root +} + +// NewTestBrokenAvailability returns an instance of Availability that +// allows for testing error cases during sampling. +// +// If the share.Root field is empty, it will return share.ErrNotAvailable on every call +// to SharesAvailable. Otherwise, it will only return ErrNotAvailable if the +// given Root hash matches the stored Root hash. +func NewTestBrokenAvailability() share.Availability { + return &TestBrokenAvailability{} +} + +func (b *TestBrokenAvailability) SharesAvailable(_ context.Context, root *share.Root) error { + if b.Root == nil || bytes.Equal(b.Root.Hash(), root.Hash()) { + return share.ErrNotAvailable + } + return nil +} + +func (b *TestBrokenAvailability) ProbabilityOfAvailability() float64 { + return 0 +} + +type TestSuccessfulAvailability struct { +} + +// NewTestSuccessfulAvailability returns an Availability that always +// returns successfully when SharesAvailable is called. +func NewTestSuccessfulAvailability() share.Availability { + return &TestSuccessfulAvailability{} +} + +func (tsa *TestSuccessfulAvailability) SharesAvailable(context.Context, *share.Root) error { + return nil +} + +func (tsa *TestSuccessfulAvailability) ProbabilityOfAvailability() float64 { + return 0 +} diff --git a/ipld/retriever_byzantine.go b/share/byzantine.go similarity index 93% rename from ipld/retriever_byzantine.go rename to share/byzantine.go index e2d17977c0..d74eb119b5 100644 --- a/ipld/retriever_byzantine.go +++ b/share/byzantine.go @@ -1,4 +1,4 @@ -package ipld +package share import ( "context" @@ -7,7 +7,7 @@ import ( "github.com/ipfs/go-blockservice" "github.com/tendermint/tendermint/pkg/da" - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/rsmt2d" ) @@ -41,7 +41,7 @@ func NewErrByzantine( sharesWithProof, err := GetProofsForShares( ctx, bGetter, - plugin.MustCidFromNamespacedSha256(root), + ipld.MustCidFromNamespacedSha256(root), errByz.Shares, ) if err != nil { diff --git a/share/doc.go b/share/doc.go index fd3739ad81..7ef8c9f7bf 100644 --- a/share/doc.go +++ b/share/doc.go @@ -7,8 +7,11 @@ sampling them at random, a particularly useful method is GetSharesByNamespace wh all shares of block data of the given namespace.ID from the block associated with the given DataAvailabilityHeader (DAH, but referred to as Root within this package). -This package also contains both implementations of the Availability interface: lightAvailability -which samples for 16 shares of block data (enough to verify the block's availability on the network) -and fullAvailability which samples for as many shares as necessary to fully reconstruct the block data. +This package also contains declaration of the Availability interface. Implementations of +the interface (light, full) are located in the availability sub-folder. +Light Availability implementation samples for 16 shares of block data (enough to verify +the block's availability on the network). +Full Availability implementation samples for as many shares as necessary to fully reconstruct +the block data. */ package share diff --git a/ipld/retriever.go b/share/eds/retriever.go similarity index 95% rename from ipld/retriever.go rename to share/eds/retriever.go index 855a35e16e..1f5f75aa29 100644 --- a/ipld/retriever.go +++ b/share/eds/retriever.go @@ -1,4 +1,4 @@ -package ipld +package eds import ( "context" @@ -10,7 +10,6 @@ import ( "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - format "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" "github.com/tendermint/tendermint/pkg/da" "github.com/tendermint/tendermint/pkg/wrapper" @@ -19,14 +18,16 @@ import ( "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" ) -var log = logging.Logger("ipld") - -var tracer = otel.Tracer("ipld") +var ( + log = logging.Logger("share/eds") + tracer = otel.Tracer("share/eds") +) // Retriever retrieves rsmt2d.ExtendedDataSquares from the IPLD network. // Instead of requesting data 'share by share' it requests data by quadrants @@ -87,7 +88,7 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader var errByz *rsmt2d.ErrByzantineData if errors.As(err, &errByz) { span.RecordError(err) - return nil, NewErrByzantine(ctx, r.bServ, dah, errByz) + return nil, share.NewErrByzantine(ctx, r.bServ, dah, errByz) } log.Warnw("not enough shares to reconstruct data square, requesting more...", "err", err) @@ -103,7 +104,7 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader // to reconstruct the block once enough shares are fetched. type retrievalSession struct { bget blockservice.BlockGetter - adder *NmtNodeAdder + adder *ipld.NmtNodeAdder treeFn rsmt2d.TreeConstructorFn codec rsmt2d.Codec @@ -126,10 +127,10 @@ type retrievalSession struct { // newSession creates a new retrieval session and kicks off requesting process. func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHeader) (*retrievalSession, error) { size := len(dah.RowsRoots) - adder := NewNmtNodeAdder( + adder := ipld.NewNmtNodeAdder( ctx, r.bServ, - format.MaxSizeBatchOption(batchSize(size)), + ipld.MaxSizeBatchOption(size), ) ses := &retrievalSession{ bget: blockservice.NewSession(ctx, r.bServ), @@ -138,7 +139,7 @@ func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHead tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, nmt.NodeVisitor(adder.Visit)) return &tree }, - codec: DefaultRSMT2DCodec(), + codec: share.DefaultRSMT2DCodec(), dah: dah, quadrants: newQuadrants(dah), sharesLks: make([]sync.Mutex, size*size), @@ -273,7 +274,7 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { for i, root := range q.roots { go func(i int, root cid.Cid) { // get the root node - nd, err := plugin.GetNode(ctx, rs.bget, root) + nd, err := ipld.GetNode(ctx, rs.bget, root) if err != nil { rs.span.RecordError(err, trace.WithAttributes( attribute.String("requesting-root", root.String()), @@ -284,7 +285,7 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { // and go get shares of left or the right side of the whole col/row axis // the left or the right side of the tree represent some portion of the quadrant // which we put into the rs.square share-by-share by calculating shares' indexes using q.index - GetShares(ctx, rs.bget, nd.Links()[q.x].Cid, size, func(j int, share Share) { + share.GetShares(ctx, rs.bget, nd.Links()[q.x].Cid, size, func(j int, share share.Share) { // NOTE: Each share can appear twice here, for a Row and Col, respectively. // These shares are always equal, and we allow only the first one to be written // in the square. diff --git a/ipld/retriever_quadrant.go b/share/eds/retriever_quadrant.go similarity index 96% rename from ipld/retriever_quadrant.go rename to share/eds/retriever_quadrant.go index d73bd623f8..7179662019 100644 --- a/ipld/retriever_quadrant.go +++ b/share/eds/retriever_quadrant.go @@ -1,4 +1,4 @@ -package ipld +package eds import ( "math" @@ -8,7 +8,7 @@ import ( "github.com/ipfs/go-cid" "github.com/tendermint/tendermint/pkg/da" - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/celestia-node/share/ipld" ) const ( @@ -65,7 +65,7 @@ func newQuadrants(dah *da.DataAvailabilityHeader) []*quadrant { size, qsize := len(daRoots[source]), len(daRoots[source])/2 roots := make([]cid.Cid, size) for i, root := range daRoots[source] { - roots[i] = plugin.MustCidFromNamespacedSha256(root) + roots[i] = ipld.MustCidFromNamespacedSha256(root) } for i := range quadrants { diff --git a/ipld/retriever_test.go b/share/eds/retriever_test.go similarity index 76% rename from ipld/retriever_test.go rename to share/eds/retriever_test.go index 18e2265d6b..741390cb58 100644 --- a/ipld/retriever_test.go +++ b/share/eds/retriever_test.go @@ -1,4 +1,4 @@ -package ipld +package eds import ( "context" @@ -6,14 +6,14 @@ import ( "testing" "time" - "github.com/gammazero/workerpool" - format "github.com/ipfs/go-ipld-format" mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/pkg/da" "github.com/tendermint/tendermint/pkg/wrapper" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" ) @@ -21,8 +21,6 @@ import ( func init() { // randomize quadrant fetching, otherwise quadrant sampling is deterministic rand.Seed(time.Now().UnixNano()) - // limit the amount of workers for tests - pool = workerpool.New(1000) } func TestRetriever_Retrieve(t *testing.T) { @@ -44,14 +42,14 @@ func TestRetriever_Retrieve(t *testing.T) { {"16x16(med)", 16}, {"32x32(med)", 32}, {"64x64(med)", 64}, - {"128x128(max)", MaxSquareSize}, + {"128x128(max)", share.MaxSquareSize}, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { // generate EDS - shares := RandShares(t, tc.squareSize*tc.squareSize) - in, err := AddShares(ctx, shares, bServ) + shares := share.RandShares(t, tc.squareSize*tc.squareSize) + in, err := share.AddShares(ctx, shares, bServ) require.NoError(t, err) // limit with timeout, specifically retrieval @@ -61,7 +59,7 @@ func TestRetriever_Retrieve(t *testing.T) { dah := da.NewDataAvailabilityHeader(in) out, err := r.Retrieve(ctx, &dah) require.NoError(t, err) - assert.True(t, EqualEDS(in, out)) + assert.True(t, share.EqualEDS(in, out)) }) } } @@ -72,26 +70,26 @@ func TestRetriever_ByzantineError(t *testing.T) { defer cancel() bserv := mdutils.Bserv() - shares := ExtractEDS(RandEDS(t, width)) - _, err := ImportShares(ctx, shares, bserv) + shares := share.ExtractEDS(share.RandEDS(t, width)) + _, err := share.ImportShares(ctx, shares, bserv) require.NoError(t, err) // corrupt shares so that eds erasure coding does not match copy(shares[14][8:], shares[15][8:]) // import corrupted eds - batchAdder := NewNmtNodeAdder(ctx, bserv, format.MaxSizeBatchOption(batchSize(width*2))) + batchAdder := ipld.NewNmtNodeAdder(ctx, bserv, ipld.MaxSizeBatchOption(width*2)) tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(width), nmt.NodeVisitor(batchAdder.Visit)) - attackerEDS, err := rsmt2d.ImportExtendedDataSquare(shares, DefaultRSMT2DCodec(), tree.Constructor) + attackerEDS, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) require.NoError(t, err) err = batchAdder.Commit() require.NoError(t, err) // ensure we rcv an error - da := da.NewDataAvailabilityHeader(attackerEDS) + dah := da.NewDataAvailabilityHeader(attackerEDS) r := NewRetriever(bserv) - _, err = r.Retrieve(ctx, &da) - var errByz *ErrByzantine + _, err = r.Retrieve(ctx, &dah) + var errByz *share.ErrByzantine require.ErrorAs(t, err, &errByz) } @@ -107,8 +105,8 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { r := NewRetriever(bServ) // generate EDS - shares := RandShares(t, squareSize*squareSize) - in, err := AddShares(ctx, shares, bServ) + shares := share.RandShares(t, squareSize*squareSize) + in, err := share.AddShares(ctx, shares, bServ) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(in) diff --git a/share/empty.go b/share/empty.go index 931f505043..75877f6acd 100644 --- a/share/empty.go +++ b/share/empty.go @@ -6,8 +6,6 @@ import ( "github.com/ipfs/go-blockservice" "github.com/tendermint/tendermint/pkg/consts" - - "github.com/celestiaorg/celestia-node/ipld" ) // EnsureEmptySquareExists checks if the given DAG contains an empty block data square. @@ -20,7 +18,7 @@ func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockServic shares[i] = tailPaddingShare } - _, err := ipld.AddShares(ctx, shares, bServ) + _, err := AddShares(ctx, shares, bServ) return err } diff --git a/share/full_availability.go b/share/full_availability.go deleted file mode 100644 index 6e4d73a31f..0000000000 --- a/share/full_availability.go +++ /dev/null @@ -1,72 +0,0 @@ -package share - -import ( - "context" - "errors" - - "github.com/ipfs/go-blockservice" - format "github.com/ipfs/go-ipld-format" - - "github.com/celestiaorg/celestia-node/ipld" -) - -// FullAvailability implements Availability using the full data square -// recovery technique. It is considered "full" because it is required -// to download enough shares to fully reconstruct the data square. -type FullAvailability struct { - rtrv *ipld.Retriever - disc *Discovery - - cancel context.CancelFunc -} - -// NewFullAvailability creates a new full Availability. -func NewFullAvailability(bServ blockservice.BlockService, disc *Discovery) *FullAvailability { - return &FullAvailability{ - rtrv: ipld.NewRetriever(bServ), - disc: disc, - } -} - -func (fa *FullAvailability) Start(context.Context) error { - ctx, cancel := context.WithCancel(context.Background()) - fa.cancel = cancel - - go fa.disc.advertise(ctx) - go fa.disc.ensurePeers(ctx) - return nil -} - -func (fa *FullAvailability) Stop(context.Context) error { - fa.cancel() - return nil -} - -// SharesAvailable reconstructs the data committed to the given Root by requesting -// enough Shares from the network. -func (fa *FullAvailability) SharesAvailable(ctx context.Context, root *Root) error { - ctx, cancel := context.WithTimeout(ctx, AvailabilityTimeout) - defer cancel() - // we assume the caller of this method has already performed basic validation on the - // given dah/root. If for some reason this has not happened, the node should panic. - if err := root.ValidateBasic(); err != nil { - log.Errorw("Availability validation cannot be performed on a malformed DataAvailabilityHeader", - "err", err) - panic(err) - } - - _, err := fa.rtrv.Retrieve(ctx, root) - if err != nil { - log.Errorw("availability validation failed", "root", root.Hash(), "err", err) - if format.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { - return ErrNotAvailable - } - - return err - } - return err -} - -func (fa *FullAvailability) ProbabilityOfAvailability() float64 { - return 1 -} diff --git a/share/get.go b/share/get.go new file mode 100644 index 0000000000..e0c16fdc9f --- /dev/null +++ b/share/get.go @@ -0,0 +1,105 @@ +package share + +import ( + "context" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/nmt/namespace" +) + +// GetShare fetches and returns the data for leaf `leafIndex` of root `rootCid`. +func GetShare( + ctx context.Context, + bGetter blockservice.BlockGetter, + rootCid cid.Cid, + leafIndex int, + totalLeafs int, // this corresponds to the extended square width +) (Share, error) { + nd, err := ipld.GetLeaf(ctx, bGetter, rootCid, leafIndex, totalLeafs) + if err != nil { + return nil, err + } + + return leafToShare(nd), nil +} + +// GetShares walks the tree of a given root and puts shares into the given 'put' func. +// Does not return any error, and returns/unblocks only on success +// (got all shares) or on context cancellation. +func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, shares int, put func(int, Share)) { + ctx, span := tracer.Start(ctx, "get-shares") + defer span.End() + + putNode := func(i int, leaf format.Node) { + put(i, leafToShare(leaf)) + } + ipld.GetLeaves(ctx, bGetter, root, shares, putNode) +} + +// GetSharesByNamespace walks the tree of a given root and returns its shares within the given namespace.ID. +// If a share could not be retrieved, err is not nil, and the returned array +// contains nil shares in place of the shares it was unable to retrieve. +func GetSharesByNamespace( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + nID namespace.ID, + maxShares int, +) ([]Share, error) { + ctx, span := tracer.Start(ctx, "get-shares-by-namespace") + defer span.End() + + leaves, err := ipld.GetLeavesByNamespace(ctx, bGetter, root, nID, maxShares) + if err != nil && leaves == nil { + return nil, err + } + + shares := make([]Share, len(leaves)) + for i, leaf := range leaves { + if leaf != nil { + shares[i] = leafToShare(leaf) + } + } + + return shares, err +} + +// GetProofsForShares fetches Merkle proofs for the given shares +// and returns the result as an array of ShareWithProof. +func GetProofsForShares( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + shares [][]byte, +) ([]*ShareWithProof, error) { + proofs := make([]*ShareWithProof, len(shares)) + for index, share := range shares { + if share != nil { + proof := make([]cid.Cid, 0) + // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same tree. + // Add options that will control what data will be fetched. + s, err := ipld.GetLeaf(ctx, bGetter, root, index, len(shares)) + if err != nil { + return nil, err + } + proof, err = ipld.GetProof(ctx, bGetter, root, proof, index, len(shares)) + if err != nil { + return nil, err + } + proofs[index] = NewShareWithProof(index, s.RawData()[1:], proof) + } + } + + return proofs, nil +} + +// leafToShare converts an NMT leaf into a Share. +func leafToShare(nd format.Node) Share { + // * First byte represents the type of the node, and is unrelated to the actual share data + // * Additional namespace is prepended so that parity data can be identified with a parity namespace, which we cut off + return nd.RawData()[1+NamespaceSize:] // TODO(@Wondertan): Rework NMT/IPLD plugin to avoid the type byte +} diff --git a/ipld/get_test.go b/share/get_test.go similarity index 89% rename from ipld/get_test.go rename to share/get_test.go index ce8b2fa433..7cced86e2e 100644 --- a/ipld/get_test.go +++ b/share/get_test.go @@ -1,4 +1,4 @@ -package ipld +package share import ( "context" @@ -20,7 +20,7 @@ import ( "github.com/tendermint/tendermint/pkg/da" "github.com/tendermint/tendermint/pkg/wrapper" - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" ) @@ -40,7 +40,7 @@ func TestGetShare(t *testing.T) { for i, leaf := range shares { row := i / size pos := i - (size * row) - share, err := GetShare(ctx, bServ, plugin.MustCidFromNamespacedSha256(eds.RowRoots()[row]), pos, size*2) + share, err := GetShare(ctx, bServ, ipld.MustCidFromNamespacedSha256(eds.RowRoots()[row]), pos, size*2) require.NoError(t, err) assert.Equal(t, leaf, share) } @@ -168,7 +168,7 @@ func TestGetSharesByNamespace(t *testing.T) { require.NoError(t, err) for _, row := range eds.RowRoots() { - rcid := plugin.MustCidFromNamespacedSha256(row) + rcid := ipld.MustCidFromNamespacedSha256(row) shares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) require.NoError(t, err) @@ -204,23 +204,23 @@ func TestGetLeavesByNamespace_IncompleteData(t *testing.T) { roots := eds.RowRoots() // remove the second share from the first row - rcid := plugin.MustCidFromNamespacedSha256(roots[0]) - node, err := plugin.GetNode(ctx, bServ, rcid) + rcid := ipld.MustCidFromNamespacedSha256(roots[0]) + node, err := ipld.GetNode(ctx, bServ, rcid) require.NoError(t, err) // Left side of the tree contains the original shares - data, err := plugin.GetNode(ctx, bServ, node.Links()[0].Cid) + data, err := ipld.GetNode(ctx, bServ, node.Links()[0].Cid) require.NoError(t, err) // Second share is the left side's right child - l, err := plugin.GetNode(ctx, bServ, data.Links()[0].Cid) + l, err := ipld.GetNode(ctx, bServ, data.Links()[0].Cid) require.NoError(t, err) - r, err := plugin.GetNode(ctx, bServ, l.Links()[1].Cid) + r, err := ipld.GetNode(ctx, bServ, l.Links()[1].Cid) require.NoError(t, err) err = bServ.DeleteBlock(ctx, r.Cid()) require.NoError(t, err) - nodes, err := getLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) + nodes, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) assert.Equal(t, nil, nodes[1]) // TODO(distractedm1nd): Decide if we should return an array containing nil assert.Equal(t, 4, len(nodes)) @@ -301,8 +301,8 @@ func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T require.NoError(t, err) for _, row := range eds.RowRoots() { - rcid := plugin.MustCidFromNamespacedSha256(row) - nodes, err := getLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) + rcid := ipld.MustCidFromNamespacedSha256(row) + nodes, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) assert.Nil(t, err) for _, node := range nodes { @@ -345,7 +345,7 @@ func TestBatchSize(t *testing.T) { count++ } extendedWidth := tt.origWidth * 2 - assert.Equalf(t, count, batchSize(extendedWidth), "batchSize(%v)", extendedWidth) + assert.Equalf(t, count, ipld.BatchSize(extendedWidth), "batchSize(%v)", extendedWidth) }) } } @@ -360,12 +360,12 @@ func assertNoRowContainsNID( // get all row root cids rowRootCIDs := make([]cid.Cid, rowRootCount) for i, rowRoot := range eds.RowRoots() { - rowRootCIDs[i] = plugin.MustCidFromNamespacedSha256(rowRoot) + rowRootCIDs[i] = ipld.MustCidFromNamespacedSha256(rowRoot) } // for each row root cid check if the minNID exists for _, rowCID := range rowRootCIDs { - data, err := getLeavesByNamespace(context.Background(), bServ, rowCID, nID, rowRootCount) + data, err := ipld.GetLeavesByNamespace(context.Background(), bServ, rowCID, nID, rowRootCount) assert.Nil(t, data) assert.Nil(t, err) } @@ -393,12 +393,12 @@ func TestGetProof(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { for _, root := range tt.roots { - rootCid := plugin.MustCidFromNamespacedSha256(root) + rootCid := ipld.MustCidFromNamespacedSha256(root) for index := 0; uint(index) < in.Width(); index++ { proof := make([]cid.Cid, 0) - proof, err = GetProof(ctx, bServ, rootCid, proof, index, int(in.Width())) + proof, err = ipld.GetProof(ctx, bServ, rootCid, proof, index, int(in.Width())) require.NoError(t, err) - node, err := GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) + node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) require.NoError(t, err) inclusion := NewShareWithProof(index, node.RawData()[1:], proof) require.True(t, inclusion.Validate(rootCid)) @@ -420,10 +420,10 @@ func TestGetProofs(t *testing.T) { dah := da.NewDataAvailabilityHeader(in) for _, root := range dah.ColumnRoots { - rootCid := plugin.MustCidFromNamespacedSha256(root) + rootCid := ipld.MustCidFromNamespacedSha256(root) data := make([][]byte, 0, in.Width()) for index := 0; uint(index) < in.Width(); index++ { - node, err := GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) + node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) require.NoError(t, err) data = append(data, node.RawData()[9:]) } diff --git a/share/ipld/get.go b/share/ipld/get.go new file mode 100644 index 0000000000..90587fe34a --- /dev/null +++ b/share/ipld/get.go @@ -0,0 +1,401 @@ +package ipld + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + + "github.com/gammazero/workerpool" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" +) + +// NumWorkersLimit sets global limit for workers spawned by GetShares. +// GetShares could be called MaxSquareSize(128) times per data square each +// spawning up to 128/2 goroutines and altogether this is 8192. Considering +// there can be N blocks fetched at the same time, e.g. during catching up data +// from the past, we multiply this number by the amount of allowed concurrent +// data square fetches(NumConcurrentSquares). +// +// NOTE: This value only limits amount of simultaneously running workers that +// are spawned as the load increases and are killed, once the load declines. +// +// TODO(@Wondertan): This assumes we have parallelized DASer implemented. Sync the values once it is shipped. +// TODO(@Wondertan): Allow configuration of values without global state. +var NumWorkersLimit = MaxSquareSize * MaxSquareSize / 2 * NumConcurrentSquares + +// NumConcurrentSquares limits the amount of squares that are fetched +// concurrently/simultaneously. +var NumConcurrentSquares = 8 + +// Global worker pool that globally controls and limits goroutines spawned by +// GetShares. +// +// TODO(@Wondertan): Idle timeout for workers needs to be configured to around block time, +// so that workers spawned between each reconstruction for every new block are reused. +var pool = workerpool.New(NumWorkersLimit) + +// GetLeaf fetches and returns the raw leaf. +// It walks down the IPLD NMT tree until it finds the requested one. +func GetLeaf( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + leaf, total int, +) (ipld.Node, error) { + // request the node + nd, err := GetNode(ctx, bGetter, root) + if err != nil { + return nil, err + } + + // look for links + lnks := nd.Links() + if len(lnks) == 1 { + // in case there is only one we reached tree's bottom, so finally request the leaf. + return GetNode(ctx, bGetter, lnks[0].Cid) + } + + // route walk to appropriate children + total /= 2 // as we are using binary tree, every step decreases total leaves in a half + if leaf < total { + root = lnks[0].Cid // if target leave on the left, go with walk down the first children + } else { + root, leaf = lnks[1].Cid, leaf-total // otherwise go down the second + } + + // recursively walk down through selected children + return GetLeaf(ctx, bGetter, root, leaf, total) +} + +// GetLeaves gets leaves from either local storage, or, if not found, requests +// them from immediate/connected peers. It puts them into the slice under index +// of node position in the tree (bin-tree-feat). +// Does not return any error, and returns/unblocks only on success +// (got all shares) or on context cancellation. +// +// It works concurrently by spawning workers in the pool which do one basic +// thing - block until data is fetched, s. t. share processing is never +// sequential, and thus we request *all* the shares available without waiting +// for others to finish. It is the required property to maximize data +// availability. As a side effect, we get concurrent tree traversal reducing +// time to data time. +// +// GetLeaves relies on the fact that the underlying data structure is a binary +// tree, so it's not suitable for anything else besides that. Parts on the +// implementation that rely on this property are explicitly tagged with +// (bin-tree-feat). +func GetLeaves(ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + maxShares int, + put func(int, ipld.Node), +) { + ctx, span := tracer.Start(ctx, "get-leaves") + defer span.End() + + // this buffer ensures writes to 'jobs' are never blocking (bin-tree-feat) + jobs := make(chan *job, (maxShares+1)/2) // +1 for the case where 'maxShares' is 1 + jobs <- &job{id: root, ctx: ctx} + // total is an amount of routines spawned and total amount of nodes we process (bin-tree-feat) + // so we can specify exact amount of loops we do, and wait for this amount + // of routines to finish processing + total := maxShares*2 - 1 + wg := sync.WaitGroup{} + wg.Add(total) + + // all preparations are done, so begin processing jobs + for i := 0; i < total; i++ { + select { + case j := <-jobs: + // work over each job concurrently, s.t. shares do not block + // processing of each other + pool.Submit(func() { + ctx, span := tracer.Start(j.ctx, "process-job") + defer span.End() + defer wg.Done() + + span.SetAttributes( + attribute.String("cid", j.id.String()), + attribute.Int("pos", j.pos), + ) + + nd, err := GetNode(ctx, bGetter, j.id) + if err != nil { + // we don't really care about errors here + // just fetch as much as possible + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return + } + // check links to know what we should do with the node + lnks := nd.Links() + if len(lnks) == 1 { // so we are almost there + // the reason why the comment on 'total' is lying, as each + // leaf has its own additional leaf(hack) so get it + nd, err := GetNode(ctx, bGetter, lnks[0].Cid) + if err != nil { + // again, we don't really care much, just fetch as much as possible + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return + } + // successfully fetched a share/leaf + // ladies and gentlemen, we got em! + span.SetStatus(codes.Ok, "") + put(j.pos, nd) + return + } + // ok, we found more links + for i, lnk := range lnks { + // send those to be processed + select { + case jobs <- &job{ + id: lnk.Cid, + // calc position for children nodes (bin-tree-feat), + // s.t. 'if' above knows where to put a share + pos: j.pos*2 + i, + // we pass the context to job so that spans are tracked in a tree + // structure + ctx: ctx, + }: + case <-ctx.Done(): + return + } + } + }) + case <-ctx.Done(): + return + } + } + // "tick-tack, how much more should I wait before you get those shares?" - the goroutine + wg.Wait() +} + +// GetLeavesByNamespace returns as many leaves from the given root with the given namespace.ID as it can retrieve. +// If no shares are found, it returns both data and error as nil. +// A non-nil error means that only partial data is returned, because at least one share retrieval failed +// The following implementation is based on `GetShares`. +func GetLeavesByNamespace( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + nID namespace.ID, + maxShares int, +) ([]ipld.Node, error) { + if len(nID) != NamespaceSize { + return nil, fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(nID)) + } + + ctx, span := tracer.Start(ctx, "get-leaves-by-namespace") + defer span.End() + + span.SetAttributes( + attribute.String("namespace", nID.String()), + attribute.String("root", root.String()), + ) + + // we don't know where in the tree the leaves in the namespace are, + // so we keep track of the bounds to return the correct slice + // maxShares acts as a sentinel to know if we find any leaves + bounds := fetchedBounds{int64(maxShares), 0} + + // buffer the jobs to avoid blocking, we only need as many + // queued as the number of shares in the second-to-last layer + jobs := make(chan *job, (maxShares+1)/2) + jobs <- &job{id: root, ctx: ctx} + + var wg wrappedWaitGroup + wg.jobs = jobs + wg.add(1) + + var ( + singleErr sync.Once + retrievalErr error + ) + + // we overallocate space for leaves since we do not know how many we will find + // on the level above, the length of the Row is passed in as maxShares + leaves := make([]ipld.Node, maxShares) + + for { + select { + case j, ok := <-jobs: + if !ok { + // if there were no leaves under the given root in the given namespace, + // both return values are nil. otherwise, the error will also be non-nil. + if bounds.lowest == int64(maxShares) { + return nil, retrievalErr + } + + return leaves[bounds.lowest : bounds.highest+1], retrievalErr + } + pool.Submit(func() { + ctx, span := tracer.Start(j.ctx, "process-job") + defer span.End() + defer wg.done() + + span.SetAttributes( + attribute.String("cid", j.id.String()), + attribute.Int("pos", j.pos), + ) + + // if an error is likely to be returned or not depends on + // the underlying impl of the blockservice, currently it is not a realistic probability + nd, err := GetNode(ctx, bGetter, j.id) + if err != nil { + singleErr.Do(func() { + retrievalErr = err + }) + log.Errorw("getSharesByNamespace: could not retrieve node", "nID", nID, "pos", j.pos, "err", err) + span.SetStatus(codes.Error, err.Error()) + // we still need to update the bounds + bounds.update(int64(j.pos)) + return + } + + links := nd.Links() + linkCount := uint64(len(links)) + if linkCount == 1 { + // successfully fetched a leaf belonging to the namespace + span.SetStatus(codes.Ok, "") + leaves[j.pos] = nd + // we found a leaf, so we update the bounds + // the update routine is repeated until the atomic swap is successful + bounds.update(int64(j.pos)) + return + } + + // this node has links in the namespace, so keep walking + for i, lnk := range links { + newJob := &job{ + id: lnk.Cid, + // position represents the index in a flattened binary tree, + // so we can return a slice of leaves in order + pos: j.pos*2 + i, + // we pass the context to job so that spans are tracked in a tree + // structure + ctx: ctx, + } + + // if the link's nID isn't in range we don't need to create a new job for it + jobNid := NamespacedSha256FromCID(newJob.id) + if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) || !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { + continue + } + + // by passing the previous check, we know we will have one more node to process + // note: it is important to increase the counter before sending to the channel + wg.add(1) + select { + case jobs <- newJob: + case <-ctx.Done(): + return + } + } + }) + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + +// GetProof fetches and returns the leaf's Merkle Proof. +// It walks down the IPLD NMT tree until it reaches the leaf and returns collected proof +func GetProof( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + proof []cid.Cid, + leaf, total int, +) ([]cid.Cid, error) { + // request the node + nd, err := GetNode(ctx, bGetter, root) + if err != nil { + return nil, err + } + // look for links + lnks := nd.Links() + if len(lnks) == 1 { + p := make([]cid.Cid, len(proof)) + copy(p, proof) + return p, nil + } + + // route walk to appropriate children + total /= 2 // as we are using binary tree, every step decreases total leaves in a half + if leaf < total { + root = lnks[0].Cid // if target leave on the left, go with walk down the first children + proof = append(proof, lnks[1].Cid) + } else { + root, leaf = lnks[1].Cid, leaf-total // otherwise go down the second + proof, err = GetProof(ctx, bGetter, root, proof, leaf, total) + if err != nil { + return nil, err + } + return append(proof, lnks[0].Cid), nil + } + + // recursively walk down through selected children + return GetProof(ctx, bGetter, root, proof, leaf, total) +} + +// wrappedWaitGroup is needed because waitgroups do not expose their internal counter, +// and we don't know in advance how many jobs we will have to wait for. +type wrappedWaitGroup struct { + wg sync.WaitGroup + jobs chan *job + counter int64 +} + +func (w *wrappedWaitGroup) add(count int64) { + w.wg.Add(int(count)) + atomic.AddInt64(&w.counter, count) +} + +func (w *wrappedWaitGroup) done() { + w.wg.Done() + numRemaining := atomic.AddInt64(&w.counter, -1) + + // Close channel if this job was the last one + if numRemaining == 0 { + close(w.jobs) + } +} + +type fetchedBounds struct { + lowest int64 + highest int64 +} + +// update checks if the passed index is outside the current bounds, +// and updates the bounds atomically if it extends them. +func (b *fetchedBounds) update(index int64) { + lowest := atomic.LoadInt64(&b.lowest) + // try to write index to the lower bound if appropriate, and retry until the atomic op is successful + // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the comparison here + for index < lowest && !atomic.CompareAndSwapInt64(&b.lowest, lowest, index) { + lowest = atomic.LoadInt64(&b.lowest) + } + // we always run both checks because element can be both the lower and higher bound + // for example, if there is only one share in the namespace + highest := atomic.LoadInt64(&b.highest) + for index > highest && !atomic.CompareAndSwapInt64(&b.highest, highest, index) { + highest = atomic.LoadInt64(&b.highest) + } +} + +// job represents an encountered node to investigate during the `GetLeaves` +// and `GetLeavesByNamespace` routines. +type job struct { + id cid.Cid + pos int + ctx context.Context +} diff --git a/ipld/plugin/namespace_hasher.go b/share/ipld/namespace_hasher.go similarity index 99% rename from ipld/plugin/namespace_hasher.go rename to share/ipld/namespace_hasher.go index 3a307e1bfe..b564e02c61 100644 --- a/ipld/plugin/namespace_hasher.go +++ b/share/ipld/namespace_hasher.go @@ -1,4 +1,4 @@ -package plugin +package ipld import ( "fmt" diff --git a/ipld/plugin/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go similarity index 98% rename from ipld/plugin/namespace_hasher_test.go rename to share/ipld/namespace_hasher_test.go index d23fd2fb6e..334ee60084 100644 --- a/ipld/plugin/namespace_hasher_test.go +++ b/share/ipld/namespace_hasher_test.go @@ -1,4 +1,4 @@ -package plugin +package ipld import ( "testing" diff --git a/ipld/plugin/nmt.go b/share/ipld/nmt.go similarity index 84% rename from ipld/plugin/nmt.go rename to share/ipld/nmt.go index 6b03096ae2..6b7ce85a3f 100644 --- a/ipld/plugin/nmt.go +++ b/share/ipld/nmt.go @@ -1,4 +1,4 @@ -package plugin +package ipld import ( "bytes" @@ -6,17 +6,26 @@ import ( "crypto/sha256" "errors" "fmt" + "math/rand" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" + logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" "github.com/tendermint/tendermint/pkg/consts" + "github.com/tendermint/tendermint/pkg/da" + "go.opentelemetry.io/otel" "github.com/celestiaorg/nmt" ) +var ( + tracer = otel.Tracer("ipld") + log = logging.Logger("ipld") +) + const ( // Below used multiformats (one codec, one multihash) seem free: // https://github.com/multiformats/multicodec/blob/master/table.csv @@ -24,13 +33,24 @@ const ( // nmtCodec is the codec used for leaf and inner nodes of a Namespaced Merkle Tree. nmtCodec = 0x7700 - // Sha256Namespace8Flagged is the multihash code used to hash blocks + // sha256Namespace8Flagged is the multihash code used to hash blocks // that contain an NMT node (inner and leaf nodes). sha256Namespace8Flagged = 0x7701 // nmtHashSize is the size of a digest created by an NMT in bytes. nmtHashSize = 2*consts.NamespaceSize + sha256.Size + // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. + MaxSquareSize = consts.MaxSquareSize + + // NamespaceSize is a system-wide size for NMT namespaces. + NamespaceSize = consts.NamespaceSize + + // cidPrefixSize is the size of the prepended buffer of the CID encoding + // for NamespacedSha256. For more information, see: + // https://multiformats.io/multihash/#the-multihash-format + cidPrefixSize = 4 + // typeSize defines the size of the serialized NMT Node type typeSize = 1 ) @@ -279,10 +299,15 @@ func MustCidFromNamespacedSha256(hash []byte) cid.Cid { return cidFromHash } -// cidPrefixSize is the size of the prepended buffer of the CID encoding -// for NamespacedSha256. For more information, see: -// https://multiformats.io/multihash/#the-multihash-format -const cidPrefixSize = 4 +// Translate transforms square coordinates into IPLD NMT tree path to a leaf node. +// It also adds randomization to evenly spread fetching from Rows and Columns. +func Translate(dah *da.DataAvailabilityHeader, row, col int) (cid.Cid, int) { + if rand.Intn(2) == 0 { //nolint:gosec + return MustCidFromNamespacedSha256(dah.ColumnRoots[col]), row + } + + return MustCidFromNamespacedSha256(dah.RowsRoots[row]), col +} // NamespacedSha256FromCID derives the Namespaced hash from the given CID. func NamespacedSha256FromCID(cid cid.Cid) []byte { diff --git a/ipld/nmt_adder.go b/share/ipld/nmt_adder.go similarity index 58% rename from ipld/nmt_adder.go rename to share/ipld/nmt_adder.go index 4b49eedfce..5156523bc6 100644 --- a/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -4,12 +4,9 @@ import ( "context" "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-merkledag" - "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/ipfs/go-merkledag" ) // NmtNodeAdder adds ipld.Nodes to the underlying ipld.Batch if it is inserted @@ -38,15 +35,14 @@ func (n *NmtNodeAdder) Visit(hash []byte, children ...[]byte) { if n.err != nil { return // protect from further visits if there is an error } - - id := plugin.MustCidFromNamespacedSha256(hash) + id := MustCidFromNamespacedSha256(hash) switch len(children) { case 1: if n.leaves.Visit(id) { - n.err = n.add.Add(n.ctx, plugin.NewNMTLeafNode(id, children[0])) + n.err = n.add.Add(n.ctx, NewNMTLeafNode(id, children[0])) } case 2: - n.err = n.add.Add(n.ctx, plugin.NewNMTNode(id, children[0], children[1])) + n.err = n.add.Add(n.ctx, NewNMTNode(id, children[0], children[1])) default: panic("expected a binary tree") } @@ -60,3 +56,26 @@ func (n *NmtNodeAdder) Commit() error { return n.add.Commit() } + +// MaxSizeBatchOption sets the maximum amount of buffered data before writing +// blocks. +func MaxSizeBatchOption(size int) ipld.BatchOption { + return ipld.MaxSizeBatchOption(BatchSize(size)) +} + +// BatchSize calculates the amount of nodes that are generated from block of 'squareSizes' +// to be batched in one write. +func BatchSize(squareSize int) int { + // (squareSize*2-1) - amount of nodes in a generated binary tree + // squareSize*2 - the total number of trees, both for rows and cols + // (squareSize*squareSize) - all the shares + // + // Note that while our IPLD tree looks like this: + // ---X + // -X---X + // X-X-X-X + // X-X-X-X + // here we count leaves only once: the CIDs are the same for columns and rows + // and for the last two layers as well: + return (squareSize*2-1)*squareSize*2 - (squareSize * squareSize) +} diff --git a/ipld/plugin/nmt_test.go b/share/ipld/nmt_test.go similarity index 99% rename from ipld/plugin/nmt_test.go rename to share/ipld/nmt_test.go index b175e18f18..d9fb6eeeb9 100644 --- a/ipld/plugin/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -1,4 +1,4 @@ -package plugin +package ipld import ( "bytes" @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/pkg/consts" - "github.com/tendermint/tendermint/pkg/da" ) diff --git a/ipld/plugin/test_helpers.go b/share/ipld/test_helpers.go similarity index 95% rename from ipld/plugin/test_helpers.go rename to share/ipld/test_helpers.go index 0ac7634817..f586aecbd8 100644 --- a/ipld/plugin/test_helpers.go +++ b/share/ipld/test_helpers.go @@ -1,4 +1,4 @@ -package plugin +package ipld import ( mrand "math/rand" diff --git a/share/light_availability_test.go b/share/light_availability_test.go deleted file mode 100644 index 963cf24258..0000000000 --- a/share/light_availability_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package share - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/celestiaorg/celestia-node/header" -) - -func TestSharesAvailable(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // randLightServiceWithSquare creates a NewLightAvailability inside, so we can test it - service, dah := randLightServiceWithSquare(t, 16) - err := service.SharesAvailable(ctx, dah) - assert.NoError(t, err) -} - -func TestSharesAvailableFailed(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // randLightServiceWithSquare creates a NewLightAvailability inside, so we can test it - s, _ := randLightServiceWithSquare(t, 16) - empty := header.EmptyDAH() - err := s.SharesAvailable(ctx, &empty) - assert.Error(t, err) -} - -func TestShareAvailableOverMocknet(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - net := NewTestDAGNet(ctx, t) - _, root := net.RandLightNode(16) - nd := net.LightNode() - net.ConnectAll() - - err := nd.SharesAvailable(ctx, root) - assert.NoError(t, err) -} diff --git a/ipld/pb/share.pb.go b/share/pb/share.pb.go similarity index 100% rename from ipld/pb/share.pb.go rename to share/pb/share.pb.go diff --git a/ipld/pb/share.proto b/share/pb/share.proto similarity index 100% rename from ipld/pb/share.proto rename to share/pb/share.proto diff --git a/share/service/service.go b/share/service/service.go new file mode 100644 index 0000000000..199fa83967 --- /dev/null +++ b/share/service/service.go @@ -0,0 +1,139 @@ +package service + +import ( + "context" + "fmt" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + "golang.org/x/sync/errgroup" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" +) + +// TODO(@Wondertan): Simple thread safety for Start and Stop would not hurt. +type ShareService struct { + share.Availability + rtrv *eds.Retriever + bServ blockservice.BlockService + // session is blockservice sub-session that applies optimization for fetching/loading related nodes, like shares + // prefer session over blockservice for fetching nodes. + session blockservice.BlockGetter + cancel context.CancelFunc +} + +// NewService creates a new basic share.Module. +func NewShareService(bServ blockservice.BlockService, avail share.Availability) *ShareService { + return &ShareService{ + rtrv: eds.NewRetriever(bServ), + Availability: avail, + bServ: bServ, + } +} + +func (s *ShareService) Start(context.Context) error { + if s.session != nil || s.cancel != nil { + return fmt.Errorf("share: service already started") + } + + // NOTE: The ctx given as param is used to control Start flow and only needed when Start is blocking, + // but this one is not. + // + // The newer context here is created to control lifecycle of the session and peer discovery. + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel + s.session = blockservice.NewSession(ctx, s.bServ) + return nil +} + +func (s *ShareService) Stop(context.Context) error { + if s.session == nil || s.cancel == nil { + return fmt.Errorf("share: service already stopped") + } + + s.cancel() + s.cancel = nil + s.session = nil + return nil +} + +func (s *ShareService) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { + root, leaf := ipld.Translate(dah, row, col) + nd, err := share.GetShare(ctx, s.bServ, root, leaf, len(dah.RowsRoots)) + if err != nil { + return nil, err + } + + return nd, nil +} + +func (s *ShareService) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) { + eds, err := s.rtrv.Retrieve(ctx, root) + if err != nil { + return nil, err + } + + origWidth := int(eds.Width() / 2) + shares := make([][]share.Share, origWidth) + + for i := 0; i < origWidth; i++ { + row := eds.Row(uint(i)) + shares[i] = make([]share.Share, origWidth) + for j := 0; j < origWidth; j++ { + shares[i][j] = row[j] + } + } + + return shares, nil +} + +// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. +func (s *ShareService) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + nID namespace.ID, +) ([]share.Share, error) { + if len(nID) != share.NamespaceSize { + return nil, fmt.Errorf("expected namespace ID of size %d, got %d", share.NamespaceSize, len(nID)) + } + + rowRootCIDs := make([]cid.Cid, 0) + for _, row := range root.RowsRoots { + if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { + rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) + } + } + if len(rowRootCIDs) == 0 { + return nil, nil + } + + errGroup, ctx := errgroup.WithContext(ctx) + shares := make([][]share.Share, len(rowRootCIDs)) + for i, rootCID := range rowRootCIDs { + // shadow loop variables, to ensure correct values are captured + i, rootCID := i, rootCID + errGroup.Go(func() (err error) { + shares[i], err = share.GetSharesByNamespace(ctx, s.bServ, rootCID, nID, len(root.RowsRoots)) + return + }) + } + + if err := errGroup.Wait(); err != nil { + return nil, err + } + + // we don't know the amount of shares in the namespace, so we cannot preallocate properly + // TODO(@Wondertan): Consider improving encoding schema for data in the shares that will also include metadata + // with the amount of shares. If we are talking about plenty of data here, proper preallocation would make a + // difference + var out []share.Share + for i := 0; i < len(rowRootCIDs); i++ { + out = append(out, shares[i]...) + } + + return out, nil +} diff --git a/share/share.go b/share/share.go index 922fbe73fd..4f0cd0291e 100644 --- a/share/share.go +++ b/share/share.go @@ -1,164 +1,106 @@ package share import ( - "context" - "fmt" - "math/rand" + "crypto/sha256" - "golang.org/x/sync/errgroup" - - "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "github.com/tendermint/tendermint/pkg/da" + "github.com/tendermint/tendermint/pkg/consts" + "go.opentelemetry.io/otel" - "github.com/celestiaorg/celestia-node/ipld" - "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/pb" "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" ) -var log = logging.Logger("share") - -// Share is a fixed-size data chunk associated with a namespace ID, whose data will be erasure-coded and committed -// to in Namespace Merkle trees. -type Share = ipld.Share +var ( + log = logging.Logger("share") + tracer = otel.Tracer("share") -// GetID extracts namespace ID out of a Share. -var GetID = ipld.ShareID + // DefaultRSMT2DCodec sets the default rsmt2d.Codec for shares. + DefaultRSMT2DCodec = consts.DefaultCodec +) -// GetData extracts data out of a Share. -var GetData = ipld.ShareData +const ( + // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. + MaxSquareSize = consts.MaxSquareSize + // NamespaceSize is a system-wide size for NMT namespaces. + NamespaceSize = consts.NamespaceSize + // Size is a system-wide size of a share, including both data and namespace ID + Size = consts.ShareSize +) -// Root represents root commitment to multiple Shares. -// In practice, it is a commitment to all the Data in a square. -type Root = da.DataAvailabilityHeader +// Share contains the raw share data without the corresponding namespace. +// NOTE: Alias for the byte is chosen to keep maximal compatibility, especially with rsmt2d. Ideally, we should define +// reusable type elsewhere and make everyone(Core, rsmt2d, ipld) to rely on it. +type Share = []byte -// TODO(@Wondertan): Simple thread safety for Start and Stop would not hurt. -type Service struct { - Availability - rtrv *ipld.Retriever - bServ blockservice.BlockService - // session is blockservice sub-session that applies optimization for fetching/loading related nodes, like shares - // prefer session over blockservice for fetching nodes. - session blockservice.BlockGetter - cancel context.CancelFunc +// ID gets the namespace ID from the share. +func ID(s Share) namespace.ID { + return s[:NamespaceSize] } -// NewService creates a new basic share.Module. -func NewService(bServ blockservice.BlockService, avail Availability) *Service { - return &Service{ - rtrv: ipld.NewRetriever(bServ), - Availability: avail, - bServ: bServ, - } +// Data gets data from the share. +func Data(s Share) []byte { + return s[NamespaceSize:] } -func (s *Service) Start(context.Context) error { - if s.session != nil || s.cancel != nil { - return fmt.Errorf("share: service already started") - } - - // NOTE: The ctx given as param is used to control Start flow and only needed when Start is blocking, - // but this one is not. - // - // The newer context here is created to control lifecycle of the session and peer discovery. - ctx, cancel := context.WithCancel(context.Background()) - s.cancel = cancel - s.session = blockservice.NewSession(ctx, s.bServ) - return nil +// ShareWithProof contains data with corresponding Merkle Proof +type ShareWithProof struct { //nolint:revive + // Share is a full data including namespace + Share + // Proof is a Merkle Proof of current share + Proof *nmt.Proof } -func (s *Service) Stop(context.Context) error { - if s.session == nil || s.cancel == nil { - return fmt.Errorf("share: service already stopped") +// NewShareWithProof takes the given leaf and its path, starting from the tree root, +// and computes the nmt.Proof for it. +func NewShareWithProof(index int, share Share, pathToLeaf []cid.Cid) *ShareWithProof { + rangeProofs := make([][]byte, 0, len(pathToLeaf)) + for i := len(pathToLeaf) - 1; i >= 0; i-- { + node := ipld.NamespacedSha256FromCID(pathToLeaf[i]) + rangeProofs = append(rangeProofs, node) } - s.cancel() - s.cancel = nil - s.session = nil - return nil -} - -func (s *Service) GetShare(ctx context.Context, dah *Root, row, col int) (Share, error) { - root, leaf := translate(dah, row, col) - nd, err := ipld.GetShare(ctx, s.bServ, root, leaf, len(dah.RowsRoots)) - if err != nil { - return nil, err + proof := nmt.NewInclusionProof(index, index+1, rangeProofs, true) + return &ShareWithProof{ + share, + &proof, } - - return nd, nil } -func (s *Service) GetShares(ctx context.Context, root *Root) ([][]Share, error) { - eds, err := s.rtrv.Retrieve(ctx, root) - if err != nil { - return nil, err - } - - origWidth := int(eds.Width() / 2) - shares := make([][]Share, origWidth) - - for i := 0; i < origWidth; i++ { - row := eds.Row(uint(i)) - shares[i] = make([]Share, origWidth) - for j := 0; j < origWidth; j++ { - shares[i][j] = row[j] - } - } - - return shares, nil +// Validate validates inclusion of the share under the given root CID. +func (s *ShareWithProof) Validate(root cid.Cid) bool { + return s.Proof.VerifyInclusion( + sha256.New(), // TODO(@Wondertan): This should be defined somewhere globally + ID(s.Share), + [][]byte{Data(s.Share)}, + ipld.NamespacedSha256FromCID(root), + ) } -// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. -func (s *Service) GetSharesByNamespace(ctx context.Context, root *Root, nID namespace.ID) ([]Share, error) { - err := ipld.SanityCheckNID(nID) - if err != nil { - return nil, err - } - rowRootCIDs := make([]cid.Cid, 0) - for _, row := range root.RowsRoots { - if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { - rowRootCIDs = append(rowRootCIDs, plugin.MustCidFromNamespacedSha256(row)) - } - } - if len(rowRootCIDs) == 0 { - return nil, nil - } - - errGroup, ctx := errgroup.WithContext(ctx) - shares := make([][]Share, len(rowRootCIDs)) - for i, rootCID := range rowRootCIDs { - // shadow loop variables, to ensure correct values are captured - i, rootCID := i, rootCID - errGroup.Go(func() (err error) { - shares[i], err = ipld.GetSharesByNamespace(ctx, s.bServ, rootCID, nID, len(root.RowsRoots)) - return - }) +func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { + return &pb.Share{ + Data: s.Share, + Proof: &pb.MerkleProof{ + Start: int64(s.Proof.Start()), + End: int64(s.Proof.End()), + Nodes: s.Proof.Nodes(), + LeafHash: s.Proof.LeafHash(), + }, } - - if err := errGroup.Wait(); err != nil { - return nil, err - } - - // we don't know the amount of shares in the namespace, so we cannot preallocate properly - // TODO(@Wondertan): Consider improving encoding schema for data in the shares that will also include metadata - // with the amount of shares. If we are talking about plenty of data here, proper preallocation would make a - // difference - var out []Share - for i := 0; i < len(rowRootCIDs); i++ { - out = append(out, shares[i]...) - } - - return out, nil } -// translate transforms square coordinates into IPLD NMT tree path to a leaf node. -// It also adds randomization to evenly spread fetching from Rows and Columns. -func translate(dah *Root, row, col int) (cid.Cid, int) { - if rand.Intn(2) == 0 { //nolint:gosec - return plugin.MustCidFromNamespacedSha256(dah.ColumnRoots[col]), row +func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { + shares := make([]*ShareWithProof, len(protoShares)) + for i, share := range protoShares { + proof := ProtoToProof(share.Proof) + shares[i] = &ShareWithProof{share.Data, &proof} } + return shares +} - return plugin.MustCidFromNamespacedSha256(dah.RowsRoots[row]), col +func ProtoToProof(protoProof *pb.MerkleProof) nmt.Proof { + return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, true) } diff --git a/ipld/test_helpers.go b/share/test_helpers.go similarity index 97% rename from ipld/test_helpers.go rename to share/test_helpers.go index 58c2a3bb10..a253fb6db6 100644 --- a/ipld/test_helpers.go +++ b/share/test_helpers.go @@ -1,4 +1,4 @@ -package ipld +package share import ( "bytes" @@ -7,10 +7,9 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/pkg/wrapper" "github.com/celestiaorg/rsmt2d" - - "github.com/tendermint/tendermint/pkg/wrapper" ) // EqualEDS check whether two given EDSes are equal. @@ -52,7 +51,7 @@ func RandShares(t *testing.T, total int) []Share { shares := make([]Share, total) for i := range shares { - nid := make([]byte, ShareSize) + nid := make([]byte, Size) _, err := mrand.Read(nid[:NamespaceSize]) require.NoError(t, err) shares[i] = nid diff --git a/share/testing.go b/share/testing.go deleted file mode 100644 index f375573a56..0000000000 --- a/share/testing.go +++ /dev/null @@ -1,295 +0,0 @@ -package share - -import ( - "bytes" - "context" - "testing" - "time" - - "github.com/ipfs/go-bitswap" - "github.com/ipfs/go-bitswap/network" - "github.com/ipfs/go-blockservice" - ds "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/ipfs/go-ipfs-routing/offline" - mdutils "github.com/ipfs/go-merkledag/test" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - record "github.com/libp2p/go-libp2p-record" - routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" - "github.com/libp2p/go-libp2p/p2p/discovery/routing" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/da" - - "github.com/celestiaorg/celestia-node/ipld" -) - -// randLightServiceWithSquare provides a share.Service filled with 'n' NMT -// trees of 'n' random shares, essentially storing a whole square. -func randLightServiceWithSquare(t *testing.T, n int) (*Service, *Root) { - bServ := mdutils.Bserv() - return NewService(bServ, TestLightAvailability(bServ)), RandFillBS(t, n, bServ) -} - -// randLightService provides an unfilled share.Service with corresponding -// blockservice.BlockService than can be filled by the test. -func randLightService() (*Service, blockservice.BlockService) { - bServ := mdutils.Bserv() - return NewService(bServ, TestLightAvailability(bServ)), bServ -} - -// randFullServiceWithSquare provides a share.Service filled with 'n' NMT -// trees of 'n' random shares, essentially storing a whole square. -func randFullServiceWithSquare(t *testing.T, n int) (*Service, *Root) { - bServ := mdutils.Bserv() - return NewService(bServ, TestFullAvailability(bServ)), RandFillBS(t, n, bServ) -} - -// randLightLocalServiceWithSquare is the same as randLightServiceWithSquare, except -// the Availability is wrapped with CacheAvailability. -func randLightLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { - bServ := mdutils.Bserv() - ds := dssync.MutexWrap(ds.NewMapDatastore()) - ca := NewCacheAvailability( - TestLightAvailability(bServ), - ds, - ) - return NewService(bServ, ca), RandFillBS(t, n, bServ) -} - -// randFullLocalServiceWithSquare is the same as randFullServiceWithSquare, except -// the Availability is wrapped with CacheAvailability. -func randFullLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { - bServ := mdutils.Bserv() - ds := dssync.MutexWrap(ds.NewMapDatastore()) - ca := NewCacheAvailability( - TestFullAvailability(bServ), - ds, - ) - return NewService(bServ, ca), RandFillBS(t, n, bServ) -} - -// RandFillBS fills the given BlockService with a random block of a given size. -func RandFillBS(t *testing.T, n int, bServ blockservice.BlockService) *Root { - shares := RandShares(t, n*n) - return FillBS(t, bServ, shares) -} - -// FillBS fills the given BlockService with the given shares. -func FillBS(t *testing.T, bServ blockservice.BlockService, shares []Share) *Root { - eds, err := ipld.AddShares(context.TODO(), shares, bServ) - require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(eds) - return &dah -} - -// RandShares provides 'n' randomized shares prefixed with random namespaces. -func RandShares(t *testing.T, n int) []Share { - return ipld.RandShares(t, n) -} - -type node struct { - net *dagNet - *Service - blockservice.BlockService - host.Host -} - -// ClearStorage cleans up the storage of the node. -func (n *node) ClearStorage() { - keys, err := n.Blockstore().AllKeysChan(n.net.ctx) - require.NoError(n.net.t, err) - - for k := range keys { - err := n.DeleteBlock(n.net.ctx, k) - require.NoError(n.net.t, err) - } -} - -type dagNet struct { - ctx context.Context - t *testing.T - net mocknet.Mocknet - nodes []*node -} - -// NewTestDAGNet creates a new testing swarm utility to spawn different nodes -// and test how they interact and/or exchange data. -func NewTestDAGNet(ctx context.Context, t *testing.T) *dagNet { //nolint:revive - return &dagNet{ - ctx: ctx, - t: t, - net: mocknet.New(), - } -} - -// RandLightNode creates a Light Node filled with a random block of the given size. -func (dn *dagNet) RandLightNode(squareSize int) (*node, *Root) { - nd := dn.LightNode() - return nd, RandFillBS(dn.t, squareSize, nd.BlockService) -} - -// RandFullNode creates a Full Node filled with a random block of the given size. -func (dn *dagNet) RandFullNode(squareSize int) (*node, *Root) { - nd := dn.FullNode() - return nd, RandFillBS(dn.t, squareSize, nd.BlockService) -} - -// LightNode creates a new empty LightAvailability Node. -func (dn *dagNet) LightNode() *node { - nd := dn.Node() - nd.Service = NewService(nd.BlockService, TestLightAvailability(nd.BlockService)) - return nd -} - -// FullNode creates a new empty FullAvailability Node. -func (dn *dagNet) FullNode() *node { - nd := dn.Node() - nd.Service = NewService(nd.BlockService, TestFullAvailability(nd.BlockService)) - return nd -} - -// Node create a plain network node that can serve and request data. -func (dn *dagNet) Node() *node { - hst, err := dn.net.GenPeer() - require.NoError(dn.t, err) - dstore := dssync.MutexWrap(ds.NewMapDatastore()) - bstore := blockstore.NewBlockstore(dstore) - routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) - bs := bitswap.New( - dn.ctx, - network.NewFromIpfsHost(hst, routing), - bstore, - bitswap.ProvideEnabled(false), // disable routines for DHT content provides, as we don't use them - bitswap.EngineBlockstoreWorkerCount(1), // otherwise it spawns 128 routines which is too much for tests - bitswap.EngineTaskWorkerCount(2), - bitswap.TaskWorkerCount(2), - bitswap.SetSimulateDontHavesOnTimeout(false), - bitswap.SetSendDontHaves(false), - ) - nd := &node{ - net: dn, - BlockService: blockservice.New(bstore, bs), - Host: hst, - } - dn.nodes = append(dn.nodes, nd) - return nd -} - -// ConnectAll connects all the peers on registered on the dagNet. -func (dn *dagNet) ConnectAll() { - err := dn.net.LinkAll() - require.NoError(dn.t, err) - - err = dn.net.ConnectAllButSelf() - require.NoError(dn.t, err) -} - -// Connect connects two given peer. -func (dn *dagNet) Connect(peerA, peerB peer.ID) { - _, err := dn.net.LinkPeers(peerA, peerB) - require.NoError(dn.t, err) - _, err = dn.net.ConnectPeers(peerA, peerB) - require.NoError(dn.t, err) -} - -// Disconnect disconnects two peers. -// It does a hard disconnect, meaning that disconnected peers won't be able to reconnect on their own -// but only with dagNet.Connect or dagNet.ConnectAll. -func (dn *dagNet) Disconnect(peerA, peerB peer.ID) { - err := dn.net.UnlinkPeers(peerA, peerB) - require.NoError(dn.t, err) - err = dn.net.DisconnectPeers(peerA, peerB) - require.NoError(dn.t, err) -} - -type subNet struct { - *dagNet - nodes []*node -} - -func (dn *dagNet) SubNet() *subNet { - return &subNet{dn, nil} -} - -func (sn *subNet) LightNode() *node { - nd := sn.dagNet.LightNode() - sn.nodes = append(sn.nodes, nd) - return nd -} - -func (sn *subNet) FullNode() *node { - nd := sn.dagNet.FullNode() - sn.nodes = append(sn.nodes, nd) - return nd -} - -func (sn *subNet) ConnectAll() { - nodes := sn.nodes - for _, n1 := range nodes { - for _, n2 := range nodes { - if n1 == n2 { - continue - } - _, err := sn.net.LinkPeers(n1.ID(), n2.ID()) - require.NoError(sn.t, err) - - _, err = sn.net.ConnectPeers(n1.ID(), n2.ID()) - require.NoError(sn.t, err) - } - } -} - -type TestBrokenAvailability struct { - Root *Root -} - -// NewTestBrokenAvailability returns an instance of Availability that -// allows for testing error cases during sampling. -// -// If the Root field is empty, it will return ErrNotAvailable on every call -// to SharesAvailable. Otherwise, it will only return ErrNotAvailable if the -// given Root hash matches the stored Root hash. -func NewTestBrokenAvailability() Availability { - return &TestBrokenAvailability{} -} - -func (b *TestBrokenAvailability) SharesAvailable(_ context.Context, root *Root) error { - if b.Root == nil || bytes.Equal(b.Root.Hash(), root.Hash()) { - return ErrNotAvailable - } - return nil -} - -func (b *TestBrokenAvailability) ProbabilityOfAvailability() float64 { - return 0 -} - -func TestLightAvailability(bServ blockservice.BlockService) *LightAvailability { - disc := NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) - return NewLightAvailability(bServ, disc) -} - -func TestFullAvailability(bServ blockservice.BlockService) *FullAvailability { - disc := NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) - return NewFullAvailability(bServ, disc) -} - -type TestSuccessfulAvailability struct { -} - -// NewTestSuccessfulAvailability returns an Availability that always -// returns successfully when SharesAvailable is called. -func NewTestSuccessfulAvailability() Availability { - return &TestSuccessfulAvailability{} -} - -func (tsa *TestSuccessfulAvailability) SharesAvailable(context.Context, *Root) error { - return nil -} - -func (tsa *TestSuccessfulAvailability) ProbabilityOfAvailability() float64 { - return 0 -} From abefe0d3371f49f4924570b3144a038490103dc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 12:44:22 +0200 Subject: [PATCH 0130/1008] chore(deps): bump github.com/spf13/cobra from 1.5.0 to 1.6.0 (#1222) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 849a96f38f..0bbe2c6fc4 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.7.0 github.com/multiformats/go-multihash v0.2.0 - github.com/spf13/cobra v1.5.0 + github.com/spf13/cobra v1.6.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 github.com/tendermint/tendermint v0.35.4 @@ -154,7 +154,7 @@ require ( github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect diff --git a/go.sum b/go.sum index 83d2201f8a..6f936caa89 100644 --- a/go.sum +++ b/go.sum @@ -702,8 +702,9 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= @@ -1571,8 +1572,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= From 93947a2eaa493caeeac42a4339e117b8cc32c8e4 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 13 Oct 2022 14:48:22 +0300 Subject: [PATCH 0131/1008] improvement(header/p2p): implement ErrHeadersLimitExceeded error (#1153) Resolves #191 --- header/interface.go | 4 ++++ header/p2p/exchange.go | 4 ++++ header/p2p/exchange_test.go | 28 ++++++++++++++++++++++++++-- header/p2p/server.go | 12 ++++++++++-- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/header/interface.go b/header/interface.go index 8d4a6c171f..1273dea103 100644 --- a/header/interface.go +++ b/header/interface.go @@ -66,6 +66,10 @@ var ( // ErrNoHead is returned when Store is empty (does not contain any known header). ErrNoHead = fmt.Errorf("header/store: no chain head") + + // ErrHeadersLimitExceeded is returned when ExchangeServer receives header request for more + // than maxRequestSize headers. + ErrHeadersLimitExceeded = errors.New("header/p2p: header limit per 1 request exceeded") ) // ErrNonAdjacent is returned when Store is appended with a header not adjacent to the stored head. diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index d98753f6fb..514e70e69a 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -30,6 +30,8 @@ const ( readDeadline = time.Minute // the target minimum amount of responses with the same chain head minResponses = 2 + // requestSize defines the max amount of headers that can be requested/handled at once. + maxRequestSize uint64 = 512 ) // PubSubTopic hardcodes the name of the ExtendedHeader @@ -258,6 +260,8 @@ func convertStatusCodeToError(code p2p_pb.StatusCode) error { return nil case p2p_pb.StatusCode_NOT_FOUND: return header.ErrNotFound + case p2p_pb.StatusCode_LIMIT_EXCEEDED: + return header.ErrHeadersLimitExceeded default: return fmt.Errorf("unknown status code %d", code) } diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index e0d60db9a4..d6c6c599ba 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -55,12 +55,36 @@ func TestExchange_RequestHeaders(t *testing.T) { // TestExchange_RequestHeadersFails tests that the Exchange instance will return // header.ErrNotFound if it will not have requested header. func TestExchange_RequestHeadersFails(t *testing.T) { + host, peer := createMocknet(t) + exchg, _ := createP2PExAndServer(t, host, peer) + tt := []struct { + amount uint64 + expectedErr *error + }{ + { + amount: 10, + expectedErr: &header.ErrNotFound, + }, + { + amount: 600, + expectedErr: &header.ErrHeadersLimitExceeded, + }, + } + for _, test := range tt { + // perform expected request + _, err := exchg.GetRangeByHeight(context.Background(), 1, test.amount) + require.Error(t, err) + require.ErrorAs(t, err, test.expectedErr) + } +} + +func TestExchange_RequestHeadersLimitExceed(t *testing.T) { host, peer := createMocknet(t) exchg, _ := createP2PExAndServer(t, host, peer) // perform expected request - _, err := exchg.GetRangeByHeight(context.Background(), 5, 3) + _, err := exchg.GetRangeByHeight(context.Background(), 1, 600) require.Error(t, err) - require.ErrorAs(t, err, &header.ErrNotFound) + require.ErrorAs(t, err, &header.ErrHeadersLimitExceeded) } // TestExchange_RequestByHash tests that the Exchange instance can diff --git a/header/p2p/server.go b/header/p2p/server.go index 35c51d89c0..4a1ce64c3e 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -86,14 +86,18 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { case nil: code = p2p_pb.StatusCode_OK case header.ErrNotFound: - // reallocate headers with 1 nil ExtendedHeader - headers = make([]*header.ExtendedHeader, 1) code = p2p_pb.StatusCode_NOT_FOUND + case header.ErrHeadersLimitExceeded: + code = p2p_pb.StatusCode_LIMIT_EXCEEDED default: stream.Reset() //nolint:errcheck return } + // reallocate headers with 1 nil ExtendedHeader if code is not StatusCode_OK + if code != p2p_pb.StatusCode_OK { + headers = make([]*header.ExtendedHeader, 1) + } // write all headers to stream for _, h := range headers { if err := stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { @@ -150,6 +154,10 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe return []*header.ExtendedHeader{head}, nil } + if to-from > maxRequestSize { + log.Errorw("server: skip request for too many headers.", "amount", to-from) + return nil, header.ErrHeadersLimitExceeded + } log.Debugw("server: handling headers request", "from", from, "to", to) headersByRange, err := serv.store.GetRangeByHeight(serv.ctx, from, to) if err != nil { From 30ed57feccf3584b56ec7d71f0a22b37e4b6899d Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Thu, 13 Oct 2022 10:25:45 -0400 Subject: [PATCH 0132/1008] .github: add new ci_release workflow to unify CI (#1191) Below are some key takeaways from the changes. - new `make cover` command - use of composite github actions - use of reusable github workflows - common CI file for PRs and release - Add hadolint and yamllint for dockerfiles and yamllinting and fix errors --- .github/ISSUE_TEMPLATE/bug-report.yml | 48 ++++++++------- .github/ISSUE_TEMPLATE/feature-request.yml | 6 +- .github/workflows/ci_release.yml | 69 ++++++++++++++++++++++ .github/workflows/go-ci.yml | 11 +--- .github/workflows/labels.yml | 2 +- .github/workflows/markdown-lint.yml | 22 ------- Makefile | 13 ++++ docker/Dockerfile | 8 ++- 8 files changed, 117 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/ci_release.yml delete mode 100644 .github/workflows/markdown-lint.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 021e3fcef4..f6a1f79857 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,6 +1,6 @@ name: Bug Report description: File a bug report to inform the community on your awesome finding! -labels: ["bug",] +labels: ["bug"] body: - type: markdown attributes: @@ -8,13 +8,15 @@ body: Thank you for filling out this bug report! - type: input id: version - attributes: + attributes: label: Celestia Node version - description: use 'celestia version' or 'git rev-parse --verify HEAD' if installed from source code + description: > + use 'celestia version' or 'git rev-parse --verify HEAD' if installed + from source code validations: required: true - type: markdown - attributes: + attributes: value: | Environment - type: input @@ -33,52 +35,48 @@ body: id: others attributes: label: Others - description: e.g. flag options, celestia config file changes, resources limitation(like cpu, ram limit, swap etc.) + description: > + e.g. flag options, celestia config file changes, resources + limitation(like cpu, ram limit, swap etc.) - type: textarea id: steps attributes: label: Steps to reproduce it - description: What steps have you made to reproduce it? + description: What steps have you made to reproduce it? placeholder: Tell us what you see! - value: validations: required: true - type: textarea id: expectation attributes: label: Expected result - description: What do you expect to happen as a final result? + description: What do you expect to happen as a final result? placeholder: Let us know what is expected - value: validations: required: true - type: textarea id: actual attributes: label: Actual result - description: What do you see happened instead as a final result? - placeholder: This is the crucial part in detecting the root cause of the issue - value: + description: What do you see happened instead as a final result? + placeholder: > + This is the crucial part in detecting the root cause of the issue validations: required: true - type: textarea id: logs attributes: label: Relevant log output - description: Please copy and paste any relevant log(max 20 lines) output. This will be automatically formatted into code, so no need for backticks. Or paste gists, pastebins links here - render: shell + description: > + Please copy and paste any relevant log(max 20 lines) output. This will + be automatically formatted into code, so no need for backticks. Or paste + gists, pastebins links here + render: Shell - type: textarea id: misc attributes: label: Notes - description: Is there anything else we need to know? - placeholder: Maybe, you have other ways to repro or what side effects there are if changing steps - #75 - # - type: checkboxes - # id: terms - # attributes: - # label: Code of Conduct - # description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) - # options: - # - label: I agree to follow this project's Code of Conduct - # required: true + description: Is there anything else we need to know? + placeholder: > + Maybe, you have other ways to repro or what side effects there are if + changing steps diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index ba7439cfbf..91c680c682 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,7 +1,9 @@ name: Feature Request -description: Request a new feature to inform the community on what will be benenificial for the project! +description: > + Request a new feature to inform the community on what will be beneficial for + the project! title: "[Feature Request]: " -labels: ["enhancement",] +labels: ["enhancement"] body: - type: markdown attributes: diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml new file mode 100644 index 0000000000..110ab42cc8 --- /dev/null +++ b/.github/workflows/ci_release.yml @@ -0,0 +1,69 @@ +name: CI and Release +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + version: + # Friendly description to be shown in the UI instead of 'name' + description: "Semver type of new version (major / minor / patch)" + # Input has to be provided for the workflow to run + required: true + type: choice + options: + - patch + - minor + - major + +jobs: + # Dockerfile Linting + hadolint: + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@main # yamllint disable-line rule:line-length + with: + dockerfile: docker/Dockerfile + + yamllint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: celestiaorg/.github/.github/actions/yamllint@main + + markdown-lint: + name: Markdown Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - run: | + npm install -g markdownlint-cli@0.32.1 + markdownlint --config .markdownlint.yaml '**/*.md' + + go-ci: + uses: ./.github/workflows/go-ci.yml + + # Make a release if this is a manually trigger job, i.e. workflow_dispatch + release: + needs: [hadolint, yamllint, markdown-lint, go-ci] + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' }} + permissions: "write-all" + steps: + - uses: actions/checkout@v3 + - name: Version Release + uses: celestiaorg/.github/.github/actions/version-release@main + with: + github-token: ${{secrets.GITHUB_TOKEN}} + version-bump: ${{inputs.version}} + + # TODO: permission issue, but not worth fixing as this should be refactored + # into the celestiaorg/.github repo, at which point any permission issues will + # be resolved. + # + # docker: + # needs: [release] + # uses: ./.github/workflows/docker-build.yml diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 7acd9147d4..40a689de4e 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -1,12 +1,7 @@ name: Go CI on: - push: - branches: - - main - pull_request: - release: - types: [published] + workflow_call: env: GO_VERSION: 1.19 @@ -61,9 +56,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Test & Coverage - run: | - go install github.com/ory/go-acc@v0.2.6 - go-acc -o coverage.txt `go list ./... | grep -v nodebuilder/tests` -- -v + run: make cover - uses: codecov/codecov-action@v3.1.1 with: file: ./coverage.txt diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index e628ebc292..63543b258b 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -16,4 +16,4 @@ jobs: with: mode: minimum count: 1 - labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:improvement, kind:feature, kind:dependencies" + labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:improvement, kind:feature, kind:dependencies" # yamllint disable-line rule:line-length diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml deleted file mode 100644 index f1600989e2..0000000000 --- a/.github/workflows/markdown-lint.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Markdown Lint - -on: - push: - branches: - - main - pull_request: - release: - types: [published] - -jobs: - markdown-lint: - name: Markdown Lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - run: | - npm install -g markdownlint-cli@0.32.1 - markdownlint --config .markdownlint.yaml '**/*.md' diff --git a/Makefile b/Makefile index ea3c594f58..7ceec73bef 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,19 @@ clean: @echo "--> Cleaning up ./build" @rm -rf build/* +## cover: generate to code coverage report. +cover: + @echo "--> Generating Code Coverage" + @go install github.com/ory/go-acc@v0.2.6 + @go-acc -o coverage.txt `go list ./... | grep -v nodebuilder/tests` -- -v +.PHONY: cover + +## deps: install dependencies. +deps: + @echo "--> Installing Dependencies" + @go mod download +.PHONY: deps + ## install: Build and install the celestia-node binary into the $PREFIX (/usr/local/ by default) directory. install: build @echo "--> Installing Celestia" diff --git a/docker/Dockerfile b/docker/Dockerfile index 99c8c3776d..9c0ca01ddc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,13 +1,15 @@ FROM --platform=$BUILDPLATFORM golang:1.19 as builder -RUN apt-get install make WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . + ARG TARGETOS TARGETARCH -RUN env GOOS=$TARGETOS GOARCH=$TARGETARCH make build +ENV GOOS=$TARGETOS +ENV GOARCH=$TARGETARCH +RUN make build -FROM ubuntu +FROM ubuntu:20.04 # Default node type can be overwritten in deployment manifest ENV NODE_TYPE bridge From 738b69fb3f11a55d535902408edb352f75d41e00 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Fri, 14 Oct 2022 04:51:10 -0600 Subject: [PATCH 0133/1008] chore: upgrade to celestia-app v0.7.0 (#1147) Closes https://github.com/celestiaorg/celestia-node/issues/1142 Opens https://github.com/celestiaorg/celestia-node/issues/1231 Co-authored-by: evan-forbes Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- Makefile | 3 +- fraud/bad_encoding.go | 6 +- go.mod | 28 ++++++--- go.sum | 62 ++++++++++++++----- header/header.go | 8 ++- header/pb/extended_header.pb.go | 30 ++++----- header/pb/extended_header.proto | 4 +- header/serde.go | 3 +- header/testing.go | 3 +- nodebuilder/tests/swamp/config.go | 13 +++- nodebuilder/tests/swamp/swamp.go | 16 +++-- nodebuilder/tests/swamp/swamp_tx.go | 11 +++- service/rpc/share.go | 4 +- share/add.go | 2 +- share/availability.go | 2 +- share/availability/cache/availability.go | 2 +- share/availability/cache/availability_test.go | 2 +- share/availability/light/availability_test.go | 19 ++++-- share/availability/test/testing.go | 2 +- share/byzantine.go | 3 +- share/eds/retriever.go | 4 +- share/eds/retriever_quadrant.go | 3 +- share/eds/retriever_test.go | 5 +- share/empty.go | 11 ++-- share/get_test.go | 5 +- share/ipld/namespace_hasher.go | 4 +- share/ipld/namespace_hasher_test.go | 5 +- share/ipld/nmt.go | 10 +-- share/ipld/nmt_test.go | 13 ++-- share/share.go | 10 +-- share/test_helpers.go | 2 +- 31 files changed, 190 insertions(+), 105 deletions(-) diff --git a/Makefile b/Makefile index 7ceec73bef..0513168903 100644 --- a/Makefile +++ b/Makefile @@ -133,13 +133,14 @@ benchmark: PB_PKGS=$(shell find . -name 'pb' -type d) PB_CORE=$(shell go list -f {{.Dir}} -m github.com/tendermint/tendermint) PB_GOGO=$(shell go list -f {{.Dir}} -m github.com/gogo/protobuf) +PB_CELESTIA_APP=$(shell go list -f {{.Dir}} -m github.com/celestiaorg/celestia-app) ## pb-gen: Generate protobuf code for all /pb/*.proto files in the project. pb-gen: @echo '--> Generating protobuf' @for dir in $(PB_PKGS); \ do for file in `find $$dir -type f -name "*.proto"`; \ - do protoc -I=. -I=${PB_CORE}/proto/ -I=${PB_GOGO} --gogofaster_out=paths=source_relative:. $$file; \ + do protoc -I=. -I=${PB_CORE}/proto/ -I=${PB_GOGO} -I=${PB_CELESTIA_APP}/proto --gogofaster_out=paths=source_relative:. $$file; \ echo '-->' $$file; \ done; \ done; diff --git a/fraud/bad_encoding.go b/fraud/bad_encoding.go index ad5afe9f14..fefd4eba0b 100644 --- a/fraud/bad_encoding.go +++ b/fraud/bad_encoding.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - "github.com/tendermint/tendermint/pkg/consts" - "github.com/tendermint/tendermint/pkg/wrapper" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/wrapper" pb "github.com/celestiaorg/celestia-node/fraud/pb" "github.com/celestiaorg/celestia-node/header" @@ -145,7 +145,7 @@ func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { } } - codec := consts.DefaultCodec() + codec := appconsts.DefaultCodec() // rebuild a row or col. rebuiltShares, err := codec.Decode(shares) if err != nil { diff --git a/go.mod b/go.mod index 0bbe2c6fc4..6800ab44e1 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,11 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/99designs/keyring v1.2.1 // indirect github.com/BurntSushi/toml v1.2.0 - github.com/celestiaorg/celestia-app v0.7.0-rc-1 + github.com/celestiaorg/celestia-app v0.7.0 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.10.0 github.com/celestiaorg/rsmt2d v0.6.0 - github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20220418184507-c53157dd63f6 + github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/dgraph-io/badger/v2 v2.2007.4 github.com/gammazero/workerpool v1.1.3 @@ -72,6 +72,7 @@ require ( filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect + github.com/StackExchange/wmi v1.2.1 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.0 // indirect github.com/aws/aws-sdk-go v1.40.45 // indirect @@ -83,6 +84,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/celestiaorg/go-leopard v0.1.0 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect + github.com/celestiaorg/quantum-gravity-bridge v1.2.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect @@ -95,6 +97,7 @@ require ( github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogoproto v1.4.2 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.0 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect @@ -104,14 +107,16 @@ require ( github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/deckarep/golang-set v1.8.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect + github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect + github.com/ethereum/go-ethereum v1.10.17 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -122,6 +127,8 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect @@ -201,11 +208,11 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect - github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect + github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -237,9 +244,12 @@ require ( github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect + github.com/rivo/uniseg v0.4.2 // indirect + github.com/rjeczalik/notify v0.9.1 // indirect github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.27.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect + github.com/shirou/gopsutil v3.21.6+incompatible // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.8.2 // indirect @@ -252,6 +262,8 @@ require ( github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tendermint/tm-db v0.6.7 // indirect + github.com/tklauser/go-sysconf v0.3.10 // indirect + github.com/tklauser/numcpus v0.4.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 // indirect @@ -280,6 +292,7 @@ require ( google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.6 // indirect + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -289,9 +302,8 @@ require ( ) replace ( - github.com/celestiaorg/celestia-app => github.com/celestiaorg/celestia-app v0.7.0-rc-1.0.20220930201625-f8540b2aec19 - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.3.0-sdk-v0.46.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20-celestia-node-patch + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 ) diff --git a/go.sum b/go.sum index 6f936caa89..65760cbab8 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,9 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -201,12 +204,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.7.0-rc-1.0.20220930201625-f8540b2aec19 h1:X4HE3OJQ70LYz6Si4PpHA++DNqHOHtPNdPVxymjYsY4= -github.com/celestiaorg/celestia-app v0.7.0-rc-1.0.20220930201625-f8540b2aec19/go.mod h1:jFaHWNtgYHD9zyZYtGxLuHKZ0DrYz9yoZMATrFr0wsE= -github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20-celestia-node-patch h1:lL8AH+b6a8/8L+ZB2G4ysEhT0j/8RSVMJShbo8Ysy18= -github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20-celestia-node-patch/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= -github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 h1:A1F7L/09uGClsU+kmugMy47Ezv4ll0uxNRNdaGa37T8= -github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0/go.mod h1:OXRC0p460CFKl77uQZWY/8p5uZmDrNum7BmVZDupq0Q= +github.com/celestiaorg/celestia-app v0.7.0 h1:+x/5FWg5yQazJ5qsWomtzzHbp4HuIHNUEm6gha0alAw= +github.com/celestiaorg/celestia-app v0.7.0/go.mod h1:LitlWqDQLq8z8UatKsa2BZbXlQ/dtOW887yf6m6lmVQ= +github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ= +github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= +github.com/celestiaorg/cosmos-sdk v1.3.0-sdk-v0.46.0 h1:VbIMBHFhaFgjHBccQ+Kyfz4tyzpaxVkad3Vei9O4KL4= +github.com/celestiaorg/cosmos-sdk v1.3.0-sdk-v0.46.0/go.mod h1:ByQ2rOrZs7s2OnPfeaiTMC8IOlcrT195xIRPgevydCI= github.com/celestiaorg/go-leopard v0.1.0 h1:28z2EkvKJIez5J9CEaiiUEC+OxalRLtTGJJ1oScfE1g= github.com/celestiaorg/go-leopard v0.1.0/go.mod h1:NtO/rjlB8dw2aq7jr06vZFKGvryQcTDXaNHelmPNOAM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= @@ -219,6 +222,8 @@ github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= github.com/celestiaorg/nmt v0.10.0 h1:HLfVWvpagHz5+uiE0QSjzv350wLhhnybNmrxq9NHLKc= github.com/celestiaorg/nmt v0.10.0/go.mod h1:3bqzTj8xKj0DgQUpOgZzoxvtNkC3MS/hTbQ6dn8SIa0= +github.com/celestiaorg/quantum-gravity-bridge v1.2.0 h1:l/LEEUP+x8MhhXB8rrWkyUVFZgQj1Ur/TAwUpnyLK38= +github.com/celestiaorg/quantum-gravity-bridge v1.2.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.6.0 h1:32Eq5t7lPNbhftPFFjxwCUeEjWg/yGgeMbshxnGw03c= github.com/celestiaorg/rsmt2d v0.6.0/go.mod h1:EZ+O2KdCq8xI7WFwjATLdhtMdrdClmAs2w7zENDr010= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -227,6 +232,7 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -270,7 +276,7 @@ github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaO github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= -github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= +github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -291,6 +297,8 @@ github.com/cosmos/cosmos-sdk/api v0.1.0/go.mod h1:CupqQBskAOiTXO1XDZ/wrtWzN/wTxU github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogoproto v1.4.2 h1:UeGRcmFW41l0G0MiefWhkPEVEwvu78SZsHBvI78dAYw= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.0 h1:sgyrjqOkycXiN7Tuupuo4QAldKFg7Sipyfeg/IL7cps= @@ -320,6 +328,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= @@ -360,14 +369,16 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg= +github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= @@ -383,6 +394,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -450,6 +462,9 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -462,6 +477,7 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -688,7 +704,9 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= @@ -1204,10 +1222,12 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -1223,8 +1243,9 @@ github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUM github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= -github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= +github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -1359,6 +1380,7 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1376,11 +1398,11 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= @@ -1480,6 +1502,7 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= @@ -1495,12 +1518,15 @@ github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNw github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= +github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -1520,6 +1546,8 @@ github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.6+incompatible h1:mmZtAlWSd8U2HeRTjswbnDLPxqsEoK01NK+GZ1P+nEM= +github.com/shirou/gopsutil v3.21.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -1626,7 +1654,11 @@ github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp1 github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1986,6 +2018,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2398,6 +2431,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/header/header.go b/header/header.go index 0e049bfa97..f62bc72487 100644 --- a/header/header.go +++ b/header/header.go @@ -11,8 +11,10 @@ import ( logging "github.com/ipfs/go-log/v2" bts "github.com/tendermint/tendermint/libs/bytes" - "github.com/tendermint/tendermint/pkg/da" core "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/da" + appshares "github.com/celestiaorg/celestia-app/pkg/shares" ) var log = logging.Logger("header") @@ -47,11 +49,11 @@ func MakeExtendedHeader( ) (*ExtendedHeader, error) { var dah DataAvailabilityHeader if len(b.Txs) > 0 { - namespacedShares, _, err := b.Data.ComputeShares(b.OriginalSquareSize) + shares, err := appshares.Split(b.Data, true) if err != nil { return nil, err } - extended, err := share.AddShares(ctx, namespacedShares.RawShares(), bServ) + extended, err := share.AddShares(ctx, appshares.ToBytes(shares), bServ) if err != nil { return nil, err } diff --git a/header/pb/extended_header.pb.go b/header/pb/extended_header.pb.go index 5ad2ab89a8..b61b9e3514 100644 --- a/header/pb/extended_header.pb.go +++ b/header/pb/extended_header.pb.go @@ -5,8 +5,8 @@ package header_pb import ( fmt "fmt" + da "github.com/celestiaorg/celestia-app/proto/da" proto "github.com/gogo/protobuf/proto" - da "github.com/tendermint/tendermint/proto/tendermint/da" types "github.com/tendermint/tendermint/proto/tendermint/types" io "io" math "math" @@ -99,24 +99,24 @@ func init() { func init() { proto.RegisterFile("header/pb/extended_header.proto", fileDescriptor_370294a9fc09133f) } var fileDescriptor_370294a9fc09133f = []byte{ - // 261 bytes of a gzipped FileDescriptorProto + // 258 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcf, 0x48, 0x4d, 0x4c, 0x49, 0x2d, 0xd2, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x87, 0x08, 0xe9, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0x71, 0xc2, 0x78, 0x49, 0x52, 0x32, 0x60, 0xf9, 0xa2, 0xdc, 0xcc, 0xbc, 0x12, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0x08, 0x09, 0x51, 0x28, 0xa5, - 0x80, 0x21, 0x5b, 0x96, 0x98, 0x93, 0x99, 0x92, 0x58, 0x92, 0x0f, 0x35, 0x4a, 0x4a, 0x07, 0x49, - 0x45, 0x4a, 0xa2, 0x7e, 0x4a, 0x62, 0x49, 0x62, 0x7c, 0x62, 0x59, 0x62, 0x66, 0x4e, 0x62, 0x52, - 0x66, 0x4e, 0x66, 0x49, 0x25, 0x8a, 0xc5, 0x4a, 0x9f, 0x18, 0xb9, 0xf8, 0x5c, 0xa1, 0x4e, 0xf2, - 0x00, 0x4b, 0x08, 0x19, 0x70, 0xb1, 0x41, 0x94, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x49, - 0xe8, 0x21, 0x4c, 0xd4, 0x83, 0xb8, 0x05, 0xa2, 0x32, 0x08, 0xaa, 0x0e, 0xa4, 0x23, 0x39, 0x3f, - 0x37, 0x37, 0xb3, 0x44, 0x82, 0x09, 0x97, 0x0e, 0x67, 0xb0, 0x7c, 0x10, 0x54, 0x9d, 0x90, 0x33, - 0x17, 0x2f, 0xdc, 0xdd, 0xf1, 0xc5, 0xa9, 0x25, 0x12, 0xcc, 0x60, 0x8d, 0x72, 0x98, 0x1a, 0xc3, - 0x60, 0xca, 0x82, 0x53, 0x4b, 0x82, 0x78, 0xca, 0x90, 0x78, 0x42, 0xe6, 0x5c, 0xcc, 0x29, 0x89, - 0x19, 0x12, 0x2c, 0x60, 0xad, 0xaa, 0xc8, 0x5a, 0x53, 0x12, 0xf5, 0x5c, 0x12, 0x4b, 0x12, 0x1d, - 0x91, 0xbc, 0x0d, 0x75, 0x32, 0x48, 0x87, 0x93, 0xc4, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, - 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, - 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x43, 0xc5, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x3d, 0xfa, 0x6c, - 0xbc, 0xb1, 0x01, 0x00, 0x00, + 0x80, 0x21, 0x5b, 0x96, 0x98, 0x93, 0x99, 0x92, 0x58, 0x92, 0x0f, 0x35, 0x4a, 0x4a, 0x31, 0x25, + 0x51, 0x3f, 0x25, 0xb1, 0x24, 0x31, 0x3e, 0xb1, 0x2c, 0x31, 0x33, 0x27, 0x31, 0x29, 0x33, 0x27, + 0xb3, 0xa4, 0x12, 0xc5, 0x36, 0xa5, 0xe7, 0x8c, 0x5c, 0x7c, 0xae, 0x50, 0x77, 0x78, 0x80, 0x25, + 0x84, 0x0c, 0xb8, 0xd8, 0x20, 0x4a, 0x24, 0x18, 0x15, 0x18, 0x35, 0xb8, 0x8d, 0x24, 0xf4, 0x10, + 0x16, 0xe9, 0x41, 0x1c, 0x00, 0x51, 0x19, 0x04, 0x55, 0x07, 0xd2, 0x91, 0x9c, 0x9f, 0x9b, 0x9b, + 0x59, 0x22, 0xc1, 0x84, 0x4b, 0x87, 0x33, 0x58, 0x3e, 0x08, 0xaa, 0x4e, 0xc8, 0x99, 0x8b, 0x17, + 0xee, 0xd8, 0xf8, 0xe2, 0xd4, 0x12, 0x09, 0x66, 0xb0, 0x46, 0x39, 0x4c, 0x8d, 0x61, 0x30, 0x65, + 0xc1, 0xa9, 0x25, 0x41, 0x3c, 0x65, 0x48, 0x3c, 0x21, 0x1d, 0x2e, 0xe6, 0x94, 0xc4, 0x0c, 0x09, + 0x16, 0xb0, 0x56, 0x29, 0xbd, 0x94, 0x44, 0x3d, 0x97, 0xc4, 0x92, 0x44, 0x47, 0x24, 0xbf, 0x42, + 0xdd, 0x09, 0x52, 0xe6, 0x24, 0x71, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, + 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x49, + 0x6c, 0xe0, 0xa0, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x55, 0x41, 0xd1, 0x9b, 0x01, + 0x00, 0x00, } func (m *ExtendedHeader) Marshal() (dAtA []byte, err error) { diff --git a/header/pb/extended_header.proto b/header/pb/extended_header.proto index b6155ca0cd..af9825365d 100644 --- a/header/pb/extended_header.proto +++ b/header/pb/extended_header.proto @@ -4,13 +4,13 @@ package header.pb; import "tendermint/types/types.proto"; import "tendermint/types/validator.proto"; -import "tendermint/da/data_availability_header.proto"; +import "da/data_availability_header.proto"; message ExtendedHeader { tendermint.types.Header header = 1; tendermint.types.Commit commit = 2; tendermint.types.ValidatorSet validator_set = 3; - tendermint.da.DataAvailabilityHeader dah = 4; + da.DataAvailabilityHeader dah = 4; } // Generated with: diff --git a/header/serde.go b/header/serde.go index 298c120448..320229b40d 100644 --- a/header/serde.go +++ b/header/serde.go @@ -1,9 +1,10 @@ package header import ( - "github.com/tendermint/tendermint/pkg/da" core "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/celestia-app/pkg/da" + header_pb "github.com/celestiaorg/celestia-node/header/pb" ) diff --git a/header/testing.go b/header/testing.go index 9dc99fc569..365e6151d3 100644 --- a/header/testing.go +++ b/header/testing.go @@ -17,12 +17,13 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bytes" tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/tendermint/tendermint/pkg/da" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/core" ) diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index f5925d0ff7..65af7a3eb4 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -4,20 +4,27 @@ import ( "time" tn "github.com/tendermint/tendermint/config" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/celestiaorg/celestia-app/testutil/testnode" ) // Components struct represents a set of pre-requisite attributes from the test scenario type Components struct { - CoreCfg *tn.Config + CoreCfg *tn.Config + ConsensusParams *tmproto.ConsensusParams + SupressLogs bool } // DefaultComponents creates a KvStore with a block retention of 200 -// In addition, the empty block interval is set to 200ms +// In addition, the empty block interval is set to 50ms func DefaultComponents() *Components { tnCfg := tn.TestConfig() tnCfg.Consensus.TimeoutCommit = 50 * time.Millisecond return &Components{ - CoreCfg: tnCfg, + CoreCfg: tnCfg, + ConsensusParams: testnode.DefaultParams(), + SupressLogs: true, } } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index df9731c56b..5f776384fa 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/cosmos/cosmos-sdk/client" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" @@ -49,7 +48,7 @@ type Swamp struct { trustedHash string comps *Components - ClientContext client.Context + ClientContext testnode.Context accounts []string } @@ -69,15 +68,22 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { // we create an arbitrary number of funded accounts accounts := make([]string, 100) - for i := 0; i < 10; i++ { - accounts = append(accounts, tmrand.Str(9)) + for i := 0; i < 100; i++ { + accounts[i] = tmrand.Str(9) } // TODO(@Bidon15): CoreClient(limitation) // Now, we are making an assumption that consensus mechanism is already tested out // so, we are not creating bridge nodes with each one containing its own core client // instead we are assigning all created BNs to 1 Core from the swamp - tmNode, app, cctx, err := testnode.New(t, ic.CoreCfg, false, accounts...) + tmNode, app, cctx, err := testnode.New( + t, + ic.ConsensusParams, + ic.CoreCfg, + ic.SupressLogs, + accounts..., + ) + require.NoError(t, err) cctx, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) diff --git a/nodebuilder/tests/swamp/swamp_tx.go b/nodebuilder/tests/swamp/swamp_tx.go index 1d2b13d014..df885429da 100644 --- a/nodebuilder/tests/swamp/swamp_tx.go +++ b/nodebuilder/tests/swamp/swamp_tx.go @@ -3,7 +3,7 @@ package swamp import ( "context" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/cosmos/cosmos-sdk/client/flags" ) // FillBlocks produces the given amount of contiguous blocks with customizable size. @@ -12,7 +12,14 @@ func (s *Swamp) FillBlocks(ctx context.Context, bsize, blocks int) chan error { errCh := make(chan error) go func() { // TODO: FillBlock must respect the context - _, err := testnode.FillBlock(s.ClientContext, bsize, s.accounts[:blocks+1]) + var err error + for i := 0; i < blocks; i++ { + _, err = s.ClientContext.FillBlock(bsize, s.accounts, flags.BroadcastBlock) + if err != nil { + break + } + } + select { case errCh <- err: case <-ctx.Done(): diff --git a/service/rpc/share.go b/service/rpc/share.go index 3c14b34a84..507b4cbfff 100644 --- a/service/rpc/share.go +++ b/service/rpc/share.go @@ -8,8 +8,8 @@ import ( "strconv" "github.com/gorilla/mux" - "github.com/tendermint/tendermint/types" + appshares "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/nmt/namespace" @@ -112,7 +112,7 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID } func dataFromShares(shares []share.Share) ([][]byte, error) { - messages, err := types.ParseMsgs(shares) + messages, err := appshares.ParseMsgs(shares) if err != nil { return nil, err } diff --git a/share/add.go b/share/add.go index f9e74c6ca9..aab73e4824 100644 --- a/share/add.go +++ b/share/add.go @@ -6,8 +6,8 @@ import ( "math" "github.com/ipfs/go-blockservice" - "github.com/tendermint/tendermint/pkg/wrapper" + "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" diff --git a/share/availability.go b/share/availability.go index d53d1d752f..5104dcb5bd 100644 --- a/share/availability.go +++ b/share/availability.go @@ -5,7 +5,7 @@ import ( "errors" "time" - "github.com/tendermint/tendermint/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/da" ) // ErrNotAvailable is returned whenever DA sampling fails. diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index 3d4f94a431..961dee7564 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -9,8 +9,8 @@ import ( "github.com/ipfs/go-datastore/autobatch" "github.com/ipfs/go-datastore/namespace" logging "github.com/ipfs/go-log/v2" - "github.com/tendermint/tendermint/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" ) diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index b49fb547e8..438d63924e 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -12,8 +12,8 @@ import ( mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/service" diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index f7c0a75d44..6cf998bf0b 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -15,9 +15,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/rand" - "github.com/tendermint/tendermint/pkg/da" core "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/celestia-app/pkg/da" + appshares "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" @@ -285,6 +286,7 @@ func TestSharesRoundTrip(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // prepare data b.Data.Messages.MessagesList = make([]core.Message, len(tc.messages)) + b.OriginalSquareSize = 16 var msgsInNamespace [][]byte require.Equal(t, len(tc.namespaces), len(tc.messages)) for i := range tc.messages { @@ -294,17 +296,22 @@ func TestSharesRoundTrip(t *testing.T) { } } - namespacedShares, _, _ := b.Data.ComputeShares(uint64(0)) + // TODO: set useShareIndexes to true. This requires updating the + // transaction data in this test to include share indexes. + shares, err := appshares.Split(b.Data, false) + if err != nil { + t.Fatal(err) + } // test round trip using only encoding, without IPLD { myShares := make([][]byte, 0) - for _, sh := range namespacedShares.RawShares() { + for _, sh := range shares { if bytes.Equal(namespace, sh[:8]) { myShares = append(myShares, sh) } } - msgs, err := core.ParseMsgs(myShares) + msgs, err := appshares.ParseMsgs(myShares) require.NoError(t, err) assert.Len(t, msgs.MessagesList, len(msgsInNamespace)) for i := range msgs.MessagesList { @@ -314,7 +321,7 @@ func TestSharesRoundTrip(t *testing.T) { // test full round trip - with IPLD + decoding shares { - extSquare, err := share.AddShares(ctx, namespacedShares.RawShares(), store) + extSquare, err := share.AddShares(ctx, appshares.ToBytes(shares), store) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(extSquare) @@ -322,7 +329,7 @@ func TestSharesRoundTrip(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, shares) - msgs, err := core.ParseMsgs(shares) + msgs, err := appshares.ParseMsgs(shares) require.NoError(t, err) assert.Len(t, msgs.MessagesList, len(msgsInNamespace)) for i := range msgs.MessagesList { diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index d1f4b29443..1e0bc7fc54 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -17,8 +17,8 @@ import ( record "github.com/libp2p/go-libp2p-record" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/service" ) diff --git a/share/byzantine.go b/share/byzantine.go index d74eb119b5..8e9e7f4b8d 100644 --- a/share/byzantine.go +++ b/share/byzantine.go @@ -5,7 +5,8 @@ import ( "fmt" "github.com/ipfs/go-blockservice" - "github.com/tendermint/tendermint/pkg/da" + + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/rsmt2d" diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 1f5f75aa29..19ba7f07d4 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -11,13 +11,13 @@ import ( "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "github.com/tendermint/tendermint/pkg/da" - "github.com/tendermint/tendermint/pkg/wrapper" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" diff --git a/share/eds/retriever_quadrant.go b/share/eds/retriever_quadrant.go index 7179662019..e223be36f8 100644 --- a/share/eds/retriever_quadrant.go +++ b/share/eds/retriever_quadrant.go @@ -6,7 +6,8 @@ import ( "time" "github.com/ipfs/go-cid" - "github.com/tendermint/tendermint/pkg/da" + + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share/ipld" ) diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 741390cb58..7d773b4c8c 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -9,8 +9,9 @@ import ( mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/da" - "github.com/tendermint/tendermint/pkg/wrapper" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" diff --git a/share/empty.go b/share/empty.go index 75877f6acd..6116307399 100644 --- a/share/empty.go +++ b/share/empty.go @@ -5,7 +5,8 @@ import ( "context" "github.com/ipfs/go-blockservice" - "github.com/tendermint/tendermint/pkg/consts" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" ) // EnsureEmptySquareExists checks if the given DAG contains an empty block data square. @@ -13,8 +14,8 @@ import ( // redundant storing of empty block data so that it is only stored once and returned // upon request for a block with an empty data square. Ref: header/header.go#L56 func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockService) error { - shares := make([][]byte, consts.MinSharecount) - for i := 0; i < consts.MinSharecount; i++ { + shares := make([][]byte, appconsts.MinShareCount) + for i := 0; i < appconsts.MinShareCount; i++ { shares[i] = tailPaddingShare } @@ -25,6 +26,6 @@ func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockServic // tail is filler for all tail padded shares // it is allocated once and used everywhere var tailPaddingShare = append( - append(make([]byte, 0, consts.ShareSize), consts.TailPaddingNamespaceID...), - bytes.Repeat([]byte{0}, consts.ShareSize-consts.NamespaceSize)..., + append(make([]byte, 0, appconsts.ShareSize), appconsts.TailPaddingNamespaceID...), + bytes.Repeat([]byte{0}, appconsts.ShareSize-appconsts.NamespaceSize)..., ) diff --git a/share/get_test.go b/share/get_test.go index 7cced86e2e..d66e949ff5 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -17,8 +17,9 @@ import ( mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/da" - "github.com/tendermint/tendermint/pkg/wrapper" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt/namespace" diff --git a/share/ipld/namespace_hasher.go b/share/ipld/namespace_hasher.go index b564e02c61..52c194208e 100644 --- a/share/ipld/namespace_hasher.go +++ b/share/ipld/namespace_hasher.go @@ -6,8 +6,8 @@ import ( "github.com/minio/sha256-simd" mhcore "github.com/multiformats/go-multihash/core" - "github.com/tendermint/tendermint/pkg/consts" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/nmt" ) @@ -42,7 +42,7 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { } ln, nln, hln := len(data), int(n.NamespaceLen), n.Hash.Size() - innerNodeSize, leafNodeSize := (nln*2+hln)*2, nln+consts.ShareSize + innerNodeSize, leafNodeSize := (nln*2+hln)*2, nln+appconsts.ShareSize switch ln { default: return 0, fmt.Errorf("ipld: wrong sized data written to the hasher") diff --git a/share/ipld/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go index 334ee60084..1e627e5db5 100644 --- a/share/ipld/namespace_hasher_test.go +++ b/share/ipld/namespace_hasher_test.go @@ -4,11 +4,12 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/tendermint/tendermint/pkg/consts" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" ) func TestNamespaceHasherWrite(t *testing.T) { - leafSize := consts.ShareSize + consts.NamespaceSize + leafSize := appconsts.ShareSize + appconsts.NamespaceSize innerSize := nmtHashSize * 2 tt := []struct { name string diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 6b7ce85a3f..27d92899fb 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -14,10 +14,10 @@ import ( ipld "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" - "github.com/tendermint/tendermint/pkg/consts" - "github.com/tendermint/tendermint/pkg/da" "go.opentelemetry.io/otel" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/nmt" ) @@ -38,13 +38,13 @@ const ( sha256Namespace8Flagged = 0x7701 // nmtHashSize is the size of a digest created by an NMT in bytes. - nmtHashSize = 2*consts.NamespaceSize + sha256.Size + nmtHashSize = 2*appconsts.NamespaceSize + sha256.Size // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. - MaxSquareSize = consts.MaxSquareSize + MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. - NamespaceSize = consts.NamespaceSize + NamespaceSize = appconsts.NamespaceSize // cidPrefixSize is the size of the prepended buffer of the CID encoding // for NamespacedSha256. For more information, see: diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index d9fb6eeeb9..2b75436987 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -10,8 +10,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/consts" - "github.com/tendermint/tendermint/pkg/da" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/da" ) // TestNamespaceFromCID checks that deriving the Namespaced hash from @@ -20,10 +21,10 @@ func TestNamespaceFromCID(t *testing.T) { var tests = []struct { randData [][]byte }{ - {randData: generateRandNamespacedRawData(4, consts.NamespaceSize, consts.ShareSize)}, - {randData: generateRandNamespacedRawData(16, 16, consts.ShareSize)}, - {randData: generateRandNamespacedRawData(4, 4, consts.ShareSize)}, - {randData: generateRandNamespacedRawData(4, consts.NamespaceSize, consts.ShareSize/2)}, + {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize)}, + {randData: generateRandNamespacedRawData(16, 16, appconsts.ShareSize)}, + {randData: generateRandNamespacedRawData(4, 4, appconsts.ShareSize)}, + {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize/2)}, } for i, tt := range tests { diff --git a/share/share.go b/share/share.go index 4f0cd0291e..604a531386 100644 --- a/share/share.go +++ b/share/share.go @@ -5,9 +5,9 @@ import ( "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "github.com/tendermint/tendermint/pkg/consts" "go.opentelemetry.io/otel" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/pb" "github.com/celestiaorg/nmt" @@ -19,16 +19,16 @@ var ( tracer = otel.Tracer("share") // DefaultRSMT2DCodec sets the default rsmt2d.Codec for shares. - DefaultRSMT2DCodec = consts.DefaultCodec + DefaultRSMT2DCodec = appconsts.DefaultCodec ) const ( // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. - MaxSquareSize = consts.MaxSquareSize + MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. - NamespaceSize = consts.NamespaceSize + NamespaceSize = appconsts.NamespaceSize // Size is a system-wide size of a share, including both data and namespace ID - Size = consts.ShareSize + Size = appconsts.ShareSize ) // Share contains the raw share data without the corresponding namespace. diff --git a/share/test_helpers.go b/share/test_helpers.go index a253fb6db6..597989c9b9 100644 --- a/share/test_helpers.go +++ b/share/test_helpers.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/pkg/wrapper" + "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" ) From 7d8c2f8472e81fa71ced942eec7074295457c275 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 11 Oct 2022 18:54:03 +0300 Subject: [PATCH 0134/1008] remove prepending byte indication node type from nmtNode --- share/get.go | 5 ++-- share/get_test.go | 5 ++-- share/ipld/namespace_hasher.go | 8 +----- share/ipld/namespace_hasher_test.go | 10 ------- share/ipld/nmt.go | 41 +++++++++-------------------- share/ipld/nmt_adder.go | 1 - 6 files changed, 18 insertions(+), 52 deletions(-) diff --git a/share/get.go b/share/get.go index e0c16fdc9f..219b73c50c 100644 --- a/share/get.go +++ b/share/get.go @@ -90,7 +90,7 @@ func GetProofsForShares( if err != nil { return nil, err } - proofs[index] = NewShareWithProof(index, s.RawData()[1:], proof) + proofs[index] = NewShareWithProof(index, s.RawData(), proof) } } @@ -99,7 +99,6 @@ func GetProofsForShares( // leafToShare converts an NMT leaf into a Share. func leafToShare(nd format.Node) Share { - // * First byte represents the type of the node, and is unrelated to the actual share data // * Additional namespace is prepended so that parity data can be identified with a parity namespace, which we cut off - return nd.RawData()[1+NamespaceSize:] // TODO(@Wondertan): Rework NMT/IPLD plugin to avoid the type byte + return nd.RawData()[NamespaceSize:] } diff --git a/share/get_test.go b/share/get_test.go index d66e949ff5..714d1c62ef 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -309,8 +309,7 @@ func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T for _, node := range nodes { // test that the data returned by getLeavesByNamespace for nid // matches the commonNamespaceData that was copied across almost all data - share := node.RawData()[1:] - assert.Equal(t, commonNamespaceData, share[NamespaceSize:]) + assert.Equal(t, commonNamespaceData, node.RawData()[NamespaceSize:]) } } } @@ -401,7 +400,7 @@ func TestGetProof(t *testing.T) { require.NoError(t, err) node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) require.NoError(t, err) - inclusion := NewShareWithProof(index, node.RawData()[1:], proof) + inclusion := NewShareWithProof(index, node.RawData(), proof) require.True(t, inclusion.Validate(rootCid)) } } diff --git a/share/ipld/namespace_hasher.go b/share/ipld/namespace_hasher.go index 52c194208e..8ea6a8600c 100644 --- a/share/ipld/namespace_hasher.go +++ b/share/ipld/namespace_hasher.go @@ -45,17 +45,11 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { innerNodeSize, leafNodeSize := (nln*2+hln)*2, nln+appconsts.ShareSize switch ln { default: - return 0, fmt.Errorf("ipld: wrong sized data written to the hasher") + return 0, fmt.Errorf("ipld: wrong sized data written to the hasher, len: %v", ln) case innerNodeSize: n.tp = nmt.NodePrefix case leafNodeSize: n.tp = nmt.LeafPrefix - case innerNodeSize + typeSize: // w/ additional type byte - n.tp = nmt.NodePrefix - data = data[typeSize:] - case leafNodeSize + typeSize: // w/ additional type byte - n.tp = nmt.LeafPrefix - data = data[typeSize:] } n.data = data diff --git a/share/ipld/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go index 1e627e5db5..64286e503e 100644 --- a/share/ipld/namespace_hasher_test.go +++ b/share/ipld/namespace_hasher_test.go @@ -26,16 +26,6 @@ func TestNamespaceHasherWrite(t *testing.T) { innerSize, innerSize, }, - { - "LeafAndType", - leafSize, - leafSize + typeSize, - }, - { - "InnerAndType", - innerSize, - innerSize + typeSize, - }, } for _, ts := range tt { diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 27d92899fb..952f746937 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -1,7 +1,6 @@ package ipld import ( - "bytes" "context" "crypto/sha256" "errors" @@ -69,37 +68,23 @@ func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid } func decodeBlock(block blocks.Block) (ipld.Node, error) { - var ( - leafPrefix = []byte{nmt.LeafPrefix} - innerPrefix = []byte{nmt.NodePrefix} - ) data := block.RawData() - if len(data) == 0 { - return &nmtLeafNode{ - cid: cid.Undef, - Data: nil, + innerNodeSize, leafNodeSize := (nmtHashSize)*2, NamespaceSize+consts.ShareSize + switch len(data) { + default: + return nil, fmt.Errorf("ipld: wrong sized data carried in block") + case innerNodeSize: + return &nmtNode{ + cid: block.Cid(), + l: data[:nmtHashSize], + r: data[nmtHashSize:], }, nil - } - domainSeparator := data[:typeSize] - if bytes.Equal(domainSeparator, leafPrefix) { + case leafNodeSize: return &nmtLeafNode{ cid: block.Cid(), - Data: data[typeSize:], - }, nil - } - if bytes.Equal(domainSeparator, innerPrefix) { - return &nmtNode{ - cid: block.Cid(), - l: data[typeSize : typeSize+nmtHashSize], - r: data[typeSize+nmtHashSize:], + Data: data, }, nil } - return nil, fmt.Errorf( - "expected first byte of block to be either the leaf or inner node prefix: (%x, %x), got: %x)", - leafPrefix, - innerPrefix, - domainSeparator, - ) } var _ ipld.Node = (*nmtNode)(nil) @@ -115,7 +100,7 @@ func NewNMTNode(id cid.Cid, l, r []byte) ipld.Node { } func (n nmtNode) RawData() []byte { - return append([]byte{nmt.NodePrefix}, append(n.l, n.r...)...) + return append(n.l, n.r...) } func (n nmtNode) Cid() cid.Cid { @@ -217,7 +202,7 @@ func NewNMTLeafNode(id cid.Cid, data []byte) ipld.Node { } func (l nmtLeafNode) RawData() []byte { - return append([]byte{nmt.LeafPrefix}, l.Data...) + return l.Data } func (l nmtLeafNode) Cid() cid.Cid { diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index 5156523bc6..c88164a800 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -74,7 +74,6 @@ func BatchSize(squareSize int) int { // ---X // -X---X // X-X-X-X - // X-X-X-X // here we count leaves only once: the CIDs are the same for columns and rows // and for the last two layers as well: return (squareSize*2-1)*squareSize*2 - (squareSize * squareSize) From ad0c2bfec50cc716a314ba3f190ad0df55a70f04 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 11 Oct 2022 20:20:29 +0300 Subject: [PATCH 0135/1008] remove nmtLeafNode link to itself --- share/ipld/get.go | 22 ++++++---------------- share/ipld/nmt.go | 39 ++++++++------------------------------- 2 files changed, 14 insertions(+), 47 deletions(-) diff --git a/share/ipld/get.go b/share/ipld/get.go index 90587fe34a..5a2f0cdcad 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -58,9 +58,9 @@ func GetLeaf( // look for links lnks := nd.Links() - if len(lnks) == 1 { - // in case there is only one we reached tree's bottom, so finally request the leaf. - return GetNode(ctx, bGetter, lnks[0].Cid) + if len(lnks) == 0 { + // in case there is none, we reached tree's bottom, so finally return the leaf. + return nd, err } // route walk to appropriate children @@ -137,16 +137,7 @@ func GetLeaves(ctx context.Context, } // check links to know what we should do with the node lnks := nd.Links() - if len(lnks) == 1 { // so we are almost there - // the reason why the comment on 'total' is lying, as each - // leaf has its own additional leaf(hack) so get it - nd, err := GetNode(ctx, bGetter, lnks[0].Cid) - if err != nil { - // again, we don't really care much, just fetch as much as possible - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - return - } + if len(lnks) == 0 { // successfully fetched a share/leaf // ladies and gentlemen, we got em! span.SetStatus(codes.Ok, "") @@ -262,8 +253,7 @@ func GetLeavesByNamespace( } links := nd.Links() - linkCount := uint64(len(links)) - if linkCount == 1 { + if len(links) == 0 { // successfully fetched a leaf belonging to the namespace span.SetStatus(codes.Ok, "") leaves[j.pos] = nd @@ -323,7 +313,7 @@ func GetProof( } // look for links lnks := nd.Links() - if len(lnks) == 1 { + if len(lnks) == 0 { p := make([]cid.Cid, len(proof)) copy(p, proof) return p, nil diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 952f746937..467cc9352d 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -49,9 +49,6 @@ const ( // for NamespacedSha256. For more information, see: // https://multiformats.io/multihash/#the-multihash-format cidPrefixSize = 4 - - // typeSize defines the size of the serialized NMT Node type - typeSize = 1 ) func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid) (ipld.Node, error) { @@ -80,10 +77,7 @@ func decodeBlock(block blocks.Block) (ipld.Node, error) { r: data[nmtHashSize:], }, nil case leafNodeSize: - return &nmtLeafNode{ - cid: block.Cid(), - Data: data, - }, nil + return &nmtLeafNode{block}, nil } } @@ -193,32 +187,15 @@ func (n nmtNode) Size() (uint64, error) { } type nmtLeafNode struct { - cid cid.Cid - Data []byte + blocks.Block } func NewNMTLeafNode(id cid.Cid, data []byte) ipld.Node { - return &nmtLeafNode{id, data} -} - -func (l nmtLeafNode) RawData() []byte { - return l.Data -} - -func (l nmtLeafNode) Cid() cid.Cid { - return l.cid -} - -func (l nmtLeafNode) String() string { - return fmt.Sprintf(` -leaf { - hash: %x, - len(Data): %v -}`, l.cid.Hash(), len(l.Data)) -} - -func (l nmtLeafNode) Loggable() map[string]interface{} { - return nil + b, err := blocks.NewBlockWithCid(data, id) + if err != nil { + log.Warn("wrong hash for block", "cid", id) + } + return &nmtLeafNode{b} } func (l nmtLeafNode) Resolve(path []string) (interface{}, []string, error) { @@ -247,7 +224,7 @@ func (l nmtLeafNode) Copy() ipld.Node { } func (l nmtLeafNode) Links() []*ipld.Link { - return []*ipld.Link{{Cid: l.Cid()}} + return nil } func (l nmtLeafNode) Stat() (*ipld.NodeStat, error) { From 63212e71f5d4fcb27b2d835b3808a937e721874d Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 13 Oct 2022 12:22:52 +0300 Subject: [PATCH 0136/1008] concatenate data in nmtNode to reduce amount of allocations when calling RawData() --- share/ipld/nmt.go | 107 +++++++++++----------------------------- share/ipld/nmt_adder.go | 4 +- 2 files changed, 30 insertions(+), 81 deletions(-) diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 467cc9352d..41431e8f6b 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -65,19 +65,14 @@ func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid } func decodeBlock(block blocks.Block) (ipld.Node, error) { - data := block.RawData() innerNodeSize, leafNodeSize := (nmtHashSize)*2, NamespaceSize+consts.ShareSize - switch len(data) { + switch len(block.RawData()) { default: return nil, fmt.Errorf("ipld: wrong sized data carried in block") case innerNodeSize: - return &nmtNode{ - cid: block.Cid(), - l: data[:nmtHashSize], - r: data[nmtHashSize:], - }, nil + return &nmtNode{block}, nil case leafNodeSize: - return &nmtLeafNode{block}, nil + return &nmtLeafNode{nmtNode{block}}, nil } } @@ -85,45 +80,27 @@ var _ ipld.Node = (*nmtNode)(nil) var _ ipld.Node = (*nmtLeafNode)(nil) type nmtNode struct { - cid cid.Cid - l, r []byte -} - -func NewNMTNode(id cid.Cid, l, r []byte) ipld.Node { - return nmtNode{id, l, r} -} - -func (n nmtNode) RawData() []byte { - return append(n.l, n.r...) -} - -func (n nmtNode) Cid() cid.Cid { - return n.cid -} - -func (n nmtNode) String() string { - return fmt.Sprintf(` -node { - hash: %x, - l: %x, - r: %x" -}`, n.cid.Hash(), n.l, n.r) + blocks.Block } -func (n nmtNode) Loggable() map[string]interface{} { - return nil +func newNMTNode(id cid.Cid, data []byte) nmtNode { + b, err := blocks.NewBlockWithCid(data, id) + if err != nil { + panic(fmt.Sprintf("wrong hash for block, cid: %s", id)) + } + return nmtNode{b} } func (n nmtNode) Resolve(path []string) (interface{}, []string, error) { switch path[0] { case "0": - left, err := CidFromNamespacedSha256(n.l) + left, err := CidFromNamespacedSha256(n.left()) if err != nil { return nil, nil, err } return &ipld.Link{Cid: left}, path[1:], nil case "1": - right, err := CidFromNamespacedSha256(n.r) + right, err := CidFromNamespacedSha256(n.right()) if err != nil { return nil, nil, err } @@ -159,21 +136,14 @@ func (n nmtNode) ResolveLink(path []string) (*ipld.Link, []string, error) { } func (n nmtNode) Copy() ipld.Node { - l := make([]byte, len(n.l)) - copy(l, n.l) - r := make([]byte, len(n.r)) - copy(r, n.r) - - return &nmtNode{ - cid: n.cid, - l: l, - r: r, - } + d := make([]byte, len(n.RawData())) + copy(d, n.RawData()) + return newNMTNode(n.Cid(), d) } func (n nmtNode) Links() []*ipld.Link { - leftCid := MustCidFromNamespacedSha256(n.l) - rightCid := MustCidFromNamespacedSha256(n.r) + leftCid := MustCidFromNamespacedSha256(n.left()) + rightCid := MustCidFromNamespacedSha256(n.right()) return []*ipld.Link{{Cid: leftCid}, {Cid: rightCid}} } @@ -186,16 +156,20 @@ func (n nmtNode) Size() (uint64, error) { return 0, nil } +func (n nmtNode) left() []byte { + return n.RawData()[:nmtHashSize] +} + +func (n nmtNode) right() []byte { + return n.RawData()[nmtHashSize:] +} + type nmtLeafNode struct { - blocks.Block + nmtNode } -func NewNMTLeafNode(id cid.Cid, data []byte) ipld.Node { - b, err := blocks.NewBlockWithCid(data, id) - if err != nil { - log.Warn("wrong hash for block", "cid", id) - } - return &nmtLeafNode{b} +func newNMTLeafNode(id cid.Cid, data []byte) nmtLeafNode { + return nmtLeafNode{newNMTNode(id, data)} } func (l nmtLeafNode) Resolve(path []string) (interface{}, []string, error) { @@ -206,35 +180,10 @@ func (l nmtLeafNode) Tree(_path string, _depth int) []string { return nil } -func (l nmtLeafNode) ResolveLink(path []string) (*ipld.Link, []string, error) { - obj, rest, err := l.Resolve(path) - if err != nil { - return nil, nil, err - } - - lnk, ok := obj.(*ipld.Link) - if !ok { - return nil, nil, errors.New("was not a link") - } - return lnk, rest, nil -} - -func (l nmtLeafNode) Copy() ipld.Node { - panic("implement me") -} - func (l nmtLeafNode) Links() []*ipld.Link { return nil } -func (l nmtLeafNode) Stat() (*ipld.NodeStat, error) { - return &ipld.NodeStat{}, nil -} - -func (l nmtLeafNode) Size() (uint64, error) { - return 0, nil -} - // CidFromNamespacedSha256 uses a hash from an nmt tree to create a CID func CidFromNamespacedSha256(namespacedHash []byte) (cid.Cid, error) { if got, want := len(namespacedHash), nmtHashSize; got != want { diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index c88164a800..19cc28d751 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -39,10 +39,10 @@ func (n *NmtNodeAdder) Visit(hash []byte, children ...[]byte) { switch len(children) { case 1: if n.leaves.Visit(id) { - n.err = n.add.Add(n.ctx, NewNMTLeafNode(id, children[0])) + n.err = n.add.Add(n.ctx, newNMTLeafNode(id, children[0])) } case 2: - n.err = n.add.Add(n.ctx, NewNMTNode(id, children[0], children[1])) + n.err = n.add.Add(n.ctx, newNMTNode(id, append(children[0], children[1]...))) default: panic("expected a binary tree") } From 7fcb0f38d40b6c3c95080e8d19fc5f82b8691b75 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 14 Oct 2022 14:07:05 +0300 Subject: [PATCH 0137/1008] - remove unnecessary nmtLeafNode type --- share/ipld/nmt.go | 121 ++++++++++------------------------------ share/ipld/nmt_adder.go | 2 +- 2 files changed, 30 insertions(+), 93 deletions(-) diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 41431e8f6b..a4196c6af3 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -17,7 +17,6 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/nmt" ) var ( @@ -39,6 +38,12 @@ const ( // nmtHashSize is the size of a digest created by an NMT in bytes. nmtHashSize = 2*appconsts.NamespaceSize + sha256.Size + // innerNodeSize is the size of data in inner nodes. + innerNodeSize = nmtHashSize * 2 + + // leafNodeSize is the size of data in leaf nodes. + leafNodeSize = NamespaceSize + appconsts.ShareSize + // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. MaxSquareSize = appconsts.MaxSquareSize @@ -61,24 +66,9 @@ func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid return nil, err } - return decodeBlock(block) + return nmtNode{Block: block}, nil } -func decodeBlock(block blocks.Block) (ipld.Node, error) { - innerNodeSize, leafNodeSize := (nmtHashSize)*2, NamespaceSize+consts.ShareSize - switch len(block.RawData()) { - default: - return nil, fmt.Errorf("ipld: wrong sized data carried in block") - case innerNodeSize: - return &nmtNode{block}, nil - case leafNodeSize: - return &nmtLeafNode{nmtNode{block}}, nil - } -} - -var _ ipld.Node = (*nmtNode)(nil) -var _ ipld.Node = (*nmtLeafNode)(nil) - type nmtNode struct { blocks.Block } @@ -86,53 +76,9 @@ type nmtNode struct { func newNMTNode(id cid.Cid, data []byte) nmtNode { b, err := blocks.NewBlockWithCid(data, id) if err != nil { - panic(fmt.Sprintf("wrong hash for block, cid: %s", id)) + panic(fmt.Sprintf("wrong hash for block, cid: %s", id.String())) } - return nmtNode{b} -} - -func (n nmtNode) Resolve(path []string) (interface{}, []string, error) { - switch path[0] { - case "0": - left, err := CidFromNamespacedSha256(n.left()) - if err != nil { - return nil, nil, err - } - return &ipld.Link{Cid: left}, path[1:], nil - case "1": - right, err := CidFromNamespacedSha256(n.right()) - if err != nil { - return nil, nil, err - } - return &ipld.Link{Cid: right}, path[1:], nil - default: - return nil, nil, errors.New("invalid path for inner node") - } -} - -func (n nmtNode) Tree(path string, depth int) []string { - if path != "" || depth != -1 { - panic("proper tree not yet implemented") - } - - return []string{ - "0", - "1", - } -} - -func (n nmtNode) ResolveLink(path []string) (*ipld.Link, []string, error) { - obj, rest, err := n.Resolve(path) - if err != nil { - return nil, nil, err - } - - lnk, ok := obj.(*ipld.Link) - if !ok { - return nil, nil, errors.New("was not a link") - } - - return lnk, rest, nil + return nmtNode{Block: b} } func (n nmtNode) Copy() ipld.Node { @@ -142,46 +88,37 @@ func (n nmtNode) Copy() ipld.Node { } func (n nmtNode) Links() []*ipld.Link { - leftCid := MustCidFromNamespacedSha256(n.left()) - rightCid := MustCidFromNamespacedSha256(n.right()) - - return []*ipld.Link{{Cid: leftCid}, {Cid: rightCid}} -} - -func (n nmtNode) Stat() (*ipld.NodeStat, error) { - return &ipld.NodeStat{}, nil -} - -func (n nmtNode) Size() (uint64, error) { - return 0, nil -} - -func (n nmtNode) left() []byte { - return n.RawData()[:nmtHashSize] -} + switch len(n.RawData()) { + default: + panic(fmt.Sprintf("unexpected size %v", len(n.RawData()))) + case innerNodeSize: + leftCid := MustCidFromNamespacedSha256(n.RawData()[:nmtHashSize]) + rightCid := MustCidFromNamespacedSha256(n.RawData()[nmtHashSize:]) -func (n nmtNode) right() []byte { - return n.RawData()[nmtHashSize:] + return []*ipld.Link{{Cid: leftCid}, {Cid: rightCid}} + case leafNodeSize: + return nil + } } -type nmtLeafNode struct { - nmtNode +func (n nmtNode) Resolve(path []string) (interface{}, []string, error) { + panic("method not implemented") } -func newNMTLeafNode(id cid.Cid, data []byte) nmtLeafNode { - return nmtLeafNode{newNMTNode(id, data)} +func (n nmtNode) Tree(path string, depth int) []string { + panic("method not implemented") } -func (l nmtLeafNode) Resolve(path []string) (interface{}, []string, error) { - return nil, nil, errors.New("invalid path for leaf node") +func (n nmtNode) ResolveLink(path []string) (*ipld.Link, []string, error) { + panic("method not implemented") } -func (l nmtLeafNode) Tree(_path string, _depth int) []string { - return nil +func (n nmtNode) Stat() (*ipld.NodeStat, error) { + panic("method not implemented") } -func (l nmtLeafNode) Links() []*ipld.Link { - return nil +func (n nmtNode) Size() (uint64, error) { + panic("method not implemented") } // CidFromNamespacedSha256 uses a hash from an nmt tree to create a CID diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index 19cc28d751..0925a8094c 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -39,7 +39,7 @@ func (n *NmtNodeAdder) Visit(hash []byte, children ...[]byte) { switch len(children) { case 1: if n.leaves.Visit(id) { - n.err = n.add.Add(n.ctx, newNMTLeafNode(id, children[0])) + n.err = n.add.Add(n.ctx, newNMTNode(id, children[0])) } case 2: n.err = n.add.Add(n.ctx, newNMTNode(id, append(children[0], children[1]...))) From d23961680eb3e47993b0f0196bd2a995105b1542 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 17 Oct 2022 11:31:35 +0200 Subject: [PATCH 0138/1008] fix: broken shutdown for services on fraud proofs receival (#1220) Closes #1205 Co-authored-by: Wondertan --- nodebuilder/daser/module.go | 6 ++---- nodebuilder/header/module.go | 6 ++---- nodebuilder/module.go | 5 ++++- nodebuilder/p2p/bitswap.go | 7 ++----- nodebuilder/p2p/pubsub.go | 5 +---- nodebuilder/p2p/routing.go | 3 +-- nodebuilder/state/module.go | 6 ++---- nodebuilder/tests/fraud_test.go | 11 +++++++---- state/core_access.go | 3 ++- 9 files changed, 23 insertions(+), 29 deletions(-) diff --git a/nodebuilder/daser/module.go b/nodebuilder/daser/module.go index 15f37cee4f..7e2e74dd94 100644 --- a/nodebuilder/daser/module.go +++ b/nodebuilder/daser/module.go @@ -7,7 +7,6 @@ import ( "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/libs/fxutil" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -19,9 +18,8 @@ func ConstructModule(tp node.Type) fx.Option { "daser", fx.Provide(fx.Annotate( NewDASer, - fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, das *das.DASer) error { - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, das *das.DASer) error { + return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, das.Start, das.Stop) }), fx.OnStop(func(ctx context.Context, das *das.DASer) error { diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index f9d71f5ed1..34eb158c72 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -11,7 +11,6 @@ import ( "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/header/sync" - "github.com/celestiaorg/celestia-node/libs/fxutil" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/params" @@ -43,7 +42,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), fx.Provide(fx.Annotate( newSyncer, - fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, syncer *sync.Syncer) error { + fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, syncer *sync.Syncer) error { syncerStartFunc := func(ctx context.Context) error { err := syncer.Start(ctx) switch err { @@ -55,8 +54,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { } return nil } - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, syncerStartFunc, syncer.Stop) }), fx.OnStop(func(ctx context.Context, syncer *sync.Syncer) error { diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 85909e88c7..1e48777b2b 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -5,6 +5,7 @@ import ( "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/daser" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -21,7 +22,9 @@ func ConstructModule(tp node.Type, cfg *Config, store Store) fx.Option { baseComponents := fx.Options( fx.Provide(params.DefaultNetwork), fx.Provide(params.BootstrappersFor), - fx.Provide(context.Background), + fx.Provide(func(lc fx.Lifecycle) context.Context { + return fxutil.WithLifecycle(context.Background(), lc) + }), fx.Supply(cfg), fx.Supply(store.Config), fx.Provide(store.Datastore), diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index c4b3007663..471cd40b29 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -14,7 +14,6 @@ import ( routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fxutil" nparams "github.com/celestiaorg/celestia-node/params" ) @@ -29,9 +28,8 @@ const ( // DataExchange provides a constructor for IPFS block's DataExchange over BitSwap. func DataExchange(params bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { - ctx := fxutil.WithLifecycle(params.Ctx, params.Lc) bs, err := blockstore.CachedBlockstore( - ctx, + params.Ctx, blockstore.NewBlockstore(params.Ds), blockstore.CacheOpts{ HasBloomFilterSize: defaultBloomFilterSize, @@ -44,7 +42,7 @@ func DataExchange(params bitSwapParams) (exchange.Interface, blockstore.Blocksto } prefix := protocol.ID(fmt.Sprintf("/celestia/%s", params.Net)) return bitswap.New( - ctx, + params.Ctx, network.NewFromIpfsHost(params.Host, &routinghelpers.Null{}, network.Prefix(prefix)), bs, bitswap.ProvideEnabled(false), @@ -60,7 +58,6 @@ type bitSwapParams struct { Ctx context.Context Net nparams.Network - Lc fx.Lifecycle Host host.Host Ds datastore.Batching } diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 8fc49348f8..01b4c61b9b 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -8,8 +8,6 @@ import ( pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" "go.uber.org/fx" "golang.org/x/crypto/blake2b" - - "github.com/celestiaorg/celestia-node/libs/fxutil" ) // PubSub provides a constructor for PubSub protocol with GossipSub routing. @@ -34,7 +32,7 @@ func PubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { } return pubsub.NewGossipSub( - fxutil.WithLifecycle(params.Ctx, params.Lc), + params.Ctx, params.Host, opts..., ) @@ -49,6 +47,5 @@ type pubSubParams struct { fx.In Ctx context.Context - Lc fx.Lifecycle Host host.Host } diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index 46a622a4e8..2064297a8d 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -11,7 +11,6 @@ import ( dht "github.com/libp2p/go-libp2p-kad-dht" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fxutil" nparams "github.com/celestiaorg/celestia-node/params" ) @@ -42,7 +41,7 @@ func PeerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) ) } - d, err := dht.New(fxutil.WithLifecycle(params.Ctx, params.Lc), params.Host, opts...) + d, err := dht.New(params.Ctx, params.Host, opts...) if err != nil { return nil, err } diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index 678c9d0779..99d7d1ddac 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -7,7 +7,6 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/libs/fxutil" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/state" @@ -27,9 +26,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(Keyring), fx.Provide(fx.Annotate( CoreAccessor, - fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, ca *state.CoreAccessor) error { - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, ca.Start, ca.Stop) + fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, ca *state.CoreAccessor) error { + return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, ca.Start, ca.Stop) }), fx.OnStop(func(ctx context.Context, ca *state.CoreAccessor) error { return ca.Stop(ctx) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 7b1f6cc899..41f91244a2 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -107,7 +107,7 @@ func TestFraudProofSyncing(t *testing.T) { store := nodebuilder.MockStore(t, cfg) bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(header.FraudMaker(t, 10))) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) err := bridge.Start(ctx) @@ -123,21 +123,24 @@ func TestFraudProofSyncing(t *testing.T) { lightCfg := nodebuilder.DefaultConfig(node.Light) lightCfg.P2P.RoutingTableRefreshPeriod = defaultTimeInterval lightCfg.Share.DiscoveryInterval = defaultTimeInterval - ln := sw.NewNodeWithStore(node.Light, nodebuilder.MockStore(t, lightCfg), - nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr})) + lightCfg.Header.TrustedPeers = append(lightCfg.Header.TrustedPeers, addrs[0].String()) + ln := sw.NewNodeWithStore(node.Light, nodebuilder.MockStore(t, lightCfg)) require.NoError(t, full.Start(ctx)) + require.NoError(t, ln.Start(ctx)) subsFn, err := full.FraudServ.Subscribe(fraud.BadEncoding) require.NoError(t, err) defer subsFn.Cancel() _, err = subsFn.Proof(ctx) require.NoError(t, err) - require.NoError(t, ln.Start(ctx)) // internal subscription for the fraud proof is done in order to ensure that light node // receives the BEFP. subsLn, err := ln.FraudServ.Subscribe(fraud.BadEncoding) require.NoError(t, err) + + err = ln.Host.Connect(ctx, *host.InfoFromHost(full.Host)) + require.NoError(t, err) _, err = subsLn.Proof(ctx) require.NoError(t, err) subsLn.Cancel() diff --git a/state/core_access.go b/state/core_access.go index 617d8c3081..cf19ec3de8 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -98,7 +98,8 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { func (ca *CoreAccessor) Stop(context.Context) error { defer ca.cancel() if ca.coreConn == nil { - return fmt.Errorf("core-access: no connection found to close") + log.Warn("no connection found to close") + return nil } // close out core connection err := ca.coreConn.Close() From ef043156385d860c4a0252a386ae818d58970fe2 Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Tue, 18 Oct 2022 13:01:28 -0400 Subject: [PATCH 0139/1008] .github: add triggering on versioned tags (#1237) Small update to include running the CI on versioned tags, i.e. any tag starting with a `v`. This allows for tagging release candidates manually. --- .github/workflows/ci_release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 110ab42cc8..b18f9bc4aa 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -3,6 +3,9 @@ on: push: branches: - main + # Trigger on version tags + tags: + - "v*" pull_request: workflow_dispatch: # Inputs the workflow accepts. From 6340d4566bf48b5b2d3bbfcab22ecd86b677a82e Mon Sep 17 00:00:00 2001 From: Hoyt Ren Date: Wed, 19 Oct 2022 16:30:39 +0800 Subject: [PATCH 0140/1008] refactor(rpc): move sanity check logic from rpc handlers to implementations. (#1213) I see only 1 implementation. Tell me if here are more. Close #998 Co-authored-by: Hlib Kanunnikov --- service/rpc/state.go | 21 --------------------- state/core_access.go | 26 +++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/service/rpc/state.go b/service/rpc/state.go index 86a9592c3f..a751dda47e 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -31,7 +31,6 @@ const addrKey = "address" var ( ErrInvalidAddressFormat = errors.New("address must be a valid account or validator address") ErrMissingAddress = errors.New("address not specified") - ErrInvalidAmount = errors.New("amount must be greater than zero") ) // submitTxRequest represents a request to submit a raw transaction @@ -202,10 +201,6 @@ func (h *Handler) handleTransfer(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, transferEndpoint, err) return } - if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, transferEndpoint, ErrInvalidAmount) - return - } addr, err := types.AccAddressFromBech32(req.To) if err != nil { // first check if it is a validator address and can be converted @@ -241,10 +236,6 @@ func (h *Handler) handleDelegation(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, delegationEndpoint, err) return } - if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, delegationEndpoint, ErrInvalidAmount) - return - } addr, err := types.ValAddressFromBech32(req.To) if err != nil { writeError(w, http.StatusBadRequest, delegationEndpoint, err) @@ -275,10 +266,6 @@ func (h *Handler) handleUndelegation(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, undelegationEndpoint, err) return } - if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, undelegationEndpoint, ErrInvalidAmount) - return - } addr, err := types.ValAddressFromBech32(req.From) if err != nil { writeError(w, http.StatusBadRequest, undelegationEndpoint, err) @@ -309,10 +296,6 @@ func (h *Handler) handleCancelUnbonding(w http.ResponseWriter, r *http.Request) writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, err) return } - if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, ErrInvalidAmount) - return - } addr, err := types.ValAddressFromBech32(req.From) if err != nil { writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, err) @@ -343,10 +326,6 @@ func (h *Handler) handleRedelegation(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) return } - if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, ErrInvalidAmount) - return - } srcAddr, err := types.ValAddressFromBech32(req.From) if err != nil { writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) diff --git a/state/core_access.go b/state/core_access.go index cf19ec3de8..f6d16c7c03 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -2,6 +2,7 @@ package state import ( "context" + "errors" "fmt" "time" @@ -25,7 +26,10 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -var log = logging.Logger("state") +var ( + log = logging.Logger("state") + ErrInvalidAmount = errors.New("state: amount must be greater than zero") +) // CoreAccessor implements service over a gRPC connection // with a celestia-core node. @@ -238,6 +242,10 @@ func (ca *CoreAccessor) Transfer( amount Int, gasLim uint64, ) (*TxResponse, error) { + if amount.IsNil() || amount.Int64() <= 0 { + return nil, ErrInvalidAmount + } + from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err @@ -258,6 +266,10 @@ func (ca *CoreAccessor) CancelUnbondingDelegation( height Int, gasLim uint64, ) (*TxResponse, error) { + if amount.IsNil() || amount.Int64() <= 0 { + return nil, ErrInvalidAmount + } + from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err @@ -278,6 +290,10 @@ func (ca *CoreAccessor) BeginRedelegate( amount Int, gasLim uint64, ) (*TxResponse, error) { + if amount.IsNil() || amount.Int64() <= 0 { + return nil, ErrInvalidAmount + } + from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err @@ -297,6 +313,10 @@ func (ca *CoreAccessor) Undelegate( amount Int, gasLim uint64, ) (*TxResponse, error) { + if amount.IsNil() || amount.Int64() <= 0 { + return nil, ErrInvalidAmount + } + from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err @@ -316,6 +336,10 @@ func (ca *CoreAccessor) Delegate( amount Int, gasLim uint64, ) (*TxResponse, error) { + if amount.IsNil() || amount.Int64() <= 0 { + return nil, ErrInvalidAmount + } + from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err From fdd8b30a584be403a23d73869020aa89c949641b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:29:56 +0200 Subject: [PATCH 0141/1008] refactor(share/ipld): Use consts for node type determination (#1244) Self-explanatory --- share/ipld/namespace_hasher.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/share/ipld/namespace_hasher.go b/share/ipld/namespace_hasher.go index 8ea6a8600c..c92f766903 100644 --- a/share/ipld/namespace_hasher.go +++ b/share/ipld/namespace_hasher.go @@ -7,7 +7,6 @@ import ( "github.com/minio/sha256-simd" mhcore "github.com/multiformats/go-multihash/core" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/nmt" ) @@ -29,7 +28,7 @@ type namespaceHasher struct { // defaultHasher constructs the namespaceHasher with default configuration func defaultHasher() *namespaceHasher { - return &namespaceHasher{Hasher: nmt.NewNmtHasher(sha256.New(), nmt.DefaultNamespaceIDLen, true)} + return &namespaceHasher{Hasher: nmt.NewNmtHasher(sha256.New(), NamespaceSize, true)} } // Write writes the namespaced data to be hashed. @@ -41,8 +40,7 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { return 0, fmt.Errorf("ipld: only one write to hasher is allowed") } - ln, nln, hln := len(data), int(n.NamespaceLen), n.Hash.Size() - innerNodeSize, leafNodeSize := (nln*2+hln)*2, nln+appconsts.ShareSize + ln := len(data) switch ln { default: return 0, fmt.Errorf("ipld: wrong sized data written to the hasher, len: %v", ln) @@ -53,7 +51,7 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { } n.data = data - return len(n.data), nil + return ln, nil } // Sum computes the hash. From f1c25e45f444e6ffd73e18d29e8ae40d1b9f8414 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 19 Oct 2022 17:47:58 +0200 Subject: [PATCH 0142/1008] fix: returning nil sum when hasher receives data with incorrect length (#1245) The nodes sync successfully now, even without explicitly handling the legacy case. Co-authored-by: Hlib Kanunnikov --- share/ipld/namespace_hasher.go | 7 +++++ share/ipld/namespace_hasher_test.go | 40 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/share/ipld/namespace_hasher.go b/share/ipld/namespace_hasher.go index c92f766903..b849b703e2 100644 --- a/share/ipld/namespace_hasher.go +++ b/share/ipld/namespace_hasher.go @@ -43,6 +43,7 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { ln := len(data) switch ln { default: + log.Warnf("unexpected data size: %d", ln) return 0, fmt.Errorf("ipld: wrong sized data written to the hasher, len: %v", ln) case innerNodeSize: n.tp = nmt.NodePrefix @@ -57,6 +58,12 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { // Sum computes the hash. // Does not append the given suffix and violating the interface. func (n *namespaceHasher) Sum([]byte) []byte { + // if n.data is empty, it hit the default case in Write. + // this will be seen by multihash.encodeHash, where it will be caught and an error will be returned. + if len(n.data) == 0 { + return nil + } + isLeafData := n.tp == nmt.LeafPrefix if isLeafData { return n.Hasher.HashLeaf(n.data) diff --git a/share/ipld/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go index 64286e503e..6bbabded29 100644 --- a/share/ipld/namespace_hasher_test.go +++ b/share/ipld/namespace_hasher_test.go @@ -56,3 +56,43 @@ func TestNamespaceHasherWrite(t *testing.T) { assert.Equal(t, 0, n) }) } + +func TestNamespaceHasherSum(t *testing.T) { + leafSize := appconsts.ShareSize + appconsts.NamespaceSize + innerSize := nmtHashSize * 2 + tt := []struct { + name string + expectedSize int + writtenSize int + }{ + { + "Leaf", + nmtHashSize, + leafSize, + }, + { + "Inner", + nmtHashSize, + innerSize, + }, + { + "ShortGarbage", + 0, + 13, + }, + { + "LongGarbage", + 0, + 500, + }, + } + + for _, ts := range tt { + t.Run("Success"+ts.name, func(t *testing.T) { + h := defaultHasher() + _, _ = h.Write(make([]byte, ts.writtenSize)) + sum := h.Sum(nil) + assert.Equal(t, len(sum), ts.expectedSize) + }) + } +} From dbaf227c8b35da13ee92b34e364cbf8deb461ff1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 19 Oct 2022 18:17:27 +0200 Subject: [PATCH 0143/1008] fix(state): `CoreAccessor` manages its own context (#1247) Resolves https://github.com/celestiaorg/celestia-node/issues/1248 and https://github.com/celestiaorg/celestia-node/issues/1246 --- nodebuilder/node_test.go | 6 ++++++ state/core_access.go | 16 ++++++++++++++-- state/core_access_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 state/core_access_test.go diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 2e6632a823..f37540349d 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -36,8 +36,14 @@ func TestLifecycle(t *testing.T) { err := node.Start(ctx) require.NoError(t, err) + // ensure the state service is running + require.False(t, node.StateServ.IsStopped()) + err = node.Stop(ctx) require.NoError(t, err) + + // ensure the state service is stopped + require.True(t, node.StateServ.IsStopped()) }) } } diff --git a/state/core_access.go b/state/core_access.go index f6d16c7c03..13c666d49a 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -73,10 +73,11 @@ func NewCoreAccessor( } func (ca *CoreAccessor) Start(ctx context.Context) error { - ca.ctx, ca.cancel = context.WithCancel(ctx) if ca.coreConn != nil { return fmt.Errorf("core-access: already connected to core endpoint") } + ca.ctx, ca.cancel = context.WithCancel(context.Background()) + // dial given celestia-core endpoint endpoint := fmt.Sprintf("%s:%s", ca.coreIP, ca.grpcPort) client, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -100,21 +101,32 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { } func (ca *CoreAccessor) Stop(context.Context) error { - defer ca.cancel() + if ca.cancel == nil { + log.Warn("core accessor already stopped") + return nil + } if ca.coreConn == nil { log.Warn("no connection found to close") return nil } + defer ca.cancelCtx() + // close out core connection err := ca.coreConn.Close() if err != nil { return err } + ca.coreConn = nil ca.queryCli = nil return nil } +func (ca *CoreAccessor) cancelCtx() { + ca.cancel() + ca.cancel = nil +} + func (ca *CoreAccessor) constructSignedTx( ctx context.Context, msg sdktypes.Msg, diff --git a/state/core_access_test.go b/state/core_access_test.go new file mode 100644 index 0000000000..2bd148edb6 --- /dev/null +++ b/state/core_access_test.go @@ -0,0 +1,33 @@ +package state + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLifecycle(t *testing.T) { + ca := NewCoreAccessor(nil, nil, "", "", "") + ctx, cancel := context.WithCancel(context.Background()) + // start the accessor + err := ca.Start(ctx) + require.NoError(t, err) + // ensure accessor isn't stopped + require.False(t, ca.IsStopped()) + // cancel the top level context (this should not affect the lifecycle of the + // accessor as it should manage its own internal context) + cancel() + // ensure accessor was unaffected by top-level context cancellation + require.False(t, ca.IsStopped()) + // stop the accessor + stopCtx, stopCancel := context.WithCancel(context.Background()) + t.Cleanup(stopCancel) + err = ca.Stop(stopCtx) + require.NoError(t, err) + // ensure accessor is stopped + require.True(t, ca.IsStopped()) + // ensure that stopping the accessor again does not return an error + err = ca.Stop(stopCtx) + require.NoError(t, err) +} From a1ca66536bbdce43a3710ed82511033182f3accd Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 19 Oct 2022 18:32:22 +0200 Subject: [PATCH 0144/1008] feat(blocksync): `share.WriteEDS` (#1139) Closes #1105 Co-authored-by: Viacheslav Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- go.mod | 1 + go.sum | 2 + share/eds/eds.go | 221 ++++++++++++++++++++++++++++++++++++++++ share/eds/eds_test.go | 155 ++++++++++++++++++++++++++++ share/ipld/nmt_adder.go | 17 ++++ 5 files changed, 396 insertions(+) create mode 100644 share/eds/eds.go create mode 100644 share/eds/eds_test.go diff --git a/go.mod b/go.mod index 6800ab44e1..8ac59d7494 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.7.0 + github.com/ipld/go-car v0.5.0 github.com/libp2p/go-libp2p v0.21.0 github.com/libp2p/go-libp2p-core v0.19.1 github.com/libp2p/go-libp2p-kad-dht v0.17.0 diff --git a/go.sum b/go.sum index 65760cbab8..9803632094 100644 --- a/go.sum +++ b/go.sum @@ -831,6 +831,8 @@ github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fG github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.7.0 h1:VyO6G4sbzX80K58N60cCaHsSsypbUNs1GjO5seGNsQ0= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= +github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= +github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= github.com/ipld/go-codec-dagpb v1.3.1 h1:yVNlWRQexCa54ln3MSIiUN++ItH7pdhBFhh0hSgZu1w= github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= diff --git a/share/eds/eds.go b/share/eds/eds.go new file mode 100644 index 0000000000..90622220c4 --- /dev/null +++ b/share/eds/eds.go @@ -0,0 +1,221 @@ +package eds + +import ( + "context" + "errors" + "fmt" + "io" + "math" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + blockstore "github.com/ipfs/go-ipfs-blockstore" + format "github.com/ipfs/go-ipld-format" + "github.com/ipld/go-car" + "github.com/ipld/go-car/util" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/wrapper" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/rsmt2d" +) + +var ErrEmptySquare = errors.New("share: importing empty data") + +// writingSession contains the components needed to write an EDS to a CARv1 file with our custom node order. +type writingSession struct { + eds *rsmt2d.ExtendedDataSquare + // store is an in-memory blockstore, used to cache the inner nodes (proofs) while we walk the nmt tree. + store blockstore.Blockstore + w io.Writer +} + +// WriteEDS writes the entire EDS into the given io.Writer as CARv1 file. +// This includes all shares in quadrant order, followed by all inner nodes of the NMT tree. +// Order: [ Carv1Header | Q1 | Q2 | Q3 | Q4 | inner nodes ] +// For more information about the header: https://ipld.io/specs/transport/car/carv1/#header +func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { + // 1. Reimport EDS. This is needed to traverse the NMT tree and cache the inner nodes (proofs) + writer, err := initializeWriter(ctx, eds, w) + if err != nil { + return fmt.Errorf("share: failure creating eds writer: %w", err) + } + + // 2. Creates and writes Carv1Header + // - Roots are the eds Row + Col roots + err = writer.writeHeader() + if err != nil { + return fmt.Errorf("share: failure writing carv1 header: %w", err) + } + + // 3. Iterates over shares in quadrant order via eds.GetCell + err = writer.writeQuadrants() + if err != nil { + return fmt.Errorf("share: failure writing shares: %w", err) + } + + // 4. Iterates over in-memory Blockstore and writes proofs to the CAR + err = writer.writeProofs(ctx) + if err != nil { + return fmt.Errorf("share: failure writing proofs: %w", err) + } + return nil +} + +// initializeWriter reimports the EDS into an in-memory blockstore in order to cache the proofs. +func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) (*writingSession, error) { + // we use an in-memory blockstore and an offline exchange + store := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) + bs := blockservice.New(store, nil) + // shares are extracted from the eds so that we can reimport them to traverse + shares := share.ExtractEDS(eds) + shareCount := len(shares) + if shareCount == 0 { + return nil, ErrEmptySquare + } + odsWidth := int(math.Sqrt(float64(shareCount)) / 2) + // (shareCount*2) - (odsWidth*4) is the amount of inner nodes visited + batchAdder := ipld.NewNmtNodeAdder(ctx, bs, format.MaxSizeBatchOption(innerNodeBatchSize(shareCount, odsWidth))) + // this adder ignores leaves, so that they are not added to the store we iterate through in writeProofs + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(odsWidth), nmt.NodeVisitor(batchAdder.VisitInnerNodes)) + eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) + if err != nil { + return nil, fmt.Errorf("failure to recompute the extended data square: %w", err) + } + // compute roots + eds.RowRoots() + // commit the batch to DAG + err = batchAdder.Commit() + if err != nil { + return nil, fmt.Errorf("failure to commit the inner nodes to the dag: %w", err) + } + + return &writingSession{ + eds: eds, + store: store, + w: w, + }, nil +} + +// writeHeader creates a CarV1 header using the EDS's Row and Column roots as the list of DAG roots. +func (w *writingSession) writeHeader() error { + rootCids, err := rootsToCids(w.eds) + if err != nil { + return fmt.Errorf("failure getting root cids: %w", err) + } + + return car.WriteHeader(&car.CarHeader{ + Roots: rootCids, + Version: 1, + }, w.w) +} + +// writeQuadrants reorders the shares to quadrant order and writes them to the CARv1 file. +func (w *writingSession) writeQuadrants() error { + shares := quadrantOrder(w.eds) + for _, share := range shares { + cid, err := ipld.CidFromNamespacedSha256(nmt.Sha256Namespace8FlaggedLeaf(share)) + if err != nil { + return fmt.Errorf("failure to get cid from share: %w", err) + } + err = util.LdWrite(w.w, cid.Bytes(), share) + if err != nil { + return fmt.Errorf("failure to write share: %w", err) + } + } + return nil +} + +// writeProofs iterates over the in-memory blockstore's keys and writes all inner nodes to the CARv1 file. +func (w *writingSession) writeProofs(ctx context.Context) error { + // we only stored proofs to the store, so we can just iterate over them here without getting any leaves + proofs, err := w.store.AllKeysChan(ctx) + if err != nil { + return fmt.Errorf("failure to get all keys from the blockstore: %w", err) + } + for proofCid := range proofs { + node, err := w.store.Get(ctx, proofCid) + if err != nil { + return fmt.Errorf("failure to get proof from the blockstore: %w", err) + } + cid, err := ipld.CidFromNamespacedSha256(nmt.Sha256Namespace8FlaggedInner(node.RawData())) + if err != nil { + return fmt.Errorf("failure to get cid: %w", err) + } + err = util.LdWrite(w.w, cid.Bytes(), node.RawData()) + if err != nil { + return fmt.Errorf("failure to write proof to the car: %w", err) + } + } + return nil +} + +// quadrantOrder reorders the shares in the EDS to quadrant row-by-row order, prepending the respective namespace +// to the shares. +// e.g. [ Q1 R1 | Q1 R2 | Q1 R3 | Q1 R4 | Q2 R1 | Q2 R2 .... ] +func quadrantOrder(eds *rsmt2d.ExtendedDataSquare) [][]byte { + size := eds.Width() * eds.Width() + shares := make([][]byte, size) + + quadrantWidth := int(eds.Width() / 2) + quadrantSize := quadrantWidth * quadrantWidth + for i := 0; i < quadrantWidth; i++ { + for j := 0; j < quadrantWidth; j++ { + cells := getQuadrantCells(eds, uint(i), uint(j)) + innerOffset := i*quadrantWidth + j + for quadrant := 0; quadrant < 4; quadrant++ { + shares[(quadrant*quadrantSize)+innerOffset] = prependNamespace(quadrant, cells[quadrant]) + } + } + } + return shares +} + +// getQuadrantCells returns the cell of each EDS quadrant with the passed inner-quadrant coordinates +func getQuadrantCells(eds *rsmt2d.ExtendedDataSquare, i, j uint) [][]byte { + cells := make([][]byte, 4) + quadrantWidth := eds.Width() / 2 + cells[0] = eds.GetCell(i, j) + cells[1] = eds.GetCell(i, j+quadrantWidth) + cells[2] = eds.GetCell(i+quadrantWidth, j) + cells[3] = eds.GetCell(i+quadrantWidth, j+quadrantWidth) + return cells +} + +// prependNamespace adds the namespace to the passed share if in the first quadrant, +// otherwise it adds the ParitySharesNamespace to the beginning. +func prependNamespace(quadrant int, share []byte) []byte { + switch quadrant { + case 0: + return append(share[:8], share...) + case 1, 2, 3: + return append(appconsts.ParitySharesNamespaceID, share...) + default: + panic("invalid quadrant") + } +} + +// rootsToCids converts the EDS's Row and Column roots to CIDs. +func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) { + var err error + roots := append(eds.RowRoots(), eds.ColRoots()...) + rootCids := make([]cid.Cid, len(roots)) + for i, r := range roots { + rootCids[i], err = ipld.CidFromNamespacedSha256(r) + if err != nil { + return nil, fmt.Errorf("failure to get cid from root: %w", err) + } + } + return rootCids, nil +} + +// innerNodeBatchSize calculates the total number of inner nodes in an EDS, +// to be flushed to the dagstore in a single write. +func innerNodeBatchSize(shareCount int, odsWidth int) int { + return (shareCount * 2) - (odsWidth * 4) +} diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go new file mode 100644 index 0000000000..00552b475b --- /dev/null +++ b/share/eds/eds_test.go @@ -0,0 +1,155 @@ +package eds + +import ( + "context" + "os" + "testing" + + ds "github.com/ipfs/go-datastore" + blockstore "github.com/ipfs/go-ipfs-blockstore" + carv1 "github.com/ipld/go-car" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" +) + +func TestQuadrantOrder(t *testing.T) { + // TODO: add more test cases + nID := []byte{0, 0, 0, 0, 0, 0, 0, 0} + parity := append(appconsts.ParitySharesNamespaceID, nID...) //nolint + doubleNID := append(nID, nID...) //nolint + result, _ := rsmt2d.ComputeExtendedDataSquare([][]byte{ + append(nID, 1), append(nID, 2), + append(nID, 3), append(nID, 4), + }, rsmt2d.NewRSGF8Codec(), rsmt2d.NewDefaultTree) + // {{1}, {2}, {7}, {13}}, + // {{3}, {4}, {13}, {31}}, + // {{5}, {14}, {19}, {41}}, + // {{9}, {26}, {47}, {69}}, + require.Equal(t, + [][]byte{ + append(doubleNID, 1), append(doubleNID, 2), append(doubleNID, 3), append(doubleNID, 4), + append(parity, 7), append(parity, 13), append(parity, 13), append(parity, 31), + append(parity, 5), append(parity, 14), append(parity, 9), append(parity, 26), + append(parity, 19), append(parity, 41), append(parity, 47), append(parity, 69), + }, quadrantOrder(result), + ) +} + +func TestWriteEDS(t *testing.T) { + writeRandomEDS(t) +} + +func TestWriteEDSHeaderRoots(t *testing.T) { + eds := writeRandomEDS(t) + f := openWrittenEDS(t) + defer f.Close() + + reader, err := carv1.NewCarReader(f) + require.NoError(t, err, "error creating car reader") + roots, err := rootsToCids(eds) + require.NoError(t, err, "error converting roots to cids") + require.Equal(t, roots, reader.Header.Roots) +} + +func TestWriteEDSStartsWithLeaves(t *testing.T) { + eds := writeRandomEDS(t) + f := openWrittenEDS(t) + defer f.Close() + + reader, err := carv1.NewCarReader(f) + require.NoError(t, err, "error creating car reader") + block, err := reader.Next() + require.NoError(t, err, "error getting first block") + + require.Equal(t, block.RawData()[ipld.NamespaceSize:], eds.GetCell(0, 0)) +} + +func TestWriteEDSIncludesRoots(t *testing.T) { + writeRandomEDS(t) + f := openWrittenEDS(t) + defer f.Close() + + bs := blockstore.NewBlockstore(ds.NewMapDatastore()) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + loaded, err := carv1.LoadCar(ctx, bs, f) + require.NoError(t, err, "error loading car file") + for _, root := range loaded.Roots { + ok, err := bs.Has(context.Background(), root) + require.NoError(t, err, "error checking if blockstore has root") + require.True(t, ok, "blockstore does not have root") + } +} + +func TestWriteEDSInQuadrantOrder(t *testing.T) { + eds := writeRandomEDS(t) + f := openWrittenEDS(t) + defer f.Close() + + reader, err := carv1.NewCarReader(f) + require.NoError(t, err, "error creating car reader") + + shares := quadrantOrder(eds) + for i := 0; i < len(shares); i++ { + block, err := reader.Next() + require.NoError(t, err, "error getting block") + require.Equal(t, block.RawData(), shares[i]) + } +} + +// TestInnerNodeBatchSize verifies that the number of unique inner nodes is equal to ipld.BatchSize - shareCount. +func TestInnerNodeBatchSize(t *testing.T) { + tests := []struct { + name string + origWidth int + }{ + {"2", 2}, + {"4", 4}, + {"8", 8}, + {"16", 16}, + {"32", 32}, + // {"64", 64}, // test case too large for CI with race detector + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extendedWidth := tt.origWidth * 2 + shareCount := extendedWidth * extendedWidth + assert.Equalf( + t, + innerNodeBatchSize(shareCount, tt.origWidth), + ipld.BatchSize(extendedWidth)-shareCount, + "batchSize(%v)", extendedWidth, + ) + }) + } +} + +func writeRandomEDS(t *testing.T) *rsmt2d.ExtendedDataSquare { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + tmpDir := t.TempDir() + err := os.Chdir(tmpDir) + require.NoError(t, err, "error changing to the temporary test directory") + f, err := os.OpenFile("test.car", os.O_WRONLY|os.O_CREATE, 0600) + require.NoError(t, err, "error opening file") + + eds := share.RandEDS(t, 4) + err = WriteEDS(ctx, eds, f) + require.NoError(t, err, "error writing EDS to file") + t.Cleanup(cancel) + f.Close() + return eds +} + +func openWrittenEDS(t *testing.T) *os.File { + t.Helper() + f, err := os.OpenFile("test.car", os.O_RDONLY, 0600) + require.NoError(t, err, "error opening file") + return f +} diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index 0925a8094c..6db890a228 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -48,6 +48,23 @@ func (n *NmtNodeAdder) Visit(hash []byte, children ...[]byte) { } } +// VisitInnerNodes is a NodeVisitor that does not store leaf nodes to the blockservice. +func (n *NmtNodeAdder) VisitInnerNodes(hash []byte, children ...[]byte) { + if n.err != nil { + return // protect from further visits if there is an error + } + + id := MustCidFromNamespacedSha256(hash) + switch len(children) { + case 1: + break + case 2: + n.err = n.add.Add(n.ctx, newNMTNode(id, append(children[0], children[1]...))) + default: + panic("expected a binary tree") + } +} + // Commit checks for errors happened during Visit and if absent commits data to inner Batch. func (n *NmtNodeAdder) Commit() error { if n.err != nil { From d08a61413d592a16cc7c36717c61c27676539b8b Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 20 Oct 2022 13:42:44 +0200 Subject: [PATCH 0145/1008] feat(blocksync): `share.ReadEDS` (#1158) Closes #1104 --- share/eds/eds.go | 75 +++++++++++++++++++++------ share/eds/eds_test.go | 47 ++++++++++++++++- share/eds/testdata/example-root.json | 22 ++++++++ share/eds/testdata/example.car | Bin 0 -> 38963 bytes 4 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 share/eds/testdata/example-root.json create mode 100644 share/eds/testdata/example.car diff --git a/share/eds/eds.go b/share/eds/eds.go index 90622220c4..0f0355e75d 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -1,6 +1,7 @@ package eds import ( + "bytes" "context" "errors" "fmt" @@ -17,6 +18,7 @@ import ( "github.com/ipld/go-car/util" "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share" @@ -43,26 +45,26 @@ func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) // 1. Reimport EDS. This is needed to traverse the NMT tree and cache the inner nodes (proofs) writer, err := initializeWriter(ctx, eds, w) if err != nil { - return fmt.Errorf("share: failure creating eds writer: %w", err) + return fmt.Errorf("share: creating eds writer: %w", err) } // 2. Creates and writes Carv1Header // - Roots are the eds Row + Col roots err = writer.writeHeader() if err != nil { - return fmt.Errorf("share: failure writing carv1 header: %w", err) + return fmt.Errorf("share: writing carv1 header: %w", err) } // 3. Iterates over shares in quadrant order via eds.GetCell err = writer.writeQuadrants() if err != nil { - return fmt.Errorf("share: failure writing shares: %w", err) + return fmt.Errorf("share: writing shares: %w", err) } // 4. Iterates over in-memory Blockstore and writes proofs to the CAR err = writer.writeProofs(ctx) if err != nil { - return fmt.Errorf("share: failure writing proofs: %w", err) + return fmt.Errorf("share: writing proofs: %w", err) } return nil } @@ -85,14 +87,14 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(odsWidth), nmt.NodeVisitor(batchAdder.VisitInnerNodes)) eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) if err != nil { - return nil, fmt.Errorf("failure to recompute the extended data square: %w", err) + return nil, fmt.Errorf("recomputing data square: %w", err) } // compute roots eds.RowRoots() // commit the batch to DAG err = batchAdder.Commit() if err != nil { - return nil, fmt.Errorf("failure to commit the inner nodes to the dag: %w", err) + return nil, fmt.Errorf("committing inner nodes to the dag: %w", err) } return &writingSession{ @@ -106,7 +108,7 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. func (w *writingSession) writeHeader() error { rootCids, err := rootsToCids(w.eds) if err != nil { - return fmt.Errorf("failure getting root cids: %w", err) + return fmt.Errorf("getting root cids: %w", err) } return car.WriteHeader(&car.CarHeader{ @@ -121,11 +123,11 @@ func (w *writingSession) writeQuadrants() error { for _, share := range shares { cid, err := ipld.CidFromNamespacedSha256(nmt.Sha256Namespace8FlaggedLeaf(share)) if err != nil { - return fmt.Errorf("failure to get cid from share: %w", err) + return fmt.Errorf("getting cid from share: %w", err) } err = util.LdWrite(w.w, cid.Bytes(), share) if err != nil { - return fmt.Errorf("failure to write share: %w", err) + return fmt.Errorf("writing share to file: %w", err) } } return nil @@ -136,20 +138,20 @@ func (w *writingSession) writeProofs(ctx context.Context) error { // we only stored proofs to the store, so we can just iterate over them here without getting any leaves proofs, err := w.store.AllKeysChan(ctx) if err != nil { - return fmt.Errorf("failure to get all keys from the blockstore: %w", err) + return fmt.Errorf("getting all keys from the blockstore: %w", err) } for proofCid := range proofs { node, err := w.store.Get(ctx, proofCid) if err != nil { - return fmt.Errorf("failure to get proof from the blockstore: %w", err) + return fmt.Errorf("getting proof from the blockstore: %w", err) } cid, err := ipld.CidFromNamespacedSha256(nmt.Sha256Namespace8FlaggedInner(node.RawData())) if err != nil { - return fmt.Errorf("failure to get cid: %w", err) + return fmt.Errorf("getting cid: %w", err) } err = util.LdWrite(w.w, cid.Bytes(), node.RawData()) if err != nil { - return fmt.Errorf("failure to write proof to the car: %w", err) + return fmt.Errorf("writing proof to the car: %w", err) } } return nil @@ -189,10 +191,11 @@ func getQuadrantCells(eds *rsmt2d.ExtendedDataSquare, i, j uint) [][]byte { // prependNamespace adds the namespace to the passed share if in the first quadrant, // otherwise it adds the ParitySharesNamespace to the beginning. +// TODO(@walldiss): this method will be obselete once the redundant namespace is removed func prependNamespace(quadrant int, share []byte) []byte { switch quadrant { case 0: - return append(share[:8], share...) + return append(share[:appconsts.NamespaceSize], share...) case 1, 2, 3: return append(appconsts.ParitySharesNamespaceID, share...) default: @@ -208,12 +211,54 @@ func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) { for i, r := range roots { rootCids[i], err = ipld.CidFromNamespacedSha256(r) if err != nil { - return nil, fmt.Errorf("failure to get cid from root: %w", err) + return nil, fmt.Errorf("getting cid from root: %w", err) } } return rootCids, nil } +// ReadEDS reads the first EDS quadrant (1/4) from an io.Reader CAR file. +// Only the first quadrant will be read, which represents the original data. +// The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS errors. +func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.ExtendedDataSquare, error) { + carReader, err := car.NewCarReader(r) + if err != nil { + return nil, fmt.Errorf("share: reading car file: %w", err) + } + + odsWidth := len(root.RowsRoots) / 2 + odsSquareSize := odsWidth * odsWidth + shares := make([][]byte, odsSquareSize) + // the first quadrant is stored directly after the header, + // so we can just read the first odsSquareSize blocks + for i := 0; i < odsSquareSize; i++ { + block, err := carReader.Next() + if err != nil { + return nil, fmt.Errorf("share: reading next car entry: %w", err) + } + // the stored first quadrant shares are wrapped with the namespace twice. + // we cut it off here, because it is added again while importing to the tree below + // TODO(@walldiss): remove redundant namespace + shares[i] = block.RawData()[appconsts.NamespaceSize:] + } + + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(odsWidth)) + eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) + if err != nil { + return nil, fmt.Errorf("share: computing eds: %w", err) + } + + newDah := da.NewDataAvailabilityHeader(eds) + if !bytes.Equal(newDah.Hash(), root.Hash()) { + return nil, fmt.Errorf( + "share: content integrity mismatch: imported root %s doesn't match expected root %s", + newDah.Hash(), + root.Hash(), + ) + } + return eds, nil +} + // innerNodeBatchSize calculates the total number of inner nodes in an EDS, // to be flushed to the dagstore in a single write. func innerNodeBatchSize(shareCount int, odsWidth int) int { diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 00552b475b..f7203283d7 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -2,6 +2,8 @@ package eds import ( "context" + "embed" + "encoding/json" "os" "testing" @@ -12,12 +14,19 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" ) +//go:embed "testdata/example-root.json" +var exampleRoot string + +//go:embed "testdata/example.car" +var f embed.FS + func TestQuadrantOrder(t *testing.T) { // TODO: add more test cases nID := []byte{0, 0, 0, 0, 0, 0, 0, 0} @@ -130,9 +139,46 @@ func TestInnerNodeBatchSize(t *testing.T) { } } +func TestReadWriteRoundtrip(t *testing.T) { + eds := writeRandomEDS(t) + dah := da.NewDataAvailabilityHeader(eds) + f := openWrittenEDS(t) + defer f.Close() + + loaded, err := ReadEDS(context.Background(), f, dah) + require.NoError(t, err, "error reading EDS from file") + require.Equal(t, eds.RowRoots(), loaded.RowRoots()) + require.Equal(t, eds.ColRoots(), loaded.ColRoots()) +} + +func TestReadEDS(t *testing.T) { + f, err := f.Open("testdata/example.car") + require.NoError(t, err, "error opening file") + + var dah da.DataAvailabilityHeader + err = json.Unmarshal([]byte(exampleRoot), &dah) + require.NoError(t, err, "error unmarshaling example root") + + loaded, err := ReadEDS(context.Background(), f, dah) + require.NoError(t, err, "error reading EDS from file") + require.Equal(t, dah.RowsRoots, loaded.RowRoots()) + require.Equal(t, dah.ColumnRoots, loaded.ColRoots()) +} + +func TestReadEDSContentIntegrityMismatch(t *testing.T) { + writeRandomEDS(t) + dah := da.NewDataAvailabilityHeader(share.RandEDS(t, 4)) + f := openWrittenEDS(t) + defer f.Close() + + _, err := ReadEDS(context.Background(), f, dah) + require.ErrorContains(t, err, "share: content integrity mismatch: imported root") +} + func writeRandomEDS(t *testing.T) *rsmt2d.ExtendedDataSquare { t.Helper() ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) tmpDir := t.TempDir() err := os.Chdir(tmpDir) require.NoError(t, err, "error changing to the temporary test directory") @@ -142,7 +188,6 @@ func writeRandomEDS(t *testing.T) *rsmt2d.ExtendedDataSquare { eds := share.RandEDS(t, 4) err = WriteEDS(ctx, eds, f) require.NoError(t, err, "error writing EDS to file") - t.Cleanup(cancel) f.Close() return eds } diff --git a/share/eds/testdata/example-root.json b/share/eds/testdata/example-root.json new file mode 100644 index 0000000000..4fd3bc6df8 --- /dev/null +++ b/share/eds/testdata/example-root.json @@ -0,0 +1,22 @@ +{ + "row_roots": [ + "Lxe8TwDLb6o/aVE1X1R953H/3/0Unde/sMZAsm9W6ZFVIrYJS6AggKB2nlARKxXb", + "TUmtZeDsUJOUZ4I++ZH3jDUN0fWsYM9ECodGf6bKcrzjtLKkaJ8cvItvvBCABj68", + "lN0KMDoWlnu0V5IHhlRI86wd3HaZ7oVAEI3eMU2IuYYnBBlOoWuG9KhxGEXLWKLi", + "14xd47ItWC/044aec4tNGjQpWR4ewA7tBuW4hIwUKD9zeKmiOt2i7vY9jCypMDqw", + "/////////////////////38Du2wPxZQDiqmHlsgfZfExVMxFTu+lYZxf+kghjytG", + "/////////////////////1Ha61C5yxaO2WDEBaP2a4JBMob9qyiVlZX4JTUNgdiP", + "/////////////////////2dLvdO8gZf0Rw7SblvEn1bxOHafppQebvvk0gwJRZyl", + "/////////////////////xbTzWEfFFv6Mke03N207ZNgRClHvGedfPUY+cVSMKJO" + ], + "column_roots": [ + "Lxe8TwDLb6rXjF3jsi1YL+25dK0eWn9KZCQcfZ3SA/7NiIaCfpd76WHM5u/CBseo", + "MoH41mlBWRHk4Opo/F4dg89Sdin6+Zht6UOF09EV7DQDAeXQJxjuUdn9u2nFR+n4", + "OsfXoZ3RTz7tXBJlIkg9HVoqp/QlSVJ3CN3x4PHVwVCYInEXGa7plRH18VGnbvXs", + "P2lRNV9Ufef044aec4tNGki2iTpjHvuLROiUx72q9vPiOIEXF7WjDGIZsbtdgZNy", + "/////////////////////x9A9aniTmETpcg2IuqcC3izwIBQSkujpw3HU6yLH5yD", + "/////////////////////xQ5w+0S5I7Xx9dReCsFi/4sObAXJ2h3F8VcgoV+2FZO", + "/////////////////////49Va1jgci1F9Y9v7kpxYfk0vmkQRo1KFa1U8WHrHnF1", + "/////////////////////+xa/2hklGNi8pmhMyZbz1i9rmsrH2JMrbhAX/b2tisJ" + ] +} \ No newline at end of file diff --git a/share/eds/testdata/example.car b/share/eds/testdata/example.car new file mode 100644 index 0000000000000000000000000000000000000000..13985c745039403b5e7de02cb5f2de0968d9a99f GIT binary patch literal 38963 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 u&}iW1fqx9!9e7|hU Date: Thu, 20 Oct 2022 13:43:23 +0200 Subject: [PATCH 0146/1008] fix: restricting PFD metrics to successful PFDs (#1255) Closes #1254 --- state/core_access.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/state/core_access.go b/state/core_access.go index 13c666d49a..bd8123468d 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -151,9 +151,13 @@ func (ca *CoreAccessor) SubmitPayForData( data []byte, gasLim uint64, ) (*TxResponse, error) { - ca.lastPayForData = time.Now().UnixMilli() - ca.payForDataCount++ - return payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim) + response, err := payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim) + // metrics should only be counted on a successful PFD tx + if response.Code == 0 && err == nil { + ca.lastPayForData = time.Now().UnixMilli() + ca.payForDataCount++ + } + return response, err } func (ca *CoreAccessor) AccountAddress(ctx context.Context) (Address, error) { From 2470699efbb29138f2d80fa6e9f036c6edb25c6b Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 20 Oct 2022 14:31:21 +0200 Subject: [PATCH 0147/1008] chore: gen less acounts to reduce test execution times (#1253) --- nodebuilder/tests/swamp/swamp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 5f776384fa..bfc42caaa8 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -67,8 +67,8 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { ctx := context.Background() // we create an arbitrary number of funded accounts - accounts := make([]string, 100) - for i := 0; i < 100; i++ { + accounts := make([]string, 10) + for i := range accounts { accounts[i] = tmrand.Str(9) } From 7dddcfc4808cdcd3325696a682327b0a91a238be Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 20 Oct 2022 18:58:03 +0200 Subject: [PATCH 0148/1008] chore(share/ipld): remove dead code (#1257) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/ipld/get.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/share/ipld/get.go b/share/ipld/get.go index 5a2f0cdcad..79ca64a342 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -203,7 +203,7 @@ func GetLeavesByNamespace( jobs := make(chan *job, (maxShares+1)/2) jobs <- &job{id: root, ctx: ctx} - var wg wrappedWaitGroup + var wg chanGroup wg.jobs = jobs wg.add(1) @@ -337,21 +337,18 @@ func GetProof( return GetProof(ctx, bGetter, root, proof, leaf, total) } -// wrappedWaitGroup is needed because waitgroups do not expose their internal counter, -// and we don't know in advance how many jobs we will have to wait for. -type wrappedWaitGroup struct { - wg sync.WaitGroup +// chanGroup implements an atomic wait group, closing a jobs chan +// when fully done. +type chanGroup struct { jobs chan *job counter int64 } -func (w *wrappedWaitGroup) add(count int64) { - w.wg.Add(int(count)) +func (w *chanGroup) add(count int64) { atomic.AddInt64(&w.counter, count) } -func (w *wrappedWaitGroup) done() { - w.wg.Done() +func (w *chanGroup) done() { numRemaining := atomic.AddInt64(&w.counter, -1) // Close channel if this job was the last one From 09b48a329399be0779bcb9a0fbfb4a6784b96138 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 24 Oct 2022 13:58:09 +0200 Subject: [PATCH 0149/1008] refactor(core/fetcher): Improve error messages for fetcher methods (#1263) Self-explanatory (found by @jbowen93) --- core/fetcher.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/fetcher.go b/core/fetcher.go index 5585a82c8c..62876ec175 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -34,7 +34,7 @@ func NewBlockFetcher(client Client) *BlockFetcher { func (f *BlockFetcher) GetBlockInfo(ctx context.Context, height *int64) (*types.Commit, *types.ValidatorSet, error) { commit, err := f.Commit(ctx, height) if err != nil { - return nil, nil, fmt.Errorf("core/fetcher: getting commit: %w", err) + return nil, nil, fmt.Errorf("core/fetcher: getting commit at height %d: %w", height, err) } // If a nil `height` is given as a parameter, there is a chance @@ -44,7 +44,7 @@ func (f *BlockFetcher) GetBlockInfo(ctx context.Context, height *int64) (*types. // prevent this potential inconsistency. valSet, err := f.ValidatorSet(ctx, &commit.Height) if err != nil { - return nil, nil, fmt.Errorf("core/fetcher: getting validator set: %w", err) + return nil, nil, fmt.Errorf("core/fetcher: getting validator set at height %d: %w", height, err) } return commit, valSet, nil @@ -58,7 +58,7 @@ func (f *BlockFetcher) GetBlock(ctx context.Context, height *int64) (*types.Bloc } if res != nil && res.Block == nil { - return nil, fmt.Errorf("core/fetcher: block not found") + return nil, fmt.Errorf("core/fetcher: block not found, height: %d", height) } return res.Block, nil @@ -71,7 +71,7 @@ func (f *BlockFetcher) GetBlockByHash(ctx context.Context, hash tmbytes.HexBytes } if res != nil && res.Block == nil { - return nil, fmt.Errorf("core/fetcher: block not found") + return nil, fmt.Errorf("core/fetcher: block not found, hash: %s", hash.String()) } return res.Block, nil @@ -86,7 +86,7 @@ func (f *BlockFetcher) Commit(ctx context.Context, height *int64) (*types.Commit } if res != nil && res.Commit == nil { - return nil, fmt.Errorf("core/fetcher: commit not found") + return nil, fmt.Errorf("core/fetcher: commit not found at height %d", height) } return res.Commit, nil @@ -105,7 +105,7 @@ func (f *BlockFetcher) ValidatorSet(ctx context.Context, height *int64) (*types. } if res != nil && len(res.Validators) == 0 { - return nil, fmt.Errorf("core/fetcher: validators not found") + return nil, fmt.Errorf("core/fetcher: validator set not found at height %d", height) } total = res.Total From 4667f42f9f823ee95e5c85d593d5a3972e4c6fbc Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 24 Oct 2022 15:22:05 +0200 Subject: [PATCH 0150/1008] refactor(params): Moving params to nodebuilder and adding config (#1168) Closes #1076 --- cmd/cel-key/node_types.go | 6 +- cmd/celestia/bridge.go | 13 ++-- cmd/celestia/full.go | 13 ++-- cmd/celestia/light.go | 13 ++-- cmd/env.go | 12 ++++ cmd/flags_node.go | 26 ++------ cmd/start.go | 2 +- das/daser_test.go | 2 +- fraud/service.go | 7 +-- header/p2p/exchange.go | 4 +- nodebuilder/header/config.go | 8 +-- nodebuilder/header/header.go | 8 +-- nodebuilder/header/module.go | 4 +- nodebuilder/init.go | 4 +- nodebuilder/module.go | 9 ++- nodebuilder/node.go | 17 +++--- nodebuilder/node_bridge_test.go | 7 +-- nodebuilder/node_light_test.go | 3 +- nodebuilder/p2p/bitswap.go | 4 +- {params => nodebuilder/p2p}/bootstrap.go | 5 +- nodebuilder/p2p/flags.go | 77 ++++++++++++++++++++++-- {params => nodebuilder/p2p}/genesis.go | 6 +- nodebuilder/p2p/host.go | 4 +- nodebuilder/p2p/misc.go | 4 +- {params => nodebuilder/p2p}/network.go | 8 ++- nodebuilder/p2p/routing.go | 6 +- nodebuilder/settings.go | 6 +- nodebuilder/state/keyring.go | 4 +- nodebuilder/testing.go | 7 +-- nodebuilder/tests/swamp/swamp.go | 4 +- params/default.go | 62 ------------------- 31 files changed, 177 insertions(+), 178 deletions(-) rename {params => nodebuilder/p2p}/bootstrap.go (95%) rename {params => nodebuilder/p2p}/genesis.go (95%) rename {params => nodebuilder/p2p}/network.go (87%) delete mode 100644 params/default.go diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index bf85418cd4..0a36b04cd9 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -8,9 +8,9 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/params" - sdkflags "github.com/cosmos/cosmos-sdk/client/flags" + + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var ( @@ -20,7 +20,7 @@ var ( func DirectoryFlags() *flag.FlagSet { flags := &flag.FlagSet{} - defaultNetwork := string(params.DefaultNetwork()) + defaultNetwork := string(p2p.DefaultNetwork) flags.String( nodeDirKey, diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index a2e8238019..512c69621e 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -3,13 +3,12 @@ package main import ( "github.com/spf13/cobra" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/state" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on @@ -48,7 +47,13 @@ var bridgeCmd = &cobra.Command{ ctx = cmdnode.WithNodeType(ctx, node.Bridge) - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) + parsedNetwork, err := p2p.ParseNetwork(cmd) + if err != nil { + return err + } + ctx = cmdnode.WithNetwork(ctx, parsedNetwork) + + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) if err != nil { return err } diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 310253cc4c..cfb749642c 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -4,14 +4,13 @@ package main import ( "github.com/spf13/cobra" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/state" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on @@ -56,7 +55,13 @@ var fullCmd = &cobra.Command{ ctx = cmdnode.WithNodeType(ctx, node.Full) - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) + parsedNetwork, err := p2p.ParseNetwork(cmd) + if err != nil { + return err + } + ctx = cmdnode.WithNetwork(ctx, parsedNetwork) + + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) if err != nil { return err } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 00e2f84d2f..e984c534c1 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -4,14 +4,13 @@ package main import ( "github.com/spf13/cobra" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/state" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on @@ -56,8 +55,14 @@ var lightCmd = &cobra.Command{ ctx = cmdnode.WithNodeType(ctx, node.Light) + parsedNetwork, err := p2p.ParseNetwork(cmd) + if err != nil { + return err + } + ctx = cmdnode.WithNetwork(ctx, parsedNetwork) + // loads existing config into the environment - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) if err != nil { return err } diff --git a/cmd/env.go b/cmd/env.go index d16f45d41c..f9860a2de8 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -8,6 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var log = logging.Logger("cmd") @@ -17,6 +18,11 @@ func NodeType(ctx context.Context) node.Type { return ctx.Value(nodeTypeKey{}).(node.Type) } +// Network reads the node type from the context. +func Network(ctx context.Context) p2p.Network { + return ctx.Value(networkKey{}).(p2p.Network) +} + // StorePath reads the store path from the context. func StorePath(ctx context.Context) string { return ctx.Value(storePathKey{}).(string) @@ -37,6 +43,11 @@ func WithNodeType(ctx context.Context, tp node.Type) context.Context { return context.WithValue(ctx, nodeTypeKey{}, tp) } +// WithNetwork sets the network in the given context. +func WithNetwork(ctx context.Context, network p2p.Network) context.Context { + return context.WithValue(ctx, networkKey{}, network) +} + // WithStorePath sets Store Path in the given context. func WithStorePath(ctx context.Context, storePath string) context.Context { return context.WithValue(ctx, storePathKey{}, storePath) @@ -67,4 +78,5 @@ type ( configKey struct{} storePathKey struct{} nodeTypeKey struct{} + networkKey struct{} ) diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 6fe91ab499..8799f6a627 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -11,13 +11,12 @@ import ( flag "github.com/spf13/pflag" "github.com/celestiaorg/celestia-node/nodebuilder" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var ( - nodeStoreFlag = "node.store" - nodeConfigFlag = "node.config" - nodeNetworkFlag = "node.network" + nodeStoreFlag = "node.store" + nodeConfigFlag = "node.config" ) // NodeFlags gives a set of hardcoded Node package flags. @@ -34,31 +33,16 @@ func NodeFlags() *flag.FlagSet { "", "Path to a customized node config TOML file", ) - flags.String( - nodeNetworkFlag, - "", - "The name of the network to connect to, e.g. "+params.ListProvidedNetworks(), - ) return flags } // ParseNodeFlags parses Node flags from the given cmd and applies values to Env. -func ParseNodeFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { - network := cmd.Flag(nodeNetworkFlag).Value.String() - if network != "" { - err := params.SetDefaultNetwork(params.Network(network)) - if err != nil { - return ctx, err - } - } else { - network = string(params.DefaultNetwork()) - } - +func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network) (context.Context, error) { store := cmd.Flag(nodeStoreFlag).Value.String() if store == "" { tp := NodeType(ctx) - store = fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(network)) + store = fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(string(network))) } ctx = WithStorePath(ctx, store) diff --git a/cmd/start.go b/cmd/start.go index 84918147f2..4e94a8206a 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -29,7 +29,7 @@ Options passed on start override configuration options only on start and are not // override config with all modifiers passed on start cfg := NodeConfig(ctx) - nd, err := nodebuilder.NewWithConfig(NodeType(ctx), store, &cfg, NodeOptions(ctx)...) + nd, err := nodebuilder.NewWithConfig(NodeType(ctx), Network(ctx), store, &cfg, NodeOptions(ctx)...) if err != nil { return err } diff --git a/das/daser_test.go b/das/daser_test.go index c87ce7bfb6..05fa0bf7c2 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -139,7 +139,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { // 15 headers from the past and 15 future headers mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) - // create fraud share and break one header + // create fraud service and break one header f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false) require.NoError(t, f.Start(ctx)) mockGet.headers[1] = header.CreateFraudExtHeader(t, mockGet.headers[1], bServ) diff --git a/fraud/service.go b/fraud/service.go index 5a0ec3e509..f3eec9a8a7 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -13,18 +13,17 @@ import ( "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/celestiaorg/celestia-node/params" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // fraudRequests is the amount of external requests that will be tried to get fraud proofs from other peers. const fraudRequests = 5 -var fraudProtocolID = protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", params.DefaultNetwork())) +var fraudProtocolID = protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", p2p.DefaultNetwork)) // ProofService is responsible for validating and propagating Fraud Proofs. // It implements the Service interface. diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 514e70e69a..956ef4c5af 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -18,7 +18,7 @@ import ( "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var log = logging.Logger("header/p2p") @@ -38,7 +38,7 @@ const ( // gossipsub topic. const PubSubTopic = "header-sub" -var exchangeProtocolID = protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", params.DefaultNetwork())) +var exchangeProtocolID = protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", p2p.DefaultNetwork)) // Exchange enables sending outbound ExtendedHeaderRequests to the network as well as // handling inbound ExtendedHeaderRequests from the network. diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 93a292d9c0..ca0dde5dc2 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -7,7 +7,7 @@ import ( "github.com/multiformats/go-multiaddr" tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // Config contains configuration parameters for header retrieval and management. @@ -28,7 +28,7 @@ func DefaultConfig() Config { } } -func (cfg *Config) trustedPeers(bpeers params.Bootstrappers) (infos []peer.AddrInfo, err error) { +func (cfg *Config) trustedPeers(bpeers p2p.Bootstrappers) (infos []peer.AddrInfo, err error) { if len(cfg.TrustedPeers) == 0 { log.Infof("No trusted peers in config, initializing with default bootstrappers as trusted peers") return bpeers, nil @@ -49,9 +49,9 @@ func (cfg *Config) trustedPeers(bpeers params.Bootstrappers) (infos []peer.AddrI return } -func (cfg *Config) trustedHash(net params.Network) (tmbytes.HexBytes, error) { +func (cfg *Config) trustedHash(net p2p.Network) (tmbytes.HexBytes, error) { if cfg.TrustedHash == "" { - gen, err := params.GenesisFor(net) + gen, err := p2p.GenesisFor(net) if err != nil { return nil, err } diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index caeb2486d8..2a19b87d0e 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -13,12 +13,12 @@ import ( "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/header/sync" - "github.com/celestiaorg/celestia-node/params" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // newP2PExchange constructs new Exchange for headers. -func newP2PExchange(cfg Config) func(params.Bootstrappers, host.Host) (header.Exchange, error) { - return func(bpeers params.Bootstrappers, host host.Host) (header.Exchange, error) { +func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, host.Host) (header.Exchange, error) { + return func(bpeers modp2p.Bootstrappers, host host.Host) (header.Exchange, error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { return nil, err @@ -45,7 +45,7 @@ type initStore header.Store func newInitStore( lc fx.Lifecycle, cfg Config, - net params.Network, + net modp2p.Network, s header.Store, ex header.Exchange, ) (initStore, error) { diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 34eb158c72..6e2d515247 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -13,7 +13,7 @@ import ( "github.com/celestiaorg/celestia-node/header/sync" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/params" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var log = logging.Logger("header-module") @@ -25,7 +25,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents := fx.Options( fx.Supply(*cfg), fx.Error(cfgErr), - fx.Supply(params.BlockTime), + fx.Supply(modp2p.BlockTime), fx.Provide(NewHeaderService), fx.Provide(fx.Annotate( store.NewStore, diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 3529d08f97..2d44647074 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -8,7 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under 'path'. @@ -104,7 +104,7 @@ func initDir(path string) error { if utils.Exists(path) { // if the dir already exists and `CELESTIA_CUSTOM` env var is set, // fail out to prevent store corruption - if _, ok := os.LookupEnv(params.EnvCustomNetwork); ok { + if _, ok := os.LookupEnv(p2p.EnvCustomNetwork); ok { return fmt.Errorf("cannot run a custom network over an already-existing node store") } return nil diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 1e48777b2b..7939509d2c 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -15,13 +15,13 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/params" ) -func ConstructModule(tp node.Type, cfg *Config, store Store) fx.Option { +func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store) fx.Option { baseComponents := fx.Options( - fx.Provide(params.DefaultNetwork), - fx.Provide(params.BootstrappersFor), + fx.Supply(tp), + fx.Supply(network), + fx.Provide(p2p.BootstrappersFor), fx.Provide(func(lc fx.Lifecycle) context.Context { return fxutil.WithLifecycle(context.Background(), lc) }), @@ -42,7 +42,6 @@ func ConstructModule(tp node.Type, cfg *Config, store Store) fx.Option { return fx.Module( "node", - fx.Supply(tp), baseComponents, ) } diff --git a/nodebuilder/node.go b/nodebuilder/node.go index fc3ea6e01e..a65ddf69ab 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -6,8 +6,6 @@ import ( "strings" "time" - "github.com/celestiaorg/celestia-node/nodebuilder/header" - "github.com/ipfs/go-blockservice" exchange "github.com/ipfs/go-ipfs-exchange-interface" logging "github.com/ipfs/go-log/v2" @@ -20,10 +18,11 @@ import ( "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/params" "github.com/celestiaorg/celestia-node/service/rpc" ) @@ -41,8 +40,8 @@ type Node struct { fx.In `ignore-unexported:"true"` Type node.Type - Network params.Network - Bootstrappers params.Bootstrappers + Network p2p.Network + Bootstrappers p2p.Bootstrappers Config *Config // rpc components @@ -67,18 +66,18 @@ type Node struct { } // New assembles a new Node with the given type 'tp' over Store 'store'. -func New(tp node.Type, store Store, options ...fx.Option) (*Node, error) { +func New(tp node.Type, network p2p.Network, store Store, options ...fx.Option) (*Node, error) { cfg, err := store.Config() if err != nil { return nil, err } - return NewWithConfig(tp, store, cfg, options...) + return NewWithConfig(tp, network, store, cfg, options...) } // NewWithConfig assembles a new Node with the given type 'tp' over Store 'store' and a custom config. -func NewWithConfig(tp node.Type, store Store, cfg *Config, options ...fx.Option) (*Node, error) { - opts := append([]fx.Option{ConstructModule(tp, cfg, store)}, options...) +func NewWithConfig(tp node.Type, network p2p.Network, store Store, cfg *Config, options ...fx.Option) (*Node, error) { + opts := append([]fx.Option{ConstructModule(tp, network, cfg, store)}, options...) return newNode(opts...) } diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index cde251b2fd..56f1bfad56 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -9,7 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/core" coremodule "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) func TestBridge_WithMockedCoreClient(t *testing.T) { @@ -20,10 +20,7 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { t.Cleanup(cancel) _, client := core.StartTestClient(ctx, t) - node, err := New(node.Bridge, repo, - coremodule.WithClient(client), - WithNetwork(params.Private), - ) + node, err := New(node.Bridge, p2p.Private, repo, coremodule.WithClient(client)) require.NoError(t, err) require.NotNil(t, node) err = node.Start(ctx) diff --git a/nodebuilder/node_light_test.go b/nodebuilder/node_light_test.go index 016ea4036f..c5e1d8b2ab 100644 --- a/nodebuilder/node_light_test.go +++ b/nodebuilder/node_light_test.go @@ -11,7 +11,6 @@ import ( nodebuilder "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/params" ) func TestNewLightWithP2PKey(t *testing.T) { @@ -43,5 +42,5 @@ func TestLight_WithMutualPeers(t *testing.T) { func TestLight_WithNetwork(t *testing.T) { node := TestNode(t, nodebuilder.Light) require.NotNil(t, node) - assert.Equal(t, params.Private, node.Network) + assert.Equal(t, p2p.Private, node.Network) } diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index 471cd40b29..49cf44891d 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -13,8 +13,6 @@ import ( "github.com/libp2p/go-libp2p-core/protocol" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "go.uber.org/fx" - - nparams "github.com/celestiaorg/celestia-node/params" ) const ( @@ -57,7 +55,7 @@ type bitSwapParams struct { fx.In Ctx context.Context - Net nparams.Network + Net Network Host host.Host Ds datastore.Batching } diff --git a/params/bootstrap.go b/nodebuilder/p2p/bootstrap.go similarity index 95% rename from params/bootstrap.go rename to nodebuilder/p2p/bootstrap.go index 7473193109..67ac4ea1e7 100644 --- a/params/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -1,13 +1,10 @@ -package params +package p2p import ( - logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" ) -var log = logging.Logger("params") - // BootstrappersFor returns address information of bootstrap peers for a given network. func BootstrappersFor(net Network) (Bootstrappers, error) { bs, err := bootstrappersFor(net) diff --git a/nodebuilder/p2p/flags.go b/nodebuilder/p2p/flags.go index 1b66c5b910..5335a96330 100644 --- a/nodebuilder/p2p/flags.go +++ b/nodebuilder/p2p/flags.go @@ -2,14 +2,20 @@ package p2p import ( "fmt" + "os" + "strings" "github.com/multiformats/go-multiaddr" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) -var ( - p2pMutualFlag = "p2p.mutual" +// EnvCustomNetwork is the environment variable name used for setting a custom network. +const EnvCustomNetwork = "CELESTIA_CUSTOM" + +const ( + networkFlag = "p2p.network" + mutualFlag = "p2p.mutual" ) // Flags gives a set of p2p flags. @@ -17,13 +23,20 @@ func Flags() *flag.FlagSet { flags := &flag.FlagSet{} flags.StringSlice( - p2pMutualFlag, + mutualFlag, nil, `Comma-separated multiaddresses of mutual peers to keep a prioritized connection with. Such connection is immune to peer scoring slashing and connection manager trimming. Peers must bidirectionally point to each other. (Format: multiformats.io/multiaddr) `, ) + flags.String( + networkFlag, + "", + "The name of the network to connect to, e.g. "+ + listProvidedNetworks()+ + ". Must be passed on both init and start to take effect.", + ) return flags } @@ -33,7 +46,7 @@ func ParseFlags( cmd *cobra.Command, cfg *Config, ) error { - mutualPeers, err := cmd.Flags().GetStringSlice(p2pMutualFlag) + mutualPeers, err := cmd.Flags().GetStringSlice(mutualFlag) if err != nil { return err } @@ -41,7 +54,7 @@ func ParseFlags( for _, peer := range mutualPeers { _, err = multiaddr.NewMultiaddr(peer) if err != nil { - return fmt.Errorf("cmd: while parsing '%s': %w", p2pMutualFlag, err) + return fmt.Errorf("cmd: while parsing '%s': %w", mutualFlag, err) } } @@ -50,3 +63,57 @@ func ParseFlags( } return nil } + +// ParseNetwork tries to parse the network from the flags and environment, +// and returns either the parsed network or the build's default network +func ParseNetwork(cmd *cobra.Command) (Network, error) { + parsedNetwork := cmd.Flag(networkFlag).Value.String() + // no network set through the flags, so check if there is an override in the env + if parsedNetwork == "" { + envNetwork, err := parseNetworkFromEnv() + // no network found in env, so use the default network + if envNetwork == "" { + return DefaultNetwork, err + } + return envNetwork, err + } + return Network(parsedNetwork), nil +} + +// parseNetworkFromEnv tries to parse the network from the environment. +// If no network is set, it returns an empty string. +func parseNetworkFromEnv() (Network, error) { + var network Network + // check if custom network option set + // format: CELESTIA_CUSTOM=:: + if custom, ok := os.LookupEnv(EnvCustomNetwork); ok { + fmt.Print("\n\nWARNING: Celestia custom network specified. Only use this option if the node is " + + "freshly created and initialized.\n**DO NOT** run a custom network over an already-existing node " + + "store!\n\n") + // ensure at least custom network is set + params := strings.Split(custom, ":") + if len(params) == 0 { + return network, fmt.Errorf("params: must provide at least to use a custom network") + } + netID := params[0] + network = Network(netID) + networksList[network] = struct{}{} + // check if genesis hash provided and register it if exists + if len(params) >= 2 { + genHash := params[1] + genesisList[network] = strings.ToUpper(genHash) + } + // check if bootstrappers were provided and register + if len(params) == 3 { + bootstrappers := params[2] + // validate bootstrappers + bs := strings.Split(bootstrappers, ",") + _, err := parseAddrInfos(bs) + if err != nil { + return DefaultNetwork, fmt.Errorf("params: env %s: contains invalid multiaddress", EnvCustomNetwork) + } + bootstrapList[Network(netID)] = bs + } + } + return network, nil +} diff --git a/params/genesis.go b/nodebuilder/p2p/genesis.go similarity index 95% rename from params/genesis.go rename to nodebuilder/p2p/genesis.go index 8c6c778662..c79f7f340d 100644 --- a/params/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -1,6 +1,8 @@ -package params +package p2p -import "fmt" +import ( + "fmt" +) // GenesisFor reports a hash of a genesis block for a given network. // Genesis is strictly defined and can't be modified. diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index ac250d2717..32f5290e61 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -15,8 +15,6 @@ import ( routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" - - nparams "github.com/celestiaorg/celestia-node/params" ) // RoutedHost constructs a wrapped Host that may fallback to address discovery, @@ -65,7 +63,7 @@ type HostBase host.Host type hostParams struct { fx.In - Net nparams.Network + Net Network Lc fx.Lifecycle ID peer.ID Key crypto.PrivKey diff --git a/nodebuilder/p2p/misc.go b/nodebuilder/p2p/misc.go index d0c89e3449..af540c8706 100644 --- a/nodebuilder/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -9,8 +9,6 @@ import ( "github.com/libp2p/go-libp2p-peerstore/pstoremem" "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/libp2p/go-libp2p/p2p/net/connmgr" - - "github.com/celestiaorg/celestia-node/params" ) // ConnManagerConfig configures connection manager. @@ -31,7 +29,7 @@ func DefaultConnManagerConfig() ConnManagerConfig { } // ConnectionManager provides a constructor for ConnectionManager. -func ConnectionManager(cfg Config, bpeers params.Bootstrappers) (coreconnmgr.ConnManager, error) { +func ConnectionManager(cfg Config, bpeers Bootstrappers) (coreconnmgr.ConnManager, error) { fpeers, err := cfg.mutualPeers() if err != nil { return nil, err diff --git a/params/network.go b/nodebuilder/p2p/network.go similarity index 87% rename from params/network.go rename to nodebuilder/p2p/network.go index d0d8b2e8e4..42d2c69a52 100644 --- a/params/network.go +++ b/nodebuilder/p2p/network.go @@ -1,4 +1,4 @@ -package params +package p2p import ( "errors" @@ -9,6 +9,8 @@ import ( // NOTE: Every time we add a new long-running network, it has to be added here. const ( + // DefaultNetwork is the default network of the current build. + DefaultNetwork = Arabica // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica" // Mamaki testnet. See: celestiaorg/networks. @@ -44,8 +46,8 @@ var networksList = map[Network]struct{}{ Private: {}, } -// ListProvidedNetworks provides a string listing all known long-standing networks for things like command hints. -func ListProvidedNetworks() string { +// listProvidedNetworks provides a string listing all known long-standing networks for things like command hints. +func listProvidedNetworks() string { var networks string for net := range networksList { // "private" network isn't really a choosable option, so skip diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index 2064297a8d..3b5fc2997e 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -10,8 +10,6 @@ import ( "github.com/libp2p/go-libp2p-core/routing" dht "github.com/libp2p/go-libp2p-kad-dht" "go.uber.org/fx" - - nparams "github.com/celestiaorg/celestia-node/params" ) var log = logging.Logger("p2p-module") @@ -60,8 +58,8 @@ type routingParams struct { fx.In Ctx context.Context - Net nparams.Network - Peers nparams.Bootstrappers + Net Network + Peers Bootstrappers Lc fx.Lifecycle Host HostBase DataStore datastore.Batching diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index b735dec8c7..1317a80ce8 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -19,18 +19,18 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/daser" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/state" ) // WithNetwork specifies the Network to which the Node should connect to. // WARNING: Use this option with caution and never run the Node with different networks over the same persisted Store. -func WithNetwork(net params.Network) fx.Option { +func WithNetwork(net p2p.Network) fx.Option { return fx.Replace(net) } // WithBootstrappers sets custom bootstrap peers. -func WithBootstrappers(peers params.Bootstrappers) fx.Option { +func WithBootstrappers(peers p2p.Bootstrappers) fx.Option { return fx.Replace(peers) } diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go index ba3bfe429f..7f6c197680 100644 --- a/nodebuilder/state/keyring.go +++ b/nodebuilder/state/keyring.go @@ -12,10 +12,10 @@ import ( apptypes "github.com/celestiaorg/celestia-app/x/payment/types" "github.com/celestiaorg/celestia-node/libs/keystore" - "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -func Keyring(cfg Config, ks keystore.Keystore, net params.Network) (*apptypes.KeyringSigner, error) { +func Keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.KeyringSigner, error) { // TODO @renaynay: Include option for setting custom `userInput` parameter with // implementation of https://github.com/celestiaorg/celestia-node/issues/415. // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 591081ede9..a7fb1e7bde 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -17,8 +17,8 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/params" ) // MockStore provides mock in memory Store for testing purposes. @@ -49,10 +49,9 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti cfg.RPC.Port = "0" opts = append(opts, - WithNetwork(params.Private), state.WithKeyringSigner(TestKeyringSigner(t)), ) - nd, err := New(tp, store, opts...) + nd, err := New(tp, p2p.Private, store, opts...) require.NoError(t, err) return nd } @@ -60,7 +59,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti func TestKeyringSigner(t *testing.T) *apptypes.KeyringSigner { encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) ring := keyring.NewInMemory(encConf.Codec) - signer := apptypes.NewKeyringSigner(ring, "", string(params.Private)) + signer := apptypes.NewKeyringSigner(ring, "", string(p2p.Private)) _, _, err := signer.NewMnemonic("test_celes", keyring.English, "", "", hd.Secp256k1) require.NoError(t, err) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index bfc42caaa8..f156f395a3 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -25,7 +25,6 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/params" ) var blackholeIP6 = net.ParseIP("100::") @@ -261,10 +260,9 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti cfg.RPC.Port = "0" options = append(options, p2p.WithHost(s.createPeer(ks)), - nodebuilder.WithNetwork(params.Private), ) - node, err := nodebuilder.New(t, store, options...) + node, err := nodebuilder.New(t, p2p.Private, store, options...) require.NoError(s.t, err) return node diff --git a/params/default.go b/params/default.go deleted file mode 100644 index 0915bc7f67..0000000000 --- a/params/default.go +++ /dev/null @@ -1,62 +0,0 @@ -package params - -import ( - "fmt" - "os" - "strings" -) - -const ( - EnvCustomNetwork = "CELESTIA_CUSTOM" -) - -// defaultNetwork defines a default network for the Celestia Node. -var defaultNetwork = Arabica - -// DefaultNetwork returns the network of the current build. -func DefaultNetwork() Network { - return defaultNetwork -} - -func SetDefaultNetwork(net Network) error { - if err := net.Validate(); err != nil { - return err - } - defaultNetwork = net - return nil -} - -func init() { - // check if custom network option set - // format: CELESTIA_CUSTOM=:: - if custom, ok := os.LookupEnv(EnvCustomNetwork); ok { - fmt.Print("\n\nWARNING: Celestia custom network specified. Only use this option if the node is " + - "freshly created and initialized.\n**DO NOT** run a custom network over an already-existing node " + - "store!\n\n") - // ensure at least custom network is set - params := strings.Split(custom, ":") - if len(params) == 0 { - panic("params: must provide at least to use a custom network") - } - netID := params[0] - defaultNetwork = Network(netID) - networksList[defaultNetwork] = struct{}{} - // check if genesis hash provided and register it if exists - if len(params) >= 2 { - genHash := params[1] - genesisList[defaultNetwork] = strings.ToUpper(genHash) - } - // check if bootstrappers were provided and register - if len(params) == 3 { - bootstrappers := params[2] - // validate bootstrappers - bs := strings.Split(bootstrappers, ",") - _, err := parseAddrInfos(bs) - if err != nil { - println(fmt.Sprintf("params: env %s: contains invalid multiaddress", EnvCustomNetwork)) - panic(err) - } - bootstrapList[Network(netID)] = bs - } - } -} From 0e48f9a1fcc0729b4d828db9d9d5dd2a9729f495 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 24 Oct 2022 16:22:53 +0200 Subject: [PATCH 0151/1008] fix: removing protocolIDs from global namespace in p2p and fraud (#1268) Based on #1168 Closes #1189 --- das/daser_test.go | 2 +- fraud/requester.go | 6 ++---- fraud/service.go | 12 ++++++------ fraud/service_test.go | 3 +++ fraud/sync.go | 2 +- header/p2p/exchange.go | 21 ++++++++++++--------- header/p2p/exchange_test.go | 14 ++++++++------ header/p2p/server.go | 14 +++++++++----- nodebuilder/fraud/fraud.go | 13 +++++++++---- nodebuilder/header/header.go | 13 +++++++++---- nodebuilder/header/module.go | 2 +- 11 files changed, 61 insertions(+), 41 deletions(-) diff --git a/das/daser_test.go b/das/daser_test.go index 05fa0bf7c2..eaeed57bc7 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -140,7 +140,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) // create fraud service and break one header - f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false) + f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, "private") require.NoError(t, f.Start(ctx)) mockGet.headers[1] = header.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() diff --git a/fraud/requester.go b/fraud/requester.go index 1da9444bbe..78184915c1 100644 --- a/fraud/requester.go +++ b/fraud/requester.go @@ -4,7 +4,6 @@ import ( "context" "time" - "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -19,14 +18,13 @@ const ( readDeadline = time.Minute ) -func requestProofs( +func (f *ProofService) requestProofs( ctx context.Context, - host host.Host, pid peer.ID, proofTypes []string, ) ([]*pb.ProofResponse, error) { msg := &pb.FraudMessageRequest{RequestedProofType: proofTypes} - stream, err := host.NewStream(ctx, pid, fraudProtocolID) + stream, err := f.host.NewStream(ctx, pid, f.protocolID) if err != nil { return nil, err } diff --git a/fraud/service.go b/fraud/service.go index f3eec9a8a7..b3b8bc5898 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -16,18 +16,16 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" - - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // fraudRequests is the amount of external requests that will be tried to get fraud proofs from other peers. const fraudRequests = 5 -var fraudProtocolID = protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", p2p.DefaultNetwork)) - // ProofService is responsible for validating and propagating Fraud Proofs. // It implements the Service interface. type ProofService struct { + protocolID protocol.ID + ctx context.Context cancel context.CancelFunc @@ -51,6 +49,7 @@ func NewProofService( getter headerFetcher, ds datastore.Datastore, syncerEnabled bool, + protocolSuffix string, ) *ProofService { return &ProofService{ pubsub: p, @@ -59,6 +58,7 @@ func NewProofService( topics: make(map[ProofType]*pubsub.Topic), stores: make(map[ProofType]datastore.Datastore), ds: ds, + protocolID: protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", protocolSuffix)), syncerEnabled: syncerEnabled, } } @@ -83,7 +83,7 @@ func (f *ProofService) Start(context.Context) error { if err := f.registerProofTopics(registeredProofTypes()...); err != nil { return err } - f.host.SetStreamHandler(fraudProtocolID, f.handleFraudMessageRequest) + f.host.SetStreamHandler(f.protocolID, f.handleFraudMessageRequest) if f.syncerEnabled { go f.syncFraudProofs(f.ctx) } @@ -92,7 +92,7 @@ func (f *ProofService) Start(context.Context) error { // Stop removes the stream handler and cancels the underlying ProofService func (f *ProofService) Stop(context.Context) error { - f.host.RemoveStreamHandler(fraudProtocolID) + f.host.RemoveStreamHandler(f.protocolID) f.cancel() return nil } diff --git a/fraud/service_test.go b/fraud/service_test.go index ec750e68ec..2b0a6f35b4 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -133,6 +133,7 @@ func TestService_ReGossiping(t *testing.T) { }, sync.MutexWrap(datastore.NewMapDatastore()), false, + "private", ) addrB := host.InfoFromHost(net.Hosts()[1]) // -> B @@ -148,6 +149,7 @@ func TestService_ReGossiping(t *testing.T) { }, sync.MutexWrap(datastore.NewMapDatastore()), false, + "private", ) // establish connections // connect peers: A -> B -> C, so A and C are not connected to each other @@ -263,5 +265,6 @@ func createServiceWithHost( store.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore()), enabledSyncer, + "private", ), store } diff --git a/fraud/sync.go b/fraud/sync.go index 9b639c7f52..0dd0eaabd1 100644 --- a/fraud/sync.go +++ b/fraud/sync.go @@ -67,7 +67,7 @@ func (f *ProofService) syncFraudProofs(ctx context.Context) { attribute.StringSlice("proof_types", proofTypes), ) log.Debugw("requesting proofs from peer", "pid", pid) - respProofs, err := requestProofs(ctx, f.host, pid, proofTypes) + respProofs, err := f.requestProofs(ctx, pid, proofTypes) if err != nil { log.Errorw("error while requesting fraud proofs", "err", err, "peer", pid) span.RecordError(err) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 956ef4c5af..17f2ca2ee3 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -18,7 +18,6 @@ import ( "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var log = logging.Logger("header/p2p") @@ -38,19 +37,24 @@ const ( // gossipsub topic. const PubSubTopic = "header-sub" -var exchangeProtocolID = protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", p2p.DefaultNetwork)) - // Exchange enables sending outbound ExtendedHeaderRequests to the network as well as // handling inbound ExtendedHeaderRequests from the network. type Exchange struct { + protocolID protocol.ID + host host.Host trustedPeers peer.IDSlice } -func NewExchange(host host.Host, peers peer.IDSlice) *Exchange { +func protocolID(protocolSuffix string) protocol.ID { + return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) +} + +func NewExchange(host host.Host, peers peer.IDSlice, protocolSuffix string) *Exchange { return &Exchange{ host: host, + protocolID: protocolID(protocolSuffix), trustedPeers: peers, } } @@ -69,7 +73,7 @@ func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { // request head from each trusted peer for _, from := range ex.trustedPeers { go func(from peer.ID) { - headers, err := request(ctx, from, ex.host, req) + headers, err := ex.request(ctx, from, req) if err != nil { log.Errorw("head request to trusted peer failed", "trustedPeer", from, "err", err) headerCh <- nil @@ -162,17 +166,16 @@ func (ex *Exchange) performRequest( //nolint:gosec // G404: Use of weak random number generator index := rand.Intn(len(ex.trustedPeers)) - return request(ctx, ex.trustedPeers[index], ex.host, req) + return ex.request(ctx, ex.trustedPeers[index], req) } // request sends the ExtendedHeaderRequest to a remote peer. -func request( +func (ex *Exchange) request( ctx context.Context, to peer.ID, - host host.Host, req *p2p_pb.ExtendedHeaderRequest, ) ([]*header.ExtendedHeader, error) { - stream, err := host.NewStream(ctx, to, exchangeProtocolID) + stream, err := ex.host.NewStream(ctx, to, ex.protocolID) if err != nil { return nil, err } diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index d6c6c599ba..3fab82df70 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -19,6 +19,8 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) +var privateProtocolID = protocolID("private") + func TestExchange_RequestHead(t *testing.T) { host, peer := createMocknet(t) exchg, store := createP2PExAndServer(t, host, peer) @@ -99,7 +101,7 @@ func TestExchange_RequestByHash(t *testing.T) { host, peer := net.Hosts()[0], net.Hosts()[1] // create and start the ExchangeServer store := createStore(t, 5) - serv := NewExchangeServer(host, store) + serv := NewExchangeServer(host, store, "private") err = serv.Start(ctx) require.NoError(t, err) t.Cleanup(func() { @@ -107,7 +109,7 @@ func TestExchange_RequestByHash(t *testing.T) { }) // start a new stream via Peer to see if Host can handle inbound requests - stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, exchangeProtocolID) + stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, privateProtocolID) require.NoError(t, err) // create request for a header at a random height reqHeight := store.headHeight - 2 @@ -205,14 +207,14 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.NoError(t, err) // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] - serv := NewExchangeServer(host, createStore(t, 0)) + serv := NewExchangeServer(host, createStore(t, 0), "private") err = serv.Start(ctx) require.NoError(t, err) t.Cleanup(func() { serv.Stop(context.Background()) //nolint:errcheck }) - stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, exchangeProtocolID) + stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, privateProtocolID) require.NoError(t, err) req := &p2p_pb.ExtendedHeaderRequest{ Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: []byte("dummy_hash")}, @@ -238,7 +240,7 @@ func createMocknet(t *testing.T) (libhost.Host, libhost.Host) { // createP2PExAndServer creates a Exchange with 5 headers already in its store. func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchange, *mockStore) { store := createStore(t, 5) - serverSideEx := NewExchangeServer(tpeer, store) + serverSideEx := NewExchangeServer(tpeer, store, "private") err := serverSideEx.Start(context.Background()) require.NoError(t, err) @@ -246,7 +248,7 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan serverSideEx.Stop(context.Background()) //nolint:errcheck }) - return NewExchange(host, []peer.ID{tpeer.ID()}), store + return NewExchange(host, []peer.ID{tpeer.ID()}, "private"), store } type mockStore struct { diff --git a/header/p2p/server.go b/header/p2p/server.go index 4a1ce64c3e..bbf21dca64 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -6,6 +6,7 @@ import ( "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/protocol" tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -17,6 +18,8 @@ import ( // ExchangeServer represents the server-side component for // responding to inbound header-related requests. type ExchangeServer struct { + protocolID protocol.ID + host host.Host store header.Store @@ -26,10 +29,11 @@ type ExchangeServer struct { // NewExchangeServer returns a new P2P server that handles inbound // header-related requests. -func NewExchangeServer(host host.Host, store header.Store) *ExchangeServer { +func NewExchangeServer(host host.Host, store header.Store, protocolSuffix string) *ExchangeServer { return &ExchangeServer{ - host: host, - store: store, + protocolID: protocolID(protocolSuffix), + host: host, + store: store, } } @@ -38,7 +42,7 @@ func (serv *ExchangeServer) Start(context.Context) error { serv.ctx, serv.cancel = context.WithCancel(context.Background()) log.Info("server: listening for inbound header requests") - serv.host.SetStreamHandler(exchangeProtocolID, serv.requestHandler) + serv.host.SetStreamHandler(serv.protocolID, serv.requestHandler) return nil } @@ -47,7 +51,7 @@ func (serv *ExchangeServer) Start(context.Context) error { func (serv *ExchangeServer) Stop(context.Context) error { log.Info("server: stopping server") serv.cancel() - serv.host.RemoveStreamHandler(exchangeProtocolID) + serv.host.RemoveStreamHandler(serv.protocolID) return nil } diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index aebcbbbde2..3aec12514c 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -10,6 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // NewModule constructs a fraud proof service with the syncer disabled. @@ -19,8 +20,9 @@ func NewModule( host host.Host, hstore header.Store, ds datastore.Batching, + network p2p.Network, ) (Module, error) { - return newFraudService(lc, sub, host, hstore, ds, false) + return newFraudService(lc, sub, host, hstore, ds, false, string(network)) } // ModuleWithSyncer constructs fraud proof service with enabled syncer. @@ -30,8 +32,9 @@ func ModuleWithSyncer( host host.Host, hstore header.Store, ds datastore.Batching, + network p2p.Network, ) (Module, error) { - return newFraudService(lc, sub, host, hstore, ds, true) + return newFraudService(lc, sub, host, hstore, ds, true, string(network)) } func newFraudService( @@ -40,8 +43,10 @@ func newFraudService( host host.Host, hstore header.Store, ds datastore.Batching, - isEnabled bool) (Module, error) { - pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled) + isEnabled bool, + protocolSuffix string, +) (Module, error) { + pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled, protocolSuffix) lc.Append(fx.Hook{ OnStart: pservice.Start, OnStop: pservice.Stop, diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 2a19b87d0e..f3499337aa 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -16,9 +16,14 @@ import ( modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -// newP2PExchange constructs new Exchange for headers. -func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, host.Host) (header.Exchange, error) { - return func(bpeers modp2p.Bootstrappers, host host.Host) (header.Exchange, error) { +// newP2PServer constructs a new ExchangeServer using the given Network as a protocolID suffix. +func newP2PServer(host host.Host, store header.Store, network modp2p.Network) *p2p.ExchangeServer { + return p2p.NewExchangeServer(host, store, string(network)) +} + +// newP2PExchange constructs a new Exchange for headers. +func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, modp2p.Network, host.Host) (header.Exchange, error) { + return func(bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host) (header.Exchange, error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { return nil, err @@ -28,7 +33,7 @@ func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, host.Host) (header.Ex ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - return p2p.NewExchange(host, ids), nil + return p2p.NewExchange(host, ids, string(network)), nil } } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 6e2d515247..60f9b8fc1c 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -71,7 +71,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), )), fx.Provide(fx.Annotate( - p2p.NewExchangeServer, + newP2PServer, fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer) error { return server.Start(ctx) }), From f4e582e9835dd69826faaecc1480cb1ac528bc07 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:02:27 +0200 Subject: [PATCH 0152/1008] refactor(nodebuilder): Allow custom networks to work over already-initialised store (#1270) After conversation with @jbowen93 , we decided to allow using custom networks over an already-initialised node store. The issue this was originally attempting to prevent is accidental corruption of chain data in the case that a user accidentally leaves their environment variable set up and connects to another network over an old node store. This issue is actually currently prevented thanks to #1073 extending the node store name with the network name. --- nodebuilder/init.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 2d44647074..b546b6d77a 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -1,14 +1,12 @@ package nodebuilder import ( - "fmt" "os" "path/filepath" "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under 'path'. @@ -102,11 +100,6 @@ func initRoot(path string) error { // initDir creates a dir if not exist func initDir(path string) error { if utils.Exists(path) { - // if the dir already exists and `CELESTIA_CUSTOM` env var is set, - // fail out to prevent store corruption - if _, ok := os.LookupEnv(p2p.EnvCustomNetwork); ok { - return fmt.Errorf("cannot run a custom network over an already-existing node store") - } return nil } return os.Mkdir(path, perms) From 906f6a51b9a3ce4b7789b98a156e926c26208367 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:21:38 +0200 Subject: [PATCH 0153/1008] refactor(nodebuilder/header): Clarify error for init store failure (#1272) Self explanatory --- nodebuilder/header/header.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index f3499337aa..a776429c1b 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -69,7 +69,7 @@ func newInitStore( // * Having some test/dev/offline mode for Node that mocks out all the networking // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step // * Or removing explicit initialization in favor of automated initialization by Syncer - log.Errorf("initializing store failed: %s", err) + log.Errorf("initializing header store failed: %s", err) } return nil }, From cb920512f3cf9a155385fbff6f9a5cc7780f986b Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 26 Oct 2022 09:35:14 +0200 Subject: [PATCH 0154/1008] feat(`api/rpc`): OpenRPC scaffolding, migrating OpenAPI gateway to `api/gateway` (#1175) Based on #1056 --- {service/rpc => api/gateway}/availability.go | 2 +- api/gateway/config.go | 31 ++++++++++ {service/rpc => api/gateway}/das.go | 2 +- {service/rpc => api/gateway}/endpoints.go | 2 +- {service/rpc => api/gateway}/handler.go | 4 +- {service/rpc => api/gateway}/header.go | 2 +- {service/rpc => api/gateway}/middleware.go | 2 +- {service/rpc => api/gateway}/server.go | 4 +- {service/rpc => api/gateway}/server_test.go | 2 +- {service/rpc => api/gateway}/share.go | 2 +- {service/rpc => api/gateway}/state.go | 2 +- {service/rpc => api/gateway}/util.go | 2 +- api/rpc/server.go | 60 ++++++++++++++++++++ go.mod | 1 + go.sum | 4 ++ nodebuilder/config.go | 2 +- nodebuilder/node.go | 4 +- {service => nodebuilder}/rpc/config.go | 0 nodebuilder/rpc/flags.go | 4 +- nodebuilder/rpc/module.go | 18 +++--- nodebuilder/rpc/rpc.go | 17 ++++-- 21 files changed, 133 insertions(+), 34 deletions(-) rename {service/rpc => api/gateway}/availability.go (99%) create mode 100644 api/gateway/config.go rename {service/rpc => api/gateway}/das.go (97%) rename {service/rpc => api/gateway}/endpoints.go (99%) rename {service/rpc => api/gateway}/handler.go (91%) rename {service/rpc => api/gateway}/header.go (99%) rename {service/rpc => api/gateway}/middleware.go (98%) rename {service/rpc => api/gateway}/server.go (95%) rename {service/rpc => api/gateway}/server_test.go (98%) rename {service/rpc => api/gateway}/share.go (99%) rename {service/rpc => api/gateway}/state.go (99%) rename {service/rpc => api/gateway}/util.go (96%) create mode 100644 api/rpc/server.go rename {service => nodebuilder}/rpc/config.go (100%) diff --git a/service/rpc/availability.go b/api/gateway/availability.go similarity index 99% rename from service/rpc/availability.go rename to api/gateway/availability.go index 9a9a25088f..37d61ee4b7 100644 --- a/service/rpc/availability.go +++ b/api/gateway/availability.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "encoding/json" diff --git a/api/gateway/config.go b/api/gateway/config.go new file mode 100644 index 0000000000..f7d8bb44b1 --- /dev/null +++ b/api/gateway/config.go @@ -0,0 +1,31 @@ +package gateway + +import ( + "fmt" + "net" + "strconv" +) + +type Config struct { + Address string + Port string +} + +func DefaultConfig() Config { + return Config{ + Address: "0.0.0.0", + // do NOT expose the same port as celestia-core by default so that both can run on the same machine + Port: "26658", + } +} + +func (cfg *Config) Validate() error { + if ip := net.ParseIP(cfg.Address); ip == nil { + return fmt.Errorf("service/gateway: invalid listen address format: %s", cfg.Address) + } + _, err := strconv.Atoi(cfg.Port) + if err != nil { + return fmt.Errorf("service/gateway: invalid port: %s", err.Error()) + } + return nil +} diff --git a/service/rpc/das.go b/api/gateway/das.go similarity index 97% rename from service/rpc/das.go rename to api/gateway/das.go index c511f4da40..565cbd7460 100644 --- a/service/rpc/das.go +++ b/api/gateway/das.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "encoding/json" diff --git a/service/rpc/endpoints.go b/api/gateway/endpoints.go similarity index 99% rename from service/rpc/endpoints.go rename to api/gateway/endpoints.go index 657b926e15..9e1c1b1c7e 100644 --- a/service/rpc/endpoints.go +++ b/api/gateway/endpoints.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "fmt" diff --git a/service/rpc/handler.go b/api/gateway/handler.go similarity index 91% rename from service/rpc/handler.go rename to api/gateway/handler.go index e1013cad1b..2602528170 100644 --- a/service/rpc/handler.go +++ b/api/gateway/handler.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( logging "github.com/ipfs/go-log/v2" @@ -9,7 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -var log = logging.Logger("rpc") +var log = logging.Logger("gateway") type Handler struct { state state.Module diff --git a/service/rpc/header.go b/api/gateway/header.go similarity index 99% rename from service/rpc/header.go rename to api/gateway/header.go index e1d44dc55a..108519b924 100644 --- a/service/rpc/header.go +++ b/api/gateway/header.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "encoding/json" diff --git a/service/rpc/middleware.go b/api/gateway/middleware.go similarity index 98% rename from service/rpc/middleware.go rename to api/gateway/middleware.go index c8981bcc05..9c49195c6d 100644 --- a/service/rpc/middleware.go +++ b/api/gateway/middleware.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "errors" diff --git a/service/rpc/server.go b/api/gateway/server.go similarity index 95% rename from service/rpc/server.go rename to api/gateway/server.go index 8551c95132..ba53de72fc 100644 --- a/service/rpc/server.go +++ b/api/gateway/server.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "context" @@ -11,7 +11,7 @@ import ( ) // Server represents an RPC server on the Node. -// TODO @renaynay: eventually, rpc server should be able to be toggled on and off. +// TODO @renaynay: eventually, gateway server should be able to be toggled on and off. type Server struct { cfg Config diff --git a/service/rpc/server_test.go b/api/gateway/server_test.go similarity index 98% rename from service/rpc/server_test.go rename to api/gateway/server_test.go index 8b077bb637..96dfac9fee 100644 --- a/service/rpc/server_test.go +++ b/api/gateway/server_test.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "context" diff --git a/service/rpc/share.go b/api/gateway/share.go similarity index 99% rename from service/rpc/share.go rename to api/gateway/share.go index 507b4cbfff..303a0c3546 100644 --- a/service/rpc/share.go +++ b/api/gateway/share.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "context" diff --git a/service/rpc/state.go b/api/gateway/state.go similarity index 99% rename from service/rpc/state.go rename to api/gateway/state.go index a751dda47e..938daab0d7 100644 --- a/service/rpc/state.go +++ b/api/gateway/state.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "encoding/hex" diff --git a/service/rpc/util.go b/api/gateway/util.go similarity index 96% rename from service/rpc/util.go rename to api/gateway/util.go index be7fe34c7b..6ae130c7f4 100644 --- a/service/rpc/util.go +++ b/api/gateway/util.go @@ -1,4 +1,4 @@ -package rpc +package gateway import ( "encoding/json" diff --git a/api/rpc/server.go b/api/rpc/server.go new file mode 100644 index 0000000000..725127b6cf --- /dev/null +++ b/api/rpc/server.go @@ -0,0 +1,60 @@ +package rpc + +import ( + "context" + "net/http" + "time" + + "github.com/filecoin-project/go-jsonrpc" + logging "github.com/ipfs/go-log/v2" +) + +var log = logging.Logger("rpc") + +type Server struct { + http *http.Server + rpc *jsonrpc.RPCServer +} + +func NewServer(address string, port string) *Server { + rpc := jsonrpc.NewServer() + return &Server{ + rpc: rpc, + http: &http.Server{ + Addr: address + ":" + port, + Handler: rpc, + // the amount of time allowed to read request headers. set to the default 2 seconds + ReadHeaderTimeout: 2 * time.Second, + }, + } +} + +// RegisterService registers a service onto the RPC server. All methods on the service will then be exposed over the +// RPC. +func (s *Server) RegisterService(namespace string, service interface{}) { + s.rpc.Register(namespace, service) +} + +// Start starts the RPC Server. +func (s *Server) Start(context.Context) error { + //nolint:errcheck + go s.http.ListenAndServe() + log.Infow("RPC server started", "listening on", s.http.Addr) + return nil +} + +// Stop stops the RPC Server. +func (s *Server) Stop(ctx context.Context) error { + // if server already stopped, return + err := s.http.Shutdown(ctx) + if err != nil { + return err + } + log.Info("RPC server stopped") + return nil +} + +// ListenAddr returns the listen address of the server. +func (s *Server) ListenAddr() string { + return s.http.Addr +} diff --git a/go.mod b/go.mod index 8ac59d7494..8107fa6de5 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/dgraph-io/badger/v2 v2.2007.4 + github.com/filecoin-project/go-jsonrpc v0.1.8 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index 9803632094..f39bcdbcac 100644 --- a/go.sum +++ b/go.sum @@ -404,6 +404,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/filecoin-project/go-jsonrpc v0.1.8 h1:uXX/ikAk3Q4f/k8DRd9Zw+fWnfiYb5I+UI1tzlQgHog= +github.com/filecoin-project/go-jsonrpc v0.1.8/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= @@ -623,6 +625,7 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -819,6 +822,7 @@ github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JP github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= +github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= diff --git a/nodebuilder/config.go b/nodebuilder/config.go index 152cafb841..aba5cb5d1e 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -10,9 +10,9 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/service/rpc" ) // ConfigLoader defines a function that loads a config from any source. diff --git a/nodebuilder/node.go b/nodebuilder/node.go index a65ddf69ab..77840684c5 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -16,6 +16,7 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -23,7 +24,6 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/service/rpc" ) const Timeout = time.Second * 15 @@ -45,7 +45,7 @@ type Node struct { Config *Config // rpc components - RPCServer *rpc.Server `optional:"true"` + RPCServer *rpc.Server // not optional // p2p components Host host.Host ConnGater *conngater.BasicConnectionGater diff --git a/service/rpc/config.go b/nodebuilder/rpc/config.go similarity index 100% rename from service/rpc/config.go rename to nodebuilder/rpc/config.go diff --git a/nodebuilder/rpc/flags.go b/nodebuilder/rpc/flags.go index 3d9abb86e5..167dbc803a 100644 --- a/nodebuilder/rpc/flags.go +++ b/nodebuilder/rpc/flags.go @@ -3,8 +3,6 @@ package rpc import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - - "github.com/celestiaorg/celestia-node/service/rpc" ) var ( @@ -31,7 +29,7 @@ func Flags() *flag.FlagSet { } // ParseFlags parses RPC flags from the given cmd and saves them to the passed config. -func ParseFlags(cmd *cobra.Command, cfg *rpc.Config) { +func ParseFlags(cmd *cobra.Command, cfg *Config) { addr := cmd.Flag(addrFlag).Value.String() if addr != "" { cfg.Address = addr diff --git a/nodebuilder/rpc/module.go b/nodebuilder/rpc/module.go index 551d3879f2..e443c527ba 100644 --- a/nodebuilder/rpc/module.go +++ b/nodebuilder/rpc/module.go @@ -5,26 +5,26 @@ import ( "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/api/rpc" headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" stateServ "github.com/celestiaorg/celestia-node/nodebuilder/state" - rpcServ "github.com/celestiaorg/celestia-node/service/rpc" ) -func ConstructModule(tp node.Type, cfg *rpcServ.Config) fx.Option { +func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() baseComponents := fx.Options( - fx.Supply(*cfg), + fx.Supply(cfg), fx.Error(cfgErr), fx.Provide(fx.Annotate( - rpcServ.NewServer, - fx.OnStart(func(ctx context.Context, server *rpcServ.Server) error { + Server, + fx.OnStart(func(ctx context.Context, server *rpc.Server) error { return server.Start(ctx) }), - fx.OnStop(func(ctx context.Context, server *rpcServ.Server) error { + fx.OnStop(func(ctx context.Context, server *rpc.Server) error { return server.Stop(ctx) }), )), @@ -35,7 +35,7 @@ func ConstructModule(tp node.Type, cfg *rpcServ.Config) fx.Option { return fx.Module( "rpc", baseComponents, - fx.Invoke(Handler), + fx.Invoke(RegisterEndpoints), ) case node.Bridge: return fx.Module( @@ -45,9 +45,9 @@ func ConstructModule(tp node.Type, cfg *rpcServ.Config) fx.Option { state stateServ.Module, share shareServ.Module, header headerServ.Module, - rpcSrv *rpcServ.Server, + rpcSrv *rpc.Server, ) { - Handler(state, share, header, rpcSrv, nil) + RegisterEndpoints(state, share, header, rpcSrv, nil) }), ) default: diff --git a/nodebuilder/rpc/rpc.go b/nodebuilder/rpc/rpc.go index 55b4ac0724..5568c70c76 100644 --- a/nodebuilder/rpc/rpc.go +++ b/nodebuilder/rpc/rpc.go @@ -1,22 +1,27 @@ package rpc import ( + "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/service/rpc" ) -// Handler constructs a new RPC Handler from the given services. -func Handler( +// RegisterEndpoints registers the given services on the rpc. +func RegisterEndpoints( state state.Module, share share.Module, header header.Module, serv *rpc.Server, daser *das.DASer, ) { - handler := rpc.NewHandler(state, share, header, daser) - handler.RegisterEndpoints(serv) - handler.RegisterMiddleware(serv) + serv.RegisterService("state", state) + serv.RegisterService("share", share) + serv.RegisterService("header", header) + serv.RegisterService("daser", daser) +} + +func Server(cfg *Config) *rpc.Server { + return rpc.NewServer(cfg.Address, cfg.Port) } From ccb6ada995a1f1004d1fb2887e8a52f9d6c83b16 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 26 Oct 2022 16:36:03 +0200 Subject: [PATCH 0155/1008] feat(blocksync): Benchmarks for `ReadEDS` and `WriteEDS` (#1197) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/eds/eds_test.go | 32 ++++++++++++++++++++++++++++++++ share/test_helpers.go | 14 ++++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index f7203283d7..659888c85f 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -1,9 +1,11 @@ package eds import ( + "bytes" "context" "embed" "encoding/json" + "fmt" "os" "testing" @@ -175,6 +177,36 @@ func TestReadEDSContentIntegrityMismatch(t *testing.T) { require.ErrorContains(t, err, "share: content integrity mismatch: imported root") } +// BenchmarkReadWriteEDS benchmarks the time it takes to write and read an EDS from disk. The benchmark is run with a +// 4x4 ODS to a 64x64 ODS - a higher value can be used, but it will run for much longer. +func BenchmarkReadWriteEDS(b *testing.B) { + ctx, cancel := context.WithCancel(context.Background()) + b.Cleanup(cancel) + for originalDataWidth := 4; originalDataWidth <= 64; originalDataWidth *= 2 { + eds := share.RandEDS(b, originalDataWidth) + dah := da.NewDataAvailabilityHeader(eds) + b.Run(fmt.Sprintf("Writing %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + f := new(bytes.Buffer) + err := WriteEDS(ctx, eds, f) + require.NoError(b, err) + } + }) + b.Run(fmt.Sprintf("Reading %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + b.StopTimer() + f := new(bytes.Buffer) + _ = WriteEDS(ctx, eds, f) + b.StartTimer() + _, err := ReadEDS(ctx, f, dah) + require.NoError(b, err) + } + }) + } +} + func writeRandomEDS(t *testing.T) *rsmt2d.ExtendedDataSquare { t.Helper() ctx, cancel := context.WithCancel(context.Background()) diff --git a/share/test_helpers.go b/share/test_helpers.go index 597989c9b9..5e2310f6ae 100644 --- a/share/test_helpers.go +++ b/share/test_helpers.go @@ -4,7 +4,6 @@ import ( "bytes" mrand "math/rand" "sort" - "testing" "github.com/stretchr/testify/require" @@ -32,8 +31,9 @@ func EqualEDS(a *rsmt2d.ExtendedDataSquare, b *rsmt2d.ExtendedDataSquare) bool { return true } -// RandEDS generates EDS filled with the random data with the given size for original square. -func RandEDS(t *testing.T, size int) *rsmt2d.ExtendedDataSquare { +// RandEDS generates EDS filled with the random data with the given size for original square. It uses require.TestingT +// to be able to take both a *testing.T and a *testing.B. +func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { shares := RandShares(t, size*size) // create the nmt wrapper to generate row and col commitments tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)) @@ -43,10 +43,12 @@ func RandEDS(t *testing.T, size int) *rsmt2d.ExtendedDataSquare { return eds } -// RandShares generate 'total' amount of shares filled with random data. -func RandShares(t *testing.T, total int) []Share { +// RandShares generate 'total' amount of shares filled with random data. It uses require.TestingT to be able to take +// both a *testing.T and a *testing.B. +func RandShares(t require.TestingT, total int) []Share { if total&(total-1) != 0 { - t.Fatal("Namespace total must be power of 2") + t.Errorf("Namespace total must be power of 2: %d", total) + t.FailNow() } shares := make([]Share, total) From 354048a8ff26acba3a023932ded3bee8253ef5dc Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 26 Oct 2022 16:36:27 +0200 Subject: [PATCH 0156/1008] feat(ipld): integration test for namespace hasher (#1256) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- .../availability/full/reconstruction_test.go | 10 +- share/availability/full/testing.go | 12 +- share/availability/light/testing.go | 10 +- share/availability/test/corrupt_data.go | 126 ++++++++++++++++++ share/availability/test/testing.go | 96 ++++--------- share/ipld/corrupted_data_test.go | 47 +++++++ 6 files changed, 217 insertions(+), 84 deletions(-) create mode 100644 share/availability/test/corrupt_data.go create mode 100644 share/ipld/corrupted_data_test.go diff --git a/share/availability/full/reconstruction_test.go b/share/availability/full/reconstruction_test.go index 74fc67baab..adde01523d 100644 --- a/share/availability/full/reconstruction_test.go +++ b/share/availability/full/reconstruction_test.go @@ -47,7 +47,7 @@ func TestShareAvailable_OneFullNode(t *testing.T) { return full.SharesAvailable(errCtx, root) }) - lights := make([]*availability_test.Node, lightNodes) + lights := make([]*availability_test.TestNode, lightNodes) for i := 0; i < len(lights); i++ { lights[i] = light.Node(net) go func(i int) { @@ -112,7 +112,9 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { }) // create light nodes and start sampling for them immediately - lights1, lights2 := make([]*availability_test.Node, lightNodes/2), make([]*availability_test.Node, lightNodes/2) + lights1, lights2 := make( + []*availability_test.TestNode, lightNodes/2), + make([]*availability_test.TestNode, lightNodes/2) for i := 0; i < len(lights1); i++ { lights1[i] = light.Node(net) go func(i int) { @@ -208,7 +210,9 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { }) // create light nodes and start sampling for them immediately - lights1, lights2 := make([]*availability_test.Node, lightNodes/2), make([]*availability_test.Node, lightNodes/2) + lights1, lights2 := make( + []*availability_test.TestNode, lightNodes/2), + make([]*availability_test.TestNode, lightNodes/2) for i := 0; i < len(lights1); i++ { lights1[i] = light.Node(net) go func(i int) { diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index dad182a8d3..f072671df0 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -23,14 +23,14 @@ func RandServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.R } // RandNode creates a Full Node filled with a random block of the given size. -func RandNode(dn *availability_test.DagNet, squareSize int) (*availability_test.Node, *share.Root) { +func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_test.TestNode, *share.Root) { nd := Node(dn) return nd, availability_test.RandFillBS(dn.T, squareSize, nd.BlockService) } // Node creates a new empty Full Node. -func Node(dn *availability_test.DagNet) *availability_test.Node { - nd := dn.Node() +func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { + nd := dn.NewTestNode() nd.ShareService = service.NewShareService(nd.BlockService, TestAvailability(nd.BlockService)) return nd } @@ -39,9 +39,3 @@ func TestAvailability(bServ blockservice.BlockService) *ShareAvailability { disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) return NewShareAvailability(bServ, disc) } - -func SubNetNode(sn *availability_test.SubNet) *availability_test.Node { - nd := Node(sn.DagNet) - sn.AddNode(nd) - return nd -} diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go index dc01d48234..9a63198b3a 100644 --- a/share/availability/light/testing.go +++ b/share/availability/light/testing.go @@ -31,14 +31,14 @@ func RandService() (*service.ShareService, blockservice.BlockService) { } // RandNode creates a Light Node filled with a random block of the given size. -func RandNode(dn *availability_test.DagNet, squareSize int) (*availability_test.Node, *share.Root) { +func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_test.TestNode, *share.Root) { nd := Node(dn) return nd, availability_test.RandFillBS(dn.T, squareSize, nd.BlockService) } // Node creates a new empty Light Node. -func Node(dn *availability_test.DagNet) *availability_test.Node { - nd := dn.Node() +func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { + nd := dn.NewTestNode() nd.ShareService = service.NewShareService(nd.BlockService, TestAvailability(nd.BlockService)) return nd } @@ -48,8 +48,8 @@ func TestAvailability(bServ blockservice.BlockService) *ShareAvailability { return NewShareAvailability(bServ, disc) } -func SubNetNode(sn *availability_test.SubNet) *availability_test.Node { - nd := Node(sn.DagNet) +func SubNetNode(sn *availability_test.SubNet) *availability_test.TestNode { + nd := Node(sn.TestDagNet) sn.AddNode(nd) return nd } diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go new file mode 100644 index 0000000000..6969c24728 --- /dev/null +++ b/share/availability/test/corrupt_data.go @@ -0,0 +1,126 @@ +package availability_test + +import ( + "context" + "fmt" + mrand "math/rand" + "testing" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + blockstore "github.com/ipfs/go-ipfs-blockstore" +) + +var _ blockstore.Blockstore = (*FraudulentBlockstore)(nil) + +// CorruptBlock is a block where the cid doesn't match the data. It fulfills the blocks.Block interface. +type CorruptBlock struct { + cid cid.Cid + data []byte +} + +func (b *CorruptBlock) RawData() []byte { + return b.data +} + +func (b *CorruptBlock) Cid() cid.Cid { + return b.cid +} + +func (b *CorruptBlock) String() string { + return fmt.Sprintf("[Block %s]", b.Cid()) +} + +func (b *CorruptBlock) Loggable() map[string]interface{} { + return map[string]interface{}{ + "block": b.Cid().String(), + } +} + +func NewCorruptBlock(data []byte, fakeCID cid.Cid) *CorruptBlock { + return &CorruptBlock{ + fakeCID, + data, + } +} + +// FraudulentBlockstore is a mock blockstore.Blockstore that saves both corrupted and original data for every block it +// receives. If FraudulentBlockstore.Attacking is true, it will serve the corrupted data on requests. +type FraudulentBlockstore struct { + ds.Datastore + Attacking bool +} + +func (fb FraudulentBlockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + return false, nil +} + +func (fb FraudulentBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + key := cid.String() + if fb.Attacking { + key = "corrupt" + key + } + + data, err := fb.Datastore.Get(ctx, ds.NewKey(key)) + if err != nil { + return nil, err + } + return NewCorruptBlock(data, cid), nil +} + +func (fb FraudulentBlockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + key := cid.String() + if fb.Attacking { + key = "corrupt" + key + } + + return fb.Datastore.GetSize(ctx, ds.NewKey(key)) +} + +func (fb FraudulentBlockstore) Put(ctx context.Context, block blocks.Block) error { + err := fb.Datastore.Put(ctx, ds.NewKey(block.Cid().String()), block.RawData()) + if err != nil { + return err + } + + // create data that doesn't match the CID with arbitrary lengths between 0 and len(block.RawData())*2 + corrupted := make([]byte, mrand.Int()%(len(block.RawData())*2)) + mrand.Read(corrupted) + return fb.Datastore.Put(ctx, ds.NewKey("corrupt"+block.Cid().String()), corrupted) +} + +func (fb FraudulentBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { + for _, b := range blocks { + err := fb.Put(ctx, b) + if err != nil { + return err + } + } + return nil +} + +func (fb FraudulentBlockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { + panic("implement me") +} + +func (fb FraudulentBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + panic("implement me") +} + +func (fb FraudulentBlockstore) HashOnRead(enabled bool) { + panic("implement me") +} + +// MockNode creates a TestNode that uses a FraudulentBlockstore to simulate serving corrupted data. +func MockNode(t *testing.T, net *TestDagNet) (*TestNode, *FraudulentBlockstore) { + t.Helper() + dstore := dssync.MutexWrap(ds.NewMapDatastore()) + mockBS := &FraudulentBlockstore{ + Datastore: dstore, + Attacking: false, + } + provider := net.NewTestNodeWithBlockstore(dstore, mockBS) + return provider, mockBS +} diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 1e0bc7fc54..5cc78168e0 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -1,7 +1,6 @@ package availability_test import ( - "bytes" "context" "testing" @@ -37,15 +36,15 @@ func FillBS(t *testing.T, bServ blockservice.BlockService, shares []share.Share) return &dah } -type Node struct { - net *DagNet +type TestNode struct { + net *TestDagNet *service.ShareService blockservice.BlockService host.Host } // ClearStorage cleans up the storage of the node. -func (n *Node) ClearStorage() { +func (n *TestNode) ClearStorage() { keys, err := n.Blockstore().AllKeysChan(n.net.ctx) require.NoError(n.net.T, err) @@ -55,29 +54,27 @@ func (n *Node) ClearStorage() { } } -type DagNet struct { +type TestDagNet struct { ctx context.Context T *testing.T net mocknet.Mocknet - nodes []*Node + nodes []*TestNode } -// NewTestDAGNet creates a new testing swarm utility to spawn different nodes -// and test how they interact and/or exchange data. -func NewTestDAGNet(ctx context.Context, t *testing.T) *DagNet { - return &DagNet{ +// NewTestDAGNet creates a new testing swarm utility to spawn different nodes and test how they interact and/or exchange +// data. +func NewTestDAGNet(ctx context.Context, t *testing.T) *TestDagNet { + return &TestDagNet{ ctx: ctx, T: t, net: mocknet.New(), } } -// Node create a plain network node that can serve and request data. -func (dn *DagNet) Node() *Node { +// NewTestNodeWithBlockstore creates a new plain TestNode with the given blockstore that can serve and request data. +func (dn *TestDagNet) NewTestNodeWithBlockstore(dstore ds.Datastore, bstore blockstore.Blockstore) *TestNode { hst, err := dn.net.GenPeer() require.NoError(dn.T, err) - dstore := dssync.MutexWrap(ds.NewMapDatastore()) - bstore := blockstore.NewBlockstore(dstore) routing := offline.NewOfflineRouter(dstore, record.NamespacedValidator{}) bs := bitswap.New( dn.ctx, @@ -90,7 +87,7 @@ func (dn *DagNet) Node() *Node { bitswap.SetSimulateDontHavesOnTimeout(false), bitswap.SetSendDontHaves(false), ) - nd := &Node{ + nd := &TestNode{ net: dn, BlockService: blockservice.New(bstore, bs), Host: hst, @@ -99,8 +96,15 @@ func (dn *DagNet) Node() *Node { return nd } -// ConnectAll connects all the peers on registered on the DagNet. -func (dn *DagNet) ConnectAll() { +// NewTestNode creates a plain network node that can serve and request data. +func (dn *TestDagNet) NewTestNode() *TestNode { + dstore := dssync.MutexWrap(ds.NewMapDatastore()) + bstore := blockstore.NewBlockstore(dstore) + return dn.NewTestNodeWithBlockstore(dstore, bstore) +} + +// ConnectAll connects all the peers on registered on the TestDagNet. +func (dn *TestDagNet) ConnectAll() { err := dn.net.LinkAll() require.NoError(dn.T, err) @@ -108,8 +112,8 @@ func (dn *DagNet) ConnectAll() { require.NoError(dn.T, err) } -// Connect connects two given peer. -func (dn *DagNet) Connect(peerA, peerB peer.ID) { +// Connect connects two given peers. +func (dn *TestDagNet) Connect(peerA, peerB peer.ID) { _, err := dn.net.LinkPeers(peerA, peerB) require.NoError(dn.T, err) _, err = dn.net.ConnectPeers(peerA, peerB) @@ -118,8 +122,8 @@ func (dn *DagNet) Connect(peerA, peerB peer.ID) { // Disconnect disconnects two peers. // It does a hard disconnect, meaning that disconnected peers won't be able to reconnect on their own -// but only with DagNet.Connect or DagNet.ConnectAll. -func (dn *DagNet) Disconnect(peerA, peerB peer.ID) { +// but only with DagNet.Connect or TestDagNet.ConnectAll. +func (dn *TestDagNet) Disconnect(peerA, peerB peer.ID) { err := dn.net.UnlinkPeers(peerA, peerB) require.NoError(dn.T, err) err = dn.net.DisconnectPeers(peerA, peerB) @@ -127,15 +131,15 @@ func (dn *DagNet) Disconnect(peerA, peerB peer.ID) { } type SubNet struct { - *DagNet - nodes []*Node + *TestDagNet + nodes []*TestNode } -func (dn *DagNet) SubNet() *SubNet { +func (dn *TestDagNet) SubNet() *SubNet { return &SubNet{dn, nil} } -func (sn *SubNet) AddNode(nd *Node) { +func (sn *SubNet) AddNode(nd *TestNode) { sn.nodes = append(sn.nodes, nd) } @@ -154,45 +158,3 @@ func (sn *SubNet) ConnectAll() { } } } - -type TestBrokenAvailability struct { - Root *share.Root -} - -// NewTestBrokenAvailability returns an instance of Availability that -// allows for testing error cases during sampling. -// -// If the share.Root field is empty, it will return share.ErrNotAvailable on every call -// to SharesAvailable. Otherwise, it will only return ErrNotAvailable if the -// given Root hash matches the stored Root hash. -func NewTestBrokenAvailability() share.Availability { - return &TestBrokenAvailability{} -} - -func (b *TestBrokenAvailability) SharesAvailable(_ context.Context, root *share.Root) error { - if b.Root == nil || bytes.Equal(b.Root.Hash(), root.Hash()) { - return share.ErrNotAvailable - } - return nil -} - -func (b *TestBrokenAvailability) ProbabilityOfAvailability() float64 { - return 0 -} - -type TestSuccessfulAvailability struct { -} - -// NewTestSuccessfulAvailability returns an Availability that always -// returns successfully when SharesAvailable is called. -func NewTestSuccessfulAvailability() share.Availability { - return &TestSuccessfulAvailability{} -} - -func (tsa *TestSuccessfulAvailability) SharesAvailable(context.Context, *share.Root) error { - return nil -} - -func (tsa *TestSuccessfulAvailability) ProbabilityOfAvailability() float64 { - return 0 -} diff --git a/share/ipld/corrupted_data_test.go b/share/ipld/corrupted_data_test.go new file mode 100644 index 0000000000..59ee922adf --- /dev/null +++ b/share/ipld/corrupted_data_test.go @@ -0,0 +1,47 @@ +package ipld_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/full" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/service" +) + +// sharesAvailableTimeout is an arbitrarily picked interval of time in which a TestNode is expected to be able to +// complete a SharesAvailable request from a connected peer in a TestDagNet. +const sharesAvailableTimeout = 2 * time.Second + +// TestNamespaceHasher_CorruptedData is an integration test that verifies that the NamespaceHasher of a recipient of +// corrupted data will not panic, and will throw away the corrupted data. +func TestNamespaceHasher_CorruptedData(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + net := availability_test.NewTestDAGNet(ctx, t) + + requestor := full.Node(net) + provider, mockBS := availability_test.MockNode(t, net) + provider.ShareService = service.NewShareService(provider.BlockService, full.TestAvailability(provider.BlockService)) + net.ConnectAll() + + // before the provider starts attacking, we should be able to retrieve successfully. We pass a size 16 block, but + // this is not important to the test and any valid block size behaves the same. + root := availability_test.RandFillBS(t, 16, provider.BlockService) + getCtx, cancelGet := context.WithTimeout(ctx, sharesAvailableTimeout) + t.Cleanup(cancelGet) + err := requestor.SharesAvailable(getCtx, root) + require.NoError(t, err) + + // clear the storage of the requester so that it must retrieve again, then start attacking + requestor.ClearStorage() + mockBS.Attacking = true + getCtx, cancelGet = context.WithTimeout(ctx, sharesAvailableTimeout) + t.Cleanup(cancelGet) + err = requestor.SharesAvailable(getCtx, root) + require.ErrorIs(t, err, share.ErrNotAvailable) +} From 2fde72c4184f069fd841a4d37e1d772a71528af6 Mon Sep 17 00:00:00 2001 From: Josh Bowen Date: Wed, 26 Oct 2022 16:37:04 +0200 Subject: [PATCH 0157/1008] add semver tags to docker image builds (#1277) --- .github/workflows/docker-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 424d319369..c844acc5a4 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -37,6 +37,9 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=sha + type=semver,pattern={{major}}.{{minor}}.{{patch}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 From ff35df8d46039b341db229ab12b69d78b2ae47d0 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 28 Oct 2022 16:01:34 +0300 Subject: [PATCH 0158/1008] refactor(share): move bad encoding fraud proof to share pkg (#1241) Resolves https://github.com/celestiaorg/celestia-node/issues/1100 --- das/daser.go | 5 +- fraud/bad_encoding_test.go | 29 -- fraud/pb/proof.pb.go | 413 +---------------- fraud/pb/proof.proto | 15 - fraud/service_test.go | 52 +-- fraud/testing.go | 53 ++- .../eds/byzantine}/bad_encoding.go | 24 +- share/{ => eds/byzantine}/byzantine.go | 3 +- share/{ => eds/byzantine}/pb/share.pb.go | 429 ++++++++++++++++-- share/eds/byzantine/pb/share.proto | 28 ++ share/eds/byzantine/share_proof.go | 105 +++++ share/eds/byzantine/share_proof_test.go | 81 ++++ share/eds/retriever.go | 3 +- share/eds/retriever_test.go | 36 +- share/get.go | 29 -- share/get_test.go | 67 --- share/pb/share.proto | 17 - share/share.go | 67 --- 18 files changed, 733 insertions(+), 723 deletions(-) delete mode 100644 fraud/bad_encoding_test.go rename {fraud => share/eds/byzantine}/bad_encoding.go (91%) rename share/{ => eds/byzantine}/byzantine.go (98%) rename share/{ => eds/byzantine}/pb/share.pb.go (56%) create mode 100644 share/eds/byzantine/pb/share.proto create mode 100644 share/eds/byzantine/share_proof.go create mode 100644 share/eds/byzantine/share_proof_test.go delete mode 100644 share/pb/share.proto diff --git a/das/daser.go b/das/daser.go index 8ca54d594e..a1e71c0b63 100644 --- a/das/daser.go +++ b/das/daser.go @@ -13,6 +13,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) var log = logging.Logger("das") @@ -153,10 +154,10 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { if err == context.Canceled { return err } - var byzantineErr *share.ErrByzantine + var byzantineErr *byzantine.ErrByzantine if errors.As(err, &byzantineErr) { log.Warn("Propagating proof...") - sendErr := d.bcast.Broadcast(ctx, fraud.CreateBadEncodingProof(h.Hash(), uint64(h.Height), byzantineErr)) + sendErr := d.bcast.Broadcast(ctx, byzantine.CreateBadEncodingProof(h.Hash(), uint64(h.Height), byzantineErr)) if sendErr != nil { log.Errorw("fraud proof propagating failed", "err", sendErr) } diff --git a/fraud/bad_encoding_test.go b/fraud/bad_encoding_test.go deleted file mode 100644 index ebb5a95692..0000000000 --- a/fraud/bad_encoding_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package fraud - -import ( - "context" - "errors" - "testing" - "time" - - mdutils "github.com/ipfs/go-merkledag/test" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/share" -) - -func TestFraudProofValidation(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer t.Cleanup(cancel) - bServ := mdutils.Bserv() - _, store := createService(t, false) - h, err := store.GetByHeight(ctx, 1) - require.NoError(t, err) - - faultDAH, err := generateByzantineError(ctx, t, h, bServ) - var errByz *share.ErrByzantine - require.True(t, errors.As(err, &errByz)) - p := CreateBadEncodingProof([]byte("hash"), uint64(faultDAH.Height), errByz) - err = p.Validate(faultDAH) - require.NoError(t, err) -} diff --git a/fraud/pb/proof.pb.go b/fraud/pb/proof.pb.go index 8ca49c4410..d5e992ab63 100644 --- a/fraud/pb/proof.pb.go +++ b/fraud/pb/proof.pb.go @@ -5,13 +5,10 @@ package fraud_pb import ( fmt "fmt" + proto "github.com/gogo/protobuf/proto" io "io" math "math" math_bits "math/bits" - - proto "github.com/gogo/protobuf/proto" - - pb "github.com/celestiaorg/celestia-node/share/pb" ) // Reference imports to suppress errors if they are not otherwise used. @@ -25,107 +22,6 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -type Axis int32 - -const ( - Axis_ROW Axis = 0 - Axis_COL Axis = 1 -) - -var Axis_name = map[int32]string{ - 0: "ROW", - 1: "COL", -} - -var Axis_value = map[string]int32{ - "ROW": 0, - "COL": 1, -} - -func (x Axis) String() string { - return proto.EnumName(Axis_name, int32(x)) -} - -func (Axis) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{0} -} - -type BadEncoding struct { - HeaderHash []byte `protobuf:"bytes,1,opt,name=HeaderHash,proto3" json:"HeaderHash,omitempty"` - Height uint64 `protobuf:"varint,2,opt,name=Height,proto3" json:"Height,omitempty"` - Shares []*pb.Share `protobuf:"bytes,3,rep,name=Shares,proto3" json:"Shares,omitempty"` - Index uint32 `protobuf:"varint,4,opt,name=Index,proto3" json:"Index,omitempty"` - Axis Axis `protobuf:"varint,5,opt,name=Axis,proto3,enum=fraud.pb.Axis" json:"Axis,omitempty"` -} - -func (m *BadEncoding) Reset() { *m = BadEncoding{} } -func (m *BadEncoding) String() string { return proto.CompactTextString(m) } -func (*BadEncoding) ProtoMessage() {} -func (*BadEncoding) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{0} -} -func (m *BadEncoding) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *BadEncoding) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_BadEncoding.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *BadEncoding) XXX_Merge(src proto.Message) { - xxx_messageInfo_BadEncoding.Merge(m, src) -} -func (m *BadEncoding) XXX_Size() int { - return m.Size() -} -func (m *BadEncoding) XXX_DiscardUnknown() { - xxx_messageInfo_BadEncoding.DiscardUnknown(m) -} - -var xxx_messageInfo_BadEncoding proto.InternalMessageInfo - -func (m *BadEncoding) GetHeaderHash() []byte { - if m != nil { - return m.HeaderHash - } - return nil -} - -func (m *BadEncoding) GetHeight() uint64 { - if m != nil { - return m.Height - } - return 0 -} - -func (m *BadEncoding) GetShares() []*pb.Share { - if m != nil { - return m.Shares - } - return nil -} - -func (m *BadEncoding) GetIndex() uint32 { - if m != nil { - return m.Index - } - return 0 -} - -func (m *BadEncoding) GetAxis() Axis { - if m != nil { - return m.Axis - } - return Axis_ROW -} - type FraudMessageRequest struct { RequestedProofType []string `protobuf:"bytes,1,rep,name=RequestedProofType,proto3" json:"RequestedProofType,omitempty"` } @@ -134,7 +30,7 @@ func (m *FraudMessageRequest) Reset() { *m = FraudMessageRequest{} } func (m *FraudMessageRequest) String() string { return proto.CompactTextString(m) } func (*FraudMessageRequest) ProtoMessage() {} func (*FraudMessageRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{1} + return fileDescriptor_318cb87a8bb2d394, []int{0} } func (m *FraudMessageRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -179,7 +75,7 @@ func (m *ProofResponse) Reset() { *m = ProofResponse{} } func (m *ProofResponse) String() string { return proto.CompactTextString(m) } func (*ProofResponse) ProtoMessage() {} func (*ProofResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{2} + return fileDescriptor_318cb87a8bb2d394, []int{1} } func (m *ProofResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -230,7 +126,7 @@ func (m *FraudMessageResponse) Reset() { *m = FraudMessageResponse{} } func (m *FraudMessageResponse) String() string { return proto.CompactTextString(m) } func (*FraudMessageResponse) ProtoMessage() {} func (*FraudMessageResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{3} + return fileDescriptor_318cb87a8bb2d394, []int{2} } func (m *FraudMessageResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -267,8 +163,6 @@ func (m *FraudMessageResponse) GetProofs() []*ProofResponse { } func init() { - proto.RegisterEnum("fraud.pb.Axis", Axis_name, Axis_value) - proto.RegisterType((*BadEncoding)(nil), "fraud.pb.BadEncoding") proto.RegisterType((*FraudMessageRequest)(nil), "fraud.pb.FraudMessageRequest") proto.RegisterType((*ProofResponse)(nil), "fraud.pb.ProofResponse") proto.RegisterType((*FraudMessageResponse)(nil), "fraud.pb.FraudMessageResponse") @@ -277,88 +171,20 @@ func init() { func init() { proto.RegisterFile("fraud/pb/proof.proto", fileDescriptor_318cb87a8bb2d394) } var fileDescriptor_318cb87a8bb2d394 = []byte{ - // 350 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0xcd, 0x4a, 0xeb, 0x40, - 0x18, 0xcd, 0x34, 0x69, 0xee, 0xed, 0xd7, 0x1f, 0xca, 0xb4, 0xdc, 0x3b, 0xb8, 0x08, 0x21, 0x0b, - 0x19, 0x5c, 0x4c, 0xa1, 0xae, 0x5c, 0x5a, 0xa9, 0x56, 0x50, 0x2a, 0xa3, 0xe8, 0x7a, 0x62, 0xa6, - 0x6d, 0xa0, 0x24, 0x63, 0xa6, 0x85, 0xfa, 0x16, 0xbe, 0x85, 0xaf, 0xe2, 0xb2, 0x4b, 0x97, 0xd2, - 0xbe, 0x88, 0xcc, 0x34, 0xa2, 0x05, 0x77, 0xe7, 0x27, 0x39, 0xe7, 0x7c, 0x0c, 0x74, 0x27, 0x85, - 0x58, 0x26, 0x3d, 0x15, 0xf7, 0x54, 0x91, 0xe7, 0x13, 0xa6, 0x8a, 0x7c, 0x91, 0xe3, 0xbf, 0x56, - 0x65, 0x2a, 0x3e, 0xe8, 0xa4, 0x6a, 0x6e, 0x6d, 0x3d, 0x13, 0x85, 0xdc, 0xd9, 0xd1, 0x2b, 0x82, - 0xfa, 0x40, 0x24, 0xc3, 0xec, 0x31, 0x4f, 0xd2, 0x6c, 0x8a, 0x03, 0x80, 0x91, 0x14, 0x89, 0x2c, - 0x46, 0x42, 0xcf, 0x08, 0x0a, 0x11, 0x6d, 0xf0, 0x1f, 0x0a, 0xfe, 0x07, 0xfe, 0x48, 0xa6, 0xd3, - 0xd9, 0x82, 0x54, 0x42, 0x44, 0x3d, 0x5e, 0x32, 0x7c, 0x08, 0xfe, 0xad, 0x89, 0xd5, 0xc4, 0x0d, - 0x5d, 0x5a, 0xef, 0xb7, 0x98, 0x69, 0x63, 0x2a, 0x66, 0x56, 0xe6, 0xa5, 0x8b, 0xbb, 0x50, 0xbd, - 0xcc, 0x12, 0xb9, 0x22, 0x5e, 0x88, 0x68, 0x93, 0xef, 0x08, 0x8e, 0xc0, 0x3b, 0x5d, 0xa5, 0x9a, - 0x54, 0x43, 0x44, 0x5b, 0xfd, 0x16, 0xfb, 0xda, 0xcc, 0xc4, 0x2a, 0xd5, 0xdc, 0x7a, 0xd1, 0x10, - 0x3a, 0xe7, 0x46, 0xbe, 0x96, 0x5a, 0x8b, 0xa9, 0xe4, 0xf2, 0x69, 0x29, 0xf5, 0x02, 0x33, 0xc0, - 0x25, 0x94, 0xc9, 0x8d, 0xb9, 0xfb, 0xee, 0x59, 0x49, 0x82, 0x42, 0x97, 0xd6, 0xf8, 0x2f, 0x4e, - 0x74, 0x02, 0x4d, 0x4b, 0xb8, 0xd4, 0x2a, 0xcf, 0xb4, 0xc4, 0x18, 0xbc, 0xf2, 0x17, 0x44, 0x6b, - 0xdc, 0x62, 0xb3, 0xf2, 0x5e, 0xcc, 0x97, 0x92, 0x54, 0x42, 0x97, 0x36, 0xf8, 0x8e, 0x44, 0x17, - 0xd0, 0xdd, 0x5f, 0x50, 0x26, 0xf4, 0xc0, 0xb7, 0x91, 0xda, 0xd6, 0xd6, 0xfb, 0xff, 0xbf, 0xf7, - 0xef, 0x55, 0xf1, 0xf2, 0xb3, 0x23, 0x02, 0x9e, 0x39, 0x0c, 0xff, 0x01, 0x97, 0x8f, 0x1f, 0xda, - 0x8e, 0x01, 0x67, 0xe3, 0xab, 0x36, 0x1a, 0x90, 0xb7, 0x4d, 0x80, 0xd6, 0x9b, 0x00, 0x7d, 0x6c, - 0x02, 0xf4, 0xb2, 0x0d, 0x9c, 0xf5, 0x36, 0x70, 0xde, 0xb7, 0x81, 0x13, 0xfb, 0xf6, 0xbd, 0x8e, - 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x5b, 0x6e, 0x3b, 0x05, 0xe6, 0x01, 0x00, 0x00, -} - -func (m *BadEncoding) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *BadEncoding) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *BadEncoding) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Axis != 0 { - i = encodeVarintProof(dAtA, i, uint64(m.Axis)) - i-- - dAtA[i] = 0x28 - } - if m.Index != 0 { - i = encodeVarintProof(dAtA, i, uint64(m.Index)) - i-- - dAtA[i] = 0x20 - } - if len(m.Shares) > 0 { - for iNdEx := len(m.Shares) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Shares[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintProof(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - } - if m.Height != 0 { - i = encodeVarintProof(dAtA, i, uint64(m.Height)) - i-- - dAtA[i] = 0x10 - } - if len(m.HeaderHash) > 0 { - i -= len(m.HeaderHash) - copy(dAtA[i:], m.HeaderHash) - i = encodeVarintProof(dAtA, i, uint64(len(m.HeaderHash))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil + // 203 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2b, 0x4a, 0x2c, + 0x4d, 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0x28, 0xca, 0xcf, 0x4f, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0xe2, 0x00, 0x8b, 0xea, 0x15, 0x24, 0x29, 0xb9, 0x72, 0x09, 0xbb, 0x81, 0xd8, 0xbe, 0xa9, + 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x7a, 0x5c, 0x42, + 0x50, 0x66, 0x6a, 0x4a, 0x00, 0x48, 0x63, 0x48, 0x65, 0x41, 0xaa, 0x04, 0xa3, 0x02, 0xb3, 0x06, + 0x67, 0x10, 0x16, 0x19, 0x25, 0x4b, 0x2e, 0x5e, 0x30, 0x27, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, + 0x38, 0x55, 0x48, 0x88, 0x8b, 0x05, 0xaa, 0x85, 0x51, 0x83, 0x33, 0x08, 0xcc, 0x16, 0x12, 0xe1, + 0x62, 0x0d, 0x4b, 0xcc, 0x29, 0x4d, 0x95, 0x60, 0x52, 0x60, 0xd6, 0xe0, 0x09, 0x82, 0x70, 0x94, + 0xdc, 0xb9, 0x44, 0x50, 0x5d, 0x00, 0x35, 0x41, 0x9f, 0x8b, 0x0d, 0x6c, 0x64, 0x31, 0xd8, 0x5a, + 0x6e, 0x23, 0x71, 0x3d, 0x98, 0xa3, 0xf5, 0x50, 0xac, 0x0a, 0x82, 0x2a, 0x73, 0x92, 0x38, 0xf1, + 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, + 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0xb0, 0xaf, 0x8d, 0x01, 0x01, 0x00, + 0x00, 0xff, 0xff, 0xa2, 0xc2, 0x65, 0x25, 0x0d, 0x01, 0x00, 0x00, } func (m *FraudMessageRequest) Marshal() (dAtA []byte, err error) { @@ -480,34 +306,6 @@ func encodeVarintProof(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *BadEncoding) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.HeaderHash) - if l > 0 { - n += 1 + l + sovProof(uint64(l)) - } - if m.Height != 0 { - n += 1 + sovProof(uint64(m.Height)) - } - if len(m.Shares) > 0 { - for _, e := range m.Shares { - l = e.Size() - n += 1 + l + sovProof(uint64(l)) - } - } - if m.Index != 0 { - n += 1 + sovProof(uint64(m.Index)) - } - if m.Axis != 0 { - n += 1 + sovProof(uint64(m.Axis)) - } - return n -} - func (m *FraudMessageRequest) Size() (n int) { if m == nil { return 0 @@ -563,181 +361,6 @@ func sovProof(x uint64) (n int) { func sozProof(x uint64) (n int) { return sovProof(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *BadEncoding) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BadEncoding: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BadEncoding: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field HeaderHash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthProof - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthProof - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.HeaderHash = append(m.HeaderHash[:0], dAtA[iNdEx:postIndex]...) - if m.HeaderHash == nil { - m.HeaderHash = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) - } - m.Height = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Height |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthProof - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthProof - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Shares = append(m.Shares, &pb.Share{}) - if err := m.Shares[len(m.Shares)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) - } - m.Index = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Index |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Axis", wireType) - } - m.Axis = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Axis |= Axis(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipProof(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProof - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *FraudMessageRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/fraud/pb/proof.proto b/fraud/pb/proof.proto index e68d52193e..95715c89c8 100644 --- a/fraud/pb/proof.proto +++ b/fraud/pb/proof.proto @@ -2,21 +2,6 @@ syntax = "proto3"; package fraud.pb; -import "ipld/pb/share.proto"; - -enum axis { - ROW = 0; - COL = 1; -} - -message BadEncoding { - bytes HeaderHash = 1; - uint64 Height = 2; - repeated ipld.pb.Share Shares = 3; - uint32 Index = 4; - axis Axis = 5; -} - message FraudMessageRequest { repeated string RequestedProofType = 1; } diff --git a/fraud/service_test.go b/fraud/service_test.go index 2b0a6f35b4..184380c2e5 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -6,8 +6,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" - "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/sync" @@ -15,6 +13,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/header" ) @@ -22,7 +21,7 @@ import ( func TestService_Subscribe(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) t.Cleanup(cancel) - s, _ := createService(t, false) + s, _ := CreateTestService(t, false) proof := newValidProof() require.NoError(t, s.Start(ctx)) _, err := s.Subscribe(proof.Type()) @@ -30,7 +29,7 @@ func TestService_Subscribe(t *testing.T) { } func TestService_SubscribeFails(t *testing.T) { - s, _ := createService(t, false) + s, _ := CreateTestService(t, false) proof := newValidProof() _, err := s.Subscribe(proof.Type()) require.Error(t, err) @@ -39,7 +38,7 @@ func TestService_SubscribeFails(t *testing.T) { func TestService_BroadcastFails(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) t.Cleanup(cancel) - s, _ := createService(t, false) + s, _ := CreateTestService(t, false) p := newValidProof() require.Error(t, s.Broadcast(ctx, p)) } @@ -48,7 +47,7 @@ func TestService_Broadcast(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) t.Cleanup(cancel) - s, _ := createService(t, false) + s, _ := CreateTestService(t, false) proof := newValidProof() require.NoError(t, s.Start(ctx)) subs, err := s.Subscribe(proof.Type()) @@ -94,7 +93,7 @@ func TestService_processIncoming(t *testing.T) { bin, err := test.proof.MarshalBinary() require.NoError(t, err) // create first fraud service that will broadcast incorrect Fraud Proof - service, _ := createServiceWithHost(ctx, t, net.Hosts()[0], false) + service, _ := createTestServiceWithHost(ctx, t, net.Hosts()[0], false) msg := &pubsub.Message{ Message: &pubsubpb.Message{ Data: bin, @@ -118,7 +117,7 @@ func TestService_ReGossiping(t *testing.T) { require.NoError(t, err) // create first fraud service that will broadcast incorrect Fraud Proof - pserviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0], false) + pserviceA, _ := createTestServiceWithHost(ctx, t, net.Hosts()[0], false) require.NoError(t, err) // create pub sub in order to listen for Fraud Proof psB, err := pubsub.NewGossipSub(ctx, net.Hosts()[1], // -> B @@ -197,7 +196,7 @@ func TestService_Get(t *testing.T) { proof := newValidProof() bin, err := proof.MarshalBinary() require.NoError(t, err) - pService, _ := createService(t, false) + pService, _ := CreateTestService(t, false) // try to fetch proof _, err = pService.Get(ctx, proof.Type()) // error is expected here because storage is empty @@ -219,8 +218,8 @@ func TestService_Sync(t *testing.T) { net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) - pserviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0], false) - pserviceB, _ := createServiceWithHost(ctx, t, net.Hosts()[1], true) + pserviceA, _ := createTestServiceWithHost(ctx, t, net.Hosts()[0], false) + pserviceB, _ := createTestServiceWithHost(ctx, t, net.Hosts()[1], true) proof := newValidProof() require.NoError(t, pserviceA.Start(ctx)) require.NoError(t, pserviceB.Start(ctx)) @@ -237,34 +236,3 @@ func TestService_Sync(t *testing.T) { _, err = subs.Proof(ctx) require.NoError(t, err) } - -func createService(t *testing.T, enabledSyncer bool) (*ProofService, *mockStore) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - t.Cleanup(cancel) - - // create mock network - net, err := mocknet.FullMeshLinked(1) - require.NoError(t, err) - return createServiceWithHost(ctx, t, net.Hosts()[0], enabledSyncer) -} - -func createServiceWithHost( - ctx context.Context, - t *testing.T, - host host.Host, - enabledSyncer bool, -) (*ProofService, *mockStore) { - // create pubsub for host - ps, err := pubsub.NewGossipSub(ctx, host, - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - store := createStore(t, 10) - return NewProofService( - ps, - host, - store.GetByHeight, - sync.MutexWrap(datastore.NewMapDatastore()), - enabledSyncer, - "private", - ), store -} diff --git a/fraud/testing.go b/fraud/testing.go index c066631585..9a8b64a0f1 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -5,10 +5,14 @@ import ( "encoding/json" "errors" "testing" + "time" - "github.com/celestiaorg/celestia-node/share/eds" - - "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + "github.com/libp2p/go-libp2p-core/host" + pubsub "github.com/libp2p/go-libp2p-pubsub" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/header" ) @@ -60,18 +64,6 @@ func (m *mockStore) GetByHeight(_ context.Context, height uint64) (*header.Exten func (m *mockStore) Close() error { return nil } -func generateByzantineError( - ctx context.Context, - t *testing.T, - h *header.ExtendedHeader, - bServ blockservice.BlockService, -) (*header.ExtendedHeader, error) { - faultHeader := header.CreateFraudExtHeader(t, h, bServ) - rtrv := eds.NewRetriever(bServ) - _, err := rtrv.Retrieve(ctx, faultHeader.DAH) - return faultHeader, err -} - const ( mockProofType ProofType = "mockProof" ) @@ -122,3 +114,34 @@ func (m *mockProof) MarshalBinary() (data []byte, err error) { func (m *mockProof) UnmarshalBinary(data []byte) error { return json.Unmarshal(data, m) } + +func CreateTestService(t *testing.T, enabledSyncer bool) (*ProofService, *mockStore) { //nolint:revive + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + t.Cleanup(cancel) + + // create mock network + net, err := mocknet.FullMeshLinked(1) + require.NoError(t, err) + return createTestServiceWithHost(ctx, t, net.Hosts()[0], enabledSyncer) +} + +func createTestServiceWithHost( + ctx context.Context, + t *testing.T, + host host.Host, + enabledSyncer bool, +) (*ProofService, *mockStore) { + // create pubsub for host + ps, err := pubsub.NewGossipSub(ctx, host, + pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) + require.NoError(t, err) + store := createStore(t, 10) + return NewProofService( + ps, + host, + store.GetByHeight, + sync.MutexWrap(datastore.NewMapDatastore()), + enabledSyncer, + "private", + ), store +} diff --git a/fraud/bad_encoding.go b/share/eds/byzantine/bad_encoding.go similarity index 91% rename from fraud/bad_encoding.go rename to share/eds/byzantine/bad_encoding.go index fefd4eba0b..122628d741 100644 --- a/fraud/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -1,4 +1,4 @@ -package fraud +package byzantine import ( "bytes" @@ -7,17 +7,15 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/wrapper" - - pb "github.com/celestiaorg/celestia-node/fraud/pb" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" - ipld_pb "github.com/celestiaorg/celestia-node/share/pb" "github.com/celestiaorg/rsmt2d" ) func init() { - Register(&BadEncodingProof{}) + fraud.Register(&BadEncodingProof{}) } type BadEncodingProof struct { @@ -26,7 +24,7 @@ type BadEncodingProof struct { // ShareWithProof contains all shares from row or col. // Shares that did not pass verification in rsmt2d will be nil. // For non-nil shares MerkleProofs are computed. - Shares []*share.ShareWithProof + Shares []*ShareWithProof // Index represents the row/col index where ErrByzantineRow/ErrByzantineColl occurred. Index uint32 // Axis represents the axis that verification failed on. @@ -38,8 +36,8 @@ type BadEncodingProof struct { func CreateBadEncodingProof( hash []byte, height uint64, - errByzantine *share.ErrByzantine, -) Proof { + errByzantine *ErrByzantine, +) fraud.Proof { return &BadEncodingProof{ headerHash: hash, @@ -51,8 +49,8 @@ func CreateBadEncodingProof( } // Type returns type of fraud proof. -func (p *BadEncodingProof) Type() ProofType { - return BadEncoding +func (p *BadEncodingProof) Type() fraud.ProofType { + return fraud.BadEncoding } // HeaderHash returns block hash. @@ -67,7 +65,7 @@ func (p *BadEncodingProof) Height() uint64 { // MarshalBinary converts BadEncodingProof to binary. func (p *BadEncodingProof) MarshalBinary() ([]byte, error) { - shares := make([]*ipld_pb.Share, 0, len(p.Shares)) + shares := make([]*pb.Share, 0, len(p.Shares)) for _, share := range p.Shares { shares = append(shares, share.ShareWithProofToProto()) } @@ -91,7 +89,7 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { befp := &BadEncodingProof{ headerHash: in.HeaderHash, BlockHeight: in.Height, - Shares: share.ProtoToShare(in.Shares), + Shares: ProtoToShare(in.Shares), Index: in.Index, Axis: rsmt2d.Axis(in.Axis), } diff --git a/share/byzantine.go b/share/eds/byzantine/byzantine.go similarity index 98% rename from share/byzantine.go rename to share/eds/byzantine/byzantine.go index 8e9e7f4b8d..5f13f06f17 100644 --- a/share/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -1,4 +1,4 @@ -package share +package byzantine import ( "context" @@ -7,7 +7,6 @@ import ( "github.com/ipfs/go-blockservice" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/rsmt2d" ) diff --git a/share/pb/share.pb.go b/share/eds/byzantine/pb/share.pb.go similarity index 56% rename from share/pb/share.pb.go rename to share/eds/byzantine/pb/share.pb.go index b3e32f8baa..dc89a62621 100644 --- a/share/pb/share.pb.go +++ b/share/eds/byzantine/pb/share.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ipld/pb/share.proto +// source: share/eds/byzantine/pb/share.proto package pb @@ -22,6 +22,31 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +type Axis int32 + +const ( + Axis_ROW Axis = 0 + Axis_COL Axis = 1 +) + +var Axis_name = map[int32]string{ + 0: "ROW", + 1: "COL", +} + +var Axis_value = map[string]int32{ + "ROW": 0, + "COL": 1, +} + +func (x Axis) String() string { + return proto.EnumName(Axis_name, int32(x)) +} + +func (Axis) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d28ce8f160a920d1, []int{0} +} + type MerkleProof struct { Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` @@ -33,7 +58,7 @@ func (m *MerkleProof) Reset() { *m = MerkleProof{} } func (m *MerkleProof) String() string { return proto.CompactTextString(m) } func (*MerkleProof) ProtoMessage() {} func (*MerkleProof) Descriptor() ([]byte, []int) { - return fileDescriptor_f1f041c2d5c8eb54, []int{0} + return fileDescriptor_d28ce8f160a920d1, []int{0} } func (m *MerkleProof) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -99,7 +124,7 @@ func (m *Share) Reset() { *m = Share{} } func (m *Share) String() string { return proto.CompactTextString(m) } func (*Share) ProtoMessage() {} func (*Share) Descriptor() ([]byte, []int) { - return fileDescriptor_f1f041c2d5c8eb54, []int{1} + return fileDescriptor_d28ce8f160a920d1, []int{1} } func (m *Share) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -142,31 +167,119 @@ func (m *Share) GetProof() *MerkleProof { return nil } +type BadEncoding struct { + HeaderHash []byte `protobuf:"bytes,1,opt,name=HeaderHash,proto3" json:"HeaderHash,omitempty"` + Height uint64 `protobuf:"varint,2,opt,name=Height,proto3" json:"Height,omitempty"` + Shares []*Share `protobuf:"bytes,3,rep,name=Shares,proto3" json:"Shares,omitempty"` + Index uint32 `protobuf:"varint,4,opt,name=Index,proto3" json:"Index,omitempty"` + Axis Axis `protobuf:"varint,5,opt,name=Axis,proto3,enum=share.eds.byzantine.pb.Axis" json:"Axis,omitempty"` +} + +func (m *BadEncoding) Reset() { *m = BadEncoding{} } +func (m *BadEncoding) String() string { return proto.CompactTextString(m) } +func (*BadEncoding) ProtoMessage() {} +func (*BadEncoding) Descriptor() ([]byte, []int) { + return fileDescriptor_d28ce8f160a920d1, []int{2} +} +func (m *BadEncoding) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BadEncoding) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BadEncoding.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *BadEncoding) XXX_Merge(src proto.Message) { + xxx_messageInfo_BadEncoding.Merge(m, src) +} +func (m *BadEncoding) XXX_Size() int { + return m.Size() +} +func (m *BadEncoding) XXX_DiscardUnknown() { + xxx_messageInfo_BadEncoding.DiscardUnknown(m) +} + +var xxx_messageInfo_BadEncoding proto.InternalMessageInfo + +func (m *BadEncoding) GetHeaderHash() []byte { + if m != nil { + return m.HeaderHash + } + return nil +} + +func (m *BadEncoding) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *BadEncoding) GetShares() []*Share { + if m != nil { + return m.Shares + } + return nil +} + +func (m *BadEncoding) GetIndex() uint32 { + if m != nil { + return m.Index + } + return 0 +} + +func (m *BadEncoding) GetAxis() Axis { + if m != nil { + return m.Axis + } + return Axis_ROW +} + func init() { - proto.RegisterType((*MerkleProof)(nil), "ipld.pb.MerkleProof") - proto.RegisterType((*Share)(nil), "ipld.pb.Share") -} - -func init() { proto.RegisterFile("ipld/pb/share.proto", fileDescriptor_f1f041c2d5c8eb54) } - -var fileDescriptor_f1f041c2d5c8eb54 = []byte{ - // 247 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xce, 0x2c, 0xc8, 0x49, - 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0xce, 0x48, 0x2c, 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, - 0x62, 0x07, 0x09, 0xea, 0x15, 0x24, 0x29, 0x65, 0x70, 0x71, 0xfb, 0xa6, 0x16, 0x65, 0xe7, 0xa4, - 0x06, 0x14, 0xe5, 0xe7, 0xa7, 0x09, 0x89, 0x70, 0xb1, 0x16, 0x97, 0x24, 0x16, 0x95, 0x48, 0x30, - 0x2a, 0x30, 0x6a, 0x30, 0x07, 0x41, 0x38, 0x42, 0x02, 0x5c, 0xcc, 0xa9, 0x79, 0x29, 0x12, 0x4c, - 0x60, 0x31, 0x10, 0x13, 0xa4, 0x2e, 0x2f, 0x3f, 0x25, 0xb5, 0x58, 0x82, 0x59, 0x81, 0x59, 0x83, - 0x27, 0x08, 0xc2, 0x11, 0x92, 0xe6, 0xe2, 0xcc, 0x49, 0x4d, 0x4c, 0x8b, 0xcf, 0x48, 0x2c, 0xce, - 0x90, 0x60, 0x51, 0x60, 0xd4, 0xe0, 0x09, 0xe2, 0x00, 0x09, 0x78, 0x24, 0x16, 0x67, 0x28, 0xb9, - 0x73, 0xb1, 0x06, 0x83, 0x5c, 0x20, 0x24, 0xc4, 0xc5, 0xe2, 0x92, 0x58, 0x92, 0x08, 0xb6, 0x82, - 0x27, 0x08, 0xcc, 0x16, 0xd2, 0xe2, 0x62, 0x05, 0x3b, 0x00, 0x6c, 0x07, 0xb7, 0x91, 0x88, 0x1e, - 0xd4, 0x7d, 0x7a, 0x48, 0x8e, 0x0b, 0x82, 0x28, 0x71, 0x72, 0x3b, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, - 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, - 0xc6, 0x63, 0x39, 0x86, 0x28, 0x9d, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, - 0xfd, 0xe4, 0xd4, 0x9c, 0xd4, 0xe2, 0x92, 0xcc, 0xc4, 0xfc, 0xa2, 0x74, 0x38, 0x5b, 0x17, 0xe4, - 0x4c, 0x7d, 0x68, 0x78, 0x24, 0xb1, 0x81, 0x83, 0xc2, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x7f, - 0x2b, 0x98, 0xa2, 0x21, 0x01, 0x00, 0x00, + proto.RegisterEnum("share.eds.byzantine.pb.Axis", Axis_name, Axis_value) + proto.RegisterType((*MerkleProof)(nil), "share.eds.byzantine.pb.MerkleProof") + proto.RegisterType((*Share)(nil), "share.eds.byzantine.pb.Share") + proto.RegisterType((*BadEncoding)(nil), "share.eds.byzantine.pb.BadEncoding") +} + +func init() { + proto.RegisterFile("share/eds/byzantine/pb/share.proto", fileDescriptor_d28ce8f160a920d1) +} + +var fileDescriptor_d28ce8f160a920d1 = []byte{ + // 380 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x41, 0x6b, 0xdb, 0x30, + 0x18, 0xb5, 0x66, 0x3b, 0xdb, 0x3e, 0x67, 0x23, 0x88, 0x11, 0x0c, 0xdb, 0x8c, 0xf1, 0x2e, 0x66, + 0x30, 0x7b, 0x64, 0xec, 0x30, 0x76, 0x6a, 0xda, 0x42, 0x0a, 0x2d, 0x29, 0x2a, 0x6d, 0xa1, 0x97, + 0x22, 0x47, 0x8a, 0x6d, 0x9a, 0x5a, 0xc1, 0x52, 0x21, 0xed, 0xaf, 0xe8, 0x8f, 0xea, 0xa1, 0xc7, + 0x1c, 0x7b, 0x2c, 0xc9, 0x1f, 0x29, 0x92, 0x43, 0xc8, 0x21, 0xb9, 0xbd, 0xf7, 0x78, 0xd2, 0x7b, + 0x9f, 0xf4, 0x41, 0x24, 0x0b, 0x5a, 0xf3, 0x94, 0x33, 0x99, 0x66, 0xf7, 0x0f, 0xb4, 0x52, 0x65, + 0xc5, 0xd3, 0x69, 0x96, 0x1a, 0x39, 0x99, 0xd6, 0x42, 0x09, 0xdc, 0x6d, 0x08, 0x67, 0x32, 0x59, + 0x7b, 0x92, 0x69, 0x16, 0x15, 0xe0, 0x9d, 0xf0, 0xfa, 0x66, 0xc2, 0x4f, 0x6b, 0x21, 0xc6, 0xf8, + 0x0b, 0xb8, 0x52, 0xd1, 0x5a, 0xf9, 0x28, 0x44, 0xb1, 0x4d, 0x1a, 0x82, 0x3b, 0x60, 0xf3, 0x8a, + 0xf9, 0xef, 0x8c, 0xa6, 0xa1, 0xf6, 0x55, 0x82, 0x71, 0xe9, 0xdb, 0xa1, 0x1d, 0xb7, 0x49, 0x43, + 0xf0, 0x57, 0xf8, 0x38, 0xe1, 0x74, 0x7c, 0x5d, 0x50, 0x59, 0xf8, 0x4e, 0x88, 0xe2, 0x36, 0xf9, + 0xa0, 0x85, 0x01, 0x95, 0x45, 0x74, 0x01, 0xee, 0x99, 0xee, 0x80, 0x31, 0x38, 0x07, 0x54, 0x51, + 0x13, 0xd1, 0x26, 0x06, 0xe3, 0x7f, 0xe0, 0x9a, 0x02, 0x26, 0xc3, 0xeb, 0xfd, 0x48, 0xb6, 0xd7, + 0x4d, 0x36, 0xba, 0x92, 0xe6, 0x44, 0xf4, 0x84, 0xc0, 0xeb, 0x53, 0x76, 0x58, 0x8d, 0x04, 0x2b, + 0xab, 0x1c, 0x07, 0x00, 0x03, 0x4e, 0x19, 0xaf, 0x75, 0xea, 0x2a, 0x64, 0x43, 0xc1, 0x5d, 0x68, + 0x0d, 0x78, 0x99, 0x17, 0xca, 0x64, 0x39, 0x64, 0xc5, 0xf0, 0x5f, 0x68, 0x99, 0x7e, 0xcd, 0x4c, + 0x5e, 0xef, 0xfb, 0xae, 0x0e, 0xc6, 0x45, 0x56, 0x66, 0xfd, 0x12, 0x47, 0x15, 0xe3, 0x33, 0x33, + 0xef, 0x27, 0xd2, 0x10, 0xfc, 0x1b, 0x9c, 0xbd, 0x59, 0x29, 0x7d, 0x37, 0x44, 0xf1, 0xe7, 0xde, + 0xb7, 0x5d, 0x57, 0xd1, 0x59, 0x29, 0x89, 0x71, 0xfe, 0xf4, 0xc1, 0xd1, 0x0c, 0xbf, 0x07, 0x9b, + 0x0c, 0x2f, 0x3b, 0x96, 0x06, 0xfb, 0xc3, 0xe3, 0x0e, 0xea, 0x9f, 0x3f, 0x2f, 0x02, 0x34, 0x5f, + 0x04, 0xe8, 0x75, 0x11, 0xa0, 0xc7, 0x65, 0x60, 0xcd, 0x97, 0x81, 0xf5, 0xb2, 0x0c, 0xac, 0xab, + 0xff, 0x79, 0xa9, 0x8a, 0xbb, 0x2c, 0x19, 0x89, 0xdb, 0x74, 0xc4, 0x27, 0x5c, 0xaa, 0x92, 0x8a, + 0x3a, 0x5f, 0xe3, 0x5f, 0xfa, 0x5b, 0xd2, 0xed, 0xdb, 0x91, 0xb5, 0xcc, 0x62, 0xfc, 0x79, 0x0b, + 0x00, 0x00, 0xff, 0xff, 0x2d, 0xf3, 0x01, 0x31, 0x3e, 0x02, 0x00, 0x00, } func (m *MerkleProof) Marshal() (dAtA []byte, err error) { @@ -260,6 +373,65 @@ func (m *Share) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *BadEncoding) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BadEncoding) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BadEncoding) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Axis != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.Axis)) + i-- + dAtA[i] = 0x28 + } + if m.Index != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.Index)) + i-- + dAtA[i] = 0x20 + } + if len(m.Shares) > 0 { + for iNdEx := len(m.Shares) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Shares[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintShare(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if m.Height != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + if len(m.HeaderHash) > 0 { + i -= len(m.HeaderHash) + copy(dAtA[i:], m.HeaderHash) + i = encodeVarintShare(dAtA, i, uint64(len(m.HeaderHash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintShare(dAtA []byte, offset int, v uint64) int { offset -= sovShare(v) base := offset @@ -313,6 +485,34 @@ func (m *Share) Size() (n int) { return n } +func (m *BadEncoding) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.HeaderHash) + if l > 0 { + n += 1 + l + sovShare(uint64(l)) + } + if m.Height != 0 { + n += 1 + sovShare(uint64(m.Height)) + } + if len(m.Shares) > 0 { + for _, e := range m.Shares { + l = e.Size() + n += 1 + l + sovShare(uint64(l)) + } + } + if m.Index != 0 { + n += 1 + sovShare(uint64(m.Index)) + } + if m.Axis != 0 { + n += 1 + sovShare(uint64(m.Axis)) + } + return n +} + func sovShare(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -593,6 +793,181 @@ func (m *Share) Unmarshal(dAtA []byte) error { } return nil } +func (m *BadEncoding) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BadEncoding: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BadEncoding: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HeaderHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HeaderHash = append(m.HeaderHash[:0], dAtA[iNdEx:postIndex]...) + if m.HeaderHash == nil { + m.HeaderHash = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Shares = append(m.Shares, &Share{}) + if err := m.Shares[len(m.Shares)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + m.Index = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Index |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Axis", wireType) + } + m.Axis = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Axis |= Axis(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipShare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthShare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipShare(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/share/eds/byzantine/pb/share.proto b/share/eds/byzantine/pb/share.proto new file mode 100644 index 0000000000..e08dffae45 --- /dev/null +++ b/share/eds/byzantine/pb/share.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package share.eds.byzantine.pb; + +message MerkleProof { + int64 start = 1; + int64 end = 2; + repeated bytes nodes = 3; + bytes leaf_hash = 4; +} + +message Share { + bytes Data = 1; + MerkleProof Proof = 2; +} + +enum axis { + ROW = 0; + COL = 1; +} + +message BadEncoding { + bytes HeaderHash = 1; + uint64 Height = 2; + repeated Share Shares = 3; + uint32 Index = 4; + axis Axis = 5; +} diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go new file mode 100644 index 0000000000..7a9344c3d7 --- /dev/null +++ b/share/eds/byzantine/share_proof.go @@ -0,0 +1,105 @@ +package byzantine + +import ( + "context" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + "github.com/minio/sha256-simd" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/nmt" +) + +var log = logging.Logger("share/byzantine") + +// ShareWithProof contains data with corresponding Merkle Proof +type ShareWithProof struct { + // Share is a full data including namespace + share.Share + // Proof is a Merkle Proof of current share + Proof *nmt.Proof +} + +// NewShareWithProof takes the given leaf and its path, starting from the tree root, +// and computes the nmt.Proof for it. +func NewShareWithProof(index int, share share.Share, pathToLeaf []cid.Cid) *ShareWithProof { + rangeProofs := make([][]byte, 0, len(pathToLeaf)) + for i := len(pathToLeaf) - 1; i >= 0; i-- { + node := ipld.NamespacedSha256FromCID(pathToLeaf[i]) + rangeProofs = append(rangeProofs, node) + } + + proof := nmt.NewInclusionProof(index, index+1, rangeProofs, true) + return &ShareWithProof{ + share, + &proof, + } +} + +// Validate validates inclusion of the share under the given root CID. +func (s *ShareWithProof) Validate(root cid.Cid) bool { + return s.Proof.VerifyInclusion( + sha256.New(), // TODO(@Wondertan): This should be defined somewhere globally + share.ID(s.Share), + [][]byte{share.Data(s.Share)}, + ipld.NamespacedSha256FromCID(root), + ) +} + +func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { + return &pb.Share{ + Data: s.Share, + Proof: &pb.MerkleProof{ + Start: int64(s.Proof.Start()), + End: int64(s.Proof.End()), + Nodes: s.Proof.Nodes(), + LeafHash: s.Proof.LeafHash(), + }, + } +} + +// GetProofsForShares fetches Merkle proofs for the given shares +// and returns the result as an array of ShareWithProof. +func GetProofsForShares( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + shares [][]byte, +) ([]*ShareWithProof, error) { + proofs := make([]*ShareWithProof, len(shares)) + for index, share := range shares { + if share != nil { + proof := make([]cid.Cid, 0) + // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same tree. + // Add options that will control what data will be fetched. + s, err := ipld.GetLeaf(ctx, bGetter, root, index, len(shares)) + if err != nil { + return nil, err + } + proof, err = ipld.GetProof(ctx, bGetter, root, proof, index, len(shares)) + if err != nil { + return nil, err + } + proofs[index] = NewShareWithProof(index, s.RawData(), proof) + } + } + + return proofs, nil +} + +func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { + shares := make([]*ShareWithProof, len(protoShares)) + for i, share := range protoShares { + proof := ProtoToProof(share.Proof) + shares[i] = &ShareWithProof{share.Data, &proof} + } + return shares +} + +func ProtoToProof(protoProof *pb.MerkleProof) nmt.Proof { + return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, true) +} diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go new file mode 100644 index 0000000000..e98c424597 --- /dev/null +++ b/share/eds/byzantine/share_proof_test.go @@ -0,0 +1,81 @@ +package byzantine + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/ipfs/go-cid" + mdutils "github.com/ipfs/go-merkledag/test" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" +) + +func TestGetProof(t *testing.T) { + const width = 4 + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + bServ := mdutils.Bserv() + + shares := share.RandShares(t, width*width) + in, err := share.AddShares(ctx, shares, bServ) + require.NoError(t, err) + + dah := da.NewDataAvailabilityHeader(in) + var tests = []struct { + roots [][]byte + }{ + {dah.RowsRoots}, + {dah.ColumnRoots}, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + for _, root := range tt.roots { + rootCid := ipld.MustCidFromNamespacedSha256(root) + for index := 0; uint(index) < in.Width(); index++ { + proof := make([]cid.Cid, 0) + proof, err = ipld.GetProof(ctx, bServ, rootCid, proof, index, int(in.Width())) + require.NoError(t, err) + node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) + require.NoError(t, err) + inclusion := NewShareWithProof(index, node.RawData(), proof) + require.True(t, inclusion.Validate(rootCid)) + } + } + }) + } +} + +func TestGetProofs(t *testing.T) { + const width = 4 + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + bServ := mdutils.Bserv() + + shares := share.RandShares(t, width*width) + in, err := share.AddShares(ctx, shares, bServ) + require.NoError(t, err) + + dah := da.NewDataAvailabilityHeader(in) + for _, root := range dah.ColumnRoots { + rootCid := ipld.MustCidFromNamespacedSha256(root) + data := make([][]byte, 0, in.Width()) + for index := 0; uint(index) < in.Width(); index++ { + node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) + require.NoError(t, err) + data = append(data, node.RawData()[9:]) + } + + proves, err := GetProofsForShares(ctx, bServ, rootCid, data) + require.NoError(t, err) + for _, proof := range proves { + require.True(t, proof.Validate(rootCid)) + } + } +} diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 19ba7f07d4..ac473ede96 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -19,6 +19,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -88,7 +89,7 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader var errByz *rsmt2d.ErrByzantineData if errors.As(err, &errByz) { span.RecordError(err) - return nil, share.NewErrByzantine(ctx, r.bServ, dah, errByz) + return nil, byzantine.NewErrByzantine(ctx, r.bServ, dah, errByz) } log.Warnw("not enough shares to reconstruct data square, requesting more...", "err", err) diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 7d773b4c8c..90f8a2665f 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -2,18 +2,22 @@ package eds import ( "context" + "errors" "math/rand" "testing" "time" + "github.com/ipfs/go-blockservice" mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" - + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -90,7 +94,7 @@ func TestRetriever_ByzantineError(t *testing.T) { dah := da.NewDataAvailabilityHeader(attackerEDS) r := NewRetriever(bserv) _, err = r.Retrieve(ctx, &dah) - var errByz *share.ErrByzantine + var errByz *byzantine.ErrByzantine require.ErrorAs(t, err, &errByz) } @@ -123,3 +127,31 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { _, err = ses.Reconstruct(ctx) assert.NoError(t, err) } + +func TestFraudProofValidation(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer t.Cleanup(cancel) + bServ := mdutils.Bserv() + + var errByz *byzantine.ErrByzantine + faultHeader, err := generateByzantineError(ctx, t, bServ) + require.True(t, errors.As(err, &errByz)) + + p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(faultHeader.Height), errByz) + err = p.Validate(faultHeader) + require.NoError(t, err) +} + +func generateByzantineError( + ctx context.Context, + t *testing.T, + bServ blockservice.BlockService, +) (*header.ExtendedHeader, error) { + _, store := fraud.CreateTestService(t, false) + h, err := store.GetByHeight(ctx, 1) + require.NoError(t, err) + + faultHeader := header.CreateFraudExtHeader(t, h, bServ) + _, err = NewRetriever(bServ).Retrieve(ctx, faultHeader.DAH) + return faultHeader, err +} diff --git a/share/get.go b/share/get.go index 219b73c50c..b6734562e7 100644 --- a/share/get.go +++ b/share/get.go @@ -68,35 +68,6 @@ func GetSharesByNamespace( return shares, err } -// GetProofsForShares fetches Merkle proofs for the given shares -// and returns the result as an array of ShareWithProof. -func GetProofsForShares( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - shares [][]byte, -) ([]*ShareWithProof, error) { - proofs := make([]*ShareWithProof, len(shares)) - for index, share := range shares { - if share != nil { - proof := make([]cid.Cid, 0) - // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same tree. - // Add options that will control what data will be fetched. - s, err := ipld.GetLeaf(ctx, bGetter, root, index, len(shares)) - if err != nil { - return nil, err - } - proof, err = ipld.GetProof(ctx, bGetter, root, proof, index, len(shares)) - if err != nil { - return nil, err - } - proofs[index] = NewShareWithProof(index, s.RawData(), proof) - } - } - - return proofs, nil -} - // leafToShare converts an NMT leaf into a Share. func leafToShare(nd format.Node) Share { // * Additional namespace is prepended so that parity data can be identified with a parity namespace, which we cut off diff --git a/share/get_test.go b/share/get_test.go index 714d1c62ef..41f3219854 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -18,9 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -370,68 +368,3 @@ func assertNoRowContainsNID( assert.Nil(t, err) } } - -func TestGetProof(t *testing.T) { - const width = 4 - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - defer cancel() - bServ := mdutils.Bserv() - - shares := RandShares(t, width*width) - in, err := AddShares(ctx, shares, bServ) - require.NoError(t, err) - - dah := da.NewDataAvailabilityHeader(in) - var tests = []struct { - roots [][]byte - }{ - {dah.RowsRoots}, - {dah.ColumnRoots}, - } - - for i, tt := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - for _, root := range tt.roots { - rootCid := ipld.MustCidFromNamespacedSha256(root) - for index := 0; uint(index) < in.Width(); index++ { - proof := make([]cid.Cid, 0) - proof, err = ipld.GetProof(ctx, bServ, rootCid, proof, index, int(in.Width())) - require.NoError(t, err) - node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) - require.NoError(t, err) - inclusion := NewShareWithProof(index, node.RawData(), proof) - require.True(t, inclusion.Validate(rootCid)) - } - } - }) - } -} - -func TestGetProofs(t *testing.T) { - const width = 4 - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - defer cancel() - bServ := mdutils.Bserv() - - shares := RandShares(t, width*width) - in, err := AddShares(ctx, shares, bServ) - require.NoError(t, err) - - dah := da.NewDataAvailabilityHeader(in) - for _, root := range dah.ColumnRoots { - rootCid := ipld.MustCidFromNamespacedSha256(root) - data := make([][]byte, 0, in.Width()) - for index := 0; uint(index) < in.Width(); index++ { - node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) - require.NoError(t, err) - data = append(data, node.RawData()[9:]) - } - - proves, err := GetProofsForShares(ctx, bServ, rootCid, data) - require.NoError(t, err) - for _, proof := range proves { - require.True(t, proof.Validate(rootCid)) - } - } -} diff --git a/share/pb/share.proto b/share/pb/share.proto deleted file mode 100644 index 7838d069f4..0000000000 --- a/share/pb/share.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; - -package ipld.pb; - -option go_package="github.com/celestiaorg/celestia-node/ipld/pb"; - -message MerkleProof { - int64 start = 1; - int64 end = 2; - repeated bytes nodes = 3; - bytes leaf_hash = 4; -} - -message Share { - bytes Data = 1; - MerkleProof Proof = 2; -} diff --git a/share/share.go b/share/share.go index 604a531386..8b05791e02 100644 --- a/share/share.go +++ b/share/share.go @@ -1,21 +1,13 @@ package share import ( - "crypto/sha256" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" "go.opentelemetry.io/otel" "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/celestia-node/share/pb" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" ) var ( - log = logging.Logger("share") tracer = otel.Tracer("share") // DefaultRSMT2DCodec sets the default rsmt2d.Codec for shares. @@ -45,62 +37,3 @@ func ID(s Share) namespace.ID { func Data(s Share) []byte { return s[NamespaceSize:] } - -// ShareWithProof contains data with corresponding Merkle Proof -type ShareWithProof struct { //nolint:revive - // Share is a full data including namespace - Share - // Proof is a Merkle Proof of current share - Proof *nmt.Proof -} - -// NewShareWithProof takes the given leaf and its path, starting from the tree root, -// and computes the nmt.Proof for it. -func NewShareWithProof(index int, share Share, pathToLeaf []cid.Cid) *ShareWithProof { - rangeProofs := make([][]byte, 0, len(pathToLeaf)) - for i := len(pathToLeaf) - 1; i >= 0; i-- { - node := ipld.NamespacedSha256FromCID(pathToLeaf[i]) - rangeProofs = append(rangeProofs, node) - } - - proof := nmt.NewInclusionProof(index, index+1, rangeProofs, true) - return &ShareWithProof{ - share, - &proof, - } -} - -// Validate validates inclusion of the share under the given root CID. -func (s *ShareWithProof) Validate(root cid.Cid) bool { - return s.Proof.VerifyInclusion( - sha256.New(), // TODO(@Wondertan): This should be defined somewhere globally - ID(s.Share), - [][]byte{Data(s.Share)}, - ipld.NamespacedSha256FromCID(root), - ) -} - -func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { - return &pb.Share{ - Data: s.Share, - Proof: &pb.MerkleProof{ - Start: int64(s.Proof.Start()), - End: int64(s.Proof.End()), - Nodes: s.Proof.Nodes(), - LeafHash: s.Proof.LeafHash(), - }, - } -} - -func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { - shares := make([]*ShareWithProof, len(protoShares)) - for i, share := range protoShares { - proof := ProtoToProof(share.Proof) - shares[i] = &ShareWithProof{share.Data, &proof} - } - return shares -} - -func ProtoToProof(protoProof *pb.MerkleProof) nmt.Proof { - return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, true) -} From 2a1c585ae797ddc0a05ec2d4159e7fdaeabad7eb Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Fri, 28 Oct 2022 09:39:21 -0400 Subject: [PATCH 0159/1008] Makefile: update go-acc version due to errors and new release (#1281) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0513168903..41f19095f5 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ clean: ## cover: generate to code coverage report. cover: @echo "--> Generating Code Coverage" - @go install github.com/ory/go-acc@v0.2.6 + @go install github.com/ory/go-acc@latest @go-acc -o coverage.txt `go list ./... | grep -v nodebuilder/tests` -- -v .PHONY: cover From fb17d244aa12733e7c44e183a0feed62ba46dee8 Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Fri, 28 Oct 2022 14:50:53 +0100 Subject: [PATCH 0160/1008] refactor(daser): use functional options pattern to configure daser (#1225) # Overview The DAS module is currently reliant on default constant global variables acting as configuration variables. This makes changing configuration values for the DAS module non-trivial, as it requires code changes and binary re-building. In this PR, we refactor the global configuration variables used in the DAS module to use the functional options pattern, which will trivialize the process of configuring a Celestia node. # Reference Issue #1063 #709 # Impact / Change The `das` package is now configurable using a set of functional options, namely: ``` WithSamplingRange(uint64) WithConcurrencyLimit(int) WithBackgroundStoreInterval(time.Duration) WithPrioritySizeQueue(int) WithGenesisHeight) ``` This allows the `das` package to be configured with values other than the default parameters defined in `das/options.go:29` An example of the `das` package being configured with these options and instantiated with them is: `nodebuilder/das/module.go:22` # Owners @derrandz --- das/coordinator.go | 15 +-- das/coordinator_test.go | 133 +++++++++++++++---------- das/daser.go | 44 ++++----- das/daser_test.go | 18 ++-- das/options.go | 140 +++++++++++++++++++++++++++ das/state.go | 42 ++++---- nodebuilder/config.go | 3 + nodebuilder/das/config.go | 31 ++++++ nodebuilder/{daser => das}/daser.go | 7 +- nodebuilder/{daser => das}/module.go | 23 ++++- nodebuilder/{daser => das}/opts.go | 2 +- nodebuilder/module.go | 4 +- nodebuilder/settings.go | 4 +- 13 files changed, 347 insertions(+), 119 deletions(-) create mode 100644 das/options.go create mode 100644 nodebuilder/das/config.go rename nodebuilder/{daser => das}/daser.go (74%) rename nodebuilder/{daser => das}/module.go (59%) rename nodebuilder/{daser => das}/opts.go (92%) diff --git a/das/coordinator.go b/das/coordinator.go index 33d4cc3633..809d7550ec 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -10,8 +10,9 @@ import ( // samplingCoordinator runs and coordinates sampling workers and updates current sampling state type samplingCoordinator struct { concurrencyLimit int - getter header.Getter - sampleFn sampleFn + + getter header.Getter + sampleFn sampleFn state coordinatorState @@ -35,15 +36,15 @@ type result struct { } func newSamplingCoordinator( - concurrency int, - samplingRange uint64, + params Parameters, getter header.Getter, - sample sampleFn) *samplingCoordinator { + sample sampleFn, +) *samplingCoordinator { return &samplingCoordinator{ - concurrencyLimit: concurrency, + concurrencyLimit: params.ConcurrencyLimit, getter: getter, sampleFn: sample, - state: newCoordinatorState(samplingRange), + state: newCoordinatorState(params), resultCh: make(chan result), updHeadCh: make(chan uint64), waitCh: make(chan *sync.WaitGroup), diff --git a/das/coordinator_test.go b/das/coordinator_test.go index e6883b41be..1fd47a6440 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -14,18 +14,13 @@ import ( ) func TestCoordinator(t *testing.T) { - concurrency := 10 - samplingRange := uint64(10) - networkHead := uint64(500) - sampleFrom := uint64(genesisHeight) - timeoutDelay := 125 * time.Second - t.Run("test run", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + testParams := defaultTestParams() - sampler := newMockSampler(sampleFrom, networkHead) + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample)) - coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, onceMiddleWare(sampler.sample)) go coordinator.run(ctx, sampler.checkpoint) // check if all jobs were sampled successfully @@ -36,25 +31,27 @@ func TestCoordinator(t *testing.T) { assert.Emptyf(t, coordinator.state.failed, "failed list should be empty") cancel() - stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) }) t.Run("discovered new headers", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + testParams := defaultTestParams() - sampler := newMockSampler(sampleFrom, networkHead) + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) - coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, sampler.sample) + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) + + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, sampler.sample) go coordinator.run(ctx, sampler.checkpoint) time.Sleep(50 * time.Millisecond) // discover new height for i := 0; i < 200; i++ { // mess the order by running in go-routine - sampler.discover(ctx, networkHead+uint64(i), coordinator.listen) + sampler.discover(ctx, testParams.networkHead+uint64(i), coordinator.listen) } // check if all jobs were sampled successfully @@ -65,36 +62,44 @@ func TestCoordinator(t *testing.T) { assert.Emptyf(t, coordinator.state.failed, "failed list should be empty") cancel() - stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) }) t.Run("prioritize newly discovered over known", func(t *testing.T) { - sampleFrom := uint64(1) - networkHead := uint64(10) + + testParams := defaultTestParams() + + testParams.dasParams.ConcurrencyLimit = 1 + testParams.dasParams.SamplingRange = 4 + + testParams.sampleFrom = 1 + testParams.networkHead = 10 toBeDiscovered := uint64(20) - samplingRange := uint64(4) - concurrency := 1 - sampler := newMockSampler(sampleFrom, networkHead) + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) - ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) // lock worker before start, to not let it indicateDone before discover - lk := newLock(sampleFrom, sampleFrom) + lk := newLock(testParams.sampleFrom, testParams.sampleFrom) // expect worker to prioritize newly discovered (20 -> 10) and then old (0 -> 10) - order := newCheckOrder().addInterval(sampleFrom, samplingRange) // worker will pick up first job before discovery - order.addStacks(networkHead+1, toBeDiscovered, samplingRange) - order.addInterval(samplingRange+1, toBeDiscovered) + order := newCheckOrder().addInterval( + testParams.sampleFrom, + testParams.dasParams.SamplingRange, + ) // worker will pick up first job before discovery + + order.addStacks(testParams.networkHead+1, toBeDiscovered, testParams.dasParams.SamplingRange) + order.addInterval(testParams.dasParams.SamplingRange+1, toBeDiscovered) // start coordinator - coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, lk.middleWare( - order.middleWare( - sampler.sample)), + order.middleWare(sampler.sample), + ), ) go coordinator.run(ctx, sampler.checkpoint) @@ -108,7 +113,7 @@ func TestCoordinator(t *testing.T) { assert.Equal(t, 0, sampler.sampledAmount()) // unblock worker - lk.release(sampleFrom) + lk.release(testParams.sampleFrom) // check if all jobs were sampled successfully assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") @@ -118,26 +123,28 @@ func TestCoordinator(t *testing.T) { assert.Emptyf(t, coordinator.state.failed, "failed list should be empty") cancel() - stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) }) t.Run("priority routine should not lock other workers", func(t *testing.T) { - networkHead := uint64(20) - ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + testParams := defaultTestParams() - sampler := newMockSampler(sampleFrom, networkHead) + testParams.networkHead = uint64(20) + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) - lk := newLock(sampleFrom, networkHead) // lock all workers before start - coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) + + lk := newLock(testParams.sampleFrom, testParams.networkHead) // lock all workers before start + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, lk.middleWare(sampler.sample)) go coordinator.run(ctx, sampler.checkpoint) time.Sleep(50 * time.Millisecond) // discover new height and lock it - discovered := networkHead + 1 + discovered := testParams.networkHead + 1 lk.add(discovered) sampler.discover(ctx, discovered, coordinator.listen) @@ -151,7 +158,7 @@ func TestCoordinator(t *testing.T) { time.Sleep(100 * time.Millisecond) // check that only last header is pending - assert.EqualValues(t, int(discovered-sampleFrom), sampler.doneAmount()) + assert.EqualValues(t, int(discovered-testParams.sampleFrom), sampler.doneAmount()) assert.False(t, sampler.heightIsDone(discovered)) // release all headers for coordinator @@ -165,27 +172,28 @@ func TestCoordinator(t *testing.T) { assert.Emptyf(t, coordinator.state.failed, "failed list is not empty") cancel() - stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) }) t.Run("failed should be stored", func(t *testing.T) { - sampleFrom := uint64(1) - ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + testParams := defaultTestParams() + testParams.sampleFrom = 1 + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) bornToFail := []uint64{4, 8, 15, 16, 23, 42} - sampler := newMockSampler(sampleFrom, networkHead, bornToFail...) + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead, bornToFail...) - coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, onceMiddleWare(sampler.sample)) + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample)) go coordinator.run(ctx, sampler.checkpoint) // wait for coordinator to indicateDone catchup assert.NoError(t, coordinator.state.waitCatchUp(ctx)) cancel() - stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) @@ -198,16 +206,18 @@ func TestCoordinator(t *testing.T) { }) t.Run("failed should retry on restart", func(t *testing.T) { - sampleFrom := uint64(50) - ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + testParams := defaultTestParams() - failedLastRun := map[uint64]int{4: 1, 8: 2, 15: 1, 16: 1, 23: 1, 42: 1, sampleFrom - 1: 1} + testParams.sampleFrom = uint64(50) + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) + + failedLastRun := map[uint64]int{4: 1, 8: 2, 15: 1, 16: 1, 23: 1, 42: 1, testParams.sampleFrom - 1: 1} failedAgain := []uint64{16} - sampler := newMockSampler(sampleFrom, networkHead, failedAgain...) + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead, failedAgain...) sampler.checkpoint.Failed = failedLastRun - coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, onceMiddleWare(sampler.sample)) + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample)) go coordinator.run(ctx, sampler.checkpoint) // check if all jobs were sampled successfully @@ -217,7 +227,7 @@ func TestCoordinator(t *testing.T) { assert.NoError(t, coordinator.state.waitCatchUp(ctx)) cancel() - stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) @@ -231,13 +241,15 @@ func TestCoordinator(t *testing.T) { } func BenchmarkCoordinator(b *testing.B) { - concurrency := 100 - samplingRange := uint64(10) timeoutDelay := 5 * time.Second + params := DefaultParameters() + params.SamplingRange = 10 + params.ConcurrencyLimit = 100 + b.Run("bench run", func(b *testing.B) { ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) - coordinator := newSamplingCoordinator(concurrency, samplingRange, newBenchGetter(), + coordinator := newSamplingCoordinator(params, newBenchGetter(), func(ctx context.Context, h *header.ExtendedHeader) error { return nil }) go coordinator.run(ctx, checkpoint{ SampleFrom: 1, @@ -522,3 +534,20 @@ func onceMiddleWare(out sampleFn) sampleFn { return out(ctx, h) } } + +type testParams struct { + networkHead uint64 + sampleFrom uint64 + timeoutDelay time.Duration + dasParams Parameters +} + +func defaultTestParams() testParams { + dasParamsDefault := DefaultParameters() + return testParams{ + networkHead: uint64(500), + sampleFrom: dasParamsDefault.SampleFrom, + timeoutDelay: 125 * time.Second, + dasParams: dasParamsDefault, + } +} diff --git a/das/daser.go b/das/daser.go index a1e71c0b63..aafb3bc589 100644 --- a/das/daser.go +++ b/das/daser.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "sync/atomic" - "time" "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -18,26 +17,10 @@ import ( var log = logging.Logger("das") -// TODO: parameters needs performance testing on real network to define optimal values -const ( - // samplingRange is the maximum amount of headers processed in one job. - samplingRange = 100 - - // concurrencyLimit defines the maximum amount of sampling workers running in parallel. - concurrencyLimit = 16 - - // backgroundStoreInterval is the period of time for background checkpointStore to perform a checkpoint backup. - backgroundStoreInterval = 10 * time.Minute - - // priorityQueueSize defines the size limit of the priority queue - priorityQueueSize = concurrencyLimit * 4 - - // genesisHeight is the height sampling will start from - genesisHeight = 1 -) - // DASer continuously validates availability of data committed to headers. type DASer struct { + params Parameters + da share.Availability bcast fraud.Broadcaster hsub header.Subscriber // listens for new headers in the network @@ -62,8 +45,10 @@ func NewDASer( getter header.Getter, dstore datastore.Datastore, bcast fraud.Broadcaster, -) *DASer { + options ...Option, +) (*DASer, error) { d := &DASer{ + params: DefaultParameters(), da: da, bcast: bcast, hsub: hsub, @@ -72,9 +57,18 @@ func NewDASer( subscriber: newSubscriber(), subscriberDone: make(chan struct{}), } - d.sampler = newSamplingCoordinator(concurrencyLimit, samplingRange, getter, d.sample) - return d + for _, applyOpt := range options { + applyOpt(d) + } + + err := d.params.Validate() + if err != nil { + return nil, err + } + + d.sampler = newSamplingCoordinator(d.params, getter, d.sample) + return d, nil } // Start initiates subscription for new ExtendedHeaders and spawns a sampling routine. @@ -94,8 +88,8 @@ func (d *DASer) Start(ctx context.Context) error { log.Warnw("checkpoint not found, initializing with height 1") cp = checkpoint{ - SampleFrom: genesisHeight, - NetworkHead: genesisHeight, + SampleFrom: d.params.SampleFrom, + NetworkHead: d.params.SampleFrom, } // attempt to get head info. No need to handle error, later DASer @@ -111,7 +105,7 @@ func (d *DASer) Start(ctx context.Context) error { go d.sampler.run(runCtx, cp) go d.subscriber.run(runCtx, sub, d.sampler.listen) - go d.store.runBackgroundStore(runCtx, backgroundStoreInterval, d.sampler.getCheckpoint) + go d.store.runBackgroundStore(runCtx, d.params.BackgroundStoreInterval, d.sampler.getCheckpoint) return nil } diff --git a/das/daser_test.go b/das/daser_test.go index eaeed57bc7..ca4b244b72 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -39,9 +39,10 @@ func TestDASerLifecycle(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - daser := NewDASer(avail, sub, mockGet, ds, mockService) + daser, err := NewDASer(avail, sub, mockGet, ds, mockService) + require.NoError(t, err) - err := daser.Start(ctx) + err = daser.Start(ctx) require.NoError(t, err) defer func() { err = daser.Stop(ctx) @@ -73,9 +74,10 @@ func TestDASer_Restart(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - daser := NewDASer(avail, sub, mockGet, ds, mockService) + daser, err := NewDASer(avail, sub, mockGet, ds, mockService) + require.NoError(t, err) - err := daser.Start(ctx) + err = daser.Start(ctx) require.NoError(t, err) // wait for dasing catch-up routine to indicateDone @@ -100,7 +102,9 @@ func TestDASer_Restart(t *testing.T) { restartCtx, restartCancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(restartCancel) - daser = NewDASer(avail, sub, mockGet, ds, mockService) + daser, err = NewDASer(avail, sub, mockGet, ds, mockService) + require.NoError(t, err) + err = daser.Start(restartCtx) require.NoError(t, err) @@ -146,7 +150,9 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { newCtx := context.Background() // create and start DASer - daser := NewDASer(avail, sub, mockGet, ds, f) + daser, err := NewDASer(avail, sub, mockGet, ds, f) + require.NoError(t, err) + resultCh := make(chan error) go fraud.OnProof(newCtx, f, fraud.BadEncoding, func(fraud.Proof) { diff --git a/das/options.go b/das/options.go new file mode 100644 index 0000000000..b4de66b776 --- /dev/null +++ b/das/options.go @@ -0,0 +1,140 @@ +package das + +import ( + "fmt" + "time" +) + +// ErrInvalidOption is an error that is returned by Parameters.Validate +// when supplied with invalid values. +// This error will also be returned by NewDASer if supplied with an invalid option +var ErrInvalidOption = fmt.Errorf("das: invalid option") + +// errInvalidOptionValue is a utility function to dedup code for error-returning +// when dealing with invalid parameter values +func errInvalidOptionValue(optionName string, value string) error { + return fmt.Errorf("%w: value %s cannot be %s", ErrInvalidOption, optionName, value) +} + +// Option is the functional option that is applied to the daser instance +// to configure DASing parameters (the Parameters struct) +type Option func(*DASer) + +// Parameters is the set of parameters that must be configured for the daser +type Parameters struct { + // SamplingRange is the maximum amount of headers processed in one job. + SamplingRange uint64 + + // ConcurrencyLimit defines the maximum amount of sampling workers running in parallel. + ConcurrencyLimit int + + // BackgroundStoreInterval is the period of time for background checkpointStore to perform a checkpoint backup. + BackgroundStoreInterval time.Duration + + // PriorityQueueSize defines the size limit of the priority queue + PriorityQueueSize int + + // SampleFrom is the height sampling will start from + SampleFrom uint64 +} + +// DefaultParameters returns the default configuration values for the daser parameters +func DefaultParameters() Parameters { + // TODO(@derrandz): parameters needs performance testing on real network to define optimal values (#1261) + return Parameters{ + SamplingRange: 100, + ConcurrencyLimit: 16, + BackgroundStoreInterval: 10 * time.Minute, + PriorityQueueSize: 16 * 4, + SampleFrom: 1, + } +} + +// Validate validates the values in Parameters +// +// All parameters must be positive and non-zero, except: +// BackgroundStoreInterval = 0 disables background storer, +// PriorityQueueSize = 0 disables prioritization of recently produced blocks for sampling +func (p *Parameters) Validate() error { + // SamplingRange = 0 will cause the jobs' queue to be empty + // Therefore no sampling jobs will be reserved and more importantly the DASer will break + if p.SamplingRange <= 0 { + return errInvalidOptionValue( + "SamplingRange", + "negative or 0", + ) + } + + // ConcurrencyLimit = 0 will cause the number of workers to be 0 and + // Thus no threads will be assigned to the waiting jobs therefore breaking the DASer + if p.ConcurrencyLimit <= 0 { + return errInvalidOptionValue( + "ConcurrencyLimit", + "negative or 0", + ) + } + + // SampleFrom = 0 would tell the DASer to start sampling from block height 0 + // which does not exist therefore breaking the DASer. + if p.SampleFrom <= 0 { + return errInvalidOptionValue( + "SampleFrome", + "negative or 0", + ) + } + + return nil +} + +// WithSamplingRange is a functional option to configure the daser's `SamplingRange` parameter +// +// Usage: +// ``` +// WithSamplingRange(10)(daser) +// ``` +// +// or +// +// ``` +// option := WithSamplingRange(10) +// // shenanigans to create daser +// option(daser) +// +// ``` +func WithSamplingRange(samplingRange uint64) Option { + return func(d *DASer) { + d.params.SamplingRange = samplingRange + } +} + +// WithConcurrencyLimit is a functional option to configure the daser's `ConcurrencyLimit` parameter +// Refer to WithSamplingRange documentation to see an example of how to use this +func WithConcurrencyLimit(concurrencyLimit int) Option { + return func(d *DASer) { + d.params.ConcurrencyLimit = concurrencyLimit + } +} + +// WithBackgroundStoreInterval is a functional option to configure the daser's `backgroundStoreInterval` parameter +// Refer to WithSamplingRange documentation to see an example of how to use this +func WithBackgroundStoreInterval(backgroundStoreInterval time.Duration) Option { + return func(d *DASer) { + d.params.BackgroundStoreInterval = backgroundStoreInterval + } +} + +// WithPriorityQueueSize is a functional option to configure the daser's `priorityQueuSize` parameter +// Refer to WithSamplingRange documentation to see an example of how to use this +func WithPriorityQueueSize(priorityQueueSize int) Option { + return func(d *DASer) { + d.params.PriorityQueueSize = priorityQueueSize + } +} + +// WithSampleFrom is a functional option to configure the daser's `SampleFrom` parameter +// Refer to WithSamplingRange documentation to see an example of how to use this +func WithSampleFrom(sampleFrom uint64) Option { + return func(d *DASer) { + d.params.SampleFrom = sampleFrom + } +} diff --git a/das/state.go b/das/state.go index b75cd1e397..1f96560dc9 100644 --- a/das/state.go +++ b/das/state.go @@ -6,11 +6,13 @@ import ( // coordinatorState represents the current state of sampling type coordinatorState struct { - rangeSize uint64 + sampleFrom uint64 // is the height from which the DASer will start sampling + samplingRange uint64 // is the maximum amount of headers processed in one job. - priority []job // list of headers heights that will be sampled with higher priority - inProgress map[int]func() workerState // keeps track of running workers - failed map[uint64]int // stores heights of failed headers with amount of attempt as value + priorityQueueSize int // the size of the priority queue + priority []job // list of headers heights that will be sampled with higher priority + inProgress map[int]func() workerState // keeps track of running workers + failed map[uint64]int // stores heights of failed headers with amount of attempt as value nextJobID int next uint64 // all headers before next were sent to workers @@ -21,17 +23,19 @@ type coordinatorState struct { } // newCoordinatorState initiates state for samplingCoordinator -func newCoordinatorState(samplingRangeSize uint64) coordinatorState { +func newCoordinatorState(params Parameters) coordinatorState { return coordinatorState{ - rangeSize: samplingRangeSize, - priority: make([]job, 0), - inProgress: make(map[int]func() workerState), - failed: make(map[uint64]int), - nextJobID: 0, - next: genesisHeight, - networkHead: genesisHeight, - catchUpDone: false, - catchUpDoneCh: make(chan struct{}), + sampleFrom: params.SampleFrom, + samplingRange: params.SamplingRange, + priorityQueueSize: params.PriorityQueueSize, + priority: make([]job, 0), + inProgress: make(map[int]func() workerState), + failed: make(map[uint64]int), + nextJobID: 0, + next: params.SampleFrom, + networkHead: params.SampleFrom, + catchUpDone: false, + catchUpDoneCh: make(chan struct{}), } } @@ -77,7 +81,7 @@ func (s *coordinatorState) updateHead(last uint64) bool { return false } - if s.networkHead == genesisHeight { + if s.networkHead == s.sampleFrom { s.networkHead = last log.Infow("found first header, starting sampling") return true @@ -85,9 +89,9 @@ func (s *coordinatorState) updateHead(last uint64) bool { // add most recent headers into priority queue from := s.networkHead + 1 - for from <= last && len(s.priority) < priorityQueueSize { + for from <= last && len(s.priority) < s.priorityQueueSize { s.priority = append(s.priority, s.newJob(from, last)) - from += s.rangeSize + from += s.samplingRange } log.Debugw("added recent headers to DASer priority queue", "from_height", s.networkHead, "to_height", last) @@ -110,7 +114,7 @@ func (s *coordinatorState) nextJob() (next job, found bool) { j := s.newJob(s.next, s.networkHead) - s.next += s.rangeSize + s.next += s.samplingRange if s.next > s.networkHead { s.next = s.networkHead + 1 } @@ -139,7 +143,7 @@ func (s *coordinatorState) putInProgress(jobID int, getState func() workerState) func (s *coordinatorState) newJob(from, max uint64) job { s.nextJobID++ - to := from + s.rangeSize - 1 + to := from + s.samplingRange - 1 if to > max { to = max } diff --git a/nodebuilder/config.go b/nodebuilder/config.go index aba5cb5d1e..886193384d 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -7,6 +7,7 @@ import ( "github.com/BurntSushi/toml" "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -27,6 +28,7 @@ type Config struct { RPC rpc.Config Share share.Config Header header.Config + DASer das.Config } // DefaultConfig provides a default Config for a given Node Type 'tp'. @@ -41,6 +43,7 @@ func DefaultConfig(tp node.Type) *Config { RPC: rpc.DefaultConfig(), Share: share.DefaultConfig(), Header: header.DefaultConfig(), + DASer: das.DefaultConfig(), } default: panic("node: invalid node type") diff --git a/nodebuilder/das/config.go b/nodebuilder/das/config.go new file mode 100644 index 0000000000..b892635d96 --- /dev/null +++ b/nodebuilder/das/config.go @@ -0,0 +1,31 @@ +package das + +import ( + "fmt" + + "github.com/celestiaorg/celestia-node/das" +) + +// Config contains configuration parameters for the DASer (or DASing process) +type Config das.Parameters + +// TODO(@derrandz): parameters needs performance testing on real network to define optimal values +// DefaultConfig provide the optimal default configuration per node type. +// For the moment, there is only one default configuration for all node types +// but this function will provide more once #1261 is addressed. +// +// TODO(@derrandz): Address #1261 +func DefaultConfig() Config { + return Config(das.DefaultParameters()) +} + +// Validate performs basic validation of the config. +// Upon encountering an invalid value, Validate returns an error of type: ErrMisConfig +func (cfg *Config) Validate() error { + err := (*das.Parameters)(cfg).Validate() + if err != nil { + return fmt.Errorf("moddas misconfiguration: %w", err) + } + + return nil +} diff --git a/nodebuilder/daser/daser.go b/nodebuilder/das/daser.go similarity index 74% rename from nodebuilder/daser/daser.go rename to nodebuilder/das/daser.go index 9582e39a63..0964fb86e2 100644 --- a/nodebuilder/daser/daser.go +++ b/nodebuilder/das/daser.go @@ -1,4 +1,4 @@ -package daser +package das import ( "github.com/ipfs/go-datastore" @@ -15,6 +15,7 @@ func NewDASer( store header.Store, batching datastore.Batching, fraudService fraud.Module, -) *das.DASer { - return das.NewDASer(da, hsub, store, batching, fraudService) + options ...das.Option, +) (*das.DASer, error) { + return das.NewDASer(da, hsub, store, batching, fraudService, options...) } diff --git a/nodebuilder/daser/module.go b/nodebuilder/das/module.go similarity index 59% rename from nodebuilder/daser/module.go rename to nodebuilder/das/module.go index 7e2e74dd94..4db4ee84dc 100644 --- a/nodebuilder/daser/module.go +++ b/nodebuilder/das/module.go @@ -1,4 +1,4 @@ -package daser +package das import ( "context" @@ -11,11 +11,30 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -func ConstructModule(tp node.Type) fx.Option { +func ConstructModule(tp node.Type, cfg *Config) fx.Option { + err := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(err), + fx.Provide( + func(c Config) []das.Option { + return []das.Option{ + das.WithSamplingRange(c.SamplingRange), + das.WithConcurrencyLimit(c.ConcurrencyLimit), + das.WithPriorityQueueSize(c.PriorityQueueSize), + das.WithBackgroundStoreInterval(c.BackgroundStoreInterval), + das.WithSampleFrom(c.SampleFrom), + } + }, + ), + ) + switch tp { case node.Light, node.Full: return fx.Module( "daser", + baseComponents, fx.Provide(fx.Annotate( NewDASer, fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, das *das.DASer) error { diff --git a/nodebuilder/daser/opts.go b/nodebuilder/das/opts.go similarity index 92% rename from nodebuilder/daser/opts.go rename to nodebuilder/das/opts.go index 3117e15035..88dad56db3 100644 --- a/nodebuilder/daser/opts.go +++ b/nodebuilder/das/opts.go @@ -1,4 +1,4 @@ -package daser +package das import ( "go.uber.org/fx" diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 7939509d2c..b2b804f169 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -7,7 +7,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/core" - "github.com/celestiaorg/celestia-node/nodebuilder/daser" + "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -36,7 +36,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store share.ConstructModule(tp, &cfg.Share), rpc.ConstructModule(tp, &cfg.RPC), core.ConstructModule(tp, &cfg.Core), - daser.ConstructModule(tp), + das.ConstructModule(tp, &cfg.DASer), fraud.ConstructModule(tp), ) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 1317a80ce8..12bda0676f 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -17,7 +17,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/nodebuilder/daser" + "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/state" @@ -49,7 +49,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti case node.Full, node.Light: opts = fx.Options( baseComponents, - fx.Invoke(daser.WithMetrics), + fx.Invoke(das.WithMetrics), // add more monitoring here ) case node.Bridge: From c537ec420a673e50aab137e3d7bf34204017e678 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 28 Oct 2022 15:59:45 +0200 Subject: [PATCH 0161/1008] feat(gateway): reenabling gateway, adding flag to enable (#1199) It adds the gateway back onto the node (closes #1182) with it's own config, disabled by default, which is activated by the flag `--gateway` (closes #1183). --- api/gateway/server.go | 42 +++++++++++++----------- api/gateway/server_test.go | 8 ++--- api/rpc/server.go | 21 +++++++++--- cmd/celestia/bridge.go | 4 +++ cmd/celestia/full.go | 4 +++ cmd/celestia/light.go | 5 +++ nodebuilder/config.go | 31 ++++++++++-------- nodebuilder/gateway/config.go | 33 +++++++++++++++++++ nodebuilder/gateway/flags.go | 58 +++++++++++++++++++++++++++++++++ nodebuilder/gateway/gateway.go | 26 +++++++++++++++ nodebuilder/gateway/module.go | 59 ++++++++++++++++++++++++++++++++++ nodebuilder/module.go | 2 ++ nodebuilder/node.go | 5 ++- 13 files changed, 254 insertions(+), 44 deletions(-) create mode 100644 nodebuilder/gateway/config.go create mode 100644 nodebuilder/gateway/flags.go create mode 100644 nodebuilder/gateway/gateway.go create mode 100644 nodebuilder/gateway/module.go diff --git a/api/gateway/server.go b/api/gateway/server.go index ba53de72fc..266d0e790e 100644 --- a/api/gateway/server.go +++ b/api/gateway/server.go @@ -2,34 +2,32 @@ package gateway import ( "context" - "fmt" "net" "net/http" + "sync/atomic" "time" "github.com/gorilla/mux" ) -// Server represents an RPC server on the Node. -// TODO @renaynay: eventually, gateway server should be able to be toggled on and off. +// Server represents a gateway server on the Node. type Server struct { - cfg Config - srv *http.Server srvMux *mux.Router // http request multiplexer listener net.Listener + started atomic.Bool } -// NewServer returns a new RPC Server. -func NewServer(cfg Config) *Server { +// NewServer returns a new gateway Server. +func NewServer(address string, port string) *Server { srvMux := mux.NewRouter() srvMux.Use(setContentType) server := &Server{ - cfg: cfg, srvMux: srvMux, } server.srv = &http.Server{ + Addr: address + ":" + port, Handler: server, // the amount of time allowed to read request headers. set to the default 2 seconds ReadHeaderTimeout: 2 * time.Second, @@ -37,31 +35,37 @@ func NewServer(cfg Config) *Server { return server } -// Start starts the RPC Server, listening on the given address. +// Start starts the gateway Server, listening on the given address. func (s *Server) Start(context.Context) error { - listenAddr := fmt.Sprintf("%s:%s", s.cfg.Address, s.cfg.Port) - listener, err := net.Listen("tcp", listenAddr) + couldStart := s.started.CompareAndSwap(false, true) + if !couldStart { + log.Warn("cannot start server: already started") + return nil + } + + listener, err := net.Listen("tcp", s.srv.Addr) if err != nil { return err } s.listener = listener - log.Infow("RPC server started", "listening on", listener.Addr().String()) + log.Infow("server started", "listening on", listener.Addr().String()) //nolint:errcheck go s.srv.Serve(listener) return nil } -// Stop stops the RPC Server. -func (s *Server) Stop(context.Context) error { - // if server already stopped, return - if s.listener == nil { +// Stop stops the gateway Server. +func (s *Server) Stop(ctx context.Context) error { + couldStop := s.started.CompareAndSwap(true, false) + if !couldStop { + log.Warn("cannot stop server: already stopped") return nil } - if err := s.listener.Close(); err != nil { + err := s.srv.Shutdown(ctx) + if err != nil { return err } - s.listener = nil - log.Info("RPC server stopped") + log.Info("server stopped") return nil } diff --git a/api/gateway/server_test.go b/api/gateway/server_test.go index 96dfac9fee..76b0e2a530 100644 --- a/api/gateway/server_test.go +++ b/api/gateway/server_test.go @@ -12,10 +12,8 @@ import ( ) func TestServer(t *testing.T) { - server := NewServer(Config{ - Address: "0.0.0.0", - Port: "0", - }) + address, port := "localhost", "0" + server := NewServer(address, port) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -27,7 +25,7 @@ func TestServer(t *testing.T) { ping := new(ping) server.RegisterHandlerFunc("/ping", ping.ServeHTTP, http.MethodGet) - url := fmt.Sprintf("http://%s/ping", server.listener.Addr().String()) + url := fmt.Sprintf("http://%s/ping", server.ListenAddr()) resp, err := http.Get(url) require.NoError(t, err) diff --git a/api/rpc/server.go b/api/rpc/server.go index 725127b6cf..4488ac53b5 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -3,6 +3,7 @@ package rpc import ( "context" "net/http" + "sync/atomic" "time" "github.com/filecoin-project/go-jsonrpc" @@ -12,8 +13,9 @@ import ( var log = logging.Logger("rpc") type Server struct { - http *http.Server - rpc *jsonrpc.RPCServer + http *http.Server + rpc *jsonrpc.RPCServer + started atomic.Bool } func NewServer(address string, port string) *Server { @@ -37,20 +39,29 @@ func (s *Server) RegisterService(namespace string, service interface{}) { // Start starts the RPC Server. func (s *Server) Start(context.Context) error { + couldStart := s.started.CompareAndSwap(false, true) + if !couldStart { + log.Warn("cannot start server: already started") + return nil + } //nolint:errcheck go s.http.ListenAndServe() - log.Infow("RPC server started", "listening on", s.http.Addr) + log.Infow("server started", "listening on", s.http.Addr) return nil } // Stop stops the RPC Server. func (s *Server) Stop(ctx context.Context) error { - // if server already stopped, return + couldStop := s.started.CompareAndSwap(true, false) + if !couldStop { + log.Warn("cannot stop server: already stopped") + return nil + } err := s.http.Shutdown(ctx) if err != nil { return err } - log.Info("RPC server stopped") + log.Info("server stopped") return nil } diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index 512c69621e..d8b650c0c0 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -5,6 +5,7 @@ import ( cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" @@ -22,6 +23,7 @@ func init() { core.Flags(), cmdnode.MiscFlags(), rpc.Flags(), + gateway.Flags(), state.Flags(), ), cmdnode.Start( @@ -30,6 +32,7 @@ func init() { core.Flags(), cmdnode.MiscFlags(), rpc.Flags(), + gateway.Flags(), state.Flags(), ), ) @@ -76,6 +79,7 @@ var bridgeCmd = &cobra.Command{ } rpc.ParseFlags(cmd, &cfg.RPC) + gateway.ParseFlags(cmd, &cfg.Gateway) state.ParseFlags(cmd, &cfg.State) // set config diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index cfb749642c..fa4f4af65f 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -6,6 +6,7 @@ import ( cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -27,6 +28,7 @@ func init() { // over an RPC connection with a celestia-core node. core.Flags(), rpc.Flags(), + gateway.Flags(), state.Flags(), ), cmdnode.Start( @@ -38,6 +40,7 @@ func init() { // over an RPC connection with a celestia-core node. core.Flags(), rpc.Flags(), + gateway.Flags(), state.Flags(), ), ) @@ -89,6 +92,7 @@ var fullCmd = &cobra.Command{ } rpc.ParseFlags(cmd, &cfg.RPC) + gateway.ParseFlags(cmd, &cfg.Gateway) state.ParseFlags(cmd, &cfg.State) // set config diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index e984c534c1..5e94f159f5 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -6,6 +6,7 @@ import ( cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -27,6 +28,7 @@ func init() { // over an RPC connection with a celestia-core node. core.Flags(), rpc.Flags(), + gateway.Flags(), state.Flags(), ), cmdnode.Start( @@ -38,6 +40,7 @@ func init() { // over an RPC connection with a celestia-core node. core.Flags(), rpc.Flags(), + gateway.Flags(), state.Flags(), ), ) @@ -88,7 +91,9 @@ var lightCmd = &cobra.Command{ if err != nil { return err } + rpc.ParseFlags(cmd, &cfg.RPC) + gateway.ParseFlags(cmd, &cfg.Gateway) state.ParseFlags(cmd, &cfg.State) // set config diff --git a/nodebuilder/config.go b/nodebuilder/config.go index 886193384d..a7ae3971f6 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -8,6 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/das" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -22,13 +23,14 @@ type ConfigLoader func() (*Config, error) // Config is main configuration structure for a Node. // It combines configuration units for all Node subsystems. type Config struct { - Core core.Config - State state.Config - P2P p2p.Config - RPC rpc.Config - Share share.Config - Header header.Config - DASer das.Config + Core core.Config + State state.Config + P2P p2p.Config + RPC rpc.Config + Gateway gateway.Config + Share share.Config + Header header.Config + DASer das.Config } // DefaultConfig provides a default Config for a given Node Type 'tp'. @@ -37,13 +39,14 @@ func DefaultConfig(tp node.Type) *Config { switch tp { case node.Bridge, node.Light, node.Full: return &Config{ - Core: core.DefaultConfig(), - State: state.DefaultConfig(), - P2P: p2p.DefaultConfig(), - RPC: rpc.DefaultConfig(), - Share: share.DefaultConfig(), - Header: header.DefaultConfig(), - DASer: das.DefaultConfig(), + Core: core.DefaultConfig(), + State: state.DefaultConfig(), + P2P: p2p.DefaultConfig(), + RPC: rpc.DefaultConfig(), + Gateway: gateway.DefaultConfig(), + Share: share.DefaultConfig(), + Header: header.DefaultConfig(), + DASer: das.DefaultConfig(), } default: panic("node: invalid node type") diff --git a/nodebuilder/gateway/config.go b/nodebuilder/gateway/config.go new file mode 100644 index 0000000000..a148d2068c --- /dev/null +++ b/nodebuilder/gateway/config.go @@ -0,0 +1,33 @@ +package gateway + +import ( + "fmt" + "net" + "strconv" +) + +type Config struct { + Address string + Port string + Enabled bool +} + +func DefaultConfig() Config { + return Config{ + Address: "0.0.0.0", + // do NOT expose the same port as celestia-core by default so that both can run on the same machine + Port: "26659", + Enabled: false, + } +} + +func (cfg *Config) Validate() error { + if ip := net.ParseIP(cfg.Address); ip == nil { + return fmt.Errorf("gateway: invalid listen address format: %s", cfg.Address) + } + _, err := strconv.Atoi(cfg.Port) + if err != nil { + return fmt.Errorf("gateway: invalid port: %s", err.Error()) + } + return nil +} diff --git a/nodebuilder/gateway/flags.go b/nodebuilder/gateway/flags.go new file mode 100644 index 0000000000..991d433275 --- /dev/null +++ b/nodebuilder/gateway/flags.go @@ -0,0 +1,58 @@ +package gateway + +import ( + logging "github.com/ipfs/go-log/v2" + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" +) + +var log = logging.Logger("gateway-module") + +var ( + enabledFlag = "gateway" + addrFlag = "gateway.addr" + portFlag = "gateway.port" +) + +// Flags gives a set of hardcoded node/gateway package flags. +func Flags() *flag.FlagSet { + flags := &flag.FlagSet{} + + flags.Bool( + enabledFlag, + false, + "Enables the REST gateway", + ) + flags.String( + addrFlag, + "", + "Set a custom gateway listen address (default: localhost)", + ) + flags.String( + portFlag, + "", + "Set a custom gateway port (default: 26659)", + ) + + return flags +} + +// ParseFlags parses gateway flags from the given cmd and saves them to the passed config. +func ParseFlags(cmd *cobra.Command, cfg *Config) { + enabled, err := cmd.Flags().GetBool(enabledFlag) + if err == nil { + cfg.Enabled = enabled + } + addr, port := cmd.Flag(addrFlag), cmd.Flag(portFlag) + if !enabled && (addr.Changed || port.Changed) { + log.Warn("custom address or port provided without enabling gateway, setting config values") + } + addrVal := addr.Value.String() + if addrVal != "" { + cfg.Address = addrVal + } + portVal := port.Value.String() + if portVal != "" { + cfg.Port = portVal + } +} diff --git a/nodebuilder/gateway/gateway.go b/nodebuilder/gateway/gateway.go new file mode 100644 index 0000000000..ae8ce73804 --- /dev/null +++ b/nodebuilder/gateway/gateway.go @@ -0,0 +1,26 @@ +package gateway + +import ( + "github.com/celestiaorg/celestia-node/api/gateway" + "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" +) + +// Handler constructs a new RPC Handler from the given services. +func Handler( + state state.Module, + share share.Module, + header header.Module, + daser *das.DASer, + serv *gateway.Server, +) { + handler := gateway.NewHandler(state, share, header, daser) + handler.RegisterEndpoints(serv) + handler.RegisterMiddleware(serv) +} + +func Server(cfg *Config) *gateway.Server { + return gateway.NewServer(cfg.Address, cfg.Port) +} diff --git a/nodebuilder/gateway/module.go b/nodebuilder/gateway/module.go new file mode 100644 index 0000000000..05378c550f --- /dev/null +++ b/nodebuilder/gateway/module.go @@ -0,0 +1,59 @@ +package gateway + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/api/gateway" + headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" + stateServ "github.com/celestiaorg/celestia-node/nodebuilder/state" +) + +func ConstructModule(tp node.Type, cfg *Config) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + if !cfg.Enabled { + return fx.Options() + } + + baseComponents := fx.Options( + fx.Supply(cfg), + fx.Error(cfgErr), + fx.Provide(fx.Annotate( + Server, + fx.OnStart(func(ctx context.Context, server *gateway.Server) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *gateway.Server) error { + return server.Stop(ctx) + }), + )), + ) + + switch tp { + case node.Light, node.Full: + return fx.Module( + "gateway", + baseComponents, + fx.Invoke(Handler), + ) + case node.Bridge: + return fx.Module( + "gateway", + baseComponents, + fx.Invoke(func( + state stateServ.Module, + share shareServ.Module, + header headerServ.Module, + serv *gateway.Server, + ) { + Handler(state, share, header, nil, serv) + }), + ) + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/module.go b/nodebuilder/module.go index b2b804f169..7a72e3bbcf 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -35,6 +36,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store header.ConstructModule(tp, &cfg.Header), share.ConstructModule(tp, &cfg.Share), rpc.ConstructModule(tp, &cfg.RPC), + gateway.ConstructModule(tp, &cfg.Gateway), core.ConstructModule(tp, &cfg.Core), das.ConstructModule(tp, &cfg.DASer), fraud.ConstructModule(tp), diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 77840684c5..3aa1dc7307 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -16,6 +16,7 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -45,7 +46,9 @@ type Node struct { Config *Config // rpc components - RPCServer *rpc.Server // not optional + RPCServer *rpc.Server // not optional + GatewayServer *gateway.Server `optional:"true"` + // p2p components Host host.Host ConnGater *conngater.BasicConnectionGater From 6adfd8a871d90eba6004d665ee4818a7a4e2ea02 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Fri, 28 Oct 2022 09:03:20 -0500 Subject: [PATCH 0162/1008] chore(nodebuilder/p2p): Update to new arabica chain-id (#1284) This PR updates the chain id for and genesis hash for arabica after the recent hardfork --- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index c79f7f340d..47cac600f0 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -21,7 +21,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "C364B5937805342009408F7D44DBBF43C02AE227F968CF14C5001613B18CE419", + Arabica: "FAAA4B939173B4F8567E812FDC570FF186355326879017A611C58267A5D6EF17", Mamaki: "41BBFD05779719E826C4D68C4CCBBC84B2B761EB52BC04CFDE0FF8603C9AA3CA", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 42d2c69a52..07d38e10ce 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Arabica // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica" + Arabica Network = "arabica-1" // Mamaki testnet. See: celestiaorg/networks. Mamaki Network = "mamaki" // Private can be used to set up any private network, including local testing setups. From faf8b783819b70e5aea36486f0454c08f50c1b7a Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 31 Oct 2022 08:09:36 +0100 Subject: [PATCH 0163/1008] refactor(share/ipld): Use `NamespaceSize` var for consistency (#1297) self-explanatory --- share/ipld/nmt.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index a4196c6af3..371ee9f091 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -35,8 +35,14 @@ const ( // that contain an NMT node (inner and leaf nodes). sha256Namespace8Flagged = 0x7701 + // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. + MaxSquareSize = appconsts.MaxSquareSize + + // NamespaceSize is a system-wide size for NMT namespaces. + NamespaceSize = appconsts.NamespaceSize + // nmtHashSize is the size of a digest created by an NMT in bytes. - nmtHashSize = 2*appconsts.NamespaceSize + sha256.Size + nmtHashSize = 2*NamespaceSize + sha256.Size // innerNodeSize is the size of data in inner nodes. innerNodeSize = nmtHashSize * 2 @@ -44,12 +50,6 @@ const ( // leafNodeSize is the size of data in leaf nodes. leafNodeSize = NamespaceSize + appconsts.ShareSize - // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.MaxSquareSize - - // NamespaceSize is a system-wide size for NMT namespaces. - NamespaceSize = appconsts.NamespaceSize - // cidPrefixSize is the size of the prepended buffer of the CID encoding // for NamespacedSha256. For more information, see: // https://multiformats.io/multihash/#the-multihash-format From 9c996a7d54c65b57a2eb9430562458c02b980527 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 1 Nov 2022 11:14:15 +0100 Subject: [PATCH 0164/1008] feat(rpc): Add client wrapper (#1195) --- api/rpc/client/client.go | 77 ++++++++++ api/rpc_test.go | 145 +++++++++++++++++++ das/daser.go | 1 + go.mod | 1 + nodebuilder/das/mocks/api.go | 51 +++++++ nodebuilder/das/module.go | 4 + nodebuilder/das/service.go | 20 +++ nodebuilder/fraud/mocks/api.go | 80 ++++++++++ nodebuilder/fraud/service.go | 19 ++- nodebuilder/header/mocks/api.go | 80 ++++++++++ nodebuilder/header/service.go | 12 ++ nodebuilder/rpc/module.go | 4 +- nodebuilder/rpc/rpc.go | 11 +- nodebuilder/share/mocks/api.go | 110 ++++++++++++++ nodebuilder/share/service.go | 14 ++ nodebuilder/state/mocks/api.go | 249 ++++++++++++++++++++++++++++++++ nodebuilder/state/service.go | 50 +++++++ nodebuilder/testing.go | 2 +- 18 files changed, 922 insertions(+), 8 deletions(-) create mode 100644 api/rpc/client/client.go create mode 100644 api/rpc_test.go create mode 100644 nodebuilder/das/mocks/api.go create mode 100644 nodebuilder/das/service.go create mode 100644 nodebuilder/fraud/mocks/api.go create mode 100644 nodebuilder/header/mocks/api.go create mode 100644 nodebuilder/share/mocks/api.go create mode 100644 nodebuilder/state/mocks/api.go diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go new file mode 100644 index 0000000000..8e328f600c --- /dev/null +++ b/api/rpc/client/client.go @@ -0,0 +1,77 @@ +package client + +import ( + "context" + + "github.com/filecoin-project/go-jsonrpc" + + "github.com/celestiaorg/celestia-node/nodebuilder/das" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" +) + +type API interface { + fraud.Module + header.Module + state.Module + share.Module + das.Module +} + +type Client struct { + Fraud fraud.API + Header header.API + State state.API + Share share.API + DAS das.API + + closer multiClientCloser +} + +// multiClientCloser is a wrapper struct to close clients across multiple namespaces. +type multiClientCloser struct { + closers []jsonrpc.ClientCloser +} + +// register adds a new closer to the multiClientCloser +func (m *multiClientCloser) register(closer jsonrpc.ClientCloser) { + m.closers = append(m.closers, closer) +} + +// closeAll closes all saved clients. +func (m *multiClientCloser) closeAll() { + for _, closer := range m.closers { + closer() + } +} + +// Close closes the connections to all namespaces registered on the client. +func (c *Client) Close() { + c.closer.closeAll() +} + +// NewClient creates a new Client with one connection per namespace. +func NewClient(ctx context.Context, addr string) (*Client, error) { + var client Client + var multiCloser multiClientCloser + + // TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 + var modules = map[string]interface{}{ + "share": &client.Share, + "state": &client.State, + "header": &client.Header, + "fraud": &client.Fraud, + "das": &client.DAS, + } + for name, module := range modules { + closer, err := jsonrpc.NewClient(ctx, addr, name, module, nil) + if err != nil { + return nil, err + } + multiCloser.register(closer) + } + + return &client, nil +} diff --git a/api/rpc_test.go b/api/rpc_test.go new file mode 100644 index 0000000000..c29b233512 --- /dev/null +++ b/api/rpc_test.go @@ -0,0 +1,145 @@ +package api + +import ( + "context" + "encoding/json" + "reflect" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/api/rpc" + "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/nodebuilder" + dasMock "github.com/celestiaorg/celestia-node/nodebuilder/das/mocks" + fraudMock "github.com/celestiaorg/celestia-node/nodebuilder/fraud/mocks" + headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + shareMock "github.com/celestiaorg/celestia-node/nodebuilder/share/mocks" + stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks" + "github.com/celestiaorg/celestia-node/state" +) + +func TestRPCCallsUnderlyingNode(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + nd, server := setupNodeWithModifiedRPC(t) + url := nd.RPCServer.ListenAddr() + client, err := client.NewClient(context.Background(), "http://"+url) + t.Cleanup(client.Close) + require.NoError(t, err) + + expectedBalance := &state.Balance{ + Amount: sdk.NewInt(100), + Denom: "utia", + } + + server.State.EXPECT().Balance(gomock.Any()).Return(expectedBalance, nil).Times(1) + + balance, err := client.State.Balance(ctx) + require.NoError(t, err) + require.Equal(t, expectedBalance, balance) +} + +func TestModulesImplementFullAPI(t *testing.T) { + api := reflect.TypeOf(new(client.API)).Elem() + client := reflect.TypeOf(new(client.Client)).Elem() + for i := 0; i < client.NumField(); i++ { + module := client.Field(i) + for j := 0; j < module.Type.NumField(); j++ { + impl := module.Type.Field(j) + method, _ := api.MethodByName(impl.Name) + // closers is the only thing on the Client struct that doesn't exist in the API + if impl.Name != "closers" { + require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) + } + } + } +} + +// TODO(@distractedm1nd): Blocked by issues #1208 and #1207 +func TestAllReturnValuesAreMarshalable(t *testing.T) { + t.Skip() + ra := reflect.TypeOf(new(client.API)).Elem() + for i := 0; i < ra.NumMethod(); i++ { + m := ra.Method(i) + for j := 0; j < m.Type.NumOut(); j++ { + implementsMarshaler(t, m.Type.Out(j)) + } + } +} + +func implementsMarshaler(t *testing.T, typ reflect.Type) { //nolint:unused + switch typ.Kind() { + case reflect.Struct: + for i := 0; i < typ.NumField(); i++ { + implementsMarshaler(t, typ.Field(i).Type) + } + return + case reflect.Map: + implementsMarshaler(t, typ.Elem()) + implementsMarshaler(t, typ.Key()) + case reflect.Ptr: + fallthrough + case reflect.Array: + fallthrough + case reflect.Slice: + fallthrough + case reflect.Chan: + implementsMarshaler(t, typ.Elem()) + case reflect.Interface: + if typ != reflect.TypeOf(new(interface{})).Elem() && typ != reflect.TypeOf(new(error)).Elem() { + require.True( + t, + typ.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()), + "type %s does not implement json.Marshaler", typ.String(), + ) + } + default: + return + } + +} + +func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ctrl := gomock.NewController(t) + + mockAPI := &mockAPI{ + stateMock.NewMockModule(ctrl), + shareMock.NewMockModule(ctrl), + fraudMock.NewMockModule(ctrl), + headerMock.NewMockModule(ctrl), + dasMock.NewMockModule(ctrl), + } + + overrideRPCHandler := fx.Invoke(func(srv *rpc.Server) { + srv.RegisterService("state", mockAPI.State) + srv.RegisterService("share", mockAPI.Share) + srv.RegisterService("fraud", mockAPI.Fraud) + srv.RegisterService("header", mockAPI.Header) + srv.RegisterService("das", mockAPI.Das) + }) + nd := nodebuilder.TestNode(t, node.Full, overrideRPCHandler) + // start node + err := nd.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = nd.Stop(ctx) + require.NoError(t, err) + }) + return nd, mockAPI +} + +type mockAPI struct { + State *stateMock.MockModule + Share *shareMock.MockModule + Fraud *fraudMock.MockModule + Header *headerMock.MockModule + Das *dasMock.MockModule +} diff --git a/das/daser.go b/das/daser.go index aafb3bc589..92859ed9f7 100644 --- a/das/daser.go +++ b/das/daser.go @@ -165,6 +165,7 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { return nil } +// SamplingStats returns the current statistics over the DA sampling process. func (d *DASer) SamplingStats(ctx context.Context) (SamplingStats, error) { return d.sampler.stats(ctx) } diff --git a/go.mod b/go.mod index 8107fa6de5..3a32f967a5 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/filecoin-project/go-jsonrpc v0.1.8 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 + github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go new file mode 100644 index 0000000000..8617811faa --- /dev/null +++ b/nodebuilder/das/mocks/api.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/das (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + das "github.com/celestiaorg/celestia-node/das" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// SamplingStats mocks base method. +func (m *MockModule) SamplingStats(arg0 context.Context) (das.SamplingStats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SamplingStats", arg0) + ret0, _ := ret[0].(das.SamplingStats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SamplingStats indicates an expected call of SamplingStats. +func (mr *MockModuleMockRecorder) SamplingStats(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SamplingStats", reflect.TypeOf((*MockModule)(nil).SamplingStats), arg0) +} diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 4db4ee84dc..40d8750a6d 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -45,6 +45,10 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return das.Stop(ctx) }), )), + // Module is needed for the RPC handler + fx.Provide(func(das *das.DASer) Module { + return das + }), ) case node.Bridge: return fx.Options() diff --git a/nodebuilder/das/service.go b/nodebuilder/das/service.go new file mode 100644 index 0000000000..9707497976 --- /dev/null +++ b/nodebuilder/das/service.go @@ -0,0 +1,20 @@ +package das + +import ( + "context" + + "github.com/celestiaorg/celestia-node/das" +) + +type Module interface { + // SamplingStats returns the current statistics over the DA sampling process. + SamplingStats(ctx context.Context) (das.SamplingStats, error) +} + +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +type API struct { + SamplingStats func(ctx context.Context) (das.SamplingStats, error) +} diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go new file mode 100644 index 0000000000..2b41464644 --- /dev/null +++ b/nodebuilder/fraud/mocks/api.go @@ -0,0 +1,80 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/fraud (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + fraud "github.com/celestiaorg/celestia-node/fraud" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// Broadcast mocks base method. +func (m *MockModule) Broadcast(arg0 context.Context, arg1 fraud.Proof) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Broadcast", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Broadcast indicates an expected call of Broadcast. +func (mr *MockModuleMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockModule)(nil).Broadcast), arg0, arg1) +} + +// Get mocks base method. +func (m *MockModule) Get(arg0 context.Context, arg1 fraud.ProofType) ([]fraud.Proof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret0, _ := ret[0].([]fraud.Proof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockModuleMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockModule)(nil).Get), arg0, arg1) +} + +// Subscribe mocks base method. +func (m *MockModule) Subscribe(arg0 fraud.ProofType) (fraud.Subscription, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Subscribe", arg0) + ret0, _ := ret[0].(fraud.Subscription) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Subscribe indicates an expected call of Subscribe. +func (mr *MockModuleMockRecorder) Subscribe(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockModule)(nil).Subscribe), arg0) +} diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go index be28981d36..b4369e53d0 100644 --- a/nodebuilder/fraud/service.go +++ b/nodebuilder/fraud/service.go @@ -1,9 +1,22 @@ package fraud -import "github.com/celestiaorg/celestia-node/fraud" +import ( + "context" -// Module encompasses the behavior necessary to subscribe and broadcast -// fraud proofs within the network. + "github.com/celestiaorg/celestia-node/fraud" +) + +// Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the network. +// Any method signature changed here needs to also be changed in the API struct. type Module interface { fraud.Service } + +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +type API struct { + Subscribe func(fraud.ProofType) (fraud.Subscription, error) + Get func(context.Context, fraud.ProofType) ([]fraud.Proof, error) +} diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go new file mode 100644 index 0000000000..66e93eb5c6 --- /dev/null +++ b/nodebuilder/header/mocks/api.go @@ -0,0 +1,80 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/header (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + header "github.com/celestiaorg/celestia-node/header" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// GetByHeight mocks base method. +func (m *MockModule) GetByHeight(arg0 context.Context, arg1 uint64) (*header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetByHeight", arg0, arg1) + ret0, _ := ret[0].(*header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetByHeight indicates an expected call of GetByHeight. +func (mr *MockModuleMockRecorder) GetByHeight(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByHeight", reflect.TypeOf((*MockModule)(nil).GetByHeight), arg0, arg1) +} + +// Head mocks base method. +func (m *MockModule) Head(arg0 context.Context) (*header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Head", arg0) + ret0, _ := ret[0].(*header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Head indicates an expected call of Head. +func (mr *MockModuleMockRecorder) Head(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockModule)(nil).Head), arg0) +} + +// IsSyncing mocks base method. +func (m *MockModule) IsSyncing() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsSyncing") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsSyncing indicates an expected call of IsSyncing. +func (mr *MockModuleMockRecorder) IsSyncing() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSyncing", reflect.TypeOf((*MockModule)(nil).IsSyncing)) +} diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index e7e31ed998..5753034cbf 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -8,6 +8,8 @@ import ( "github.com/celestiaorg/celestia-node/header/sync" ) +// Module exposes the functionality needed for querying headers from the network. +// Any method signature changed here needs to also be changed in the API struct. type Module interface { // GetByHeight returns the ExtendedHeader at the given height, blocking // until header has been processed by the store or context deadline is exceeded. @@ -18,6 +20,16 @@ type Module interface { IsSyncing() bool } +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +type API struct { + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) + Head func(context.Context) (*header.ExtendedHeader, error) + IsSyncing func() bool +} + // service represents the header service that can be started / stopped on a node. // service's main function is to manage its sub-services. service can contain several // sub-services, such as Exchange, ExchangeServer, Syncer, and so forth. diff --git a/nodebuilder/rpc/module.go b/nodebuilder/rpc/module.go index e443c527ba..3f04ca543f 100644 --- a/nodebuilder/rpc/module.go +++ b/nodebuilder/rpc/module.go @@ -6,6 +6,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/api/rpc" + fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" @@ -44,10 +45,11 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Invoke(func( state stateServ.Module, share shareServ.Module, + fraud fraudServ.Module, header headerServ.Module, rpcSrv *rpc.Server, ) { - RegisterEndpoints(state, share, header, rpcSrv, nil) + RegisterEndpoints(state, share, fraud, header, nil, rpcSrv) }), ) default: diff --git a/nodebuilder/rpc/rpc.go b/nodebuilder/rpc/rpc.go index 5568c70c76..583f3a5ff9 100644 --- a/nodebuilder/rpc/rpc.go +++ b/nodebuilder/rpc/rpc.go @@ -2,7 +2,8 @@ package rpc import ( "github.com/celestiaorg/celestia-node/api/rpc" - "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/nodebuilder/das" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -12,14 +13,18 @@ import ( func RegisterEndpoints( state state.Module, share share.Module, + fraud fraud.Module, header header.Module, + daser das.Module, serv *rpc.Server, - daser *das.DASer, ) { serv.RegisterService("state", state) serv.RegisterService("share", share) + serv.RegisterService("fraud", fraud) serv.RegisterService("header", header) - serv.RegisterService("daser", daser) + if daser != nil { + serv.RegisterService("das", daser) + } } func Server(cfg *Config) *rpc.Server { diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go new file mode 100644 index 0000000000..a18b3546b6 --- /dev/null +++ b/nodebuilder/share/mocks/api.go @@ -0,0 +1,110 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/share (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + da "github.com/celestiaorg/celestia-app/pkg/da" + namespace "github.com/celestiaorg/nmt/namespace" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// GetShare mocks base method. +func (m *MockModule) GetShare(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2, arg3 int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetShare", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetShare indicates an expected call of GetShare. +func (mr *MockModuleMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShare", reflect.TypeOf((*MockModule)(nil).GetShare), arg0, arg1, arg2, arg3) +} + +// GetShares mocks base method. +func (m *MockModule) GetShares(arg0 context.Context, arg1 *da.DataAvailabilityHeader) ([][][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetShares", arg0, arg1) + ret0, _ := ret[0].([][][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetShares indicates an expected call of GetShares. +func (mr *MockModuleMockRecorder) GetShares(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShares", reflect.TypeOf((*MockModule)(nil).GetShares), arg0, arg1) +} + +// GetSharesByNamespace mocks base method. +func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 namespace.ID) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSharesByNamespace indicates an expected call of GetSharesByNamespace. +func (mr *MockModuleMockRecorder) GetSharesByNamespace(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSharesByNamespace", reflect.TypeOf((*MockModule)(nil).GetSharesByNamespace), arg0, arg1, arg2) +} + +// ProbabilityOfAvailability mocks base method. +func (m *MockModule) ProbabilityOfAvailability() float64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProbabilityOfAvailability") + ret0, _ := ret[0].(float64) + return ret0 +} + +// ProbabilityOfAvailability indicates an expected call of ProbabilityOfAvailability. +func (mr *MockModuleMockRecorder) ProbabilityOfAvailability() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProbabilityOfAvailability", reflect.TypeOf((*MockModule)(nil).ProbabilityOfAvailability)) +} + +// SharesAvailable mocks base method. +func (m *MockModule) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SharesAvailable", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SharesAvailable indicates an expected call of SharesAvailable. +func (mr *MockModuleMockRecorder) SharesAvailable(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SharesAvailable", reflect.TypeOf((*MockModule)(nil).SharesAvailable), arg0, arg1) +} diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go index 284255b8c2..643ab43f21 100644 --- a/nodebuilder/share/service.go +++ b/nodebuilder/share/service.go @@ -24,6 +24,8 @@ import ( // * Fetch the Share from the provider // * Store the Share // * Return +// +// Any method signature changed here needs to also be changed in the API struct. type Module interface { share.Availability GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) @@ -43,3 +45,15 @@ func NewModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Ava }) return serv } + +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +type API struct { + SharesAvailable func(context.Context, *share.Root) error + ProbabilityOfAvailability func() float64 + GetShare func(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) + GetShares func(ctx context.Context, root *share.Root) ([][]share.Share, error) + GetSharesByNamespace func(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) +} diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go new file mode 100644 index 0000000000..0dfaea594a --- /dev/null +++ b/nodebuilder/state/mocks/api.go @@ -0,0 +1,249 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/state (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + math "cosmossdk.io/math" + namespace "github.com/celestiaorg/nmt/namespace" + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/staking/types" + gomock "github.com/golang/mock/gomock" + types1 "github.com/tendermint/tendermint/types" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// AccountAddress mocks base method. +func (m *MockModule) AccountAddress(arg0 context.Context) (types.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AccountAddress", arg0) + ret0, _ := ret[0].(types.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AccountAddress indicates an expected call of AccountAddress. +func (mr *MockModuleMockRecorder) AccountAddress(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccountAddress", reflect.TypeOf((*MockModule)(nil).AccountAddress), arg0) +} + +// Balance mocks base method. +func (m *MockModule) Balance(arg0 context.Context) (*types.Coin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Balance", arg0) + ret0, _ := ret[0].(*types.Coin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Balance indicates an expected call of Balance. +func (mr *MockModuleMockRecorder) Balance(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Balance", reflect.TypeOf((*MockModule)(nil).Balance), arg0) +} + +// BalanceForAddress mocks base method. +func (m *MockModule) BalanceForAddress(arg0 context.Context, arg1 types.Address) (*types.Coin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BalanceForAddress", arg0, arg1) + ret0, _ := ret[0].(*types.Coin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BalanceForAddress indicates an expected call of BalanceForAddress. +func (mr *MockModuleMockRecorder) BalanceForAddress(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BalanceForAddress", reflect.TypeOf((*MockModule)(nil).BalanceForAddress), arg0, arg1) +} + +// BeginRedelegate mocks base method. +func (m *MockModule) BeginRedelegate(arg0 context.Context, arg1, arg2 types.ValAddress, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BeginRedelegate", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BeginRedelegate indicates an expected call of BeginRedelegate. +func (mr *MockModuleMockRecorder) BeginRedelegate(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginRedelegate", reflect.TypeOf((*MockModule)(nil).BeginRedelegate), arg0, arg1, arg2, arg3, arg4) +} + +// CancelUnbondingDelegation mocks base method. +func (m *MockModule) CancelUnbondingDelegation(arg0 context.Context, arg1 types.ValAddress, arg2, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelUnbondingDelegation", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CancelUnbondingDelegation indicates an expected call of CancelUnbondingDelegation. +func (mr *MockModuleMockRecorder) CancelUnbondingDelegation(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelUnbondingDelegation", reflect.TypeOf((*MockModule)(nil).CancelUnbondingDelegation), arg0, arg1, arg2, arg3, arg4) +} + +// Delegate mocks base method. +func (m *MockModule) Delegate(arg0 context.Context, arg1 types.ValAddress, arg2 math.Int, arg3 uint64) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delegate", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Delegate indicates an expected call of Delegate. +func (mr *MockModuleMockRecorder) Delegate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delegate", reflect.TypeOf((*MockModule)(nil).Delegate), arg0, arg1, arg2, arg3) +} + +// IsStopped mocks base method. +func (m *MockModule) IsStopped() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsStopped") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsStopped indicates an expected call of IsStopped. +func (mr *MockModuleMockRecorder) IsStopped() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsStopped", reflect.TypeOf((*MockModule)(nil).IsStopped)) +} + +// QueryDelegation mocks base method. +func (m *MockModule) QueryDelegation(arg0 context.Context, arg1 types.ValAddress) (*types0.QueryDelegationResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QueryDelegation", arg0, arg1) + ret0, _ := ret[0].(*types0.QueryDelegationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueryDelegation indicates an expected call of QueryDelegation. +func (mr *MockModuleMockRecorder) QueryDelegation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryDelegation", reflect.TypeOf((*MockModule)(nil).QueryDelegation), arg0, arg1) +} + +// QueryRedelegations mocks base method. +func (m *MockModule) QueryRedelegations(arg0 context.Context, arg1, arg2 types.ValAddress) (*types0.QueryRedelegationsResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QueryRedelegations", arg0, arg1, arg2) + ret0, _ := ret[0].(*types0.QueryRedelegationsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueryRedelegations indicates an expected call of QueryRedelegations. +func (mr *MockModuleMockRecorder) QueryRedelegations(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryRedelegations", reflect.TypeOf((*MockModule)(nil).QueryRedelegations), arg0, arg1, arg2) +} + +// QueryUnbonding mocks base method. +func (m *MockModule) QueryUnbonding(arg0 context.Context, arg1 types.ValAddress) (*types0.QueryUnbondingDelegationResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QueryUnbonding", arg0, arg1) + ret0, _ := ret[0].(*types0.QueryUnbondingDelegationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueryUnbonding indicates an expected call of QueryUnbonding. +func (mr *MockModuleMockRecorder) QueryUnbonding(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryUnbonding", reflect.TypeOf((*MockModule)(nil).QueryUnbonding), arg0, arg1) +} + +// SubmitPayForData mocks base method. +func (m *MockModule) SubmitPayForData(arg0 context.Context, arg1 namespace.ID, arg2 []byte, arg3 uint64) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubmitPayForData", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubmitPayForData indicates an expected call of SubmitPayForData. +func (mr *MockModuleMockRecorder) SubmitPayForData(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForData", reflect.TypeOf((*MockModule)(nil).SubmitPayForData), arg0, arg1, arg2, arg3) +} + +// SubmitTx mocks base method. +func (m *MockModule) SubmitTx(arg0 context.Context, arg1 types1.Tx) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubmitTx", arg0, arg1) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SubmitTx indicates an expected call of SubmitTx. +func (mr *MockModuleMockRecorder) SubmitTx(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitTx", reflect.TypeOf((*MockModule)(nil).SubmitTx), arg0, arg1) +} + +// Transfer mocks base method. +func (m *MockModule) Transfer(arg0 context.Context, arg1 types.AccAddress, arg2 math.Int, arg3 uint64) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Transfer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Transfer indicates an expected call of Transfer. +func (mr *MockModuleMockRecorder) Transfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transfer", reflect.TypeOf((*MockModule)(nil).Transfer), arg0, arg1, arg2, arg3) +} + +// Undelegate mocks base method. +func (m *MockModule) Undelegate(arg0 context.Context, arg1 types.ValAddress, arg2 math.Int, arg3 uint64) (*types.TxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Undelegate", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*types.TxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Undelegate indicates an expected call of Undelegate. +func (mr *MockModuleMockRecorder) Undelegate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Undelegate", reflect.TypeOf((*MockModule)(nil).Undelegate), arg0, arg1, arg2, arg3) +} diff --git a/nodebuilder/state/service.go b/nodebuilder/state/service.go index 63c10bf87b..06dcc78756 100644 --- a/nodebuilder/state/service.go +++ b/nodebuilder/state/service.go @@ -72,3 +72,53 @@ type Module interface { dstValAddr state.ValAddress, ) (*types.QueryRedelegationsResponse, error) } + +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +type API struct { + IsStopped func() bool + Balance func(ctx context.Context) (*state.Balance, error) + BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) + Transfer func( + ctx context.Context, + to state.AccAddress, + amount math.Int, + gasLimit uint64, + ) (*state.TxResponse, error) + SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) + SubmitPayForData func(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) ( + *state.TxResponse, + error, + ) + CancelUnbondingDelegation func( + ctx context.Context, + valAddr state.ValAddress, + amount, + height state.Int, + gasLim uint64, + ) (*state.TxResponse, error) + BeginRedelegate func( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) + Undelegate func(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) ( + *state.TxResponse, + error, + ) + Delegate func(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) ( + *state.TxResponse, + error, + ) + QueryDelegation func(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) + QueryUnbonding func(ctx context.Context, valAddr state.ValAddress) (*types.QueryUnbondingDelegationResponse, error) + QueryRedelegations func( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + ) (*types.QueryRedelegationsResponse, error) +} diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index a7fb1e7bde..2e55096f91 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -46,7 +46,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti require.NoError(t, err) cfg.Core.IP = ip cfg.Core.RPCPort = port - cfg.RPC.Port = "0" + cfg.RPC.Port = "26655" opts = append(opts, state.WithKeyringSigner(TestKeyringSigner(t)), From f5f5adb1f6e892e2e0cb8b97ab6349c80e01a611 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 1 Nov 2022 11:22:04 +0100 Subject: [PATCH 0165/1008] fix(das): Only debug log sampled header if it was actually successful (#1295) Self explanatory Co-authored-by: Viacheslav --- das/worker.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/das/worker.go b/das/worker.go index 5828097e68..427f08a0a5 100644 --- a/das/worker.go +++ b/das/worker.go @@ -70,8 +70,13 @@ func (w *worker) run( } w.setResult(curr, err) metrics.observeSample(ctx, h, time.Since(startSample), err) - log.Debugw("sampled header", "height", h.Height, "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startSample)) + if err != nil { + log.Debugw("failed to sampled header", "height", h.Height, "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err) + } else { + log.Debugw("sampled header", "height", h.Height, "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startSample)) + } } if w.state.Curr > w.state.From { From ed54f3ee6dbdaaa9792fb9d9f243fb77ba049630 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 2 Nov 2022 10:36:04 +0100 Subject: [PATCH 0166/1008] fix(rpc): custom JSON ExtendedHeader marshal/unmarshaling using amino wrapper (#1292) --- api/rpc_test.go | 4 ++++ header/header.go | 48 +++++++++++++++++++++++++++++++++++++++++--- header/serde_test.go | 22 +++++++++++++++++--- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/api/rpc_test.go b/api/rpc_test.go index c29b233512..b61c4705fa 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -73,6 +73,10 @@ func TestAllReturnValuesAreMarshalable(t *testing.T) { } func implementsMarshaler(t *testing.T, typ reflect.Type) { //nolint:unused + if typ.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()) { + return + } + switch typ.Kind() { case reflect.Struct: for i := 0; i < typ.NumField(); i++ { diff --git a/header/header.go b/header/header.go index f62bc72487..2fa93b4412 100644 --- a/header/header.go +++ b/header/header.go @@ -3,18 +3,20 @@ package header import ( "bytes" "context" + "encoding/json" "fmt" - "github.com/celestiaorg/celestia-node/share" - "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" bts "github.com/tendermint/tendermint/libs/bytes" + amino "github.com/tendermint/tendermint/libs/json" core "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-app/pkg/da" appshares "github.com/celestiaorg/celestia-app/pkg/shares" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" ) var log = logging.Logger("header") @@ -144,3 +146,43 @@ func (eh *ExtendedHeader) UnmarshalBinary(data []byte) error { *eh = *out return nil } + +// MarshalJSON marshals an ExtendedHeader to JSON. The ValidatorSet is wrapped with amino encoding, to be able to +// unmarshal the crypto.PubKey type back from JSON. +func (eh *ExtendedHeader) MarshalJSON() ([]byte, error) { + type Alias ExtendedHeader + validatorSet, err := amino.Marshal(eh.ValidatorSet) + if err != nil { + return nil, err + } + return json.Marshal(&struct { + ValidatorSet json.RawMessage `json:"validator_set"` + *Alias + }{ + ValidatorSet: validatorSet, + Alias: (*Alias)(eh), + }) +} + +// UnmarshalJSON unmarshals an ExtendedHeader from JSON. The ValidatorSet is wrapped with amino encoding, to be able to +// unmarshal the crypto.PubKey type back from JSON. +func (eh *ExtendedHeader) UnmarshalJSON(data []byte) error { + type Alias ExtendedHeader + aux := &struct { + ValidatorSet json.RawMessage `json:"validator_set"` + *Alias + }{ + Alias: (*Alias)(eh), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + valSet := new(core.ValidatorSet) + if err := amino.Unmarshal(aux.ValidatorSet, valSet); err != nil { + return err + } + + eh.ValidatorSet = valSet + return nil +} diff --git a/header/serde_test.go b/header/serde_test.go index 36c0301eb9..2972b8c706 100644 --- a/header/serde_test.go +++ b/header/serde_test.go @@ -9,13 +9,29 @@ import ( func TestMarshalUnmarshalExtendedHeader(t *testing.T) { in := RandExtendedHeader(t) - data, err := in.MarshalBinary() + binaryData, err := in.MarshalBinary() require.NoError(t, err) out := &ExtendedHeader{} - err = out.UnmarshalBinary(data) + err = out.UnmarshalBinary(binaryData) require.NoError(t, err) - assert.Equal(t, in.ValidatorSet, out.ValidatorSet) + equalExtendedHeader(t, in, out) + + // A custom JSON marshal/unmarshal is necessary which wraps the ValidatorSet with amino + // encoding, to be able to marshal the crypto.PubKey type back from JSON. + jsonData, err := in.MarshalJSON() + require.NoError(t, err) + + out = &ExtendedHeader{} + err = out.UnmarshalJSON(jsonData) + require.NoError(t, err) + equalExtendedHeader(t, in, out) +} + +func equalExtendedHeader(t *testing.T, in, out *ExtendedHeader) { + // ValidatorSet.totalVotingPower is not set (is a cached value that can be recomputed client side) + assert.Equal(t, in.ValidatorSet.Validators, out.ValidatorSet.Validators) + assert.Equal(t, in.ValidatorSet.Proposer, out.ValidatorSet.Proposer) assert.True(t, in.DAH.Equals(out.DAH)) // not the check for equality as time.Time is not serialized exactly 1:1 assert.NotZero(t, out.RawHeader) From bcad107bb23fffd85ffdac01f508ad995ace5067 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 3 Nov 2022 15:08:07 +0100 Subject: [PATCH 0167/1008] feat(rpc): adding daser stub for friendly API error (#1308) --- nodebuilder/das/daser.go | 17 +++++++++++++++++ nodebuilder/das/mocks/api.go | 3 ++- nodebuilder/das/module.go | 6 +++++- nodebuilder/fraud/mocks/api.go | 3 ++- nodebuilder/header/mocks/api.go | 3 ++- nodebuilder/node.go | 4 ++-- nodebuilder/node_bridge_test.go | 25 +++++++++++++++++++++++++ nodebuilder/rpc/module.go | 20 +------------------- nodebuilder/rpc/rpc.go | 4 +--- nodebuilder/share/mocks/api.go | 3 ++- nodebuilder/state/mocks/api.go | 3 ++- 11 files changed, 61 insertions(+), 30 deletions(-) diff --git a/nodebuilder/das/daser.go b/nodebuilder/das/daser.go index 0964fb86e2..424b8aedc6 100644 --- a/nodebuilder/das/daser.go +++ b/nodebuilder/das/daser.go @@ -1,6 +1,9 @@ package das import ( + "context" + "fmt" + "github.com/ipfs/go-datastore" "github.com/celestiaorg/celestia-node/das" @@ -9,6 +12,20 @@ import ( "github.com/celestiaorg/celestia-node/share" ) +var _ Module = (*daserStub)(nil) + +// daserStub is a stub implementation of the DASer that is used on bridge nodes, so that we can provide a friendlier +// error when users try to access the daser over the API. +type daserStub struct{} + +func (d daserStub) SamplingStats(context.Context) (das.SamplingStats, error) { + return das.SamplingStats{}, fmt.Errorf("moddas: dasing is not available on bridge nodes") +} + +func newDaserStub() Module { + return &daserStub{} +} + func NewDASer( da share.Availability, hsub header.Subscriber, diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go index 8617811faa..04a123115a 100644 --- a/nodebuilder/das/mocks/api.go +++ b/nodebuilder/das/mocks/api.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - das "github.com/celestiaorg/celestia-node/das" gomock "github.com/golang/mock/gomock" + + das "github.com/celestiaorg/celestia-node/das" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 40d8750a6d..0f694b1b4c 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -51,7 +51,11 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), ) case node.Bridge: - return fx.Options() + return fx.Module( + "daser", + baseComponents, + fx.Provide(newDaserStub), + ) default: panic("invalid node type") } diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 2b41464644..f0cae5f291 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - fraud "github.com/celestiaorg/celestia-node/fraud" gomock "github.com/golang/mock/gomock" + + fraud "github.com/celestiaorg/celestia-node/fraud" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 66e93eb5c6..7ddae3f113 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - header "github.com/celestiaorg/celestia-node/header" gomock "github.com/golang/mock/gomock" + + header "github.com/celestiaorg/celestia-node/header" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 3aa1dc7307..c7225d3298 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -18,7 +18,7 @@ import ( "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" - "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -62,7 +62,7 @@ type Node struct { HeaderServ header.Module // not optional StateServ state.Module // not optional FraudServ fraud.Module // not optional - DASer *das.DASer `optional:"true"` + DASer das.Module // not optional // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop start, stop lifecycleFunc diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index 56f1bfad56..cf2c389fe2 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -4,9 +4,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/das" coremodule "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -29,3 +31,26 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { err = node.Stop(ctx) require.NoError(t, err) } + +// TestBridge_HasStubDaser verifies that a bridge node implements a stub daser that returns an error and empty +// das.SamplingStats +func TestBridge_HasStubDaser(t *testing.T) { + repo := MockStore(t, DefaultConfig(node.Bridge)) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + _, client := core.StartTestClient(ctx, t) + node, err := New(node.Bridge, p2p.Private, repo, coremodule.WithClient(client)) + require.NoError(t, err) + require.NotNil(t, node) + err = node.Start(ctx) + require.NoError(t, err) + + stats, err := node.DASer.SamplingStats(ctx) + assert.EqualError(t, err, "moddas: dasing is not available on bridge nodes") + assert.Equal(t, stats, das.SamplingStats{}) + + err = node.Stop(ctx) + require.NoError(t, err) +} diff --git a/nodebuilder/rpc/module.go b/nodebuilder/rpc/module.go index 3f04ca543f..d83e09750e 100644 --- a/nodebuilder/rpc/module.go +++ b/nodebuilder/rpc/module.go @@ -6,11 +6,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/api/rpc" - fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" - headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" - shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" - stateServ "github.com/celestiaorg/celestia-node/nodebuilder/state" ) func ConstructModule(tp node.Type, cfg *Config) fx.Option { @@ -32,26 +28,12 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { ) switch tp { - case node.Light, node.Full: + case node.Light, node.Full, node.Bridge: return fx.Module( "rpc", baseComponents, fx.Invoke(RegisterEndpoints), ) - case node.Bridge: - return fx.Module( - "rpc", - baseComponents, - fx.Invoke(func( - state stateServ.Module, - share shareServ.Module, - fraud fraudServ.Module, - header headerServ.Module, - rpcSrv *rpc.Server, - ) { - RegisterEndpoints(state, share, fraud, header, nil, rpcSrv) - }), - ) default: panic("invalid node type") } diff --git a/nodebuilder/rpc/rpc.go b/nodebuilder/rpc/rpc.go index 583f3a5ff9..8a1b0e1f85 100644 --- a/nodebuilder/rpc/rpc.go +++ b/nodebuilder/rpc/rpc.go @@ -22,9 +22,7 @@ func RegisterEndpoints( serv.RegisterService("share", share) serv.RegisterService("fraud", fraud) serv.RegisterService("header", header) - if daser != nil { - serv.RegisterService("das", daser) - } + serv.RegisterService("das", daser) } func Server(cfg *Config) *rpc.Server { diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index a18b3546b6..e2c791036d 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" namespace "github.com/celestiaorg/nmt/namespace" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 0dfaea594a..ab97996a00 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,11 +9,12 @@ import ( reflect "reflect" math "cosmossdk.io/math" - namespace "github.com/celestiaorg/nmt/namespace" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" + + namespace "github.com/celestiaorg/nmt/namespace" ) // MockModule is a mock of Module interface. From 9fc44b502c4d720de41999ba93b67c0b5a01446a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Nov 2022 12:22:49 +0100 Subject: [PATCH 0168/1008] chore(deps): bump golangci/golangci-lint-action from 3.2.0 to 3.3.0 (#1264) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 40a689de4e..5844a82796 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -23,7 +23,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: golangci-lint - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v3.3.0 with: version: v1.49.0 From 3824c3b52a8689c020d858a323e4482c3aba1a26 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 9 Nov 2022 15:35:40 +0100 Subject: [PATCH 0169/1008] chore(share/eds): adding test utility for generating embedded test data (#1320) --- share/eds/eds_test.go | 37 ++++++++++++++++++++++++++++++++++++ share/eds/testdata/README.md | 5 +++++ 2 files changed, 42 insertions(+) create mode 100644 share/eds/testdata/README.md diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 659888c85f..303dd4b509 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -230,3 +230,40 @@ func openWrittenEDS(t *testing.T) *os.File { require.NoError(t, err, "error opening file") return f } + +/* +use this function as needed to create new test data. + +example: + + func Test_CreateData(t *testing.T) { + createTestData(t, "celestia-node/share/eds/testdata") + } +*/ +func createTestData(t *testing.T, testDir string) { //nolint:unused + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + err := os.Chdir(testDir) + require.NoError(t, err, "changing to the directory") + os.RemoveAll("example.car") + require.NoError(t, err, "removing old file") + f, err := os.OpenFile("example.car", os.O_WRONLY|os.O_CREATE, 0600) + require.NoError(t, err, "opening file") + + eds := share.RandEDS(t, 4) + err = WriteEDS(ctx, eds, f) + require.NoError(t, err, "writing EDS to file") + f.Close() + dah := da.NewDataAvailabilityHeader(eds) + + header, err := json.MarshalIndent(dah, "", "") + require.NoError(t, err, "marshaling example root") + os.RemoveAll("example-root.json") + require.NoError(t, err, "removing old file") + f, err = os.OpenFile("example-root.json", os.O_WRONLY|os.O_CREATE, 0600) + require.NoError(t, err, "opening file") + _, err = f.Write(header) + require.NoError(t, err, "writing example root to file") + f.Close() +} diff --git a/share/eds/testdata/README.md b/share/eds/testdata/README.md new file mode 100644 index 0000000000..960549e2a0 --- /dev/null +++ b/share/eds/testdata/README.md @@ -0,0 +1,5 @@ +# CARxEDS Testdata + +This directory contains an example CARv1 file of an EDS and its matching data availability header. + +They might need to be regenerated when modifying constants such as the default share size. This can be done by running the test utility in `eds_test.go` called `createTestData`. From 72d8bc45f01b4136d6f2315523ffb7873bd5945a Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 9 Nov 2022 09:20:39 -0600 Subject: [PATCH 0170/1008] chore: bump celestia app v0.9.0 (#1300) bumps to v0.9.0 of celestia-app this also requires a bump to v0.11.0 of nmt, v0.7.0 of rsmt2d, and v1.4.0 of our fork of the sdk most of the diff is updating to the new api introduced in rsmt2d and the wrapper pls feel free to push or edit this branch to fix the remaining bugs Co-authored-by: Wondertan Co-authored-by: Vlad --- go.mod | 23 ++++++------ go.sum | 50 +++++++++++++++------------ nodebuilder/tests/fraud_test.go | 6 ++-- nodebuilder/tests/swamp/config.go | 6 ++-- share/add.go | 17 ++++++--- share/eds/byzantine/bad_encoding.go | 6 ++-- share/eds/byzantine/share_proof.go | 7 ++++ share/eds/eds.go | 15 +++++--- share/eds/retriever.go | 4 +-- share/eds/retriever_test.go | 8 +++-- share/eds/testdata/example-root.json | 40 ++++++++++----------- share/eds/testdata/example.car | Bin 38963 -> 55235 bytes share/get_test.go | 19 +++++----- share/ipld/nmt_adder.go | 21 +++++++++-- share/test_helpers.go | 4 +-- 15 files changed, 135 insertions(+), 91 deletions(-) diff --git a/go.mod b/go.mod index 3a32f967a5..a65e827288 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/99designs/keyring v1.2.1 // indirect github.com/BurntSushi/toml v1.2.0 - github.com/celestiaorg/celestia-app v0.7.0 + github.com/celestiaorg/celestia-app v0.9.0 github.com/celestiaorg/go-libp2p-messenger v0.1.0 - github.com/celestiaorg/nmt v0.10.0 - github.com/celestiaorg/rsmt2d v0.6.0 + github.com/celestiaorg/nmt v0.11.0 + github.com/celestiaorg/rsmt2d v0.7.0 github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/dgraph-io/badger/v2 v2.2007.4 @@ -50,7 +50,7 @@ require ( github.com/multiformats/go-multihash v0.2.0 github.com/spf13/cobra v1.6.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 go.opentelemetry.io/otel v1.10.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 @@ -62,7 +62,7 @@ require ( go.uber.org/fx v1.18.2 go.uber.org/multierr v1.8.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f + golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 google.golang.org/grpc v1.49.0 ) @@ -85,7 +85,6 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd v0.22.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/celestiaorg/go-leopard v0.1.0 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect github.com/celestiaorg/quantum-gravity-bridge v1.2.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -185,7 +184,8 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/klauspost/compress v1.15.6 // indirect - github.com/klauspost/cpuid/v2 v2.0.14 // indirect + github.com/klauspost/cpuid/v2 v2.1.1 // indirect + github.com/klauspost/reedsolomon v1.11.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect @@ -282,13 +282,14 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.15.0 // indirect go.uber.org/zap v1.21.0 // indirect + golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1 // indirect + golang.org/x/tools v0.1.12 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.81.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -305,7 +306,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.3.0-sdk-v0.46.0 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 diff --git a/go.sum b/go.sum index f39bcdbcac..aaf6f2bcc1 100644 --- a/go.sum +++ b/go.sum @@ -204,14 +204,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.7.0 h1:+x/5FWg5yQazJ5qsWomtzzHbp4HuIHNUEm6gha0alAw= -github.com/celestiaorg/celestia-app v0.7.0/go.mod h1:LitlWqDQLq8z8UatKsa2BZbXlQ/dtOW887yf6m6lmVQ= +github.com/celestiaorg/celestia-app v0.9.0 h1:c5Vlx9ajCn2qne2NpAojblicFRGq4gnXKLGDoJMXOPA= +github.com/celestiaorg/celestia-app v0.9.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= -github.com/celestiaorg/cosmos-sdk v1.3.0-sdk-v0.46.0 h1:VbIMBHFhaFgjHBccQ+Kyfz4tyzpaxVkad3Vei9O4KL4= -github.com/celestiaorg/cosmos-sdk v1.3.0-sdk-v0.46.0/go.mod h1:ByQ2rOrZs7s2OnPfeaiTMC8IOlcrT195xIRPgevydCI= -github.com/celestiaorg/go-leopard v0.1.0 h1:28z2EkvKJIez5J9CEaiiUEC+OxalRLtTGJJ1oScfE1g= -github.com/celestiaorg/go-leopard v0.1.0/go.mod h1:NtO/rjlB8dw2aq7jr06vZFKGvryQcTDXaNHelmPNOAM= +github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= +github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0/go.mod h1:ByQ2rOrZs7s2OnPfeaiTMC8IOlcrT195xIRPgevydCI= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= github.com/celestiaorg/go-libp2p-messenger v0.1.0/go.mod h1:XzNksXrH0VxuNRGOnjPL9Ck4UyQlbmMpCYg9YwSBerI= github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 h1:Tb1lPVAGSJvBjRCM7YpC+VaITzdZjjno4+KEnbPT6tU= @@ -220,12 +218,12 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.10.0 h1:HLfVWvpagHz5+uiE0QSjzv350wLhhnybNmrxq9NHLKc= -github.com/celestiaorg/nmt v0.10.0/go.mod h1:3bqzTj8xKj0DgQUpOgZzoxvtNkC3MS/hTbQ6dn8SIa0= +github.com/celestiaorg/nmt v0.11.0 h1:iqTaNwnVzM3njBmPklpHzb3A4Xy/JKahoclRPbAzxNc= +github.com/celestiaorg/nmt v0.11.0/go.mod h1:NN3W8EEoospv8EHCw50DDNWwPLpJkFHoEFiqCEcNCH4= github.com/celestiaorg/quantum-gravity-bridge v1.2.0 h1:l/LEEUP+x8MhhXB8rrWkyUVFZgQj1Ur/TAwUpnyLK38= github.com/celestiaorg/quantum-gravity-bridge v1.2.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= -github.com/celestiaorg/rsmt2d v0.6.0 h1:32Eq5t7lPNbhftPFFjxwCUeEjWg/yGgeMbshxnGw03c= -github.com/celestiaorg/rsmt2d v0.6.0/go.mod h1:EZ+O2KdCq8xI7WFwjATLdhtMdrdClmAs2w7zENDr010= +github.com/celestiaorg/rsmt2d v0.7.0 h1:r8fybOWhE2/VJ2XEJ6BncnYTSlLYx2c7dQDUD+5hBqg= +github.com/celestiaorg/rsmt2d v0.7.0/go.mod h1:hhlsTi6G3+X5jOP/8Lb/d7i5y2XNFmnyMddYbFSmrgo= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -573,7 +571,6 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= @@ -907,10 +904,12 @@ github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= -github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= +github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= +github.com/klauspost/reedsolomon v1.11.1/go.mod h1:FXLZzlJIdfqEnQLdUKWNRuMZg747hZ4oYp2Ml60Lb/k= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -1625,8 +1624,9 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1636,8 +1636,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= @@ -1851,6 +1852,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= +golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1954,8 +1957,8 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= -golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1991,8 +1994,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2112,8 +2115,9 @@ golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2204,8 +2208,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1 h1:NHLFZ56qCjD+0hYY3kE5Wl40Z7q4Gn9Ln/7YU0lsGko= -golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 41f91244a2..181cd9f238 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -31,7 +31,9 @@ Steps: Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. */ func TestFraudProofBroadcasting(t *testing.T) { - sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) + // we increase the timeout for this test to decrease flakiness in CI + testTimeout := time.Millisecond * 200 + sw := swamp.NewSwamp(t, swamp.WithBlockTime(testTimeout)) bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(header.FraudMaker(t, 20))) @@ -64,7 +66,7 @@ func TestFraudProofBroadcasting(t *testing.T) { // If we cannot get a height header within a timeframe it means the syncer was stopped // FIXME: Eventually, this should be a check on service registry managing and keeping // lifecycles of each Module. - syncCtx, syncCancel := context.WithTimeout(context.Background(), time.Millisecond*100) + syncCtx, syncCancel := context.WithTimeout(context.Background(), testTimeout) _, err = full.HeaderServ.GetByHeight(syncCtx, 100) require.ErrorIs(t, err, context.DeadlineExceeded) syncCancel() diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index 65af7a3eb4..985295103f 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -16,11 +16,11 @@ type Components struct { SupressLogs bool } -// DefaultComponents creates a KvStore with a block retention of 200 -// In addition, the empty block interval is set to 50ms +// DefaultComponents creates a celestia-app instance with a block time of around +// 100ms func DefaultComponents() *Components { tnCfg := tn.TestConfig() - tnCfg.Consensus.TimeoutCommit = 50 * time.Millisecond + tnCfg.Consensus.TimeoutCommit = 100 * time.Millisecond return &Components{ CoreCfg: tnCfg, ConsensusParams: testnode.DefaultParams(), diff --git a/share/add.go b/share/add.go index aab73e4824..cddd992521 100644 --- a/share/add.go +++ b/share/add.go @@ -26,9 +26,13 @@ func AddShares( // create nmt adder wrapping batch adder with calculated size batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) // create the nmt wrapper to generate row and col commitments - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(squareSize), nmt.NodeVisitor(batchAdder.Visit)) // recompute the eds - eds, err := rsmt2d.ComputeExtendedDataSquare(shares, DefaultRSMT2DCodec(), tree.Constructor) + eds, err := rsmt2d.ComputeExtendedDataSquare( + shares, + DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(squareSize), + nmt.NodeVisitor(batchAdder.Visit)), + ) if err != nil { return nil, fmt.Errorf("failure to recompute the extended data square: %w", err) } @@ -49,10 +53,13 @@ func ImportShares( squareSize := int(math.Sqrt(float64(len(shares)))) // create nmt adder wrapping batch adder with calculated size batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) - // create the nmt wrapper to generate row and col commitments - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(squareSize/2), nmt.NodeVisitor(batchAdder.Visit)) // recompute the eds - eds, err := rsmt2d.ImportExtendedDataSquare(shares, DefaultRSMT2DCodec(), tree.Constructor) + eds, err := rsmt2d.ImportExtendedDataSquare( + shares, + DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(squareSize/2), + nmt.NodeVisitor(batchAdder.Visit)), + ) if err != nil { return nil, fmt.Errorf("failure to recompute the extended data square: %w", err) } diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 122628d741..d957209be0 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -155,9 +155,9 @@ func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { } rebuiltShares = append(rebuiltShares, rebuiltExtendedShares...) - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(len(shares) / 2)) - for i, share := range rebuiltShares { - tree.Push(share, rsmt2d.SquareIndex{Axis: uint(p.Index), Cell: uint(i)}) + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(len(shares)/2), uint(p.Index)) + for _, share := range rebuiltShares { + tree.Push(share) } // comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block. diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 7a9344c3d7..4515b9c7ca 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -51,6 +51,10 @@ func (s *ShareWithProof) Validate(root cid.Cid) bool { } func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { + if s == nil { + return &pb.Share{} + } + return &pb.Share{ Data: s.Share, Proof: &pb.MerkleProof{ @@ -94,6 +98,9 @@ func GetProofsForShares( func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { shares := make([]*ShareWithProof, len(protoShares)) for i, share := range protoShares { + if share.Proof == nil { + continue + } proof := ProtoToProof(share.Proof) shares[i] = &ShareWithProof{share.Data, &proof} } diff --git a/share/eds/eds.go b/share/eds/eds.go index 0f0355e75d..23ab44e0a9 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -84,8 +84,12 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. // (shareCount*2) - (odsWidth*4) is the amount of inner nodes visited batchAdder := ipld.NewNmtNodeAdder(ctx, bs, format.MaxSizeBatchOption(innerNodeBatchSize(shareCount, odsWidth))) // this adder ignores leaves, so that they are not added to the store we iterate through in writeProofs - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(odsWidth), nmt.NodeVisitor(batchAdder.VisitInnerNodes)) - eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) + eds, err := rsmt2d.ImportExtendedDataSquare( + shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(odsWidth), + nmt.NodeVisitor(batchAdder.VisitInnerNodes)), + ) if err != nil { return nil, fmt.Errorf("recomputing data square: %w", err) } @@ -242,8 +246,11 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.Extende shares[i] = block.RawData()[appconsts.NamespaceSize:] } - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(odsWidth)) - eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) + eds, err := rsmt2d.ComputeExtendedDataSquare( + shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(odsWidth)), + ) if err != nil { return nil, fmt.Errorf("share: computing eds: %w", err) } diff --git a/share/eds/retriever.go b/share/eds/retriever.go index ac473ede96..6de60300fd 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -136,8 +136,8 @@ func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHead ses := &retrievalSession{ bget: blockservice.NewSession(ctx, r.bServ), adder: adder, - treeFn: func() rsmt2d.Tree { - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, nmt.NodeVisitor(adder.Visit)) + treeFn: func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index, nmt.NodeVisitor(adder.Visit)) return &tree }, codec: share.DefaultRSMT2DCodec(), diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 90f8a2665f..2f8dd0c64b 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -84,8 +84,12 @@ func TestRetriever_ByzantineError(t *testing.T) { // import corrupted eds batchAdder := ipld.NewNmtNodeAdder(ctx, bserv, ipld.MaxSizeBatchOption(width*2)) - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(width), nmt.NodeVisitor(batchAdder.Visit)) - attackerEDS, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), tree.Constructor) + attackerEDS, err := rsmt2d.ImportExtendedDataSquare( + shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(width), + nmt.NodeVisitor(batchAdder.Visit)), + ) require.NoError(t, err) err = batchAdder.Commit() require.NoError(t, err) diff --git a/share/eds/testdata/example-root.json b/share/eds/testdata/example-root.json index 4fd3bc6df8..f61ebaca37 100644 --- a/share/eds/testdata/example-root.json +++ b/share/eds/testdata/example-root.json @@ -1,22 +1,22 @@ { - "row_roots": [ - "Lxe8TwDLb6o/aVE1X1R953H/3/0Unde/sMZAsm9W6ZFVIrYJS6AggKB2nlARKxXb", - "TUmtZeDsUJOUZ4I++ZH3jDUN0fWsYM9ECodGf6bKcrzjtLKkaJ8cvItvvBCABj68", - "lN0KMDoWlnu0V5IHhlRI86wd3HaZ7oVAEI3eMU2IuYYnBBlOoWuG9KhxGEXLWKLi", - "14xd47ItWC/044aec4tNGjQpWR4ewA7tBuW4hIwUKD9zeKmiOt2i7vY9jCypMDqw", - "/////////////////////38Du2wPxZQDiqmHlsgfZfExVMxFTu+lYZxf+kghjytG", - "/////////////////////1Ha61C5yxaO2WDEBaP2a4JBMob9qyiVlZX4JTUNgdiP", - "/////////////////////2dLvdO8gZf0Rw7SblvEn1bxOHafppQebvvk0gwJRZyl", - "/////////////////////xbTzWEfFFv6Mke03N207ZNgRClHvGedfPUY+cVSMKJO" - ], - "column_roots": [ - "Lxe8TwDLb6rXjF3jsi1YL+25dK0eWn9KZCQcfZ3SA/7NiIaCfpd76WHM5u/CBseo", - "MoH41mlBWRHk4Opo/F4dg89Sdin6+Zht6UOF09EV7DQDAeXQJxjuUdn9u2nFR+n4", - "OsfXoZ3RTz7tXBJlIkg9HVoqp/QlSVJ3CN3x4PHVwVCYInEXGa7plRH18VGnbvXs", - "P2lRNV9Ufef044aec4tNGki2iTpjHvuLROiUx72q9vPiOIEXF7WjDGIZsbtdgZNy", - "/////////////////////x9A9aniTmETpcg2IuqcC3izwIBQSkujpw3HU6yLH5yD", - "/////////////////////xQ5w+0S5I7Xx9dReCsFi/4sObAXJ2h3F8VcgoV+2FZO", - "/////////////////////49Va1jgci1F9Y9v7kpxYfk0vmkQRo1KFa1U8WHrHnF1", - "/////////////////////+xa/2hklGNi8pmhMyZbz1i9rmsrH2JMrbhAX/b2tisJ" - ] +"row_roots": [ +"IZBw0fNjDzw8rVzYOmsmzL8gIn4iUU1SGf9UheuvEWQYHd/SaarVD7SysSeeQ2R5", +"anU++0fzoDSJpADRWwUyTvYwZoJDLX0Lj1eeQ01vaSBCfKuRNNa/hfeMNJ7Oj+Z4", +"i4P8AN2Ze9vlGn5t8YJPxupnI+5/qgTFnuH029QqSg8oUrku7OJkmPRB6WK0yQ5g", +"6tR+3zc/C9T/WerT48XBj3FpEbOf0UTqjFVUMNmLkGGz2KNw99IVc6SCIyTPfKPD", +"/////////////////////0FmCIqkffiCKDgIwJoPfmrz40xqMzfHhDGeB+yGuDSc", +"/////////////////////1+BZ04UftGUSBZU96AuDIZrrI96rXiRppcLNT1177Ab", +"/////////////////////+v0FFPDkWPoUKpCiI+nbautXgZNGYOKpXWdRRCYdtkO", +"/////////////////////4EHD3dEkvimWgxs7dqy7fZBc5FRiqG0fyT+Jy1XTIM5" +], +"column_roots": [ +"IZBw0fNjDzzq1H7fNz8L1P9OTm5AI5Kr3tnL39TH7nEr+bJzDbprD9S2tHOf/bij", +"Lyod2pBlyrb3P4+cgtvBzqVhQJScQ1mk0hc5LvcZ8GEsGb7OF/rU3wqO+MZZfUGF", +"Mi47oqOs+zD9/Te8h3jnjCRLC5bTfVfoCkpFNpx//xRpRb+tzbIckhjHHa1XpD21", +"PK1c2DprJsz/WerT48XBj3fCvURegK/zpJY3i7rXm0C6QyL/8tat3/XH2MwrzDTL", +"/////////////////////yobAPxEVP+c1M5rGpjtYmajcC4MWSFizs9s42xpYsnw", +"/////////////////////7Amq619RgUhO6MmHywcubh5wGk6+5rDwn82IzyeqBoH", +"/////////////////////xtzAjExf9hu56xbwREDQQvJ0t3kzPvZNJmchRWWu9Ib", +"/////////////////////91tDCFn/64TQorwMFtFfyjA51UM+aH9xJmA3XRqIls+" +] } \ No newline at end of file diff --git a/share/eds/testdata/example.car b/share/eds/testdata/example.car index 13985c745039403b5e7de02cb5f2de0968d9a99f..34ddb3731e3acbe5777d88f2233d1879974ea564 100644 GIT binary patch literal 55235 zcmaI6Q*>?H+C>{@#kOtRwwSm`<}m)edbGjY3*h9I?zY| zcER(F>}_lu9b#7~)j0qFeXjuht^nx?Vr^&d4dA%BtF%@)El4MN@Cm&M6=W1KzLkT{ zYY+_4akl5o$`|1p>+8s}g$&*Pt8DJX`!05$%N$k+Fslj5DEmZj>@P&)1r?)~EhJ-O zhA-$@5y!mL6Z9O(oIMqDX{LvbkdCnBEqEa?BQ^7sGw2^jwhFZ6oxCujflHyzH~{C;2BoQZ8XkRcoVBDj?~ z^M96g{Y+$$y=N1}QI(%_X`zEHDr4NMT;qyUpjddEu4}RWXZidAS*btH;6%P$Ff^vb zs-j8z$yvQj_1U}Vy750CY{lMW|gyUa|e?wbduGL>Y2{^b2ehrriQ=p}El^=d-oozshu!IBT&+W6jTe zF`54By;DVc{E3-D8ijLc9JJ3EH+s|<-BW093)_&h{-+^F6)4&xe(WJG&GU{C}3Q9Do=Y zd{(TFD>Zu&fCZpN=Qa-}-dCB^GJ{Z(JLdkkB%4;y1Sa3rNP^)v^qL|*>bQ(tO5Um{NJnkXVr^Gx5(g6aZq-beStD2c6=Z#1Gh}#A#{iQ z53soee!2YQe%KZH1}_VL)_T^Sc7E%yAm^7qc!&@TPmhT2=NUykIL3xge+@DKxQ0-o zsBZYV*6TJGMIr7}iG=l-UT2u8dXz#li{o@owwu2)@Z70Q1+K|GQSUG_&4EvWB41cK{4A%zM$ilTNYnx3#EFnpM|bT>P%Oz1|g&IA<}50aE=VgXMO6tBYR|1 zyYnuANqn*P#zZMWp1I7tEVu7LD5yTbr3Xa^VTia_3WF`CUSo)RLq+*ff`ph628hSfhWpiw#pJZq)qy%En)ah0bt3hiY8nvJeBX;Q<>p4D|63yAU39AT&#Ul zodz!Tj(_l3goss0weTy<(B^^x=@DU%Z@M>&e!L?8A$|8K^zG2|Q2xP#h_mw7PKbts zW<%1~z%j;fwocvhJNn1dyYwZpCRiVSUpo3eQa~xRnvv5l(lACbTJdFbZy)YlYI?r3 z&)>#kX_4O5F#ts2p}PFPAIRgM2{sUrfISs$2jJ|kbNr0 z*%R^&V>9%b9yF_kgrkkx^2X$B@tRoY0+04?Ply>!#{=OPFx5Pn9r+m;w<7;B=;xCE zp=Q5xX>6Flr9hVkW-O{;Srd#oHhTk;4spP@wED89?@#WB7&!rD5Ox63FQGfS{CfL# zg%FtpdA)80^mT9TU>Muzf+#7JUR_pP;|89@UM`_mvtMysS<|(m=Bgc566;h`SCZNW zw5S4+ET(<#vsRsW#sXFxbh6Z$J=_FMbB^`b8fHtfScRI#((NiP?d@!*k|Jc)SA-kZ zjJp2SiNZ93WxW*5m6Y>W41}^O=HuGc_4d>I3cfEIa!AdY`V!rRyL|7pTUx(p|qRa`BsKNBmbV zwwxILxNg@^YG}g;)t{ix+6oduJhMp54oRW2Xu#?N%6x{2FU2k0A+JUP<^YcuK==#* zby2Yi&1|hc6@Dv5&`#ac?O8= zTqcmR$DwLJxgVn7)}1M34E{MFTECK^9}B~MYUi3EwjavrWYp>A0Q`&=1{~>CD~I5e z)Rm-SxBE`0H`RFff|Ja!^J$~zw5NS=1uMWjAsX^uFj6KDLJ3XR0Uw5o)5$+BSaZ1D z#wAovuRl+B9}l!wo-^ARvd)Yc|J?yKT#5VWy1PgzS_9VikCHFN(*YVZjL&Ht}R?FWoakz9a|nPW#tMVE_peGQ+vFg(Cb152 zO23#`U{9{N6bvw7Of@?UMr$N~rKtSK2%U@@^94_d1y5cxPiSx=~d`0A=4$+3Nh@Me7oZR$Dp5w z{|^i7f}*DsWPPH;-Zz2qG0R_gAnn8A12jIjEqRjwqo{szKV;RmU>^Ul9K5TCTi&Y= z2U6> zi(k9MK@rZd(dXea<8JHYdn}G9?N=V@YeMsaDSMm1+Ts_XXkyl3ihTM7u(i$zK^ zJ~^D|S)oE1A}-1PDI&L%AWrM6EUV*=m;5|srjSfWQ69nUCM4O*m&^GOOrkC0o7r)8KhQcem7Rnida-F{z~o*iZ-{Rs zSh8Im%1T1+Zc$cwYjc8iRnTr>=76q+heCs4JP3$^>Z#+y)F*Igo4v|BQ6RkMlv*In z?U5IGUAg&u8AXJLIU{zAV;GQt27tFRy5hx4ZVM+459`+>Oz}|E z3vQ0SwVVn=@BL7Hvizd)G^SVXTD#v{{x1`X&aRw|WbccV#g4#rW-7lA5n|rS2qli+ zj)M@9k*a5(r6@xuGhhae?DGvpc^dyQ=x2lSLnUr7arB;1KTu)u^j^J3`z!26kBVoEHardTSxzr!XFy$ZT zcz+%Z84RZEQ%y_KkF|2fx|E-G!tNz8S9yQRG3lL<6%@4Wz6s3)Ik+R51%9%g;#$x8 zHlA3tEXnlOP_P#M_)GS%K=-j_(tD)5;=QxEVq7f>iJAd_}&Zeu857W{HHP^WynSs6^(quncpi<$LYHikhB6%RE&U`NVa5tHZQ6_h|q%P zv8m55@fg3>%NbJW2?8tO^#pu(J59+@mYZEC@~nS$?1uCO_O6(bJ=o zbQU4qTES7T8a@zGj{4!$dj2-kK!r2@iU_gD)oas_c{V*Xhs9^JZNN}!U1{Tq0$h93 z$!rcq%-4M7c)El&+5y`#7qr=}OrKuh_>7$&&d5@$?z^T%d)xDV0(@MR?)!`e}~q+q~+0bn!titvQx#!n2&Ruy-q^&)4%=_mKY0Y&T5vj?yC z{bzhS1^QT+`dk?M6f7q1(uuVdv0+YbPbv)#;CSvO9c5O=!Uea|sQb(?AXG2$A|iz& zlQkL5ce--P$|$|BiTzAuM7`UpC5w|}?L(MsrxdSAdJAg5gniUS7r;*@Qb_}?;jPNc z*h7VP{ub@o1CnpO<2=PLqgOucqu0aToxz?;0indDWyR_h>-+_Yk%`eM==VJ-upOMT zbR{aBK~r`~BmUF}MI-}Esf)W6Qk$X2!egct(Ub##u6L_<0=JdutdeO72g45edqy^m z`3$E3F}b(}k6N|QbT@FkJZpPu-q3!kR*M*JmbsT|j!Z`^QNJIU0lU6y=LZ zd9yq1d^ujO^f7Tq_#o|g{iI0|wn38MNDW_irZpEpw~p*SsjdbEmE*As3Fzl}17R)w z!;d2rIt-%8IHfhZDm#{J#XIa#qgeM3NS^^%gvz2RlQ0{KfH)eWEygM9^}0wMBe#jAj3NbZZ*agR zEJ%S~#v%s0oJC@(oD4u^#G)NmqN$D{HdfDt{}^N^<8vB*$3yqSo*Iu`+N&oxF|&w8 z3hO>xb7K3NLtLxdn2Q%}>opC+pWF{O3&_1?i2SjqK!TG20hVU7;%}vQx&ZBrID^|J zbaE7GlEbnI@8DVkFxF@zcfqgattkTz_c$9YOB~q7Mmk4Uq|z&BP$bCg?Qvcw^=^eT6x$19HrK0r2!R;rBSHTK!QzTc956?E z6tG7hsejXMdo9PD#sh6mu$fQ(>V>EZ=W?qLW@QWlW?nXJ6U0#NI;8C8*GX)JXo>)c z=^xBI3&|JY{y1F8$l6U#Ao1nvvf2Yto{fW0y4&M;azy`{{PWDqv_=;eyXf zPnONv&T#XWkqC{b(cg2M20|=>bGlk#rkL(-eRe?F>gNmqR3}SF{ zzzTnO3q5I6am`m-gNvd(eZYK$WA6gG1gcai%Px@n)d5QM2>O(2cvQ1%r%Br&2@;P- zgB|{T5PRmEe+>Hhy!JzOD_Vti?{uoUN2X1R?Bz6NyABU{RS-l&Mcgfi7`(*3$KKk<5> zr-1V71(f0fZ2&R?&^s)0qKH!>KiL$agGP4nqc02^e??27;a!cJvpIDe7sS||Dau!g zyc!j#7ec_ztB<7Th8{eSw09JI9zg=+_c289T;#1yEw8*1_Iu2)!XlbH<4ie+QbVN3 zu2-4WPKT9SH_3ixgw(~u4Rl#^hpk))0X!xQMV=vpsMadAeo!gM6BG~4R2Aq1sdoNj zzxLavHpzxJax_yF#X$MWhwpwE1bo7Xvt+)civLaf^f=N)a##XM z$X@b33Wu}`C0{xYzadGpnxN${(u=vut4W1 z^)D;;I^+o7#4RKVL|@wRI8>^b1&_PwbZT3lkSGXie?>Ug<}u`v>$=KrsX8s&jq;>x zFx{KzAkZ?*8>ouJ{a;M6v|QQY6?S8bGNg$Ds7fw~=i||T4Ep)j`opv5Auf6eD$VnD zsE7%Nq2JDn004cg&a!=y?JFh1${0VnALcpWUR?_wBF(&bR67VcbpUluMMoReB_rr>=;w`f>~R>Zvo5P6$**k#r|e z_-2J0vCg|B_&8PVdVVP;5YUrW95xRd$|*ZGwdDM5^81E(fNy%$h(f`KS=Wgut+V6T zO&p_!)EKeyNt<|#e{+0~0I6qts+OQ5=@wuR=WD&h=J#&qIJ#5AaX&1Pz7dDGVe_BR zvDfxqcd-CYhS?ba`-%Yc;XZmus=d~PdP4W^K|n;{engElV6NzEYu(m_&YooWb}YN5 zL8Qn-wfe5bqJrZw0*|P+Ml|>)0oMcNEG>uUr~Z-O^<-X^lP6McenC#G#$qtB-2DS3 zM1zuzzJj=LDptwzMOgzdd<4~g%S3aL%0A-D6xZJxg#rc!1=2^rcQ_%)n*zmK0T?(> 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 OM2e;X9S~_{>Hh&*(5NB+ literal 38963 zcmaI+Q*dN)yFPx$&cwED+qP}nw#|v1OfccZp4hf++x+i?x8LXY?5f#)a-Cdvb=7Bm zyKAkx)-Gg`nX`j~t4rbvwI&x3aNrei&=oKv14gF;(6~c|fRz%PfvVrJ)A#l#YVLA( z?T}!-gZgQb8gVnUOaWnFfqR}J5)In=e+|n?Rh#WxDE>^b2LBzN% zCb`JAa0)Oi`9hoUhu=<^V&j@cd;c}O9ILlqPp8T7upgf15+jGrN~ML5-}B=V;;=O| z7L`)K#k0JKd$Z{3i7%G6oRPcszlOj6*Czn9!xmvM1vI)mB5j1g?3PJ&LQMX;#3;w$ zO_C^qM*ROAR$4n(Y#T?9Uo{*6|Mg@OEW{lCSxK3in)*V<1|PJN@P7_l$o!q}3`&2H z_%Ua%Gmx)-%juq9nu2ftelQ0IEtXUAe-5M1PZ|-R>bx;aG;VA*Uj8%`rIP5h$n|~1 zd>vF~ERz4PVZe0;z+JYvR^w|2NSl)4`sL1neoaP(2m7b{oEl9WUH3r@|NgIG=Af4) zD{ZMlcEZ4_IZZ`+yj3c*f4umwVZgBjTxZGV zC~gz{_ZZQWl;OV>PxpJAK^PcKzu=6q>N@m-emeix@c+8Z-vok><$Ll*C?zB8#Ai9M zo((;LiqbN_%HW4ps$vLoLjKQTRIdI@|$`R;_w#bdK-j_J4l!h4#0lX^M&QU1lK* zxz4oa-x?bl0%O_gRzZWOr)C;x3wJYT7b^#Q;2x0w{i_CC^*in@mJSV6_~>4ms7Ffs zDRNnv1oIrNn?1i5S79q29Y7C|E}OndjhICoEelj1SqCGzIb_n{NWF20(^$72YB5~_ zJsG2K#b!#$$AmXDW(xF>e=HWPG8XBsO*~z_YKRbT%RPQhFahDSZKkIX^QcNn6{_pr#o=c zznk;ab)8#NEU?Bw^@i~sm@>}VZ(Ooq0-j8cr+CgmG z++a$4`h_ed=l&^<@EO#7&>Tj*<#EQu;tt|Da*$+jg!-y~L83zFeqTDYoT4}XRD6gbLV;6 z5K|xh6V&PU2?OhxS6Yiu&unG_rSpVT98Ikvma~LyC~k>+Cndw(Xh7NAXnI*zNBH)GlPM-cG42V}Xv;*(|| zqZUQqQ1^A5LlX>J{gN3o9;H&>>+dI`Qdd9^kSR3la75uD*LnONv-lMrd8M72I)gB! z?V27RwQ`bZoi!5#k4fa6tGDjfAidr*}8R9F&j4oo$hX4Uo+0pY@NinFLS(QuwQjN+LB{tI-z04UJr(f z$MRln)yyk@d{?6*9WEXX8=r9xp6}JN7o(b!ZQ!B-C6mmL?$Oh%V97uBbXNS8s=#_0 z@<1T;|IuYzrJYou$zc<2sQw>8z@Hx=wMGef=@@Wumq1klg}EGruB?Vk5eofJ?`-18 zDXfNx0X;yr!xPrsf32NH4>G(~f=r%Y)qh7qD=!?-2;ZI>1A*~yHS}{?WTF5uIlplO zm5RCN3=D4HzCmxBzDPKy{pJl)FDJC+$@Y*s=U=!(0IKS2-jW=WO57p)^^0r!i1l70 z$d>j(?s~9<^xZfEcsI*ZyIpJj(Jk7h++l^zKrfk=z$uaLydvnuO;#7B|Kk2RnPI(c z)BZ?D+9ZpSFkKKzvYa`8Y}iZ0)X+Un?*gZU^FudYB-v@_Fo)C}X*fKK-tEmxrenHZ zo_My5fK&pEoc3VP5VtlCDraQs)c}9gu5-eEs(gXRbH0I4YR@dcZj?{t2MkJj`) zB9uI(bPr>u?<*ZEJfyvrmor|6$l;+=5!Y_0^?*&Rq)&?NeH)uOD$Tpv`|&rpID9Kn zm_lvbSWOoZL5h6-;y6QzD7{w6FI>0S;E&gPpD0ENTJHN)db#BvU5!=-u#>{Yd@wX4 zQQeN9dYNAxLlzyVe!Ap$vr$YXmK%AG*4s~fp1inlgOf|F0geK9>*Z%wdY`)RAC4ZN zcn-298c5^QYX8u-gGu01x4-GsNpH>p^gqJY z2vi!a>BZXkJ-RzWxVm3*U!lrusM`nYL&(%DohE2~Ialy-;)2xBBX|C~)Lrycj2S@Y+mDbINtt zee5DQM6g{(>x+pJN(X2h<;wz8LC{>Vp7tL>H;k*cW3dSyK&~?eYq|Rr_rxt0hvRCDrnq~nzs@~aXi^3-<*%xF z7y^2L)aztI%2yggr%M+P3)K?yv2JHCN5`WDfwavI5TOw!k&f7&0%@$^8}9ylEAZ5K z33aRfC%K{v^_P?ij;ANNt6@q#-EsSIKdZmJJsWcU4bW21$w|esuWk{aQJf@Z@THv* zqz9OnN&IXpI#rV3yeTVY|M?nnAY4+W;Syim ze5p*|m=A$kf4iA5X2>RRQzqSrPe-}u;u9_=31W-Rb`Vp%Ml`Jpoo?;e=0;c+GbzsR z6-*0pM)%;=Nz4}{`RH}tnP+sn66LFNy5Dl|Hts!VCSYkj#P_aYIAxd6$^Q`qJYE2@ zOP;Tg-dL$rby7Z3Zq9MWu|~B3EKHU9*+gMpDShP*&;z9O)$e1q-GZoPd5Jgc{xTT; zDF?FlDx3@Fl{A7eDMOeUGI{Txf!TPZeo+xZ*>ttepkpvHjNc8^5)QZ=o2RAt&L7fA zSuAJnEO0>Xz>(P(@P3>VZAYxRsiA!2e`E3toQoHp>8;W?Lurh6GE9-8fNqHp)VzB&%ur)KF2EH)58Yh13%c%r!5 zEIVTya};_nSQ?`CB;Kvz=6O?`)zy*+gnP+;pMsQAgv;R0GH05mSQq?qa7tklU5Bq8KJw6)GH+|y6;k4myx`v z+wF@A;66)B>gt^|3xFOVBRs$^WzZg|FK7&;(5g1XyHU7s{hVW73zpk_jS+G1RYb0q z-Y$)&CfPgUS%5-zn=g=uD7c{BiRg%a<_KcuA)08I3ivn#dG{YV8>)lf^PwgMuU1=B z3I2Uv)!)4TJum~^tM6NJM?aSXUaf=Ja&qKF*Akl_UyXkIWlG3eGV5v+V4f(ioUrFSsuR)eDA9UOT&q)k|VS*gA^3B z^=sOPlK%*nFrIf+jI9s>@+|2)C>&*}qEQn&_{zOrGBFO{7HrdXy`KM*SaLa`2G9c} z#vys2d(~=SC}GAKl}{)Bu4n7n$JunXJswnPR-O!PA=s24sym~zsK??gmov$>(_VyK z%OJW`E?i2RV}q)Z0>qGydzPDTHnY>8eG~~DO;g?G&z>sf6k6*Lm@Ima8SI+st6naf?S4Cd`F{x`0>xFM%W}u+OwOEJ zhU-;9I&16MT5jLx#E#PcG1C0m*y3TD+xEHPj8+Z{=fjmXfT{A35HMbI@ab|@h-+JG zSz2MhOoFzW0DkQmWcwM9^B=*=^?mX;_#CoT&N7dS2w9 z0~vPsVYTIRQ{>}*YbjdNq|3?t8DFNi6-AXfvv9Pcm(B_nywQXB<#F`B> zs~thk$B2lAqr{H~Qg1P*hNpOjeluoCR-o5DYVa&sYosUI3(+v{Npq3$D%W!cx^SE4M zV`3SvfW5}$Ag|$BsL2|p;MQ`?rL+%qJ)uJ=BmCW8>8N>b1Gpt6qKbhH&VwkPCG|w@ zy4yiC=`~n|mNtfyga*P;;S&uZe(PGu&h{3TU$Z%l)g6pAL@C^7qgljwusjFN4q>8! zg!Uo$*wwXyX?GnkksayDtjjG{+6V1}4CN;<;?cE&3%Z`&X(>0_;Xl8?LGe8%GmJbO zIzaqK5U~9Kq!nn+(fFE^$!6Xsvtf@1yDoIHakKSfmDOyqYDWC^PJAIo{{)N=e z7KJyvI}vyh0X?NTfCmsf|vJ>LG<-y(EGb}WAx})A-1srrqL6` zh-*(=BjUm%GM%~02{iaq6(wzr=7aOf%iiK028hW6QIU#a)8mGA)!+Vex{+~4V)1C@ zg~oHa*rIIdkr0JoFkrz{h~x8i0wp3TqArkL_+qhp7Us3OtYH3%i0LYHkDl+KqQ$#c zyq7UC%ZZuY>JY~kkP-gQG@dFgB{8E9I2S?arzi!y{}BYNfB+dqC@0Kfj(j#KE|%^n z9I=v<7a;>rema-D3{3x%gJ2!d1LTkwcZwE%y*%{;D<65kOZy+$G!};po~8*(%4iDo z=P_39nMEIYDjLFm=Iq|5b-08Bb>1~d_d~|!H$_5*!3QX+83Z+bZDQPyv-GVOu5=kA zKCx<<5_Cj1u>OelcNVZE@iaLtO=p?UHZ&J5CC1Ay?jH%vcWp zY#_45C5YuyuQ8T&gI_>)Y+=$pb}!5bvdOLBEKuj&ShnVdCifFDRI!#6=&0Kq@)T7( z@3Q`r`#TG8S5GMSN4aF3&OQYTs=vZfYsN0Zm3MVs-s*p=8EOUx1A38+?WY~{s#bu>PFh5d9+SO~MTUN<Vqz*S~rhRo2S4e>ux;x=oKmhBJXhr=v_}MDeXcC1FCO>bv{u zp$cL};P|V*lA}b^Ug3tQ;x6~un-`IYWwY&bo;AQ7sES(qdU292w#jwTTqz)hklr~gvQArhR~pfSmfN;QdHNsGbz*C3}k`4$u$k(jPXh14Av9cXI< zThA{#QV*{^rhKj~CV+$vM%DdC@PF?o12~^H#<3>Oo2yzG-Z__ZAu!BsCST8wbtLXUR@IewSy{}J zMS*(b)xMP6%Gb^Nf(M=a`atT*U|o#NzhswT#MZr}M0mzUHju1^p;dIgpQqPQ7xKf= z*6RFi3)AgJSjiHa;udiTpS79gf~kOT{Yd!AFdK!1s~uZ*2<5!_<#fdIkw?^xo%p=gqUgpR(7 z(4aC}R{0Bf+I|YX3)+Skx(z46WLNY36Bk4*SxHLfrj%HYKUJ)POTjQb7{E=46N^^? z_aWu4@)Egj6GydwZ6RZt$6^jtW|YDHYW06W&k5o=e=g-r>d!Jma*q8(ad$M4tuJQn zNvhQGLZZyrUm?dm*PCfY4W@Epr>~?*q0cSb#%Rs}mpf{UyKrLY0$^@N6>%>>@BLKsYR zov>zv-fNMcfxD?rfW-Y+WHJ#Y4+8Y+JB+>(L&vctgTTe-V9`OfB`pL$_++9&Uq?Of zcnhz_$auIOsBgGNL@BNLX$t3m8syXMfll%DMykY?WY=!^k6myd-{~-~H<9|Ed)i%k z!oh^3;Q68EUhf^o7x{vLlM(y(Wn@Z_&OUYy;48n)Dc8eKeueEUn~emusWWS^Z1st>72*Il()`{}Vz6XDHkoM3hvAxhh=7*}K-bz{DG z$Zj#9%5JzjxYSEsp`{w8c)23_?=vV(Gc8da1D~ly@VRrljZ)^nFuU+x71quJDW`&m zEG-NWRQ2z#Ypbx!Jk{oZo>;7nLA?1naEB@w@E{$Ec?@!qLAlBnp)4bD)eOO|wLFmnWp`oow&?0WvvGDvHEnZv$la=P)$wLe95Ea>}|9AUMT=k4Qo56M*-vm#HyU zD(mZXQy1Y}eZ4R%>c+`lh}KO`=!&qlm2(Pe&407y@vK1+7ZnbX?0YmSKi4J`6y#?@)W^AulCpMVNj?{=SZG=0c7V6eKX`^e+5^dgRdv z8$Gik#v-)mA%F?jEzS(L&4m*@W@KaX!E^tvq#GUV^=*o;W4}^tml%A>GzTlJV-JtG zIxiY>e|RcYAi5)lhmBP?15^S3r!sWmvkZ()-4V82#mBz|W4Wq25_PvNx?`yC)Vm2l0KJLlT=(#-8kS zGluhH_7L9-kmBN9tZABQSq?qL6xGQPXw9(HPozV)q=%Zu69FU2t9QOCuQiUOB4&B- zR~i9Pc{RK8im=d>Ur={`f8SM4M*w!!pw{1o&u$?Gu36{#usz4OmtsqjAaSdrz3!$Kej2 zB`NG=8XBETNrReKK#^UUqG}U>B+ZJ>OnGYbuY@6MPeLqk^j+G}XpGinAy1HP9@0=0 zCTQd3D1<^^iIey^sZjr0@EoQD-8fQx_Z5p~Zk<&^mdt1I=+$y;2lT^AJZJ1_2OucZ zMjU`?v6VtD7T2?j3TNlB(8?8CRG(gX8#`PXHCc8DDf6j!8c1g{#B%`xJpk&R$h3bJ zLH=v9SWQNLW}$-RuiAAPGs+Jx_aI;d*cRBxhBo}BkqhozE8J`$@wOE=MoxzzJBKXH zyvBHbRw;UWj$%TQ+LdX_2urHEcsqV_A}pvOjRWfa+Mf)-3}74mWCX@=1Y&Htz%^qS zyA)#3o2U31)*z5zxPJ-eN@+S=%lcH9SHa&lVlMBYwdNn@3QK?tz#zBAs7AY~@TOe3usy5`1`s-b*GrD!n zLU&-qm`H-~h<)sCs*AptNkPYAp-Prq9=#CO!H^~Pb#baKdXi5wy zLRPl6hh9b5*9e4})+_e(n@!lRL2$JYs@^E)zXgY?g+9J^xK3@w0$<9(x}I5m)LRY^ z+g+=uJROg|FB1TQs4dovm=NH}`eFq|@zndV^W9BAdVL+=JipSAxDwzKeg>`Rr{VuyFkO6TDLxl6iTdSDX&wK*E4}vn z;0+-)>HuA)r`6ysg%UPnT>&v?#b)x!j__ud)b_)R=9h3^US{lGz!D0=7ecm*t`h`a zZU)}v_t*qvu4oVJ7ab8aZF_8d#qDl)is0jcZYW%9Fa{B_Xw-nd^^?(b<)4JHvpl|8 zG!$)utT}nML5QJt?1XyQ-f9jZ>La7}-z$)8V?E-EFy#|!_`jbsHSHXk!nHU#?oW0E zuU5%6sZh{hMgA@5WPU_qK4QZ*YLNe%74!L3Z5>Dfg{=D{&w&7!Ge+kWAb4F75=Lc< zghQ`>GZd_^+yPJ8*aKSH5z6ap{=0t*IJvBg;0%!|z_9bM+>Hj=*#ubmtgA1CljqIQqT_| zL>l>|KSlM^l3DLm^kXj{nh)U?KUH|l5PsFPWux-6b){tbWAg#)Oe+F_%|Ax99 z>zQVnts(Y0(l&k%4l+*qz5FfVLC&U_pD#sX0NMNaIB86jvXYoT{{v#%q`BL;793=I zkhmpNWTg%F3?Atw$x!-QQQw4`sz`1QRR${>$7V{*L~n2}Gl&yg3C``Jd9Z5buv=(bU7 z`5}T}!V)5{*O>SC>&3u<>-a_rM0SD&%|3=JlDv@QlV=?COWM{+tSTP|)rn(+cldLQ zsZsClq4gJw5wI*$H5_ZXagrr8K-v>fuzmE91U8t-*9SFf^XX`zVxk8s|B7W2sf8tJ8fvmOT|KO*Lvo=XO7ICGURnx7q4<2)L@E?-|d#~ZK)c4aI)G* zd0+_zP7^eyJ*$~Ur}B~?dr=C_4e#-^dy&o;e5}uH+j$Ecbar;?UDW({V)XL(4{J8r zjZ*n{wD$H;z66hgi_1IA<0fs5G|WKy%(f##78b-0cyyErI$vBYo3L! zOCE~dgdsS-2O+Mb37pxtr6fak)6mm)nYj~tHn9e=F^615rla7Ap`(M4{0!_85qRMECo>U-0 zQ%s>4WHYoYh+h?{N+c^vR{p8^eSxfZ*A8T?cizhCwsnGM6_2JyJB-wp#z18D3Z&=j zWwoCWzgd`=lh-$4%bUYj_0OjUGsqKnMhLV9L|~0YCcj;b6=J|zXsupWa@Ob8dapvk zxqsVwt+_kZw)U(oadfldwU4eX5Jx0`Xwl(DiZ=v%72o_3;Ny=du0Tk~4I3+&$dI89 zJA%3&nI`~2w!RaxUC${sEy>4^$&LB9An;Re=QC|$+J}__@$rBlJE`U5-XI6KbgN6a?~p&p@cuOKqdbUC)*AkVEmKOYNq^|OXxSt-x!@gBjS71?uV zjS~iq665ErKPI}$_6IrIVW5toftEP?WX->#AvTpS)SzY|h?mN|T1`P*tZmZf3MoL$5 zH7F%AU?`2<-QM{yM#Xt#y=F%)P}0oz3|?WuN*UqRIct6_`2j|b^=F^V3@Tm&-t2k^ zGm`gz3lcXd5r}LBl$$1=znpT?UhIr;I3l0_2s3w(hclmgvH}R2bqY1#vc+2~Xb-`d zno0CXlBYIQ2PcZcxy#Wup-@@BMWK%en)bU>}ChFAS9 zN=CPMrfR40c`j;lG#1L{xvw(t=Ik70MdbFJ?mFSV*kq|nAc-?)J*?xMFDLg=bcd#- zvLK)N1X4fCNvq6XZ3Fom_sE=iZJhFmu_(RFGb{f8! zWRz34E+!^5s)vh;W_JnuT8Xvr+1EDvjs}nVXy$i`(N}TbH|L_gqh4CdH_t==d!M{P$;G55-0| zjYte*T(>i*ldUL?!bGz7vPtdn%p98 z^wZ?Kw5ng{^t`w_s}7RJ&0~P3T`K$^{;n-HHRX?Cyc3i?EP|BQfPBh9yqQ^lW3MW8 zuZ@fo6u$cax@yx=9?H)!Eg!y;!bs+%X`&Hi7)G^MuC{wurDALLdZQ%gW8w^9XUl7+ z3PMNVAR7P5I~j1kuS`0apqDfUrF55*th;4ZvHl-HhXPmyFBElsBQNks*`C8Th|cS1LkBEW5=;+VDfQ!4CVfAtT6E z72h;n(ZmYr9_!6eW@BS)QT{a~&5>_T!gC=`jfbdHZt2(H6PX}pia6Q|Kwzs~)2}c1 z!E>M!*5G+*6E$fcKi(~1xKxY&Y+;b4A|e;c3HFp=pgGo#N&aku$pOZijlbGVw{jM6 z9qYQ1`j@lDb-dNP-I6%pd_w1~^3>t%PrD4r}mP#+d$mFceb z1dSMRwi&wxm0_X&3bugIiprpBS_NnD|zP{ zUW6dzEx6*(*IXM64FwHfsdXO~7J+(vs*Qx&a|QMw9<@jeP6P(^=NGyISu#-4jrEr^ ztZNRKnUzv;*o7tAXgrUK1FVGxC2fE9Hphib%T|wFY3W3~!QQlY zFw8w_0>*Zyc(Mk|YGz<>Iuwm(YZ~D=C%+fr&b^iVDM@Gew4a4rmpcMTjvk!ISX^*( znijvg_F-e6>k739#}sh8u6Q55O^7$Qi^kzWPgO>URx=w(rlJ>cG!n=kphcF;{v01E zA!JEt<*X;!E5A+3-1|u70om*)zbP?UAO=*xAQS&v(6i0mtgFez{Dw?diNqagKWdhG z%B=AxStfsMZyy#TK0xqAlD;esUg<^@)6zob)ucK$AIxbVmhO7utYDc{rAsIW^!ENZ zuqQ&ID$)cfR_qWNFZN)zK3)(^JQ z3JAiM;@1z#(2k*0ZAx$U0t&kNFd#oZ@U!18Kp#sAd$Nd_l_Xbra{c9l^2kGBY*xb0 zoHnuCGL&76Mcq3`i7GujzJvrjX7c1#Lu&Bs5)7j1(>~)Lt)yG93QlsT?xUE;&GJ8& zxAKVE$j&s+AI~Tgs2+?{Z?Gx5z-WJQsJ&*0^X!_L3ZN=-2}rM-_CUzoe$n*9$8K02 z_LV#N-5_Q`@hLw4A9vKXC`#gZ)1dT2McKhD?j1IH^yiur%4wp(p+9Y{Qxie~f?>c0 z?ORy|=*hx2ZA7sU5E=d%2f*s*-BiA3Tg&mf)>d9-FHprrH}&WR$=~FrI>+*`sc0e5 zHL}~Ue3Wgw&0~ekmemfUHIaxYy|u@2^U0Bnt!V|Ln3N*lKK&de&yeh|zInDXlfNQt zny@m~79%u-44B2tUs^>|aorxl3Y((Ar>t<_%RcBV5uJgT{FoDnI zvpS}>bPfH%=9$uD*pGbfAEaO%{6KVZfd}!2D#zLwB5Jq422PPR(TE=3y6ycpC<+{x zfG1U;dH?u#Ph9ZYAx)2V_|-?kuNLQ++inhmj?#=zXb{Ea}Ro5y-(zgX$5^kSOZ9Hijk8G zz28kM3z*kD;$Q%R&^n^1Z*2vKn*lDOD>Y%*zS1IB?C-6=+bdD&qgKGM^Un^yonDMC zp|NZP@pagK!{Z?C#v#Pg63#a}5^iF|&h_&DM5|n?o7E**fbRG&D2_vT$50Humh9xlyJ+k~s}Dq^U6) z=Wo)Ex*Ymw5c3PFA(E?)CIb>m`w<=?PFQ$3!@+GL!zzB05D|n+%Q8^>1M$#(zrxst zvZf2%qU^T7DJL_3yhG5*#?4?@|E08)yt-yTm#^(b!f9ZyP!h4B@gl|m!7smkBtOYb zVJkZ^!Ex$G7|y}|FdVF(l_@-vXJwMzUE8yxlEm}uTA+ik!7W#m8-YGG`Df9A@O^}d zkC-eRFt71<{;MhtZ;SxQrryZ70a`R_{g9Q25 z$@P6Q!|M|cQst_h{L*QIUY2OJ9rdN z>HQJn+leK;l^LkH|MO~Vp3&1a?j)X8pQp*h zU{={~=dj{8(c_Ei>nZq^;9aLS17D=-0fGt-rDzUvz7_?vtr<`HY=r6NC(jOAARj`p(X}Gk1C*8cD zr2NW^6$%(AwIj)GY^_Qrfkm%I2}BD1ye$?QanJL2F5CX716)*6e_8g1>00Xp$`MtWO#R*x-UxEM-L`5@v2%nKu3y1@VOOoB?HC&)CT^y)eTDny8<K})(wws zTLeBoH3wQ_phLvQxj-e;O-8?e4Ds4%R&r-%bTi?U+DII3Bh zku6n5oMj|2HIBKiswRfDpeYtNdot-tUOWlZkM5{I!s^T0t%hou5|}rs6leOPaSvEN zjIj_C>1mLXfbjxcVF$`15d zlSw{Wrqh3MVECkV8+MDpvlE3#yoiaPPRM~U{1~B7pNfy;EMxN#hljV%w=R$#^Ac*S z3YgUD-)B&5-yn~bT)8wu5IaQo+kMm7l!n9Y*N-cLG!AZ&N472*lOIcI%^ zXLRi0U#fl-C)Oxni7YLV*+~Eo<5ki?%jN zX?S@maSO(8d~ce{%V znB?k7Qf^FRs&0L8wW?2=xhIxc3N0dqINo^L_ImR(eyq{JgUsB9hon*%CBt**OBaZm ztUftXStDibDZEF1-}f%d5Dg*e+|03_iQg%lI;0Baa9yG$zv|zDEQC|~pWxouQ9UpN zDJ?&L!A}P%cmkvC3()q%vMT~9lXRwWZ!QVjOC+7IobFH+4{nR=e{ zAaquKJy!59P(^D&Rmya=6;Ls)Gs}9nR?O?pA!7s9F=*E#l1ASFUBa zC0g3m#DfxFmOMsD#!i!U6RE$cu||;5t>!S}fTb`w*;l(u_O{orjGcr-M8Ha)xaK19 z&Y*O+_SZAV0r%W0Pc?#=y}12b&}Nkd?!sSn=>TzWkQ|#Q2Ep9H$*VqGF)XUrbDmkY z1t7@QRBqZcOuS+sJ^KfL_N6&XtoZiyOK|*-c{;=qC(tJz3YT^oVkY-Evoi6=U2YT)*v?CMLUgNi7(s4T1-6x8tgFH?F^*| zIj03hNy<>D3W<6scKxf}d$k~Hild!faF;b5?>I|=n*wL(?j2DzD1kF>TU0P%A5?&7 zhKW(P#SEJ+{RTBjKnGbdjqxHZo4459bJZEY=@G55oWwf^=@rg4al;=Jq*O}C?)=Js z3+`ndi9Q9w3^wwaVI_4a;<9($z4C&XC`glSLS6t@8v+E&ziHe)ZRC*Pa%pPA3d#i^ zZ&7a>WX7qJ<)wxS?asd~OZT^8de6eWF9-OrwUsQ=6-N)%YW4lh<|E$FsZ3a>;H&$PWiV(>;{+1~4#-cL+AUJjVB}q9)IAG-z z@I=m@xQb4=e+7OBt|>}0Jp(_gVkz^Flv&5!gB9oopekdQdmHe}^{}xoyMD$p}&eJ23%QH4n3n)N2 z5|y%hqpFLc!TjnEC#}qx-O;xu*eLSbhCnps2qQ-mwrVs?Xm{>tBfNua&41N>Ck1w`=;I}JBxE?XtdyG^3PD?DwnmSZVI<*aI)3iG}Mgo?@wd@4MMM zB9#0qd=(<8f1^I8JA~xmj;-u4`-vDUelptl&zp`r@M97NlJDC>a;+82I%`iUDIA4l zsbd4kF8X>?xCn@j4V{*<%Xc*a+`?(~wvq}`}UE9}gj zClBTUb$(3MBBWGKxPJ@6n;mN=4$5*_+XoG#Ghk+$aw?<}?Ib8bCmz_9uOkTo1nYu4 zF=Zl+Yw-~>v|3wV;ImSx1l&U|2h2*xNV<0**(E4zO=Bx(|HzwpJ$t^px80LVSmT_K z;C+c`)?DK=zbRuD&ASP+dAGeJ&QSGyEe4Ni#lA_u8EMG(^_kY{TKg@dXCTK>cXXi~ z(YggXlBa%$t~kuk*1Uu2c3=x5GOpj07p~MNUk{xt{M~r)t;;(+6TD?wAT31Y%uAVt z!{I1&5jtab(%TVKYDYm9BjYkmW?L}-z3cK9iWhd5oSaN^IMok!bZ*r+vt(~4Dfyiz z>BXCAMh$p%JLe#oF6b}wfDo$cK`Gy@0aV#ynz!FKUSR1!1N52DC0k?^mCwHg$L_`% zR+&xl{Pjli-=>esi0|9FEbBd(mMYyTE-uB-0fI~7bs?^)MH&d@OZIiGh`c>$>u=72 z!?F8P_}Fp6ynnvHI%XZ9Y=ekuqN6JmrZ;d#Hn$I*%A*A6=PUH?wM5m7 z#eq(A+sXKq4A&4(q?cXVD|WoaWMp42j@h(daI~vD9(W@P zPf<_DQ5;pvp-njU@B4pYk+hk*aIVc3#$}Dte$|p|kd*8)t{S1?);WnX$w4KF9x)Ph zu1xJNaqd$Kn8G_^gjfNe%OeH7#M?|QtzK0{id%qNEieGlv4rCl25*P`m{~&UEBfg8 zJ&ePF1XG!udQ0W4u;yw}6lzyF&3fye3XlD7!R~zCS`;!XDv)#14>0Au-cFQ!<`B>; zUyYx_1jwGU0|3FrNcGLgfJGc~v+Ao^bT&31EJpHi?8dCItyrs`Kl+3e;D>F5-av)b zk(rdzCL_r(%?Zt4ZrcgWKOmef3N5eqso}oUz+VS6uHyoCu-^(IJ!8||{aDmGK-{E4 zm&BJyW@v}M+<@EkO_x?w8HmTSe+ZFA7P)d|>@+&iAtFD1AKH@2lO=s5c+H9A1|qYn zUoNidt?GBP*^ zz_&dI=hEbqU5peM=Bn=a!Z_@2!~47WDv+GK&ibk-+vtu_pcmvXPh_~SXA(ATEzJqq zOGneQf@nN2{w?T77o>C42@gj;MiDaX>;(m$iuM%vnK`0eC%nBjN?Z&OOou*_$vK>X z05KW%8$KsJ#&>#=tc4>9QY?lt6-el{@I>1X4y(jr{PD?vE4H9gDa<6poGX9jxrQh? zX<{iZ}wl_`&Xt!E%RQ9;fAq4R(cUM;owxG2=SiOtzoYiS7$0oGd2I;rvI zKHWX(unmv=gL{iV3Y{k6XpTB5)awx8*ziifL#@LKj;3nK(!he2G@a2y#;*@k7``s9 z49l{g=!_4-fnURv;`_5>C!cBC(v!CoC6Qh-n)q9W(U703^qKIvK%Lv9>NpuDp1)7| z^Dn-ixFEH!fJgFTXaHqxD>H-;XY^2`U~?oNvY?jbg}*uMqoRxh9j1|KRy zxg#n)*yhgBCb3N4(d>PguX9i+^cnyE|ARDAiDpZ0Fj3}970+3vGi@zCDefIAt+VXE zw9xE_E5_Xri_2B$vGg6nPZcvA9~oR1#C5m&Ewsx(u;Va>Apw z{E%3FgdBNq?+Ig zGO8{ri`+~$nP7nOZK12nrh0-iAu&RO8m2;gOm_7ytDRB_#O-;yE zbjDAj%w4MTY2ZI=>&LxDFM!zbaElbCJnG383rZ{Xm_c=yuGr7t6IZNsr#=*G+7#H1 zoMAw6o`irir$DQiX9U&73@`GBIQIItpkGWJ&Y{VVs_6NP0AsMq3DhUxV8X8mozGOQ z>3xJL62KWG%0Ua4xro!MdTjG4fnXUXO%HLNhz?r)s+y;j3sOX``AMCC^rMZ1=9Wlb zc3`(Yt1(Yk%N;3D3~asUwU~?e=CWJ|sFuWv_N>+d_jt`o#Y*TGHm2=j?ZSfBG4>Iq zfi-=Ai`E-;HWr&W~cW$wTH!J)0pe5(JaL`a)v{{1vgQMUjSFvrrC71a6yt z!nRiP(?mL-A8Tok2UUWBFlN&zR!IB_NBT0S$&D2gW?}5GIb{P&T88LrC5VgXX+i7H zBUis3qu!mUjM~-ON7r+W*@8kYm7w z$gmBG{}3`fAvBC+R5abkXmo~{@0~^6)-=AddON`6Ln)U^Rxw9W_ zUyvLULvHIzb??Cn8xjhEXIa=p|^0qZ|ZO58CCYrG(X`T0Cts=L2-KvUM zyT`WU&^*$duRv?=fN8Hw85oWD3SEBuDmo8fA9$~A>|RmuKmQga6c8^RYp`=RG+0m& zsH_=u$lbgdE6p-gagijNI z)XX75`~NyS?|3f1_m5i<%HA_8E7>DN$W}&nMpjm3C!x$_lx$_ct%MMgQ9@*oD7z># zTV{jb=bz*Kczo~2`*+k|uYca>+|T>E&-=cwbDis4=ep^6E++Z`aShs~P6{`C)v_hk z-NZ^w!3&#aN)q(yot*_FIfb>9s7tTrsvius(~>UP+Zpp;QdK7twkv&Leo|N|Gxgf_ zo-SA6V9`0gknk_xqm{46((|2^wj{lRRa3~VQQ@R@>z(iD02d`iErIEjTOevm)BPj1 zbzawf-aJ(rs^Eq6f>LR`-&X_%f3APrc&Q@Z?(#WWwJ#~?nYg`~(p)|cA$bJD{}Wlz zKHJAVjg?7x+{{%^UtKp}eY8C!o4EL;ke+MA=>6$^Ec`>1R5r=PODGeQ!h^{aN*q?utG>PwDMCly&O#ZvDe zd%pANdxl7BBC}C;HJcSBn~z(+LvX}nR?@uW>O$(3mQQCn#fBSq#b!pVaV@T~*$oU- z)zJ869%(BepH2GVFMMg0iLj|-Pi-M_wm{6}_Xqd7;ZvlT#~`nW!|K4Y{<&L&JZA5m zn=`art1rFCqrZ*MM7Cx9+q558aD<=3S*fmBB6-!$VWoCUD#LBd_`eCn6drk}4!yHk z>dd*(sQrntI{gBl`#*n*-jtVmuQUFZLyJE#xO5k{+tU)4zn4$SIGJDexpaUJo>{hU zYnPw-P>x$tQ^)tlU9+Uvo8RNe1vUr996sbm8Q=8eTJRKl$7WYs;CfmiBZX8U_sz;= zj+Gd$h{xP8dpG~8DbaQNY{M__o>Nr#w#4_a6@I4;d~3n`@c7hwrRInAZ=@RvB_W0O10ba=-3mtT*p$`mo{6a5tU-CTM0 zy*|~`xq}>5No(xuc>|xd&t}TpiD0@Pvq_m_C&eMQUn&)L#Jagj?$1^NYH{t`*XIPr z`jS1$EOGZsaXn&5wxXJI6i@e<*7K1!)YS~6u(`jhA}FxS+9bL-ekA+cLiQ`yYd@Z) zl9aqs!`ptTqB4H9gPhoL<6hi{tU#fm5nT0T;iqSf77VvM?tczDl@sxvOdz*4_+cd< zZn>4M*^&iaqrNNK!@s*ulgmGR$wFE9n?~Bu_C?;(_KH_rU+suO7;B3qb{DIM zrsJdk_Vb!lH(noLzWMc;vt)gL@%w8Q!V*mX`TENDV|=0uuRP{wd`yu4p_A0ZO6V4E zQJsUYgMm_9cE>?d}O5nj;-@SOoRC z`h$DEQRg*;-iT<4ojkS7kK>leJ?)=Bv9=`={H!Xrc+Z{qOT6NmJV)`(0k6!@{c+@H z^0wrSjx&;M7~SQ;sf~}VDkQp9YB%wfnay5dC9#kod>R z=+5p@S>53oC`ds{xi?AdQQ`7fm`uT^x6SXQgXDPG2C$%-i!o_#;T>8|3uPHkN#5vE zFVa_iYkI3%WnKcG+3LJ%3m5N;6E((LUDL`AtTOz0c5iU&!rOsbKYOKwhy+0s3$_ay zGB00AjodV=V=MV_&$e=uXLOg?pE2sYcBb=&NTk_H7YI6SQR() zmE?TEDNjt5t2C7$OJ(Xi9f`|x*mEwb%66;ql+)n60^wbutCaTrgZ zNEkh#82G;DR7@vJaJmjQQ%oIIo<>4WLXyg$fw@VQo3PPX!@{`Tp1|)PovwqKa*Y*a9?cR)|ajMiR zb>`2ww+Wr4X5(#AG|x$NF1c%UaqY$2T3)ftI8rqvm%>tPm&x^kvApm7Q|e3|3b%4A zybd1PK+y%lY{@R4M_(eGXQQJomVaa~d2)NxxNCK?K@)3QRZ`^UIjgPTIZux^wdhza z=}t-%DNSmZ^90dn_;ZasWp326=q(l0aTH1sUM$z3z|yA3UpiUkz@~Va`BJ=lTF`x_ z$+GFPT(ir23)fR!=3}<9IIxcH_}x8Q|I6WHu6Y<%)D!0KjGIsP$UY3X1Pg^-8IyS9 zU3p5UmeKOZhRoN5BSLbnqAMu33~DFL7R-s8{l&$nWnZnRq0(d3@+jOcV9#|rnwH+R z>DJ{J>R!NtA)`7nhI$ziYHOcmQ7um=OFWp>h}`a#6?BdS(ff9M;N-#nDs0)mq^qUD zGdWl5dPaKr4zVPD&cxeOQtk6bUoR8Qmv`vIhMK+=#ATR%?wQ58Z(FXbb&2zdLQRTi zYHM1f#N+D$`>|SOM;?f-e7US^<}&jw%0j;Ai6t3hLtiDst6nPam?`sHDV4qg%ob^L zH-qjvXVq5bRVj6nXr$&@O+*X7!Figrr?1eTmHm1q5qF>VlFv(~yhsJvUVcFm)Pzdo zkh3aB0qKA|LnfmEt$4nraZ<{MuXEVN`u^Q^10?5&Pcv z?=h#TPG;MX%ydU2e2rn3Z{**4^N~RaC5mn??e zKhb?#HI8xBQ83}y7*G59WN7Tl6Tki+cwzjsG6oYgHKR$K*=d$VxMSy7q~1>U_*PUH zUvwDPnx6@LIq#n$iTm25d3Us(LP=_Yh;RCa@-mIv8@AUwu2_@1PJ!#nQ&jJ4U+_A5 z+n*dL_YOEJ9XuBLgmy8QO({J6Rq{ssvZOc9voyMZTfGcsD7p{F!o$@C@_zk4^WqZ< zy*{1AU7_n0k;ZDQ-K_0Jg-L%d7_P?&HeR6OS~`d zckyX|I3Zi7pE2WKM(g*kPL_X1=Nh|+>1PWp2a<{br>ZQysJZx5^GofIZ1ujBwqGS& zu@Tx?__+C^U-NvGuZ`4koV2u7L7booY!lPC%Qe3RPy^ST+KCC{ZCrUXaQm}E{$$F$ zIa)%}&RS=ez9-#+CG@Vib(`7;RSA3|8tZO1K^y z54UR; zFt^7G>72PoUp1u?xz+LOfhC2-NlW$b!=5iXZLaJjmpsM>^#{S20_qUa7dHjMf?C%6h*E3%~u6$i%Va z9$U@m2>KGzWfNsax=ugc)O!fD!|sg%Xt13in9)K;+COP$bl9zFIJw11jtudA`tHzd zSFsXN?M{&R0yhhRcG&bJfCfv(L8F0^a$pO+V62B}2_AX8e5zoH0hRINY=)qLFs^9k zfVC+jjtoI;Y|sGIU~(Suj$t6H7PvD1SZ5ctA1&u@vpr25(c$Pz;IAw^OATt2fD6Rh z+Z@Ak-(INIv#EY{8duBHqiabjmZP`4Tr4NSX5uIs4crV+8M)0T)!4z6@jevnQ_o@} zy_ZhjinZWvwloUD{-iG0jIi2agV*2!uuv^#v_!vKxtBvjZxo%$puWWwC2LJ??C{yN z*if6}!Yu_#pQ{Ko*Gf>ADW(W6r7d~@75BN45T03*(`D9S|8)(1xP@XeQLbP zlyN)Qa#5gKD>dA-)P~ifa8) z4K`>16jl=qbPN<$0Sk!+{^M2eAEYSXbN9tJ~Yrjvbm&TrYLC+mpUi69lgh z(7>f&_X0{pfeR45I(2<&uyBx1+rnV#C&}fXr9G55Q_&LCMIw*36VhIw(ZCP@dNto6 z^29rd-1kKz{MRHUJ98pq*Xh3%EwwB#G;$_#Hz3B^VR!3-3&8fKqR~LjA@D{d6idi8xl%v@hO51XI?G?0(zto>M>R6g1aY7>#8#Dl=c0$BI*~reCL=2Yf_}S`Ij|eoL zXg+sxy___$i zGa9V=_5m8W6zpDrwSby3m6oF%YocE6gxr2%>{Qdr!StuQ@5^zYKhGzzCGVa`qk$Gc z?>}>uKEw5>SkyY0iKx}*xZ>wM<)7PmwT~lO-Dn)%w;>)V%pU+9167P_Z{!+fHu&YQ zVE2SfS(0+%yCiS^(8{$g?A+jiiCWBPF0tcp;-V&|zo_ov={${A)S}*S*=XY0d{KQH zKXg8k0)ckehz3A|dH$Hu(jRFKY$c@N1~K7OjO}XQkYD>j{dkx`*`pKfz$BhN~=x4eiQTCf} zmjDvb9+oF|4?4(&y(?z_+x73S@NG6|4D~Hji6O!??%VU6K{Ogb0;3vMoczEk0u5kZ z6hH#%ESr8lZ^73`@s9Hwxh3I%IK}E;yF2QF6$XckZYgl8%u0-`}UiC z9Q9hyridPacGw-a0PXP87Vwn@FL^ep$mjShEB&$B=}~GMXIAEwM*N7t}T!e=7&E;E= zRy*Y0C}6e2Pg_6>ps?=<0)&gi3p1=dWb>C zKw%SI%vNhypcv0nofWs#&QSolsCQn-0?I=D3+TXr%VV(*a4GbCJ zPQgy*I2dc70nEVz)&dIqNTbm}3!tz!BEo9Yg~szA_NBcy%`HOHgv)r^oM;o5Rzf?E zIoqln7hQM+ItB`RabiY;&BYLChh6dkXt2;IW;EC*4uKYSa%|mm<4=Q)$J?6XeREry z6!x&*=b_8>oh6wk#Y>a{5>S}`fEjHuYvx+XmO-?z;v_1QgLX@CX{7R@Tm^w8Iu&18A@UMVQfWc4rDkDsfk5sBFrGE^x68z3!S#niISJNo`*c6=zC>K>OQB z5vrL=n0lb(_nioGT?PlAMuoodf0-8=kgSu#>Ihs9jLGIW+X{XJHpXDX@((;=F>(j0e&2?c$ z>xkrIr9>rXShSf&>lUFZR_m=|9*s1rV0U|Wrt%V9M4)*zGfCbF3E3)j*%E478>g#R zO={}5Rg2$S8p}$rO|u0^Kw&`@G#YSP!NCJec7dY9{aB?|JdUJa{f$eq^&axrlz+If zPW;V3L1pa5ftL#!!cKSr9Rr1(gn&i^Hv{y!5XF7>YIP>-TT*vPm-c1)tUf6#Y0k3Y zAM0~k)No-!SdCA>KbO`OYXsHLwfbXnSkJqB){`NT>9Va;MVWZZ6c6YaXi|YjU_DB} zsIUmvE*)H~(w-PaAZ zn+UYSrZWOGSn?ibG}sAN2(%+vE#vPa1fEuVHkf&LWy+uKe)~$at?a083`;r|*$I7s z1Qhl^!;F>|euDjWY~~TisYqQ1r6+gn354Uh#LxP7^i`!#rx5NT&|tw|fCLowSTLih ztN-fEpnM|o8t24tUI#mc;4ER@Rf*Ffx715EG9CCf5NJwL?YcFQCckQuWX+Rexr$IU zYq-t~D)kGR9;wG)@b&;CpfZW4n7TgA-mN>bO}bjA6Ez7F(q^+)!bNs=i75#&Nau>TomG+00zVKrE= z9UuXPH6qXsySNX~4)3pl zcqWL@mHJ)Is%c$1uWx0m_I!wWhSa;FE;QV(_Ka?;3Nh!eWo*y@D6Ff+q&|yq8RkvQJZ6*G!9j2i5{=)cYj> zPoJ>gtMd8z)9I@UzZAFzFQJCsW%8}@QD{FTM5BQiJBWXBq?j81xxqXj$5g^fuBtdM zbIzfxmjBH&JN3!(#I|ex2f=v|Y==ejK{y&*z(db;XPl|3o&lR!KArlW_c6&i2aXR} zpGIbxx!rLH*3f9+xqt~0EI|F&4-|9^6m~QK8V%SPD9oKg{0$CUrwv>HwzP{G4dzH7 z&<{(yC=_r4WWQCu9hA{10_|^?G+;}+n9tIHM*&BhD*UR`4r=mD;M)@dn)s_^ie-M$1tes3?P09Vp)-}g8 zfCO}_goFN=>J75}*suKx_9PE=is`LC&LwU0`eFGRCn>vQMr(@=S)QqNAp9do7)j?7 zph!m@QXTbq(9C3!fY-k(z7ByFz;UOxS6$2Q4OXD<78S3w0oGKEtfE4cFzyza<Z@QBhs}%DlR=y-(;+_Q;$fBBEij z;$9jVW7gBXU)Z1lP}td+pktu0Gvv@{z{3MePOwE@Y|sEG%ryiZ1BD&Nfkp#%1`7M` zARZ`e$_qLM`tKgYW7|t*P#N<1QX{?<794;ULgpORoS4 z=;2dPK)?sA)4&dLK8O^721nKmIZzu?fwx7j;(Cp&%eIyo*I)U0>iI2pd^z_#78F1N z3XAukA1HA3fj>>nFcoDfwx#eYnGu%Hy^ z80g`%W5CS-Z9AiQY%e({+ z01{AGoB)A#*di3*0{E%nvlJm79{n!Exu&X%bFs--WI5ASSYsW@$zBYwYJn zs!uB*&|qT+AOVHdv%-v~M;?52$|zciEO&>bjIB>hiI+#CDubSfI6ZhN-jiJkfd)%v z1xP?)<$p1w!A?C#ps7+*^dC(dFej$8HjMKV&3DsjukEI(^}l$ckzTwzHxeKLh0Tl5 zXyDBW=3=nKs)KYV&=9tk4mt+* zmB{c6VYTRMCQb6JeEOR10q>8yI{LmUA1duWi|w?1r+8+qpb#70F5?q zHlA=7P~MDoS=Ml~+7)}_NT%{YgSuFM&1!+g%?p7BTR8$91BLmjn9<16qG-9<9%_fa z9@-FjI-t_R8g+)TMq!U2t-H;THlGoJ21{Q8NI(yt^#@u2wU&(?{JOKaUr%bhLYpch zCTQj)D|32RRPS5!mCx~Yh6ps+mko3bv^zM1lr$5ieTk@(N+U4KJcd{s)+-L`tv_?OLDlg zT(uZxO7{Mf>A+5g|L4Yb{D6_d!Q24&`mo6ah%JH(z=FkSG?2drV!5y!{e%29&=7VY zJ?I!H?BHfJ8W;jV@8Z^8BW;eyjVK69Y-P1u6V~rkzPOTWm1_A@jV+pA1@S;(FGA2U zP}oWn8ts1{s3~9m2A8^?_tEjSiM5XnI?3#AwDiyC6UaB$bn|a)E*?Bk@PJ?=03ZQ{ zy#ml^paoD^;0Iwfm=^^)1`11wz>Efa?I6$&Tka2_!CX(wXt3xC0_|@%kie>hU`B(b zVIa_&3HPpuh$}F@Q`8!9D(e4&SHyGj18TQ&O--U6^_46OSPLjDfQLo{D-B@Y58JSV z4H^K2Em44uf&P17!G2R$=KC4%5t{OgNCeUGf%Jge`L&@B$DrcOIaorDi%fm z#_@L-0u8q843L1rLQ$B}X!^UXSg9`m6j7}h8?RW3yK{>fzw~LET&YM9Gh~ST8;s-* zeYz`|BqQtQ9A17Rox~@gt*?T-;iZ@k@A7CzR0psYP}nX{%xJJHA*=?=mIX*aVeuZ! zXt0D)1R88x2S5S}`$%I(gXIe%(EfG<2`s>h84b1?hCut&M0f-mY<&|v zNTB~7m4xLNBG6zPv83r8#h3#ENqk)?N3fn$_u-e}S=V6-w u&}iW1fqx9!9e7|hU Date: Wed, 9 Nov 2022 18:16:15 +0100 Subject: [PATCH 0171/1008] feat(share/availability/light): Add some debug logs for sampling (#1294) Need further insight into debugging #1290 --- share/availability/light/availability.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index eec3141f1e..6f8b227791 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -73,12 +73,18 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) defer cancel() + log.Debugw("starting sampling session", "root", dah.Hash()) ses := blockservice.NewSession(ctx, la.bserv) errs := make(chan error, len(samples)) for _, s := range samples { go func(s Sample) { root, leaf := ipld.Translate(dah, s.Row, s.Col) + + log.Debugw("fetching share", "root", dah.Hash(), "leaf CID", leaf) _, err := share.GetShare(ctx, ses, root, leaf, len(dah.RowsRoots)) + if err != nil { + log.Debugw("error fetching share", "root", dah.Hash(), "leaf CID", leaf) + } // we don't really care about Share bodies at this point // it also means we now saved the Share in local storage select { From f7026ff0d47bd99ef80220a36a13bc9d3d31dfd9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 9 Nov 2022 18:39:57 +0100 Subject: [PATCH 0172/1008] fix(gateway): switch to separate Listen/Serve (#1314) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Closes https://github.com/celestiaorg/celestia-node/issues/1286 --- api/gateway/server.go | 12 ++++++++---- api/rpc/server.go | 28 ++++++++++++++++++++-------- api/rpc_test.go | 25 ++++++++++++++++++++----- nodebuilder/testing.go | 2 +- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/api/gateway/server.go b/api/gateway/server.go index 266d0e790e..451df3da31 100644 --- a/api/gateway/server.go +++ b/api/gateway/server.go @@ -15,11 +15,12 @@ type Server struct { srv *http.Server srvMux *mux.Router // http request multiplexer listener net.Listener - started atomic.Bool + + started atomic.Bool } // NewServer returns a new gateway Server. -func NewServer(address string, port string) *Server { +func NewServer(address, port string) *Server { srvMux := mux.NewRouter() srvMux.Use(setContentType) @@ -42,13 +43,12 @@ func (s *Server) Start(context.Context) error { log.Warn("cannot start server: already started") return nil } - listener, err := net.Listen("tcp", s.srv.Addr) if err != nil { return err } s.listener = listener - log.Infow("server started", "listening on", listener.Addr().String()) + log.Infow("server started", "listening on", s.srv.Addr) //nolint:errcheck go s.srv.Serve(listener) return nil @@ -65,6 +65,7 @@ func (s *Server) Stop(ctx context.Context) error { if err != nil { return err } + s.listener = nil log.Info("server stopped") return nil } @@ -88,5 +89,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // ListenAddr returns the listen address of the server. func (s *Server) ListenAddr() string { + if s.listener == nil { + return "" + } return s.listener.Addr().String() } diff --git a/api/rpc/server.go b/api/rpc/server.go index 4488ac53b5..dbb080e41e 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -2,6 +2,7 @@ package rpc import ( "context" + "net" "net/http" "sync/atomic" "time" @@ -13,16 +14,18 @@ import ( var log = logging.Logger("rpc") type Server struct { - http *http.Server - rpc *jsonrpc.RPCServer + srv *http.Server + rpc *jsonrpc.RPCServer + listener net.Listener + started atomic.Bool } -func NewServer(address string, port string) *Server { +func NewServer(address, port string) *Server { rpc := jsonrpc.NewServer() return &Server{ rpc: rpc, - http: &http.Server{ + srv: &http.Server{ Addr: address + ":" + port, Handler: rpc, // the amount of time allowed to read request headers. set to the default 2 seconds @@ -44,9 +47,14 @@ func (s *Server) Start(context.Context) error { log.Warn("cannot start server: already started") return nil } + listener, err := net.Listen("tcp", s.srv.Addr) + if err != nil { + return err + } + s.listener = listener + log.Infow("server started", "listening on", s.srv.Addr) //nolint:errcheck - go s.http.ListenAndServe() - log.Infow("server started", "listening on", s.http.Addr) + go s.srv.Serve(listener) return nil } @@ -57,15 +65,19 @@ func (s *Server) Stop(ctx context.Context) error { log.Warn("cannot stop server: already stopped") return nil } - err := s.http.Shutdown(ctx) + err := s.srv.Shutdown(ctx) if err != nil { return err } + s.listener = nil log.Info("server stopped") return nil } // ListenAddr returns the listen address of the server. func (s *Server) ListenAddr() string { - return s.http.Addr + if s.listener == nil { + return "" + } + return s.listener.Addr().String() } diff --git a/api/rpc_test.go b/api/rpc_test.go index b61c4705fa..4e40a66151 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "reflect" "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -28,8 +29,20 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { t.Cleanup(cancel) nd, server := setupNodeWithModifiedRPC(t) url := nd.RPCServer.ListenAddr() - client, err := client.NewClient(context.Background(), "http://"+url) - t.Cleanup(client.Close) + // we need to run this a few times to prevent the race where the server is not yet started + var ( + rpcClient *client.Client + err error + ) + for i := 0; i < 3; i++ { + time.Sleep(time.Second * 1) + rpcClient, err = client.NewClient(ctx, "http://"+url) + if err == nil { + t.Cleanup(rpcClient.Close) + break + } + } + require.NotNil(t, rpcClient) require.NoError(t, err) expectedBalance := &state.Balance{ @@ -39,7 +52,7 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { server.State.EXPECT().Balance(gomock.Any()).Return(expectedBalance, nil).Times(1) - balance, err := client.State.Balance(ctx) + balance, err := rpcClient.State.Balance(ctx) require.NoError(t, err) require.Equal(t, expectedBalance, balance) } @@ -122,14 +135,16 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { dasMock.NewMockModule(ctrl), } - overrideRPCHandler := fx.Invoke(func(srv *rpc.Server) { + // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root level module. For + // further information, check the documentation on fx.Invoke. + invokeRPC := fx.Invoke(func(srv *rpc.Server) { srv.RegisterService("state", mockAPI.State) srv.RegisterService("share", mockAPI.Share) srv.RegisterService("fraud", mockAPI.Fraud) srv.RegisterService("header", mockAPI.Header) srv.RegisterService("das", mockAPI.Das) }) - nd := nodebuilder.TestNode(t, node.Full, overrideRPCHandler) + nd := nodebuilder.TestNode(t, node.Full, invokeRPC) // start node err := nd.Start(ctx) require.NoError(t, err) diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 2e55096f91..a7fb1e7bde 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -46,7 +46,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti require.NoError(t, err) cfg.Core.IP = ip cfg.Core.RPCPort = port - cfg.RPC.Port = "26655" + cfg.RPC.Port = "0" opts = append(opts, state.WithKeyringSigner(TestKeyringSigner(t)), From f79bda42376ccd1e878a81a17fd6b2800d8771f2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:47:48 +0100 Subject: [PATCH 0173/1008] feat(api/gateway): Implement context cancellation middleware (#1321) Resolves #726 --- api/gateway/middleware.go | 23 ++++++++++++++--- api/gateway/server.go | 8 +++--- api/gateway/server_test.go | 53 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/api/gateway/middleware.go b/api/gateway/middleware.go index 9c49195c6d..f71d3c3d23 100644 --- a/api/gateway/middleware.go +++ b/api/gateway/middleware.go @@ -1,17 +1,24 @@ package gateway import ( + "context" "errors" "net/http" + "time" "github.com/gorilla/mux" "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -func (h *Handler) RegisterMiddleware(rpc *Server) { - rpc.RegisterMiddleware(setContentType) - rpc.RegisterMiddleware(checkPostDisabled(h.state)) +const timeout = time.Minute + +func (h *Handler) RegisterMiddleware(srv *Server) { + srv.RegisterMiddleware( + setContentType, + checkPostDisabled(h.state), + wrapRequestContext, + ) } func setContentType(next http.Handler) http.Handler { @@ -34,3 +41,13 @@ func checkPostDisabled(state state.Module) mux.MiddlewareFunc { }) } } + +// wrapRequestContext ensures we implement a deadline on serving requests +// via the gateway server-side to prevent context leaks. +func wrapRequestContext(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), timeout) + defer cancel() + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/api/gateway/server.go b/api/gateway/server.go index 451df3da31..4149db818b 100644 --- a/api/gateway/server.go +++ b/api/gateway/server.go @@ -71,9 +71,11 @@ func (s *Server) Stop(ctx context.Context) error { } // RegisterMiddleware allows to register a custom middleware that will be called before http.Request will reach handler. -func (s *Server) RegisterMiddleware(m mux.MiddlewareFunc) { - // `router.Use` appends new middleware to existing - s.srvMux.Use(m) +func (s *Server) RegisterMiddleware(middlewareFuncs ...mux.MiddlewareFunc) { + for _, m := range middlewareFuncs { + // `router.Use` appends new middleware to existing + s.srvMux.Use(m) + } } // RegisterHandlerFunc registers the given http.HandlerFunc on the Server's multiplexer diff --git a/api/gateway/server_test.go b/api/gateway/server_test.go index 76b0e2a530..857b8beef2 100644 --- a/api/gateway/server_test.go +++ b/api/gateway/server_test.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -41,9 +42,61 @@ func TestServer(t *testing.T) { require.NoError(t, err) } +// TestServer_contextLeakProtection tests to ensure a context +// deadline was added by the context wrapper middleware server-side. +func TestServer_contextLeakProtection(t *testing.T) { + address, port := "localhost", "0" + server := NewServer(address, port) + server.RegisterMiddleware(wrapRequestContext) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err := server.Start(ctx) + require.NoError(t, err) + + // register ping handler + ch := new(contextHandler) + server.RegisterHandlerFunc("/ch", ch.ServeHTTP, http.MethodGet) + + url := fmt.Sprintf("http://%s/ch", server.ListenAddr()) + req, err := http.NewRequest(http.MethodGet, url, nil) + require.NoError(t, err) + + cli := new(http.Client) + + originalCtx, originalCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute)) + t.Cleanup(originalCancel) + resp, err := cli.Do(req.WithContext(originalCtx)) + require.NoError(t, err) + buf, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + + dur := new(time.Time) + err = dur.UnmarshalJSON(buf) + require.NoError(t, err) + assert.True(t, dur.After(time.Now())) +} + type ping struct{} func (p ping) ServeHTTP(w http.ResponseWriter, r *http.Request) { //nolint:errcheck w.Write([]byte("pong")) } + +type contextHandler struct{} + +func (ch contextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + deadline, ok := r.Context().Deadline() + if !ok { + w.Write([]byte("no deadline")) //nolint:errcheck + return + } + bin, err := deadline.MarshalJSON() + if err != nil { + panic(err) + } + w.Write(bin) //nolint:errcheck +} From a59ac5c3fbc1b98dec0499c052cda154e89dc825 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:50:39 +0100 Subject: [PATCH 0174/1008] feat(.github): add labels on issues workflow (#1315) Resolves #1259 --- .github/workflows/issue-labels.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/issue-labels.yml diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml new file mode 100644 index 0000000000..5ad5fb6d20 --- /dev/null +++ b/.github/workflows/issue-labels.yml @@ -0,0 +1,21 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +name: Label issues +on: + issues: + types: [opened] + +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Label issues + uses: mheap/github-action-required-labels@v2 + with: + add-labels: "needs:triage" + ignore-if-labeled: true + repo-token: ${{ secrets.GITHUB_TOKEN }} From 545aae48e79a2bd2340d3ed8055958f9722a26cc Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:58:07 +0100 Subject: [PATCH 0175/1008] refactor(nodebuilder): Logger names in subpackages to `constructor/` (#1324) Resolves #1288 --- nodebuilder/fraud/module.go | 2 +- nodebuilder/gateway/flags.go | 3 --- nodebuilder/gateway/module.go | 3 +++ nodebuilder/header/module.go | 2 +- nodebuilder/p2p/module.go | 3 +++ nodebuilder/p2p/routing.go | 3 --- nodebuilder/state/module.go | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index b115990345..258d752d54 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -8,7 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -var log = logging.Logger("fraud-module") +var log = logging.Logger("module/fraud") func ConstructModule(tp node.Type) fx.Option { baseComponent := fx.Provide(func(module Module) fraud.Getter { diff --git a/nodebuilder/gateway/flags.go b/nodebuilder/gateway/flags.go index 991d433275..a104000cf8 100644 --- a/nodebuilder/gateway/flags.go +++ b/nodebuilder/gateway/flags.go @@ -1,13 +1,10 @@ package gateway import ( - logging "github.com/ipfs/go-log/v2" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) -var log = logging.Logger("gateway-module") - var ( enabledFlag = "gateway" addrFlag = "gateway.addr" diff --git a/nodebuilder/gateway/module.go b/nodebuilder/gateway/module.go index 05378c550f..ab4503c13e 100644 --- a/nodebuilder/gateway/module.go +++ b/nodebuilder/gateway/module.go @@ -3,6 +3,7 @@ package gateway import ( "context" + logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/api/gateway" @@ -12,6 +13,8 @@ import ( stateServ "github.com/celestiaorg/celestia-node/nodebuilder/state" ) +var log = logging.Logger("module/gateway") + func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 60f9b8fc1c..c611b26ce5 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -16,7 +16,7 @@ import ( modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -var log = logging.Logger("header-module") +var log = logging.Logger("module/header") func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index a84bdfded2..f02c9a0bf1 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -1,11 +1,14 @@ package p2p import ( + logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) +var log = logging.Logger("module/p2p") + // ConstructModule collects all the components and services related to p2p. func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index 3b5fc2997e..12aff41eb7 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -5,15 +5,12 @@ import ( "fmt" "github.com/ipfs/go-datastore" - logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/protocol" "github.com/libp2p/go-libp2p-core/routing" dht "github.com/libp2p/go-libp2p-kad-dht" "go.uber.org/fx" ) -var log = logging.Logger("p2p-module") - // ContentRouting constructs nil content routing, // as for our use-case existing ContentRouting mechanisms, e.g DHT, are unsuitable func ContentRouting(r routing.PeerRouting) routing.ContentRouting { diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index 99d7d1ddac..b1e8c95297 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -12,7 +12,7 @@ import ( "github.com/celestiaorg/celestia-node/state" ) -var log = logging.Logger("state-module") +var log = logging.Logger("module/state") // ConstructModule provides all components necessary to construct the // state service. From 793ec2cc89dbe1c147b6e1ace510ad4e19cfc18d Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 9 Nov 2022 20:01:36 +0100 Subject: [PATCH 0176/1008] refactor: linewrapping comments to 100 width (#1274) --- Makefile | 1 + api/rpc/server.go | 4 ++-- cmd/celestia/bridge.go | 4 ++-- cmd/celestia/full.go | 4 ++-- cmd/celestia/light.go | 4 ++-- das/options.go | 15 +++++++++------ fraud/interface.go | 3 ++- fraud/proof.go | 3 ++- fraud/registry.go | 4 ++-- fraud/service.go | 6 ++++-- header/header.go | 11 ++++++----- header/p2p/exchange.go | 4 ++-- header/p2p/subscriber.go | 17 +++++++++-------- header/store/height_indexer.go | 5 +++-- header/store/store.go | 6 ++++-- header/sync/ranges.go | 5 +++-- header/sync/sync.go | 14 +++++++++----- header/sync/sync_head.go | 12 +++++++----- header/testing.go | 4 ++-- libs/fslock/locker.go | 4 ++-- libs/fxutil/fxutil.go | 8 ++++---- nodebuilder/config.go | 5 +++-- nodebuilder/core/module.go | 3 ++- nodebuilder/das/daser.go | 4 ++-- nodebuilder/das/service.go | 4 +++- nodebuilder/fraud/service.go | 8 +++++--- nodebuilder/header/config.go | 4 ++-- nodebuilder/header/flags.go | 6 ++++-- nodebuilder/header/header.go | 4 ++-- nodebuilder/header/service.go | 4 +++- nodebuilder/init.go | 3 ++- nodebuilder/node.go | 17 +++++++++-------- nodebuilder/node/type.go | 9 +++++---- nodebuilder/node_bridge_test.go | 4 ++-- nodebuilder/p2p/config.go | 10 ++++++---- nodebuilder/p2p/host.go | 3 ++- nodebuilder/p2p/misc.go | 3 ++- nodebuilder/p2p/network.go | 3 ++- nodebuilder/p2p/pubsub.go | 4 ++-- nodebuilder/settings.go | 3 ++- nodebuilder/share/service.go | 4 +++- nodebuilder/state/keyring.go | 3 ++- nodebuilder/state/service.go | 7 +++++-- nodebuilder/store.go | 8 ++++---- nodebuilder/tests/swamp/swamp.go | 6 +++--- share/add.go | 6 ++++-- share/availability.go | 7 ++++--- share/availability/cache/availability.go | 4 ++-- share/availability/discovery/backoff.go | 9 +++++---- share/availability/light/availability.go | 4 ++-- share/availability/light/sample.go | 3 ++- share/availability/test/corrupt_data.go | 11 +++++++---- share/availability/test/testing.go | 11 ++++++----- share/eds/byzantine/bad_encoding.go | 5 +++-- share/eds/byzantine/share_proof.go | 4 ++-- share/eds/eds.go | 19 ++++++++++++------- share/eds/eds_test.go | 8 +++++--- share/eds/retriever.go | 12 ++++++------ share/eds/retriever_quadrant.go | 3 ++- share/get.go | 7 ++++--- share/ipld/corrupted_data_test.go | 12 ++++++------ share/ipld/get.go | 11 ++++++----- share/ipld/nmt.go | 3 ++- share/service/service.go | 17 +++++++++-------- share/share.go | 8 +++++--- share/test_helpers.go | 11 ++++++----- state/core_access.go | 3 ++- 67 files changed, 258 insertions(+), 187 deletions(-) diff --git a/Makefile b/Makefile index 41f19095f5..cb03b81be6 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,7 @@ fmt: @find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs gofmt -w -s @find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs goimports -w -local github.com/celestiaorg @go mod tidy -compat=1.17 + @cfmt -w -m=100 ./... @markdownlint --fix --quiet --config .markdownlint.yaml . .PHONY: fmt diff --git a/api/rpc/server.go b/api/rpc/server.go index dbb080e41e..85d59ccab0 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -34,8 +34,8 @@ func NewServer(address, port string) *Server { } } -// RegisterService registers a service onto the RPC server. All methods on the service will then be exposed over the -// RPC. +// RegisterService registers a service onto the RPC server. All methods on the service will then be +// exposed over the RPC. func (s *Server) RegisterService(namespace string, service interface{}) { s.rpc.Register(namespace, service) } diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index d8b650c0c0..a4ddd99268 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -12,8 +12,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on -// parent command. +// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the +// PersistentPreRun func on parent command. func init() { bridgeCmd.AddCommand( diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index fa4f4af65f..254545218d 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -14,8 +14,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on -// parent command. +// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the +// PersistentPreRun func on parent command. func init() { fullCmd.AddCommand( diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 5e94f159f5..d035f0aea2 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -14,8 +14,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on -// parent command. +// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the +// PersistentPreRun func on parent command. func init() { lightCmd.AddCommand( diff --git a/das/options.go b/das/options.go index b4de66b776..5662bf281e 100644 --- a/das/options.go +++ b/das/options.go @@ -28,7 +28,8 @@ type Parameters struct { // ConcurrencyLimit defines the maximum amount of sampling workers running in parallel. ConcurrencyLimit int - // BackgroundStoreInterval is the period of time for background checkpointStore to perform a checkpoint backup. + // BackgroundStoreInterval is the period of time for background checkpointStore to perform a + // checkpoint backup. BackgroundStoreInterval time.Duration // PriorityQueueSize defines the size limit of the priority queue @@ -40,7 +41,8 @@ type Parameters struct { // DefaultParameters returns the default configuration values for the daser parameters func DefaultParameters() Parameters { - // TODO(@derrandz): parameters needs performance testing on real network to define optimal values (#1261) + // TODO(@derrandz): parameters needs performance testing on real network to define optimal values + // (#1261) return Parameters{ SamplingRange: 100, ConcurrencyLimit: 16, @@ -115,16 +117,17 @@ func WithConcurrencyLimit(concurrencyLimit int) Option { } } -// WithBackgroundStoreInterval is a functional option to configure the daser's `backgroundStoreInterval` parameter -// Refer to WithSamplingRange documentation to see an example of how to use this +// WithBackgroundStoreInterval is a functional option to configure the daser's +// `backgroundStoreInterval` parameter Refer to WithSamplingRange documentation to see an example +// of how to use this func WithBackgroundStoreInterval(backgroundStoreInterval time.Duration) Option { return func(d *DASer) { d.params.BackgroundStoreInterval = backgroundStoreInterval } } -// WithPriorityQueueSize is a functional option to configure the daser's `priorityQueuSize` parameter -// Refer to WithSamplingRange documentation to see an example of how to use this +// WithPriorityQueueSize is a functional option to configure the daser's `priorityQueuSize` +// parameter Refer to WithSamplingRange documentation to see an example of how to use this func WithPriorityQueueSize(priorityQueueSize int) Option { return func(d *DASer) { d.params.PriorityQueueSize = priorityQueueSize diff --git a/fraud/interface.go b/fraud/interface.go index 9fa8cc3d2b..1dd4ffe69a 100644 --- a/fraud/interface.go +++ b/fraud/interface.go @@ -24,7 +24,8 @@ type Service interface { Getter } -// Broadcaster is a generic interface that sends a `Proof` to all nodes subscribed on the Broadcaster's topic. +// Broadcaster is a generic interface that sends a `Proof` to all nodes subscribed on the +// Broadcaster's topic. type Broadcaster interface { // Broadcast takes a fraud `Proof` data structure that implements standard BinaryMarshal // interface and broadcasts it to all subscribed peers. diff --git a/fraud/proof.go b/fraud/proof.go index 65e7a183a1..e87e34440c 100644 --- a/fraud/proof.go +++ b/fraud/proof.go @@ -48,7 +48,8 @@ type Proof interface { Height() uint64 // Validate check the validity of fraud proof. // Validate throws an error if some conditions don't pass and thus fraud proof is not valid. - // NOTE: header.ExtendedHeader should pass basic validation otherwise it will panic if it's malformed. + // NOTE: header.ExtendedHeader should pass basic validation otherwise it will panic if it's + // malformed. Validate(*header.ExtendedHeader) error encoding.BinaryMarshaler diff --git a/fraud/registry.go b/fraud/registry.go index afc7cb5097..5e76ae2b37 100644 --- a/fraud/registry.go +++ b/fraud/registry.go @@ -19,8 +19,8 @@ func Register(p Proof) { panic(fmt.Sprintf("fraud: unmarshaler for %s proof is registered", p.Type())) } defaultUnmarshalers[p.Type()] = func(data []byte) (Proof, error) { - // the underlying type of `p` is a pointer to a struct and assigning `p` to a new variable is not the - // case, because it could lead to data races. + // the underlying type of `p` is a pointer to a struct and assigning `p` to a new variable is not + // the case, because it could lead to data races. // So, there is no easier way to create a hard copy of Proof other than using a reflection. proof := reflect.New(reflect.ValueOf(p).Elem().Type()).Interface().(Proof) err := proof.UnmarshalBinary(data) diff --git a/fraud/service.go b/fraud/service.go index b3b8bc5898..cea4ec0a60 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -18,7 +18,8 @@ import ( "go.opentelemetry.io/otel/trace" ) -// fraudRequests is the amount of external requests that will be tried to get fraud proofs from other peers. +// fraudRequests is the amount of external requests that will be tried to get fraud proofs from +// other peers. const fraudRequests = 5 // ProofService is responsible for validating and propagating Fraud Proofs. @@ -77,7 +78,8 @@ func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { return nil } -// Start joins fraud proofs topics, sets the stream handler for fraudProtocolID and starts syncing if syncer is enabled. +// Start joins fraud proofs topics, sets the stream handler for fraudProtocolID and starts syncing +// if syncer is enabled. func (f *ProofService) Start(context.Context) error { f.ctx, f.cancel = context.WithCancel(context.Background()) if err := f.registerProofTopics(registeredProofTypes()...); err != nil { diff --git a/header/header.go b/header/header.go index 2fa93b4412..79ea4941b0 100644 --- a/header/header.go +++ b/header/header.go @@ -76,7 +76,8 @@ func MakeExtendedHeader( } // Hash returns Hash of the wrapped RawHeader. -// NOTE: It purposely overrides Hash method of RawHeader to get it directly from Commit without recomputing. +// NOTE: It purposely overrides Hash method of RawHeader to get it directly from Commit without +// recomputing. func (eh *ExtendedHeader) Hash() bts.HexBytes { return eh.Commit.BlockID.Hash } @@ -147,8 +148,8 @@ func (eh *ExtendedHeader) UnmarshalBinary(data []byte) error { return nil } -// MarshalJSON marshals an ExtendedHeader to JSON. The ValidatorSet is wrapped with amino encoding, to be able to -// unmarshal the crypto.PubKey type back from JSON. +// MarshalJSON marshals an ExtendedHeader to JSON. The ValidatorSet is wrapped with amino encoding, +// to be able to unmarshal the crypto.PubKey type back from JSON. func (eh *ExtendedHeader) MarshalJSON() ([]byte, error) { type Alias ExtendedHeader validatorSet, err := amino.Marshal(eh.ValidatorSet) @@ -164,8 +165,8 @@ func (eh *ExtendedHeader) MarshalJSON() ([]byte, error) { }) } -// UnmarshalJSON unmarshals an ExtendedHeader from JSON. The ValidatorSet is wrapped with amino encoding, to be able to -// unmarshal the crypto.PubKey type back from JSON. +// UnmarshalJSON unmarshals an ExtendedHeader from JSON. The ValidatorSet is wrapped with amino +// encoding, to be able to unmarshal the crypto.PubKey type back from JSON. func (eh *ExtendedHeader) UnmarshalJSON(data []byte) error { type Alias ExtendedHeader aux := &struct { diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 17f2ca2ee3..f7fd4f996d 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -220,7 +220,6 @@ func (ex *Exchange) request( if err = stream.Close(); err != nil { log.Errorw("closing stream", "err", err) } - // ensure at least one header was retrieved if len(headers) == 0 { return nil, header.ErrNotFound } @@ -230,7 +229,8 @@ func (ex *Exchange) request( // bestHead chooses ExtendedHeader that matches the conditions: // * should have max height among received; // * should be received at least from 2 peers; -// If neither condition is met, then latest ExtendedHeader will be returned (header of the highest height). +// If neither condition is met, then latest ExtendedHeader will be returned (header of the highest +// height). func bestHead(result []*header.ExtendedHeader) (*header.ExtendedHeader, error) { if len(result) == 0 { return nil, header.ErrNotFound diff --git a/header/p2p/subscriber.go b/header/p2p/subscriber.go index 64a4c2787e..7ae46178d4 100644 --- a/header/p2p/subscriber.go +++ b/header/p2p/subscriber.go @@ -80,7 +80,8 @@ func (p *Subscriber) Broadcast(ctx context.Context, header *header.ExtendedHeade } // msgID computes an id for a pubsub message -// TODO(@Wondertan): This cause additional allocations per each recvd message in the topic. Find a way to avoid those. +// TODO(@Wondertan): This cause additional allocations per each recvd message in the topic. Find a +// way to avoid those. func msgID(pmsg *pb.Message) string { mID := func(data []byte) string { hash := blake2b.Sum256(data) @@ -95,13 +96,13 @@ func msgID(pmsg *pb.Message) string { } // IMPORTANT NOTE: - // Due to the nature of the Tendermint consensus, validators don't necessarily collect commit signatures from the - // entire validator set, but only the minimum required amount of them (>2/3 of voting power). In addition, - // signatures are collected asynchronously. Therefore, each validator may have a different set of signatures that - // pass the minimum required voting power threshold, causing nondeterminism in the header message gossiped over the - // network. Subsequently, this causes message duplicates as each Bridge Node, connected to a personal validator, - // sends the validator's own view of commits of effectively the same header. - // + // Due to the nature of the Tendermint consensus, validators don't necessarily collect commit + // signatures from the entire validator set, but only the minimum required amount of them (>2/3 of + // voting power). In addition, signatures are collected asynchronously. Therefore, each validator + // may have a different set of signatures that pass the minimum required voting power threshold, + // causing nondeterminism in the header message gossiped over the network. Subsequently, this + // causes message duplicates as each Bridge Node, connected to a personal validator, sends the + // validator's own view of commits of effectively the same header. // To solve the problem above, we exclude nondeterministic value from message id calculation h.Commit.Signatures = nil diff --git a/header/store/height_indexer.go b/header/store/height_indexer.go index 2fd70671fb..a1e64986a4 100644 --- a/header/store/height_indexer.go +++ b/header/store/height_indexer.go @@ -10,8 +10,9 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -// TODO(@Wondertan): There should be a more clever way to index heights, than just storing HeightToHash pair... -// heightIndexer simply stores and cashes mappings between header Height and Hash. +// TODO(@Wondertan): There should be a more clever way to index heights, than just storing +// HeightToHash pair... heightIndexer simply stores and cashes mappings between header Height and +// Hash. type heightIndexer struct { ds datastore.Batching cache *lru.ARCCache diff --git a/header/store/store.go b/header/store/store.go index 9a447ed355..8331e03034 100644 --- a/header/store/store.go +++ b/header/store/store.go @@ -18,11 +18,13 @@ import ( var log = logging.Logger("header/store") -// TODO(@Wondertan): Those values must be configurable and proper defaults should be set for specific node type. (#709) +// TODO(@Wondertan): Those values must be configurable and proper defaults should be set for +// specific node type. (#709) var ( // DefaultStoreCacheSize defines the amount of max entries allowed in the Header Store cache. DefaultStoreCacheSize = 4096 - // DefaultIndexCacheSize defines the amount of max entries allowed in the Height to Hash index cache. + // DefaultIndexCacheSize defines the amount of max entries allowed in the Height to Hash index + // cache. DefaultIndexCacheSize = 16384 // DefaultWriteBatchSize defines the size of the batched header write. // Headers are written in batches not to thrash the underlying Datastore with writes. diff --git a/header/sync/ranges.go b/header/sync/ranges.go index 98b0a4b6b4..c4f38b709f 100644 --- a/header/sync/ranges.go +++ b/header/sync/ranges.go @@ -6,8 +6,9 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -// ranges keeps non-overlapping and non-adjacent header ranges which are used to cache headers (in ascending order). -// This prevents unnecessary / duplicate network requests for additional headers during sync. +// ranges keeps non-overlapping and non-adjacent header ranges which are used to cache headers (in +// ascending order). This prevents unnecessary / duplicate network requests for additional headers +// during sync. type ranges struct { lk sync.RWMutex ranges []*headerRange diff --git a/header/sync/sync.go b/header/sync/sync.go index ab3233ad42..55df34e99c 100644 --- a/header/sync/sync.go +++ b/header/sync/sync.go @@ -125,8 +125,9 @@ func (s State) Duration() time.Duration { } // State reports state of the current (if in progress), or last sync (if finished). -// Note that throughout the whole Syncer lifetime there might an initial sync and multiple catch-ups. -// All of them are treated as different syncs with different state IDs and other information. +// Note that throughout the whole Syncer lifetime there might an initial sync and multiple +// catch-ups. All of them are treated as different syncs with different state IDs and other +// information. func (s *Syncer) State() State { s.stateLk.RLock() state := s.state @@ -227,7 +228,8 @@ func (s *Syncer) doSync(ctx context.Context, fromHead, toHead *header.ExtendedHe return err } -// processHeaders gets and stores headers starting at the given 'from' height up to 'to' height - [from:to] +// processHeaders gets and stores headers starting at the given 'from' height up to 'to' height - +// [from:to] func (s *Syncer) processHeaders(ctx context.Context, from, to uint64) (int, error) { headers, err := s.findHeaders(ctx, from, to) if err != nil { @@ -237,14 +239,16 @@ func (s *Syncer) processHeaders(ctx context.Context, from, to uint64) (int, erro return s.store.Append(ctx, headers...) } -// TODO(@Wondertan): Number of headers that can be requested at once. Either make this configurable or, +// TODO(@Wondertan): Number of headers that can be requested at once. Either make this configurable +// or, // // find a proper rationale for constant. // // TODO(@Wondertan): Make configurable var requestSize uint64 = 512 -// findHeaders gets headers from either remote peers or from local cache of headers received by PubSub - [from:to] +// findHeaders gets headers from either remote peers or from local cache of headers received by +// PubSub - [from:to] func (s *Syncer) findHeaders(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { amount := to - from + 1 // + 1 to include 'to' height as well if amount > requestSize { diff --git a/header/sync/sync_head.go b/header/sync/sync_head.go index 26b8534288..ee64cc8d81 100644 --- a/header/sync/sync_head.go +++ b/header/sync/sync_head.go @@ -59,9 +59,9 @@ func (s *Syncer) subjectiveHead(ctx context.Context) (*header.ExtendedHeader, er } // networkHead returns the latest network header. -// Known subjective head is considered network head if it is recent enough(now-timestamp<=blocktime). -// Otherwise, network header is requested from a trusted peer and set as the new subjective head, -// assuming that trusted peer is always synced. +// Known subjective head is considered network head if it is recent +// enough(now-timestamp<=blocktime). Otherwise, network header is requested from a trusted peer and +// set as the new subjective head, assuming that trusted peer is always synced. func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error) { sbjHead, err := s.subjectiveHead(ctx) if err != nil { @@ -102,7 +102,8 @@ func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error // incomingNetHead processes new gossiped network headers. func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHeader) pubsub.ValidationResult { - // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network header + // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network + // header _, err := s.store.Append(ctx, netHead) if err == nil { // a happy case where we appended maybe head directly, so accept @@ -128,7 +129,8 @@ func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHe return s.newNetHead(ctx, netHead, false) } -// newNetHead sets the network header as the new subjective head with preceding validation(per request). +// newNetHead sets the network header as the new subjective head with preceding validation(per +// request). func (s *Syncer) newNetHead(ctx context.Context, netHead *header.ExtendedHeader, trust bool) pubsub.ValidationResult { // validate netHead against subjective head if !trust { diff --git a/header/testing.go b/header/testing.go index 365e6151d3..cb55caff37 100644 --- a/header/testing.go +++ b/header/testing.go @@ -1,5 +1,5 @@ -// TODO(@Wondertan): Ideally, we should move that into subpackage, so this does not get included into binary of -// production code, but that does not matter at the moment. +// TODO(@Wondertan): Ideally, we should move that into subpackage, so this does not get included +// into binary of production code, but that does not matter at the moment. package header diff --git a/libs/fslock/locker.go b/libs/fslock/locker.go index a858d0bf7d..f451c42cc1 100644 --- a/libs/fslock/locker.go +++ b/libs/fslock/locker.go @@ -21,8 +21,8 @@ func Lock(path string) (*Locker, error) { } // Locker is a simple utility meant to create lock files. -// This is to prevent multiple processes from managing the same working directory by purpose or accident. -// NOTE: Windows is not supported. +// This is to prevent multiple processes from managing the same working directory by purpose or +// accident. NOTE: Windows is not supported. type Locker struct { file *os.File path string diff --git a/libs/fxutil/fxutil.go b/libs/fxutil/fxutil.go index a03ab0cb20..872f7c91a6 100644 --- a/libs/fxutil/fxutil.go +++ b/libs/fxutil/fxutil.go @@ -43,14 +43,14 @@ func InvokeIf(cond bool, function interface{}) fx.Option { return fx.Options() } -// ProvideAs creates an FX option that provides constructor 'cnstr' with the returned values types as 'cnstrs' -// It is as simple utility that hides away FX annotation details. +// ProvideAs creates an FX option that provides constructor 'cnstr' with the returned values types +// as 'cnstrs' It is as simple utility that hides away FX annotation details. func ProvideAs(cnstr interface{}, cnstrs ...interface{}) fx.Option { return fx.Provide(fx.Annotate(cnstr, fx.As(cnstrs...))) } -// ReplaceAs creates an FX option that substitutes types defined by constructors 'cnstrs' with the value 'val'. -// It is as simple utility that hides away FX annotation details. +// ReplaceAs creates an FX option that substitutes types defined by constructors 'cnstrs' with the +// value 'val'. It is as simple utility that hides away FX annotation details. func ReplaceAs(val interface{}, cnstrs ...interface{}) fx.Option { return fx.Replace(fx.Annotate(val, fx.As(cnstrs...))) } diff --git a/nodebuilder/config.go b/nodebuilder/config.go index a7ae3971f6..56a90fe4c5 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -77,9 +77,10 @@ func LoadConfig(path string) (*Config, error) { } // TODO(@Wondertan): We should have a description for each field written into w, -// so users can instantly understand purpose of each field. Ideally, we should have a utility program to parse comments -// from actual sources(*.go files) and generate docs from comments. Hint: use 'ast' package. +// so users can instantly understand purpose of each field. Ideally, we should have a utility +// program to parse comments from actual sources(*.go files) and generate docs from comments. +// Hint: use 'ast' package. // Encode encodes a given Config into w. func (cfg *Config) Encode(w io.Writer) error { return toml.NewEncoder(w).Encode(cfg) diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index 59d6db273c..fb33c86a42 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -12,7 +12,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -// ConstructModule collects all the components and services related to managing the relationship with the Core node. +// ConstructModule collects all the components and services related to managing the relationship +// with the Core node. func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() diff --git a/nodebuilder/das/daser.go b/nodebuilder/das/daser.go index 424b8aedc6..01d5414da7 100644 --- a/nodebuilder/das/daser.go +++ b/nodebuilder/das/daser.go @@ -14,8 +14,8 @@ import ( var _ Module = (*daserStub)(nil) -// daserStub is a stub implementation of the DASer that is used on bridge nodes, so that we can provide a friendlier -// error when users try to access the daser over the API. +// daserStub is a stub implementation of the DASer that is used on bridge nodes, so that we can +// provide a friendlier error when users try to access the daser over the API. type daserStub struct{} func (d daserStub) SamplingStats(context.Context) (das.SamplingStats, error) { diff --git a/nodebuilder/das/service.go b/nodebuilder/das/service.go index 9707497976..ee4a9e8334 100644 --- a/nodebuilder/das/service.go +++ b/nodebuilder/das/service.go @@ -14,7 +14,9 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. // -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +// Module +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { SamplingStats func(ctx context.Context) (das.SamplingStats, error) } diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go index b4369e53d0..85862b6cd4 100644 --- a/nodebuilder/fraud/service.go +++ b/nodebuilder/fraud/service.go @@ -6,8 +6,8 @@ import ( "github.com/celestiaorg/celestia-node/fraud" ) -// Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the network. -// Any method signature changed here needs to also be changed in the API struct. +// Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the +// network. Any method signature changed here needs to also be changed in the API struct. type Module interface { fraud.Service } @@ -15,7 +15,9 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. // -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +// Module +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { Subscribe func(fraud.ProofType) (fraud.Subscription, error) Get func(context.Context, fraud.ProofType) ([]fraud.Proof, error) diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index ca0dde5dc2..3f9318dba3 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -16,8 +16,8 @@ type Config struct { // Only affects the node once on initial sync. TrustedHash string // TrustedPeers are the peers we trust to fetch headers from. - // Note: The trusted does *not* imply Headers are not verified, but trusted as reliable to fetch headers - // at any moment. + // Note: The trusted does *not* imply Headers are not verified, but trusted as reliable to fetch + // headers at any moment. TrustedPeers []string } diff --git a/nodebuilder/header/flags.go b/nodebuilder/header/flags.go index db9b5958e8..26e7c105ed 100644 --- a/nodebuilder/header/flags.go +++ b/nodebuilder/header/flags.go @@ -47,7 +47,8 @@ func TrustedPeersFlags() *flag.FlagSet { return flags } -// ParseTrustedPeerFlags parses Header package flags from the given cmd and applies them to the passed config. +// ParseTrustedPeerFlags parses Header package flags from the given cmd and applies them to the +// passed config. func ParseTrustedPeerFlags( cmd *cobra.Command, cfg *Config, @@ -79,7 +80,8 @@ func TrustedHashFlags() *flag.FlagSet { return flags } -// ParseTrustedHashFlags parses Header package flags from the given cmd and saves them to the passed config. +// ParseTrustedHashFlags parses Header package flags from the given cmd and saves them to the +// passed config. func ParseTrustedHashFlags( cmd *cobra.Command, cfg *Config, diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index a776429c1b..7b6392c32d 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -64,8 +64,8 @@ func newInitStore( err = store.Init(ctx, s, ex, trustedHash) if err != nil { // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. - // This is due to requesting step of initialization, which fetches initial Header by trusted hash from - // the network. The step can't be done during unit tests and fixing it would require either + // This is due to requesting step of initialization, which fetches initial Header by trusted hash + // from the network. The step can't be done during unit tests and fixing it would require either // * Having some test/dev/offline mode for Node that mocks out all the networking // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step // * Or removing explicit initialization in favor of automated initialization by Syncer diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 5753034cbf..4f1b266ca4 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -23,7 +23,9 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. // -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +// Module +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) Head func(context.Context) (*header.ExtendedHeader, error) diff --git a/nodebuilder/init.go b/nodebuilder/init.go index b546b6d77a..5172cacab5 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -9,7 +9,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -// Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under 'path'. +// Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under +// 'path'. func Init(cfg Config, path string, tp node.Type) error { path, err := storePath(path) if err != nil { diff --git a/nodebuilder/node.go b/nodebuilder/node.go index c7225d3298..fd9065aaf1 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -31,9 +31,9 @@ const Timeout = time.Second * 15 var log = logging.Logger("node") -// Node represents the core structure of a Celestia node. It keeps references to all Celestia-specific -// components and services in one place and provides flexibility to run a Celestia node in different modes. -// Currently supported modes: +// Node represents the core structure of a Celestia node. It keeps references to all +// Celestia-specific components and services in one place and provides flexibility to run a +// Celestia node in different modes. Currently supported modes: // * Bridge // * Light // * Full @@ -78,7 +78,8 @@ func New(tp node.Type, network p2p.Network, store Store, options ...fx.Option) ( return NewWithConfig(tp, network, store, cfg, options...) } -// NewWithConfig assembles a new Node with the given type 'tp' over Store 'store' and a custom config. +// NewWithConfig assembles a new Node with the given type 'tp' over Store 'store' and a custom +// config. func NewWithConfig(tp node.Type, network p2p.Network, store Store, cfg *Config, options ...fx.Option) (*Node, error) { opts := append([]fx.Option{ConstructModule(tp, network, cfg, store)}, options...) return newNode(opts...) @@ -125,8 +126,8 @@ func (n *Node) Run(ctx context.Context) error { } // Stop shuts down the Node, all its running Modules/Services and returns. -// Canceling the given context earlier 'ctx' unblocks the Stop and aborts graceful shutdown forcing remaining -// Modules/Services to close immediately. +// Canceling the given context earlier 'ctx' unblocks the Stop and aborts graceful shutdown forcing +// remaining Modules/Services to close immediately. func (n *Node) Stop(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, Timeout) defer cancel() @@ -143,8 +144,8 @@ func (n *Node) Stop(ctx context.Context) error { // newNode creates a new Node from given DI options. // DI options allow initializing the Node with a customized set of components and services. -// NOTE: newNode is currently meant to be used privately to create various custom Node types e.g. Light, unless we -// decide to give package users the ability to create custom node types themselves. +// NOTE: newNode is currently meant to be used privately to create various custom Node types e.g. +// Light, unless we decide to give package users the ability to create custom node types themselves. func newNode(opts ...fx.Option) (*Node, error) { node := new(Node) app := fx.New( diff --git a/nodebuilder/node/type.go b/nodebuilder/node/type.go index b6d8918edc..6ba304871e 100644 --- a/nodebuilder/node/type.go +++ b/nodebuilder/node/type.go @@ -5,11 +5,12 @@ package node type Type uint8 const ( - // Bridge is a Celestia Node that bridges the Celestia consensus network and data availability network. - // It maintains a trusted channel/connection to a Celestia Core node via the core.Client API. + // Bridge is a Celestia Node that bridges the Celestia consensus network and data availability + // network. It maintains a trusted channel/connection to a Celestia Core node via the core.Client + // API. Bridge Type = iota + 1 - // Light is a stripped-down Celestia Node which aims to be lightweight while preserving the highest possible - // security guarantees. + // Light is a stripped-down Celestia Node which aims to be lightweight while preserving the highest + // possible security guarantees. Light // Full is a Celestia Node that stores blocks in their entirety. Full diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index cf2c389fe2..a50eb6c0bc 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -32,8 +32,8 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { require.NoError(t, err) } -// TestBridge_HasStubDaser verifies that a bridge node implements a stub daser that returns an error and empty -// das.SamplingStats +// TestBridge_HasStubDaser verifies that a bridge node implements a stub daser that returns an +// error and empty das.SamplingStats func TestBridge_HasStubDaser(t *testing.T) { repo := MockStore(t, DefaultConfig(node.Bridge)) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index fb6678b1cd..991eb0b046 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -16,11 +16,13 @@ type Config struct { ListenAddresses []string // AnnounceAddresses - Addresses to be announced/advertised for peers to connect to AnnounceAddresses []string - // NoAnnounceAddresses - Addresses the P2P subsystem may know about, but that should not be announced/advertised, - // as undialable from WAN + // NoAnnounceAddresses - Addresses the P2P subsystem may know about, but that should not be + // announced/advertised, as undialable from WAN NoAnnounceAddresses []string - // TODO(@Wondertan): This should be a build-time parameter. See https://github.com/celestiaorg/celestia-node/issues/63 - // Bootstrapper is flag telling this node is a bootstrapper. + // TODO(@Wondertan): This should be a build-time parameter. See + // https://github.com/celestiaorg/celestia-node/issues/63 Bootstrapper is flag telling this node is + + // a bootstrapper. Bootstrapper bool // MutualPeers are peers which have a bidirectional peering agreement with the configured node. // Connections with those peers are protected from being trimmed, dropped or negatively scored. diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index 32f5290e61..23dfce9845 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -41,7 +41,8 @@ func Host(cfg Config, params hostParams) (HostBase, error) { libp2p.DefaultMuxers, } - // TODO(@Wondertan): Other, non Celestia bootstrapper may also enable NATService to contribute the network. + // TODO(@Wondertan): Other, non Celestia bootstrapper may also enable NATService to contribute the + // network. if cfg.Bootstrapper { opts = append(opts, libp2p.EnableNATService()) } diff --git a/nodebuilder/p2p/misc.go b/nodebuilder/p2p/misc.go index af540c8706..fd8555d630 100644 --- a/nodebuilder/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -15,7 +15,8 @@ import ( type ConnManagerConfig struct { // Low and High are watermarks governing the number of connections that'll be maintained. Low, High int - // GracePeriod is the amount of time a newly opened connection is given before it becomes subject to pruning. + // GracePeriod is the amount of time a newly opened connection is given before it becomes subject + // to pruning. GracePeriod time.Duration } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 07d38e10ce..2289f6d635 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -46,7 +46,8 @@ var networksList = map[Network]struct{}{ Private: {}, } -// listProvidedNetworks provides a string listing all known long-standing networks for things like command hints. +// listProvidedNetworks provides a string listing all known long-standing networks for things like +// command hints. func listProvidedNetworks() string { var networks string for net := range networksList { diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 01b4c61b9b..98947331cf 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -21,8 +21,8 @@ func PubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { // * Hash-based MsgId function. // * Validate default peer scoring params for our use-case. // * Strict subscription filter - // * For different network types(mainnet/testnet/devnet) we should have different network topic names. - // * Hardcode positive score for bootstrap peers + // * For different network types(mainnet/testnet/devnet) we should have different network topic + // names. * Hardcode positive score for bootstrap peers // * Bootstrappers should only gossip and PX // * Peers should trust boostrappers, so peerscore for them should always be high. opts := []pubsub.Option{ diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 12bda0676f..e02be3d3dc 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -24,7 +24,8 @@ import ( ) // WithNetwork specifies the Network to which the Node should connect to. -// WARNING: Use this option with caution and never run the Node with different networks over the same persisted Store. +// WARNING: Use this option with caution and never run the Node with different networks over the +// same persisted Store. func WithNetwork(net p2p.Network) fx.Option { return fx.Replace(net) } diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go index 643ab43f21..4933a50f32 100644 --- a/nodebuilder/share/service.go +++ b/nodebuilder/share/service.go @@ -49,7 +49,9 @@ func NewModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Ava // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. // -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +// Module +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { SharesAvailable func(context.Context, *share.Root) error ProbabilityOfAvailability func() float64 diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go index 7f6c197680..1c4ff9b1b4 100644 --- a/nodebuilder/state/keyring.go +++ b/nodebuilder/state/keyring.go @@ -19,7 +19,8 @@ func Keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.Keyri // TODO @renaynay: Include option for setting custom `userInput` parameter with // implementation of https://github.com/celestiaorg/celestia-node/issues/415. // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed - // here instead of hardcoded `BackendTest`: https://github.com/celestiaorg/celestia-node/issues/603. + // here instead of hardcoded `BackendTest`: + // https://github.com/celestiaorg/celestia-node/issues/603. encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) ring, err := keyring.New(app.Name, keyring.BackendTest, ks.Path(), os.Stdin, encConf.Codec) if err != nil { diff --git a/nodebuilder/state/service.go b/nodebuilder/state/service.go index 06dcc78756..e9d0ab39c9 100644 --- a/nodebuilder/state/service.go +++ b/nodebuilder/state/service.go @@ -31,7 +31,8 @@ type Module interface { // `AppHash` is the result of applying the previous block's transaction list. BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) - // Transfer sends the given amount of coins from default wallet of the node to the given account address. + // Transfer sends the given amount of coins from default wallet of the node to the given account + // address. Transfer(ctx context.Context, to state.AccAddress, amount math.Int, gasLimit uint64) (*state.TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in @@ -76,7 +77,9 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. // -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . Module +// Module +// +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { IsStopped func() bool Balance func(ctx context.Context) (*state.Balance, error) diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 2f5ca418ac..ba75beaeb9 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -46,8 +46,8 @@ type Store interface { // OpenStore creates new FS Store under the given 'path'. // To be opened the Store must be initialized first, otherwise ErrNotInited is thrown. -// OpenStore takes a file Lock on directory, hence only one Store can be opened at a time under the given 'path', -// otherwise ErrOpened is thrown. +// OpenStore takes a file Lock on directory, hence only one Store can be opened at a time under the +// given 'path', otherwise ErrOpened is thrown. func OpenStore(path string) (Store, error) { path, err := storePath(path) if err != nil { @@ -139,8 +139,8 @@ func (f *fsStore) Datastore() (_ datastore.Batching, err error) { opts.TableLoadingMode = options.MemoryMap // Truncate set to true will truncate corrupted data on start if there is any. // If we don't truncate, the node will refuse to start and will beg for recovering, etc. - // If we truncate, the node will start with any uncorrupted data and reliably sync again what was corrupted - // in most cases. + // If we truncate, the node will start with any uncorrupted data and reliably sync again what was + // corrupted in most cases. opts.Truncate = true // MaxTableSize defines in memory and on disk size of LSM tree // Bigger values constantly takes more RAM diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index f156f395a3..6351c8f2c8 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -315,9 +315,9 @@ func (s *Swamp) Connect(t *testing.T, peerA, peerB peer.ID) { require.NoError(t, err) } -// Disconnect allows to break a connection between two peers without any possibility to re-establish it. -// Order is very important here. We have to unlink peers first, and only after that call disconnect. -// This is hard disconnect and peers will not be able to reconnect. +// Disconnect allows to break a connection between two peers without any possibility to +// re-establish it. Order is very important here. We have to unlink peers first, and only after +// that call disconnect. This is hard disconnect and peers will not be able to reconnect. // In order to reconnect peers again, please use swamp.Connect func (s *Swamp) Disconnect(t *testing.T, peerA, peerB peer.ID) { require.NoError(t, s.Network.UnlinkPeers(peerA, peerB)) diff --git a/share/add.go b/share/add.go index cddd992521..4e60eabb2c 100644 --- a/share/add.go +++ b/share/add.go @@ -13,7 +13,8 @@ import ( "github.com/celestiaorg/rsmt2d" ) -// AddShares erasures and extends shares to blockservice.BlockService using the provided ipld.NodeAdder. +// AddShares erasures and extends shares to blockservice.BlockService using the provided +// ipld.NodeAdder. func AddShares( ctx context.Context, shares []Share, @@ -42,7 +43,8 @@ func AddShares( return eds, batchAdder.Commit() } -// ImportShares imports flattend chunks of data into Extended Data square and saves it in blockservice.BlockService +// ImportShares imports flattend chunks of data into Extended Data square and saves it in +// blockservice.BlockService func ImportShares( ctx context.Context, shares [][]byte, diff --git a/share/availability.go b/share/availability.go index 5104dcb5bd..7bd52109ec 100644 --- a/share/availability.go +++ b/share/availability.go @@ -11,8 +11,8 @@ import ( // ErrNotAvailable is returned whenever DA sampling fails. var ErrNotAvailable = errors.New("share: data not available") -// AvailabilityTimeout specifies timeout for DA validation during which data have to be found on the network, -// otherwise ErrNotAvailable is fired. +// AvailabilityTimeout specifies timeout for DA validation during which data have to be found on +// the network, otherwise ErrNotAvailable is fired. // TODO: https://github.com/celestiaorg/celestia-node/issues/10 const AvailabilityTimeout = 20 * time.Minute @@ -22,7 +22,8 @@ type Root = da.DataAvailabilityHeader // Availability defines interface for validation of Shares' availability. type Availability interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. + // SharesAvailable subjectively validates if Shares committed to the given Root are available on + // the Network. SharesAvailable(context.Context, *Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index 961dee7564..f8f3d8760c 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -19,8 +19,8 @@ var log = logging.Logger("share/cache") var ( // DefaultWriteBatchSize defines the size of the batched header write. // Headers are written in batches not to thrash the underlying Datastore with writes. - // TODO(@Wondertan, @renaynay): Those values must be configurable and proper defaults should be set for specific node - // type. (#709) + // TODO(@Wondertan, @renaynay): Those values must be configurable and proper defaults should be set + // for specific node type. (#709) DefaultWriteBatchSize = 2048 cacheAvailabilityPrefix = datastore.NewKey("sampling_result") diff --git a/share/availability/discovery/backoff.go b/share/availability/discovery/backoff.go index 9b0f2f6acc..9e5c627aa7 100644 --- a/share/availability/discovery/backoff.go +++ b/share/availability/discovery/backoff.go @@ -42,8 +42,8 @@ func newBackoffConnector(h host.Host, factory backoff.BackoffFactory) *backoffCo // Connect puts peer to the backoffCache and tries to establish a connection with it. func (b *backoffConnector) Connect(ctx context.Context, p peer.AddrInfo) error { - // we should lock the mutex before calling connectionData and not inside because otherwise it could be modified - // from another goroutine as it returns a pointer + // we should lock the mutex before calling connectionData and not inside because otherwise it could + // be modified from another goroutine as it returns a pointer b.cacheLk.Lock() cache := b.connectionData(p.ID) if time.Now().Before(cache.nexttry) { @@ -67,8 +67,9 @@ func (b *backoffConnector) connectionData(p peer.ID) *backoffData { return cache } -// RestartBackoff resets delay time between attempts and adds a delay for the next connection attempt to remote peer. -// It will mostly be called when host receives a notification that remote peer was disconnected. +// RestartBackoff resets delay time between attempts and adds a delay for the next connection +// attempt to remote peer. It will mostly be called when host receives a notification that remote +// peer was disconnected. func (b *backoffConnector) RestartBackoff(p peer.ID) { b.cacheLk.Lock() defer b.cacheLk.Unlock() diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 6f8b227791..ac8c805df8 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -54,8 +54,8 @@ func (la *ShareAvailability) Stop(context.Context) error { return nil } -// SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given Root. -// This way SharesAvailable subjectively verifies that Shares are available. +// SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given +// Root. This way SharesAvailable subjectively verifies that Shares are available. func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { log.Debugw("Validate availability", "root", dah.Hash()) // We assume the caller of this method has already performed basic validation on the diff --git a/share/availability/light/sample.go b/share/availability/light/sample.go index fa2719cd1f..12d8505397 100644 --- a/share/availability/light/sample.go +++ b/share/availability/light/sample.go @@ -6,7 +6,8 @@ import ( "math/big" ) -// DefaultSampleAmount sets the default amount of samples to be sampled from the network by ShareAvailability. +// DefaultSampleAmount sets the default amount of samples to be sampled from the network by +// ShareAvailability. var DefaultSampleAmount = 16 // Sample is a point in 2D space over square. diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index 6969c24728..1d7716cbb4 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -15,7 +15,8 @@ import ( var _ blockstore.Blockstore = (*FraudulentBlockstore)(nil) -// CorruptBlock is a block where the cid doesn't match the data. It fulfills the blocks.Block interface. +// CorruptBlock is a block where the cid doesn't match the data. It fulfills the blocks.Block +// interface. type CorruptBlock struct { cid cid.Cid data []byte @@ -46,8 +47,9 @@ func NewCorruptBlock(data []byte, fakeCID cid.Cid) *CorruptBlock { } } -// FraudulentBlockstore is a mock blockstore.Blockstore that saves both corrupted and original data for every block it -// receives. If FraudulentBlockstore.Attacking is true, it will serve the corrupted data on requests. +// FraudulentBlockstore is a mock blockstore.Blockstore that saves both corrupted and original data +// for every block it receives. If FraudulentBlockstore.Attacking is true, it will serve the +// corrupted data on requests. type FraudulentBlockstore struct { ds.Datastore Attacking bool @@ -85,7 +87,8 @@ func (fb FraudulentBlockstore) Put(ctx context.Context, block blocks.Block) erro return err } - // create data that doesn't match the CID with arbitrary lengths between 0 and len(block.RawData())*2 + // create data that doesn't match the CID with arbitrary lengths between 0 and + // len(block.RawData())*2 corrupted := make([]byte, mrand.Int()%(len(block.RawData())*2)) mrand.Read(corrupted) return fb.Datastore.Put(ctx, ds.NewKey("corrupt"+block.Cid().String()), corrupted) diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 5cc78168e0..e9399ddad6 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -61,8 +61,8 @@ type TestDagNet struct { nodes []*TestNode } -// NewTestDAGNet creates a new testing swarm utility to spawn different nodes and test how they interact and/or exchange -// data. +// NewTestDAGNet creates a new testing swarm utility to spawn different nodes and test how they +// interact and/or exchange data. func NewTestDAGNet(ctx context.Context, t *testing.T) *TestDagNet { return &TestDagNet{ ctx: ctx, @@ -71,7 +71,8 @@ func NewTestDAGNet(ctx context.Context, t *testing.T) *TestDagNet { } } -// NewTestNodeWithBlockstore creates a new plain TestNode with the given blockstore that can serve and request data. +// NewTestNodeWithBlockstore creates a new plain TestNode with the given blockstore that can serve +// and request data. func (dn *TestDagNet) NewTestNodeWithBlockstore(dstore ds.Datastore, bstore blockstore.Blockstore) *TestNode { hst, err := dn.net.GenPeer() require.NoError(dn.T, err) @@ -121,8 +122,8 @@ func (dn *TestDagNet) Connect(peerA, peerB peer.ID) { } // Disconnect disconnects two peers. -// It does a hard disconnect, meaning that disconnected peers won't be able to reconnect on their own -// but only with DagNet.Connect or TestDagNet.ConnectAll. +// It does a hard disconnect, meaning that disconnected peers won't be able to reconnect on their +// own but only with DagNet.Connect or TestDagNet.ConnectAll. func (dn *TestDagNet) Disconnect(peerA, peerB peer.ID) { err := dn.net.UnlinkPeers(peerA, peerB) require.NoError(dn.T, err) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index d957209be0..6632ac4972 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -31,8 +31,9 @@ type BadEncodingProof struct { Axis rsmt2d.Axis } -// CreateBadEncodingProof creates a new Bad Encoding Fraud Proof that should be propagated through network. -// The fraud proof will contain shares that did not pass verification and their relevant Merkle proofs. +// CreateBadEncodingProof creates a new Bad Encoding Fraud Proof that should be propagated through +// network. The fraud proof will contain shares that did not pass verification and their relevant +// Merkle proofs. func CreateBadEncodingProof( hash []byte, height uint64, diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 4515b9c7ca..2d360f2aee 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -78,8 +78,8 @@ func GetProofsForShares( for index, share := range shares { if share != nil { proof := make([]cid.Cid, 0) - // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same tree. - // Add options that will control what data will be fetched. + // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same + // tree. Add options that will control what data will be fetched. s, err := ipld.GetLeaf(ctx, bGetter, root, index, len(shares)) if err != nil { return nil, err diff --git a/share/eds/eds.go b/share/eds/eds.go index 23ab44e0a9..c86bab72d0 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -29,10 +29,12 @@ import ( var ErrEmptySquare = errors.New("share: importing empty data") -// writingSession contains the components needed to write an EDS to a CARv1 file with our custom node order. +// writingSession contains the components needed to write an EDS to a CARv1 file with our custom +// node order. type writingSession struct { eds *rsmt2d.ExtendedDataSquare - // store is an in-memory blockstore, used to cache the inner nodes (proofs) while we walk the nmt tree. + // store is an in-memory blockstore, used to cache the inner nodes (proofs) while we walk the nmt + // tree. store blockstore.Blockstore w io.Writer } @@ -137,9 +139,11 @@ func (w *writingSession) writeQuadrants() error { return nil } -// writeProofs iterates over the in-memory blockstore's keys and writes all inner nodes to the CARv1 file. +// writeProofs iterates over the in-memory blockstore's keys and writes all inner nodes to the +// CARv1 file. func (w *writingSession) writeProofs(ctx context.Context) error { - // we only stored proofs to the store, so we can just iterate over them here without getting any leaves + // we only stored proofs to the store, so we can just iterate over them here without getting any + // leaves proofs, err := w.store.AllKeysChan(ctx) if err != nil { return fmt.Errorf("getting all keys from the blockstore: %w", err) @@ -161,8 +165,8 @@ func (w *writingSession) writeProofs(ctx context.Context) error { return nil } -// quadrantOrder reorders the shares in the EDS to quadrant row-by-row order, prepending the respective namespace -// to the shares. +// quadrantOrder reorders the shares in the EDS to quadrant row-by-row order, prepending the +// respective namespace to the shares. // e.g. [ Q1 R1 | Q1 R2 | Q1 R3 | Q1 R4 | Q2 R1 | Q2 R2 .... ] func quadrantOrder(eds *rsmt2d.ExtendedDataSquare) [][]byte { size := eds.Width() * eds.Width() @@ -223,7 +227,8 @@ func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) { // ReadEDS reads the first EDS quadrant (1/4) from an io.Reader CAR file. // Only the first quadrant will be read, which represents the original data. -// The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS errors. +// The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS +// errors. func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.ExtendedDataSquare, error) { carReader, err := car.NewCarReader(r) if err != nil { diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 303dd4b509..0e4db027cd 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -114,7 +114,8 @@ func TestWriteEDSInQuadrantOrder(t *testing.T) { } } -// TestInnerNodeBatchSize verifies that the number of unique inner nodes is equal to ipld.BatchSize - shareCount. +// TestInnerNodeBatchSize verifies that the number of unique inner nodes is equal to ipld.BatchSize +// - shareCount. func TestInnerNodeBatchSize(t *testing.T) { tests := []struct { name string @@ -177,8 +178,9 @@ func TestReadEDSContentIntegrityMismatch(t *testing.T) { require.ErrorContains(t, err, "share: content integrity mismatch: imported root") } -// BenchmarkReadWriteEDS benchmarks the time it takes to write and read an EDS from disk. The benchmark is run with a -// 4x4 ODS to a 64x64 ODS - a higher value can be used, but it will run for much longer. +// BenchmarkReadWriteEDS benchmarks the time it takes to write and read an EDS from disk. The +// benchmark is run with a 4x4 ODS to a 64x64 ODS - a higher value can be used, but it will run for +// much longer. func BenchmarkReadWriteEDS(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) b.Cleanup(cancel) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 6de60300fd..a7b9ab16f3 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -53,10 +53,10 @@ func NewRetriever(bServ blockservice.BlockService) *Retriever { // Retrieve retrieves all the data committed to DataAvailabilityHeader. // -// If not available locally, it aims to request from the network only one quadrant (1/4) of the data square -// and reconstructs the other three quadrants (3/4). If the requested quadrant is not available within -// RetrieveQuadrantTimeout, it starts requesting another quadrant until either the data is -// reconstructed, context is canceled or ErrByzantine is generated. +// If not available locally, it aims to request from the network only one quadrant (1/4) of the +// data square and reconstructs the other three quadrants (3/4). If the requested quadrant is not +// available within RetrieveQuadrantTimeout, it starts requesting another quadrant until either the +// data is reconstructed, context is canceled or ErrByzantine is generated. func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader) (*rsmt2d.ExtendedDataSquare, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() // cancels all the ongoing requests if reconstruction succeeds early @@ -178,8 +178,8 @@ func (rs *retrievalSession) Reconstruct(ctx context.Context) (*rsmt2d.ExtendedDa // TODO(@Wondertan): This is bad! // * We should not reimport the square multiple times - // * We should set shares into imported square via SetShare(https://github.com/celestiaorg/rsmt2d/issues/83) - // to accomplish the above point. + // * We should set shares into imported square via + // SetShare(https://github.com/celestiaorg/rsmt2d/issues/83) to accomplish the above point. { squareImported, err := rsmt2d.ImportExtendedDataSquare(rs.square, rs.codec, rs.treeFn) if err != nil { diff --git a/share/eds/retriever_quadrant.go b/share/eds/retriever_quadrant.go index e223be36f8..9a637a27be 100644 --- a/share/eds/retriever_quadrant.go +++ b/share/eds/retriever_quadrant.go @@ -100,7 +100,8 @@ func newQuadrants(dah *da.DataAvailabilityHeader) []*quadrant { // - Goal to make formula generic for both rows and cols // - While data square is flattened by rows only // -// TODO(@Wondertan): This can be simplified by making rsmt2d working over 3D byte slice(not flattened) +// TODO(@Wondertan): This can be simplified by making rsmt2d working over 3D byte slice(not +// flattened) func (q *quadrant) index(rootIdx, cellIdx int) int { size := len(q.roots) // half square offsets, e.g. share is from Q3, diff --git a/share/get.go b/share/get.go index b6734562e7..d452378926 100644 --- a/share/get.go +++ b/share/get.go @@ -40,8 +40,8 @@ func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.C ipld.GetLeaves(ctx, bGetter, root, shares, putNode) } -// GetSharesByNamespace walks the tree of a given root and returns its shares within the given namespace.ID. -// If a share could not be retrieved, err is not nil, and the returned array +// GetSharesByNamespace walks the tree of a given root and returns its shares within the given +// namespace.ID. If a share could not be retrieved, err is not nil, and the returned array // contains nil shares in place of the shares it was unable to retrieve. func GetSharesByNamespace( ctx context.Context, @@ -70,6 +70,7 @@ func GetSharesByNamespace( // leafToShare converts an NMT leaf into a Share. func leafToShare(nd format.Node) Share { - // * Additional namespace is prepended so that parity data can be identified with a parity namespace, which we cut off + // * Additional namespace is prepended so that parity data can be identified with a parity + // namespace, which we cut off return nd.RawData()[NamespaceSize:] } diff --git a/share/ipld/corrupted_data_test.go b/share/ipld/corrupted_data_test.go index 59ee922adf..8b14f6d2aa 100644 --- a/share/ipld/corrupted_data_test.go +++ b/share/ipld/corrupted_data_test.go @@ -13,12 +13,12 @@ import ( "github.com/celestiaorg/celestia-node/share/service" ) -// sharesAvailableTimeout is an arbitrarily picked interval of time in which a TestNode is expected to be able to -// complete a SharesAvailable request from a connected peer in a TestDagNet. +// sharesAvailableTimeout is an arbitrarily picked interval of time in which a TestNode is expected +// to be able to complete a SharesAvailable request from a connected peer in a TestDagNet. const sharesAvailableTimeout = 2 * time.Second -// TestNamespaceHasher_CorruptedData is an integration test that verifies that the NamespaceHasher of a recipient of -// corrupted data will not panic, and will throw away the corrupted data. +// TestNamespaceHasher_CorruptedData is an integration test that verifies that the NamespaceHasher +// of a recipient of corrupted data will not panic, and will throw away the corrupted data. func TestNamespaceHasher_CorruptedData(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -29,8 +29,8 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { provider.ShareService = service.NewShareService(provider.BlockService, full.TestAvailability(provider.BlockService)) net.ConnectAll() - // before the provider starts attacking, we should be able to retrieve successfully. We pass a size 16 block, but - // this is not important to the test and any valid block size behaves the same. + // before the provider starts attacking, we should be able to retrieve successfully. We pass a size + // 16 block, but this is not important to the test and any valid block size behaves the same. root := availability_test.RandFillBS(t, 16, provider.BlockService) getCtx, cancelGet := context.WithTimeout(ctx, sharesAvailableTimeout) t.Cleanup(cancelGet) diff --git a/share/ipld/get.go b/share/ipld/get.go index 79ca64a342..e0590de7d5 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -170,10 +170,10 @@ func GetLeaves(ctx context.Context, wg.Wait() } -// GetLeavesByNamespace returns as many leaves from the given root with the given namespace.ID as it can retrieve. -// If no shares are found, it returns both data and error as nil. -// A non-nil error means that only partial data is returned, because at least one share retrieval failed -// The following implementation is based on `GetShares`. +// GetLeavesByNamespace returns as many leaves from the given root with the given namespace.ID as +// it can retrieve. If no shares are found, it returns both data and error as nil. +// A non-nil error means that only partial data is returned, because at least one share retrieval +// failed The following implementation is based on `GetShares`. func GetLeavesByNamespace( ctx context.Context, bGetter blockservice.BlockGetter, @@ -367,7 +367,8 @@ type fetchedBounds struct { func (b *fetchedBounds) update(index int64) { lowest := atomic.LoadInt64(&b.lowest) // try to write index to the lower bound if appropriate, and retry until the atomic op is successful - // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the comparison here + // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the + // comparison here for index < lowest && !atomic.CompareAndSwapInt64(&b.lowest, lowest, index) { lowest = atomic.LoadInt64(&b.lowest) } diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 371ee9f091..cd881b10cd 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -35,7 +35,8 @@ const ( // that contain an NMT node (inner and leaf nodes). sha256Namespace8Flagged = 0x7701 - // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. + // MaxSquareSize is currently the maximum size supported for unerasured data in + // rsmt2d.ExtendedDataSquare. MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. diff --git a/share/service/service.go b/share/service/service.go index 199fa83967..6688159c2b 100644 --- a/share/service/service.go +++ b/share/service/service.go @@ -20,8 +20,8 @@ type ShareService struct { share.Availability rtrv *eds.Retriever bServ blockservice.BlockService - // session is blockservice sub-session that applies optimization for fetching/loading related nodes, like shares - // prefer session over blockservice for fetching nodes. + // session is blockservice sub-session that applies optimization for fetching/loading related + // nodes, like shares prefer session over blockservice for fetching nodes. session blockservice.BlockGetter cancel context.CancelFunc } @@ -40,8 +40,8 @@ func (s *ShareService) Start(context.Context) error { return fmt.Errorf("share: service already started") } - // NOTE: The ctx given as param is used to control Start flow and only needed when Start is blocking, - // but this one is not. + // NOTE: The ctx given as param is used to control Start flow and only needed when Start is + // blocking, but this one is not. // // The newer context here is created to control lifecycle of the session and peer discovery. ctx, cancel := context.WithCancel(context.Background()) @@ -91,7 +91,8 @@ func (s *ShareService) GetShares(ctx context.Context, root *share.Root) ([][]sha return shares, nil } -// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. +// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the +// given namespace.ID. func (s *ShareService) GetSharesByNamespace( ctx context.Context, root *share.Root, @@ -127,9 +128,9 @@ func (s *ShareService) GetSharesByNamespace( } // we don't know the amount of shares in the namespace, so we cannot preallocate properly - // TODO(@Wondertan): Consider improving encoding schema for data in the shares that will also include metadata - // with the amount of shares. If we are talking about plenty of data here, proper preallocation would make a - // difference + // TODO(@Wondertan): Consider improving encoding schema for data in the shares that will also + // include metadata with the amount of shares. If we are talking about plenty of data here, proper + // preallocation would make a difference var out []share.Share for i := 0; i < len(rowRootCIDs); i++ { out = append(out, shares[i]...) diff --git a/share/share.go b/share/share.go index 8b05791e02..8923a65dd9 100644 --- a/share/share.go +++ b/share/share.go @@ -15,7 +15,8 @@ var ( ) const ( - // MaxSquareSize is currently the maximum size supported for unerasured data in rsmt2d.ExtendedDataSquare. + // MaxSquareSize is currently the maximum size supported for unerasured data in + // rsmt2d.ExtendedDataSquare. MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize @@ -24,8 +25,9 @@ const ( ) // Share contains the raw share data without the corresponding namespace. -// NOTE: Alias for the byte is chosen to keep maximal compatibility, especially with rsmt2d. Ideally, we should define -// reusable type elsewhere and make everyone(Core, rsmt2d, ipld) to rely on it. +// NOTE: Alias for the byte is chosen to keep maximal compatibility, especially with rsmt2d. +// Ideally, we should define reusable type elsewhere and make everyone(Core, rsmt2d, ipld) to rely +// on it. type Share = []byte // ID gets the namespace ID from the share. diff --git a/share/test_helpers.go b/share/test_helpers.go index f2b12a2076..1475ff099b 100644 --- a/share/test_helpers.go +++ b/share/test_helpers.go @@ -13,7 +13,8 @@ import ( // EqualEDS check whether two given EDSes are equal. // TODO(Wondertan): Move to rsmt2d -// TODO(Wondertan): Propose use of int by default instead of uint for the sake convenience and Golang practices +// TODO(Wondertan): Propose use of int by default instead of uint for the sake convenience and +// Golang practices func EqualEDS(a *rsmt2d.ExtendedDataSquare, b *rsmt2d.ExtendedDataSquare) bool { if a.Width() != b.Width() { return false @@ -31,8 +32,8 @@ func EqualEDS(a *rsmt2d.ExtendedDataSquare, b *rsmt2d.ExtendedDataSquare) bool { return true } -// RandEDS generates EDS filled with the random data with the given size for original square. It uses require.TestingT -// to be able to take both a *testing.T and a *testing.B. +// RandEDS generates EDS filled with the random data with the given size for original square. It +// uses require.TestingT to be able to take both a *testing.T and a *testing.B. func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { shares := RandShares(t, size*size) // recompute the eds @@ -41,8 +42,8 @@ func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { return eds } -// RandShares generate 'total' amount of shares filled with random data. It uses require.TestingT to be able to take -// both a *testing.T and a *testing.B. +// RandShares generate 'total' amount of shares filled with random data. It uses require.TestingT +// to be able to take both a *testing.T and a *testing.B. func RandShares(t require.TestingT, total int) []Share { if total&(total-1) != 0 { t.Errorf("Namespace total must be power of 2: %d", total) diff --git a/state/core_access.go b/state/core_access.go index bd8123468d..a7fdfe5b3f 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -184,7 +184,8 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B // construct an ABCI query for the height at head-1 because // the AppHash contained in the head is actually the state root // after applying the transactions contained in the previous block. - // TODO @renaynay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use this method instead + // TODO @renaynay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use this method + // instead prefixedAccountKey := append(banktypes.CreateAccountBalancesPrefix(addr.Bytes()), []byte(app.BondDenom)...) abciReq := abci.RequestQuery{ // TODO @renayay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use const instead From 22f3a6c60abc26c90277f98ff41586b5f89dae65 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:25:55 +0100 Subject: [PATCH 0177/1008] fix(.github): Point at correct action (#1335) Sorry, I forgot to add the import :( --- .github/workflows/issue-labels.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml index 5ad5fb6d20..579457a9f7 100644 --- a/.github/workflows/issue-labels.yml +++ b/.github/workflows/issue-labels.yml @@ -3,6 +3,8 @@ # separate terms of service, privacy policy, and support # documentation. name: Label issues +uses: andymckay/labeler@1.0.4 + on: issues: types: [opened] From deb8e0a1893b33215ce6ead555d79b106d2afaf8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 10 Nov 2022 15:58:21 +0100 Subject: [PATCH 0178/1008] fix(.github/workflows): Implement working label automation (#1346) It is now implemented correctly and tested :) --- .github/workflows/issue-label-automation.yml | 16 ++++++++++++++ .github/workflows/issue-labels.yml | 23 -------------------- 2 files changed, 16 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/issue-label-automation.yml delete mode 100644 .github/workflows/issue-labels.yml diff --git a/.github/workflows/issue-label-automation.yml b/.github/workflows/issue-label-automation.yml new file mode 100644 index 0000000000..fd2921fc28 --- /dev/null +++ b/.github/workflows/issue-label-automation.yml @@ -0,0 +1,16 @@ +name: issue-automation +on: + issues: + types: [opened] +jobs: + automate-issues-labels: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Triage labeling + uses: andymckay/labeler@master + with: + add-labels: "needs:triage" + ignore-if-labeled: true + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml deleted file mode 100644 index 579457a9f7..0000000000 --- a/.github/workflows/issue-labels.yml +++ /dev/null @@ -1,23 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -name: Label issues -uses: andymckay/labeler@1.0.4 - -on: - issues: - types: [opened] - -jobs: - label_issues: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - name: Label issues - uses: mheap/github-action-required-labels@v2 - with: - add-labels: "needs:triage" - ignore-if-labeled: true - repo-token: ${{ secrets.GITHUB_TOKEN }} From 688ca3ba531b783f578cb4c2ee6c23efc95f8d0f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 10 Nov 2022 17:31:36 +0200 Subject: [PATCH 0179/1008] improvement(header/server): implement timeout for GetRangeByHeight request (#1318) closes https://github.com/celestiaorg/celestia-node/issues/1316 --- header/p2p/server.go | 4 +++- header/p2p/server_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 header/p2p/server_test.go diff --git a/header/p2p/server.go b/header/p2p/server.go index bbf21dca64..5d5fd7e39b 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -163,7 +163,9 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe return nil, header.ErrHeadersLimitExceeded } log.Debugw("server: handling headers request", "from", from, "to", to) - headersByRange, err := serv.store.GetRangeByHeight(serv.ctx, from, to) + ctx, cancel := context.WithTimeout(serv.ctx, time.Second*5) + defer cancel() + headersByRange, err := serv.store.GetRangeByHeight(ctx, from, to) if err != nil { log.Errorw("server: getting headers", "from", from, "to", to, "err", err) return nil, err diff --git a/header/p2p/server_test.go b/header/p2p/server_test.go new file mode 100644 index 0000000000..43d0ad0a64 --- /dev/null +++ b/header/p2p/server_test.go @@ -0,0 +1,26 @@ +package p2p + +import ( + "context" + "testing" + + "github.com/ipfs/go-datastore" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/header/store" +) + +func TestExchangeServer_handleRequestTimeout(t *testing.T) { + _, peer := createMocknet(t) + s, err := store.NewStore(datastore.NewMapDatastore()) + require.NoError(t, err) + server := NewExchangeServer(peer, s, "private") + err = server.Start(context.Background()) + require.NoError(t, err) + t.Cleanup(func() { + server.Stop(context.Background()) //nolint:errcheck + }) + + _, err = server.handleRequest(1, 200) + require.Error(t, err) +} From 4d72c4513a80f098e776f922d8b09127bffbfba5 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 10 Nov 2022 16:56:06 +0100 Subject: [PATCH 0180/1008] fix(rpc): `fraud.Proof` (un)marshalling and subscription as `chan` (#1307) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Closes https://github.com/celestiaorg/celestia-node/issues/1208 closes https://github.com/celestiaorg/celestia-node/issues/1207 closes https://github.com/celestiaorg/celestia-node/issues/1303 --- api/gateway/server.go | 3 +- api/rpc_test.go | 17 ++++-- nodebuilder/das/daser.go | 4 +- nodebuilder/das/module.go | 2 +- nodebuilder/das/service.go | 5 +- nodebuilder/fraud/constructors.go | 39 ++++++++++++++ nodebuilder/fraud/fraud.go | 88 +++++-------------------------- nodebuilder/fraud/lifecycle.go | 42 +++++++++++++++ nodebuilder/fraud/mocks/api.go | 29 +++------- nodebuilder/fraud/module.go | 8 +-- nodebuilder/fraud/service.go | 87 +++++++++++++++++++++++++----- nodebuilder/header/module.go | 2 +- nodebuilder/header/service.go | 6 +-- nodebuilder/share/service.go | 6 +-- nodebuilder/state/module.go | 2 +- nodebuilder/state/service.go | 6 +-- nodebuilder/tests/fraud_test.go | 26 +++++---- share/eds/eds.go | 3 +- 18 files changed, 225 insertions(+), 150 deletions(-) create mode 100644 nodebuilder/fraud/constructors.go create mode 100644 nodebuilder/fraud/lifecycle.go diff --git a/api/gateway/server.go b/api/gateway/server.go index 4149db818b..181bfdfe55 100644 --- a/api/gateway/server.go +++ b/api/gateway/server.go @@ -70,7 +70,8 @@ func (s *Server) Stop(ctx context.Context) error { return nil } -// RegisterMiddleware allows to register a custom middleware that will be called before http.Request will reach handler. +// RegisterMiddleware allows to register a custom middleware that will be called before +// http.Request will reach handler. func (s *Server) RegisterMiddleware(middlewareFuncs ...mux.MiddlewareFunc) { for _, m := range middlewareFuncs { // `router.Use` appends new middleware to existing diff --git a/api/rpc_test.go b/api/rpc_test.go index 4e40a66151..db53f81460 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -73,9 +73,7 @@ func TestModulesImplementFullAPI(t *testing.T) { } } -// TODO(@distractedm1nd): Blocked by issues #1208 and #1207 func TestAllReturnValuesAreMarshalable(t *testing.T) { - t.Skip() ra := reflect.TypeOf(new(client.API)).Elem() for i := 0; i < ra.NumMethod(); i++ { m := ra.Method(i) @@ -85,13 +83,22 @@ func TestAllReturnValuesAreMarshalable(t *testing.T) { } } -func implementsMarshaler(t *testing.T, typ reflect.Type) { //nolint:unused +func implementsMarshaler(t *testing.T, typ reflect.Type) { + // the passed type may already implement json.Marshaler and we don't need to go deeper if typ.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()) { return } switch typ.Kind() { case reflect.Struct: + // a user defined struct could implement json.Marshaler on the pointer receiver, so check there first. + // note that the "non-pointer" receiver is checked before the switch. + pointerType := reflect.TypeOf(reflect.New(typ).Elem().Addr().Interface()) + if pointerType.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()) { + return + } + // struct doesn't implement the interface itself, check all individual fields + reflect.New(typ).Pointer() for i := 0; i < typ.NumField(); i++ { implementsMarshaler(t, typ.Field(i).Type) } @@ -135,8 +142,8 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { dasMock.NewMockModule(ctrl), } - // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root level module. For - // further information, check the documentation on fx.Invoke. + // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root + // level module. For further information, check the documentation on fx.Invoke. invokeRPC := fx.Invoke(func(srv *rpc.Server) { srv.RegisterService("state", mockAPI.State) srv.RegisterService("share", mockAPI.Share) diff --git a/nodebuilder/das/daser.go b/nodebuilder/das/daser.go index 01d5414da7..49ef9f37c6 100644 --- a/nodebuilder/das/daser.go +++ b/nodebuilder/das/daser.go @@ -7,8 +7,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/share" ) @@ -31,7 +31,7 @@ func NewDASer( hsub header.Subscriber, store header.Store, batching datastore.Batching, - fraudService fraud.Module, + fraudService fraud.Service, options ...das.Option, ) (*das.DASer, error) { return das.NewDASer(da, hsub, store, batching, fraudService, options...) diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 0f694b1b4c..a2b22cf195 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -37,7 +37,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents, fx.Provide(fx.Annotate( NewDASer, - fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, das *das.DASer) error { + fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, das *das.DASer) error { return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, das.Start, das.Stop) }), diff --git a/nodebuilder/das/service.go b/nodebuilder/das/service.go index ee4a9e8334..45ef2e10cb 100644 --- a/nodebuilder/das/service.go +++ b/nodebuilder/das/service.go @@ -6,6 +6,7 @@ import ( "github.com/celestiaorg/celestia-node/das" ) +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // SamplingStats returns the current statistics over the DA sampling process. SamplingStats(ctx context.Context) (das.SamplingStats, error) @@ -13,10 +14,6 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. -// -// Module -// -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { SamplingStats func(ctx context.Context) (das.SamplingStats, error) } diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go new file mode 100644 index 0000000000..d0afddcbd2 --- /dev/null +++ b/nodebuilder/fraud/constructors.go @@ -0,0 +1,39 @@ +package fraud + +import ( + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" +) + +func newFraudService(syncerEnabled bool) func( + fx.Lifecycle, + *pubsub.PubSub, + host.Host, + header.Store, + datastore.Batching, + p2p.Network, +) (Module, fraud.Service, error) { + return func( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore header.Store, + ds datastore.Batching, + network p2p.Network, + ) (Module, fraud.Service, error) { + pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, syncerEnabled, string(network)) + lc.Append(fx.Hook{ + OnStart: pservice.Start, + OnStop: pservice.Stop, + }) + return &Service{ + Service: pservice, + }, pservice, nil + } +} diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 3aec12514c..5eacd0dba3 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -3,83 +3,23 @@ package fraud import ( "context" - "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -// NewModule constructs a fraud proof service with the syncer disabled. -func NewModule( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore header.Store, - ds datastore.Batching, - network p2p.Network, -) (Module, error) { - return newFraudService(lc, sub, host, hstore, ds, false, string(network)) -} - -// ModuleWithSyncer constructs fraud proof service with enabled syncer. -func ModuleWithSyncer( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore header.Store, - ds datastore.Batching, - network p2p.Network, -) (Module, error) { - return newFraudService(lc, sub, host, hstore, ds, true, string(network)) -} - -func newFraudService( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore header.Store, - ds datastore.Batching, - isEnabled bool, - protocolSuffix string, -) (Module, error) { - pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled, protocolSuffix) - lc.Append(fx.Hook{ - OnStart: pservice.Start, - OnStop: pservice.Stop, - }) - return pservice, nil +// Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the +// network. Any method signature changed here needs to also be changed in the API struct. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + // Get fetches fraud proofs from the disk by its type. + Get(context.Context, fraud.ProofType) ([]Proof, error) + // Subscribe allows to subscribe on a Proof pub sub topic by its type. + Subscribe(context.Context, fraud.ProofType) (<-chan Proof, error) } -// Lifecycle controls the lifecycle of service depending on fraud proofs. -// It starts the service only if no fraud-proof exists and stops the service automatically -// if a proof arrives after the service was started. -func Lifecycle( - startCtx, lifecycleCtx context.Context, - p fraud.ProofType, - fraudModule Module, - start, stop func(context.Context) error, -) error { - proofs, err := fraudModule.Get(startCtx, p) - switch err { - default: - return err - case nil: - return &fraud.ErrFraudExists{Proof: proofs} - case datastore.ErrNotFound: - } - err = start(startCtx) - if err != nil { - return err - } - // handle incoming Fraud Proofs - go fraud.OnProof(lifecycleCtx, fraudModule, p, func(fraud.Proof) { - if err := stop(lifecycleCtx); err != nil { - log.Error(err) - } - }) - return nil +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +type API struct { + Subscribe func(context.Context, fraud.ProofType) (<-chan Proof, error) + Get func(context.Context, fraud.ProofType) ([]Proof, error) } diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go new file mode 100644 index 0000000000..a94bc61749 --- /dev/null +++ b/nodebuilder/fraud/lifecycle.go @@ -0,0 +1,42 @@ +package fraud + +import ( + "context" + "time" + + "github.com/ipfs/go-datastore" + + "github.com/celestiaorg/celestia-node/fraud" +) + +// Lifecycle controls the lifecycle of service depending on fraud proofs. +// It starts the service only if no fraud-proof exists and stops the service automatically +// if a proof arrives after the service was started. +func Lifecycle( + startCtx, lifecycleCtx context.Context, + p fraud.ProofType, + fraudServ fraud.Service, + start, stop func(context.Context) error, +) error { + proofs, err := fraudServ.Get(startCtx, p) + switch err { + default: + return err + case nil: + return &fraud.ErrFraudExists{Proof: proofs} + case datastore.ErrNotFound: + } + err = start(startCtx) + if err != nil { + return err + } + // handle incoming Fraud Proofs + go fraud.OnProof(lifecycleCtx, fraudServ, p, func(fraud.Proof) { + ctx, cancel := context.WithTimeout(lifecycleCtx, time.Minute) + defer cancel() + if err := stop(ctx); err != nil { + log.Error(err) + } + }) + return nil +} diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index f0cae5f291..cc94a4e794 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -11,6 +11,7 @@ import ( gomock "github.com/golang/mock/gomock" fraud "github.com/celestiaorg/celestia-node/fraud" + fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" ) // MockModule is a mock of Module interface. @@ -36,25 +37,11 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { return m.recorder } -// Broadcast mocks base method. -func (m *MockModule) Broadcast(arg0 context.Context, arg1 fraud.Proof) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Broadcast", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// Broadcast indicates an expected call of Broadcast. -func (mr *MockModuleMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockModule)(nil).Broadcast), arg0, arg1) -} - // Get mocks base method. -func (m *MockModule) Get(arg0 context.Context, arg1 fraud.ProofType) ([]fraud.Proof, error) { +func (m *MockModule) Get(arg0 context.Context, arg1 fraud.ProofType) ([]fraud0.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0, arg1) - ret0, _ := ret[0].([]fraud.Proof) + ret0, _ := ret[0].([]fraud0.Proof) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -66,16 +53,16 @@ func (mr *MockModuleMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { } // Subscribe mocks base method. -func (m *MockModule) Subscribe(arg0 fraud.ProofType) (fraud.Subscription, error) { +func (m *MockModule) Subscribe(arg0 context.Context, arg1 fraud.ProofType) (<-chan fraud0.Proof, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Subscribe", arg0) - ret0, _ := ret[0].(fraud.Subscription) + ret := m.ctrl.Call(m, "Subscribe", arg0, arg1) + ret0, _ := ret[0].(<-chan fraud0.Proof) ret1, _ := ret[1].(error) return ret0, ret1 } // Subscribe indicates an expected call of Subscribe. -func (mr *MockModuleMockRecorder) Subscribe(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Subscribe(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockModule)(nil).Subscribe), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockModule)(nil).Subscribe), arg0, arg1) } diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index 258d752d54..d1eb8011e3 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -11,21 +11,21 @@ import ( var log = logging.Logger("module/fraud") func ConstructModule(tp node.Type) fx.Option { - baseComponent := fx.Provide(func(module Module) fraud.Getter { - return module + baseComponent := fx.Provide(func(serv fraud.Service) fraud.Getter { + return serv }) switch tp { case node.Light: return fx.Module( "fraud", baseComponent, - fx.Provide(ModuleWithSyncer), + fx.Provide(newFraudService(true)), ) case node.Full, node.Bridge: return fx.Module( "fraud", baseComponent, - fx.Provide(NewModule), + fx.Provide(newFraudService(false)), ) default: panic("invalid node type") diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go index 85862b6cd4..ed2e3295aa 100644 --- a/nodebuilder/fraud/service.go +++ b/nodebuilder/fraud/service.go @@ -2,23 +2,86 @@ package fraud import ( "context" + "encoding/json" "github.com/celestiaorg/celestia-node/fraud" ) -// Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the -// network. Any method signature changed here needs to also be changed in the API struct. -type Module interface { +var _ Module = (*Service)(nil) + +// Service is an implementation of Module that uses fraud.Service as a backend. It is used to +// provide fraud proofs as a non-interface type to the API, and wrap fraud.Subscriber with a +// channel of Proofs. +type Service struct { fraud.Service } -// API is a wrapper around Module for the RPC. -// TODO(@distractedm1nd): These structs need to be autogenerated. -// -// Module -// -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . -type API struct { - Subscribe func(fraud.ProofType) (fraud.Subscription, error) - Get func(context.Context, fraud.ProofType) ([]fraud.Proof, error) +func (s *Service) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) { + subscription, err := s.Service.Subscribe(proofType) + if err != nil { + return nil, err + } + proofs := make(chan Proof) + go func() { + defer close(proofs) + for { + proof, err := subscription.Proof(ctx) + if err != nil { + if err != context.DeadlineExceeded && err != context.Canceled { + log.Errorw("fetching proof from subscription", "err", err) + } + return + } + select { + case <-ctx.Done(): + return + case proofs <- Proof{Proof: proof}: + } + } + }() + return proofs, nil +} + +func (s *Service) Get(ctx context.Context, proofType fraud.ProofType) ([]Proof, error) { + originalProofs, err := s.Service.Get(ctx, proofType) + if err != nil { + return nil, err + } + proofs := make([]Proof, len(originalProofs)) + for i, originalProof := range originalProofs { + proofs[i].Proof = originalProof + } + return proofs, nil +} + +// Proof embeds the fraud.Proof interface type to provide a concrete type for JSON serialization. +type Proof struct { + fraud.Proof +} + +type fraudProofJSON struct { + ProofType fraud.ProofType `json:"proof_type"` + Data []byte `json:"data"` +} + +func (f *Proof) UnmarshalJSON(data []byte) error { + var fp fraudProofJSON + err := json.Unmarshal(data, &fp) + if err != nil { + return err + } + f.Proof, err = fraud.Unmarshal(fp.ProofType, fp.Data) + return err +} + +func (f *Proof) MarshalJSON() ([]byte, error) { + marshaledProof, err := f.MarshalBinary() + if err != nil { + return nil, err + } + fraudProof := &fraudProofJSON{ + ProofType: f.Type(), + Data: marshaledProof, + } + return json.Marshal(fraudProof) } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index c611b26ce5..e465e1374a 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -42,7 +42,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), fx.Provide(fx.Annotate( newSyncer, - fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, syncer *sync.Syncer) error { + fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, syncer *sync.Syncer) error { syncerStartFunc := func(ctx context.Context) error { err := syncer.Start(ctx) switch err { diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 4f1b266ca4..42d9e4e002 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -10,6 +10,8 @@ import ( // Module exposes the functionality needed for querying headers from the network. // Any method signature changed here needs to also be changed in the API struct. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // GetByHeight returns the ExtendedHeader at the given height, blocking // until header has been processed by the store or context deadline is exceeded. @@ -22,10 +24,6 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. -// -// Module -// -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) Head func(context.Context) (*header.ExtendedHeader, error) diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go index 4933a50f32..9da51e1ab4 100644 --- a/nodebuilder/share/service.go +++ b/nodebuilder/share/service.go @@ -26,6 +26,8 @@ import ( // * Return // // Any method signature changed here needs to also be changed in the API struct. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { share.Availability GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) @@ -48,10 +50,6 @@ func NewModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Ava // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. -// -// Module -// -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { SharesAvailable func(context.Context, *share.Root) error ProbabilityOfAvailability func() float64 diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index b1e8c95297..11b78fa162 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -26,7 +26,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(Keyring), fx.Provide(fx.Annotate( CoreAccessor, - fx.OnStart(func(startCtx, ctx context.Context, fservice fraudServ.Module, ca *state.CoreAccessor) error { + fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, ca *state.CoreAccessor) error { return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, ca.Start, ca.Stop) }), fx.OnStop(func(ctx context.Context, ca *state.CoreAccessor) error { diff --git a/nodebuilder/state/service.go b/nodebuilder/state/service.go index e9d0ab39c9..0b2a95f2d1 100644 --- a/nodebuilder/state/service.go +++ b/nodebuilder/state/service.go @@ -14,6 +14,8 @@ import ( // Module represents the behaviors necessary for a user to // query for state-related information and submit transactions/ // messages to the Celestia network. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // IsStopped checks if the Module's context has been stopped IsStopped() bool @@ -76,10 +78,6 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. -// -// Module -// -//go:generate go run github.com/golang/mock/mockgen -destination=mocks/api.go -package=mocks . type API struct { IsStopped func() bool Balance func(ctx context.Context) (*state.Balance, error) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 181cd9f238..db5ba91c57 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -55,11 +55,10 @@ func TestFraudProofBroadcasting(t *testing.T) { // subscribe to fraud proof before node starts helps // to prevent flakiness when fraud proof is propagating before subscribing on it - subscr, err := full.FraudServ.Subscribe(fraud.BadEncoding) + subscr, err := full.FraudServ.Subscribe(ctx, fraud.BadEncoding) require.NoError(t, err) - p, err := subscr.Proof(ctx) - require.NoError(t, err) + p := <-subscr require.Equal(t, 20, int(p.Height())) // This is an obscure way to check if the Syncer was stopped. @@ -130,20 +129,25 @@ func TestFraudProofSyncing(t *testing.T) { require.NoError(t, full.Start(ctx)) require.NoError(t, ln.Start(ctx)) - subsFn, err := full.FraudServ.Subscribe(fraud.BadEncoding) - require.NoError(t, err) - defer subsFn.Cancel() - _, err = subsFn.Proof(ctx) + subsFN, err := full.FraudServ.Subscribe(ctx, fraud.BadEncoding) require.NoError(t, err) // internal subscription for the fraud proof is done in order to ensure that light node // receives the BEFP. - subsLn, err := ln.FraudServ.Subscribe(fraud.BadEncoding) + subsLN, err := ln.FraudServ.Subscribe(ctx, fraud.BadEncoding) require.NoError(t, err) + // ensure that the full and light node are connected to preempt flakiness err = ln.Host.Connect(ctx, *host.InfoFromHost(full.Host)) require.NoError(t, err) - _, err = subsLn.Proof(ctx) - require.NoError(t, err) - subsLn.Cancel() + + // wait for BEFP to come through both subscriptions + for i := 0; i < 2; i++ { + select { + case <-subsFN: + case <-subsLN: + case <-ctx.Done(): + t.Fatal(ctx.Err()) + } + } } diff --git a/share/eds/eds.go b/share/eds/eds.go index c86bab72d0..5824ae114d 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -85,7 +85,8 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. odsWidth := int(math.Sqrt(float64(shareCount)) / 2) // (shareCount*2) - (odsWidth*4) is the amount of inner nodes visited batchAdder := ipld.NewNmtNodeAdder(ctx, bs, format.MaxSizeBatchOption(innerNodeBatchSize(shareCount, odsWidth))) - // this adder ignores leaves, so that they are not added to the store we iterate through in writeProofs + // this adder ignores leaves, so that they are not added to the store we iterate through in + // writeProofs eds, err := rsmt2d.ImportExtendedDataSquare( shares, share.DefaultRSMT2DCodec(), From 90bd741bbf64cac6d7645ade49431a6a9fd8f68b Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:53:00 +0300 Subject: [PATCH 0181/1008] fix(share): incorrect TestGetSharesByNamespace test (#1342) Resolves https://github.com/celestiaorg/celestia-node/issues/1333 --- share/get_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/share/get_test.go b/share/get_test.go index a7802e1b58..6b76923554 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -158,19 +158,22 @@ func TestGetSharesByNamespace(t *testing.T) { // change rawData to contain several shares with same nID tt.rawData[(len(tt.rawData)/2)+1] = expected - // put raw data in BlockService eds, err := AddShares(ctx, tt.rawData, bServ) require.NoError(t, err) + var shares []Share for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) - shares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) + rowShares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) require.NoError(t, err) - for _, share := range shares { - assert.Equal(t, expected, share) - } + shares = append(shares, rowShares...) + } + + assert.Equal(t, 2, len(shares)) + for _, share := range shares { + assert.Equal(t, expected, share) } }) } From a95b114cf0353a4760314bcd198e8cd9c831f83b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 11 Nov 2022 14:23:18 +0100 Subject: [PATCH 0182/1008] fix(nodebuilder): Use test node for tests instead of New (#1350) Fixes issue with this test not using in-memory keyring. --- nodebuilder/node_bridge_test.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index a50eb6c0bc..95b3e0f143 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -35,22 +35,19 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { // TestBridge_HasStubDaser verifies that a bridge node implements a stub daser that returns an // error and empty das.SamplingStats func TestBridge_HasStubDaser(t *testing.T) { - repo := MockStore(t, DefaultConfig(node.Bridge)) - ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) _, client := core.StartTestClient(ctx, t) - node, err := New(node.Bridge, p2p.Private, repo, coremodule.WithClient(client)) - require.NoError(t, err) - require.NotNil(t, node) - err = node.Start(ctx) + nd := TestNode(t, node.Bridge, coremodule.WithClient(client)) + require.NotNil(t, nd) + err := nd.Start(ctx) require.NoError(t, err) - stats, err := node.DASer.SamplingStats(ctx) + stats, err := nd.DASer.SamplingStats(ctx) assert.EqualError(t, err, "moddas: dasing is not available on bridge nodes") assert.Equal(t, stats, das.SamplingStats{}) - err = node.Stop(ctx) + err = nd.Stop(ctx) require.NoError(t, err) } From 8356c21700e65ac50b1aa8b55f0855f1f760e276 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 11 Nov 2022 16:16:13 +0200 Subject: [PATCH 0183/1008] improvement(header/p2p): get bestHead if timeout is reached (#1319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit сloses https://github.com/celestiaorg/celestia-node/issues/1309 --- header/p2p/exchange.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index f7fd4f996d..11a803f541 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -61,6 +61,10 @@ func NewExchange(host host.Host, peers peer.IDSlice, protocolSuffix string) *Exc // Head requests the latest ExtendedHeader. Note that the ExtendedHeader // must be verified thereafter. +// NOTE: +// It is fine to continue handling head request if the timeout will be reached. +// As we are requesting head from multiple trusted peers, +// we may already have some headers when the timeout will be reached. func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { log.Debug("requesting head") // create request @@ -85,6 +89,7 @@ func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { } result := make([]*header.ExtendedHeader, 0, len(ex.trustedPeers)) +LOOP: for range ex.trustedPeers { select { case h := <-headerCh: @@ -92,7 +97,7 @@ func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { result = append(result, h) } case <-ctx.Done(): - return nil, ctx.Err() + break LOOP } } From 19dd38fe99d5b9ace7e5e535b37b22920d9ff095 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 11 Nov 2022 16:01:51 +0100 Subject: [PATCH 0184/1008] refactor(ipld): use Set/GetCell API from rstm2d (#1173) --- share/eds/retriever.go | 98 +++++++++++++++------------------ share/eds/retriever_quadrant.go | 55 ++++++------------ 2 files changed, 60 insertions(+), 93 deletions(-) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index a7b9ab16f3..2e8713c72f 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -104,23 +104,18 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader // quadrant request retries. Also, provides an API // to reconstruct the block once enough shares are fetched. type retrievalSession struct { + dah *da.DataAvailabilityHeader bget blockservice.BlockGetter adder *ipld.NmtNodeAdder - treeFn rsmt2d.TreeConstructorFn - codec rsmt2d.Codec - - dah *da.DataAvailabilityHeader - squareImported *rsmt2d.ExtendedDataSquare - - quadrants []*quadrant - sharesLks []sync.Mutex - sharesCount uint32 - - squareLk sync.RWMutex - square [][]byte - squareSig chan struct{} - squareDn chan struct{} + // TODO(@Wondertan): Extract into a separate data structure https://github.com/celestiaorg/rsmt2d/issues/135 + squareQuadrants []*quadrant + squareCellsLks [][]sync.Mutex + squareCellsCount uint32 + squareSig chan struct{} + squareDn chan struct{} + squareLk sync.RWMutex + square *rsmt2d.ExtendedDataSquare span trace.Span } @@ -133,29 +128,31 @@ func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHead r.bServ, ipld.MaxSizeBatchOption(size), ) - ses := &retrievalSession{ - bget: blockservice.NewSession(ctx, r.bServ), - adder: adder, - treeFn: func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index, nmt.NodeVisitor(adder.Visit)) - return &tree - }, - codec: share.DefaultRSMT2DCodec(), - dah: dah, - quadrants: newQuadrants(dah), - sharesLks: make([]sync.Mutex, size*size), - square: make([][]byte, size*size), - squareSig: make(chan struct{}, 1), - squareDn: make(chan struct{}), - span: trace.SpanFromContext(ctx), + + treeFn := func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index, nmt.NodeVisitor(adder.Visit)) + return &tree } - square, err := rsmt2d.ImportExtendedDataSquare(ses.square, ses.codec, ses.treeFn) + square, err := rsmt2d.ImportExtendedDataSquare(make([][]byte, size*size), share.DefaultRSMT2DCodec(), treeFn) if err != nil { return nil, err } - ses.squareImported = square + ses := &retrievalSession{ + dah: dah, + bget: blockservice.NewSession(ctx, r.bServ), + adder: adder, + squareQuadrants: newQuadrants(dah), + squareCellsLks: make([][]sync.Mutex, size), + squareSig: make(chan struct{}, 1), + squareDn: make(chan struct{}), + square: square, + span: trace.SpanFromContext(ctx), + } + for i := range ses.squareCellsLks { + ses.squareCellsLks[i] = make([]sync.Mutex, size) + } go ses.request(ctx) return ses, nil } @@ -170,36 +167,24 @@ func (rs *retrievalSession) Done() <-chan struct{} { // Reconstruct tries to reconstruct the data square and returns it on success. func (rs *retrievalSession) Reconstruct(ctx context.Context) (*rsmt2d.ExtendedDataSquare, error) { if rs.isReconstructed() { - return rs.squareImported, nil + return rs.square, nil } // prevent further writes to the square rs.squareLk.Lock() defer rs.squareLk.Unlock() - // TODO(@Wondertan): This is bad! - // * We should not reimport the square multiple times - // * We should set shares into imported square via - // SetShare(https://github.com/celestiaorg/rsmt2d/issues/83) to accomplish the above point. - { - squareImported, err := rsmt2d.ImportExtendedDataSquare(rs.square, rs.codec, rs.treeFn) - if err != nil { - return nil, err - } - rs.squareImported = squareImported - } - _, span := tracer.Start(ctx, "reconstruct-square") defer span.End() // and try to repair with what we have - err := rs.squareImported.Repair(rs.dah.RowsRoots, rs.dah.ColumnRoots) + err := rs.square.Repair(rs.dah.RowsRoots, rs.dah.ColumnRoots) if err != nil { span.RecordError(err) return nil, err } log.Infow("data square reconstructed", "data_hash", hex.EncodeToString(rs.dah.Hash()), "size", len(rs.dah.RowsRoots)) close(rs.squareDn) - return rs.squareImported, nil + return rs.square, nil } // isReconstructed report true whether the square attached to the session @@ -232,8 +217,8 @@ func (rs *retrievalSession) Close() error { func (rs *retrievalSession) request(ctx context.Context) { t := time.NewTicker(RetrieveQuadrantTimeout) defer t.Stop() - for retry := 0; retry < len(rs.quadrants); retry++ { - q := rs.quadrants[retry] + for retry := 0; retry < len(rs.squareQuadrants); retry++ { + q := rs.squareQuadrants[retry] log.Debugw("requesting quadrant", "axis", q.source, "x", q.x, @@ -241,7 +226,7 @@ func (rs *retrievalSession) request(ctx context.Context) { "size", len(q.roots), ) rs.span.AddEvent("requesting quadrant", trace.WithAttributes( - attribute.Int("axis", q.source), + attribute.Int("axis", int(q.source)), attribute.Int("x", q.x), attribute.Int("y", q.y), attribute.Int("size", len(q.roots)), @@ -260,7 +245,7 @@ func (rs *retrievalSession) request(ctx context.Context) { "size", len(q.roots), ) rs.span.AddEvent("quadrant request timeout", trace.WithAttributes( - attribute.Int("axis", q.source), + attribute.Int("axis", int(q.source)), attribute.Int("x", q.x), attribute.Int("y", q.y), attribute.Int("size", len(q.roots)), @@ -292,10 +277,10 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { // in the square. // NOTE-2: We never actually fetch shares from the network *twice*. // Once a share is downloaded from the network it is cached on the IPLD(blockservice) level. - // calc index of the share - idx := q.index(i, j) + // calc position of the share + x, y := q.pos(i, j) // try to lock the share - ok := rs.sharesLks[idx].TryLock() + ok := rs.squareCellsLks[x][y].TryLock() if !ok { // if already locked and written - do nothing return @@ -312,14 +297,17 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { if rs.isReconstructed() { return } - rs.square[idx] = share + if rs.square.GetCell(uint(x), uint(y)) != nil { + return + } + rs.square.SetCell(uint(x), uint(y), share) // if we have >= 1/4 of the square we can start trying to Reconstruct // TODO(@Wondertan): This is not an ideal way to know when to start // reconstruction and can cause idle reconstruction tries in some cases, // but it is totally fine for the happy case and for now. // The earlier we correctly know that we have the full square - the earlier // we cancel ongoing requests - the less data is being wastedly transferred. - if atomic.AddUint32(&rs.sharesCount, 1) >= uint32(size*size) { + if atomic.AddUint32(&rs.squareCellsCount, 1) >= uint32(size*size) { select { case rs.squareSig <- struct{}{}: default: diff --git a/share/eds/retriever_quadrant.go b/share/eds/retriever_quadrant.go index 9a637a27be..8b8037ce85 100644 --- a/share/eds/retriever_quadrant.go +++ b/share/eds/retriever_quadrant.go @@ -1,13 +1,13 @@ package eds import ( - "math" "math/rand" "time" "github.com/ipfs/go-cid" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -42,10 +42,8 @@ type quadrant struct { // |(0;1)| |(1;1)| // ------ ------- x, y int - // source defines the axis for quadrant - // it can be either 1 or 0 similar to x and y - // where 0 is Row source and 1 is Col respectively - source int + // source defines the axis(Row or Col) to fetch the quadrant from + source rsmt2d.Axis } // newQuadrants constructs a slice of quadrants from DAHeader. @@ -70,17 +68,13 @@ func newQuadrants(dah *da.DataAvailabilityHeader) []*quadrant { } for i := range quadrants { - // convert quadrant index into coordinates + // convert quadrant 1D into into 2D coordinates x, y := i%2, i/2 - if source == 1 { // swap coordinates for column - x, y = y, x - } - quadrants[i] = &quadrant{ roots: roots[qsize*y : qsize*(y+1)], x: x, y: y, - source: source, + source: rsmt2d.Axis(source), } } } @@ -93,31 +87,16 @@ func newQuadrants(dah *da.DataAvailabilityHeader) []*quadrant { return quadrants } -// index calculates index for a share in a data square slice flattened by rows. -// -// NOTE: The complexity of the formula below comes from: -// - Goal to avoid share copying -// - Goal to make formula generic for both rows and cols -// - While data square is flattened by rows only -// -// TODO(@Wondertan): This can be simplified by making rsmt2d working over 3D byte slice(not -// flattened) -func (q *quadrant) index(rootIdx, cellIdx int) int { - size := len(q.roots) - // half square offsets, e.g. share is from Q3, - // so we add to index Q1+Q2 - halfSquareOffsetCol := pow(size*2, q.source) - halfSquareOffsetRow := pow(size*2, q.source^1) - // offsets for the axis, e.g. share is from Q4. - // so we add to index Q3 - offsetX := q.x * halfSquareOffsetCol * size - offsetY := q.y * halfSquareOffsetRow * size - - rootIdx *= halfSquareOffsetRow - cellIdx *= halfSquareOffsetCol - return rootIdx + cellIdx + offsetX + offsetY -} - -func pow(x, y int) int { - return int(math.Pow(float64(x), float64(y))) +// pos calculates position of a share in a data square. +func (q *quadrant) pos(rootIdx, cellIdx int) (int, int) { + cellIdx += len(q.roots) * q.x + rootIdx += len(q.roots) * q.y + switch q.source { + case rsmt2d.Row: + return rootIdx, cellIdx + case rsmt2d.Col: + return cellIdx, rootIdx + default: + panic("unknown axis") + } } From bd13b7619dd86ba88d65f350f128fadf93f98ef8 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Sat, 12 Nov 2022 12:28:30 +0100 Subject: [PATCH 0185/1008] docs(adr): Public API (#506) --- docs/adr/adr-009-public-api.md | 438 +++++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 docs/adr/adr-009-public-api.md diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md new file mode 100644 index 0000000000..575ad3b9fd --- /dev/null +++ b/docs/adr/adr-009-public-api.md @@ -0,0 +1,438 @@ +# ADR 009: Public API + +## Authors + +@Wondertan @renaynay + +## Changelog + +- 2022-03-08: initial version +- 2022-08-03: additional details + +## Context + +Celestia Node has been built for almost half a year with a bottom-up approach to +development. The core lower level components are built first and public API +around them is getting organically shaped. Now that the project is maturing and +its architecture is better defined, it's a good time to formally define a set of +modules provided by the node and their respective APIs. + +## Alternative Approaches + +### Node Type centric Design + +Another discussed approach to defining an API could be an onion style, where +each new layer is a feature set of a broader node type. However, it turns out +that this approach does not seem to match the reality we have, where each node +type implements all the possible APIs but with the variable implementations +matching resource constraints of a type. + +## Design + +### Goals + +- Ergonomic. Simple, idiomatic and self-explanatory. +- Module centric(modular). The API is not monolithic and is segregated into +different categorized and independent modules. +- Unified. All the node types implement the same set of APIs. The difference is +defined by different implementations of some modules to meet resource +requirements of a type. Example: FullAvailability and LightAvailability. +- Embeddable. Simply constructable Node with library style API. Not an +SDK/Framework which dictates users the way to build an app, but users are those +who decide how they want to build the app using the API. +- Language agnostic. It should be simple to implement similar module +interfaces/traits in other languages over RPC clients. + +### Implementation + +The tracking issue can be found +[here](https://github.com/celestiaorg/celestia-node/issues/944). It provides a +more detailed step-by-step process for how the below described design will be +implemented. + +### High-level description + +All node types in `celestia-node` will be referred to as `data availability +nodes (DA nodes)` whose sole purpose is to interact with the `data availability +network layer (DA layer)` such that the node contains all functionality +necessary to post and retrieve messages from the DA layer. + +This means that DA nodes will be able to query for / modify celestia state such +that the DA nodes are able to pay for posting their messages on the network. +The state-related API will be documented below in detail. + +Furthermore, interaction between the celestia consensus network and the +celestia data availability network will be the responsibility of the **bridge** +node type. However, that interaction will not be exposed on a public level +(meaning a **bridge** node will not expose the same API as the +celestia-core node to which it is connected). A **bridge** node, for all intents +and purposes, will provide the same API as that of a **full** node (with a +stubbed-out DAS module as bridge nodes do not perform sampling). + +### Details + +#### Services Deprecation + +The initial step is to deprecate services in favor of modules. Ex. +`HeaderService` -> `HeaderModule`. + +- We're organically moving towards the direction of modularized libraries. That +is, our `share`, `header` and `state` packages are getting shaped as independent +modules which now lack their own API definition. +- Consistency. Semantically, modules are closer to what Celestia's overarching +project goals and slogans. +- Disassociate centralization. Services have always been associated with +centralized infrastructure with older monolithic services and newer distributed +microservice architectures. + +#### Modules Definition + +##### Header + +```go +type HeaderModule interface { +// Head returns the node's local head (tip of the chain of the header store). +Head(ctx context.Context) (*header.ExtendedHeader, error) +// Get returns the header of the given hash from the node's header store. +Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) +// GetByHeight returns the header of the given height from the node's header store. +// If the header of the given height is not yet available, the request will hang +// until it becomes available in the node's header store. +GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) +// GetRangeByHeight returns the given range [from:to) of ExtendedHeaders. +GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) +// Subscribe creates long-living Subscription for validated ExtendedHeaders. +// Multiple Subscriptions can be created. +Subscribe() (Subscription, error) +// SyncState returns the current state of the header Syncer. +SyncState() sync.State +// SyncWait blocks until the header Syncer is synced to network head. +SyncWait(ctx context.Context) error +// SyncHead provides the Syncer's view of the current network head. +SyncHead(ctx context.Context) (*header.ExtendedHeader, error) +} +``` + +##### Shares + +```go + type SharesModule interface { + // GetShare returns the Share from the given data Root at the given row/col + // coordinates. + GetShare(ctx context.Context, root *Root, row, col int) (Share, error) + // GetSharesByNamespace returns all shares of the given nID from the given data + // Root. + GetSharesByNamespace( + ctx context.Context, + root *Root, + nID namespace.ID, + ) ([]Share, error) + // SharesAvailable subjectively validates if Shares committed to the given data + // Root are available on the network. + SharesAvailable(ctx context.Context, root *Root) error + // ProbabilityOfAvailability calculates the probability of the data square + // being available based on the number of samples collected. + ProbabilityOfAvailability() float64 + } +``` + +##### P2P + +```go + type P2PModule interface { + // Info returns basic information about the node's p2p host/operations. + Info() p2p.Info + // Peers returns all peer IDs used across all inner stores. + Peers() peer.IDSlice + // PeerInfo returns a small slice of information Peerstore has on the + // given peer. + PeerInfo(id peer.ID) peer.AddrInfo + + // Connect ensures there is a connection between this host and the peer with + // given peer. + Connect(ctx context.Context, pi peer.AddrInfo) error + // ClosePeer closes the connection to a given peer. + ClosePeer(id peer.ID) error + // Connectedness returns a state signaling connection capabilities. + Connectedness(id peer.ID) network.Connectedness + // NATStatus returns the current NAT status. + NATStatus() network.Reachability + + // BlockPeer adds a peer to the set of blocked peers. + BlockPeer(p peer.ID) error + // UnblockPeer removes a peer from the set of blocked peers. + UnblockPeer(p peer.ID) error + // ListBlockedPeers returns a list of blocked peers. + ListBlockedPeers() []peer.ID + + // MutualAdd adds a peer to the list of peers who have a bidirectional + // peering agreement that they are protected from being trimmed, dropped + // or negatively scored. + MutualAdd(id peer.ID, tag string) + // MutualAdd removes a peer from the list of peers who have a bidirectional + // peering agreement that they are protected from being trimmed, dropped + // or negatively scored, returning a bool representing whether the given + // peer is protected or not. + MutualRm(id peer.ID, tag string) bool + // IsMutual returns whether the given peer is a mutual peer. + IsMutual(id peer.ID, tag string) bool + + // BandwidthStats returns a Stats struct with bandwidth metrics for all + // data sent/received by the local peer, regardless of protocol or remote + // peer IDs. + BandwidthStats() Stats + // BandwidthForPeer returns a Stats struct with bandwidth metrics associated + // with the given peer.ID. The metrics returned include all traffic sent / + // received for the peer, regardless of protocol. + BandwidthForPeer(id peer.ID) Stats + // BandwidthForProtocol returns a Stats struct with bandwidth metrics + // associated with the given protocol.ID. + BandwidthForProtocol(proto protocol.ID) Stats + + // ResourceState returns the state of the resource manager. + ResourceState() rcmgr.ResourceManagerStat + + // PubSubPeers returns the peer IDs of the peers joined on + // the given topic. + PubSubPeers(topic string) ([]peer.ID, error) + } +``` + +### NodeModule + +```go + + type NodeModule interface { + // Type returns the node type. + Type() node.Type + // Version returns information about the current binary build. + Version() string + + // LogLevelSet sets the given component log level to the given level. + LogLevelSet(ctx context.Context, name, level string) error + } + +``` + +#### DAS + +```go + type DASModule interface { + // Stats returns current stats of the DASer. + Stats() (das.SamplingStats, error) + } +``` + +#### State + +```go + type StateModule interface { + // AccountAddress retrieves the address of the node's account/signer + AccountAddress(ctx context.Context) (state.Address, error) + // Balance retrieves the Celestia coin balance for the node's account/signer + // and verifies it against the corresponding block's AppHash. + Balance(ctx context.Context) (*state.Balance, error) + // BalanceForAddress retrieves the Celestia coin balance for the given + // address and verifies the returned balance against the corresponding + // block's AppHash. + BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) + // SubmitTx submits the given transaction/message to the Celestia network + // and blocks until the tx is included in a block. + SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) + // SubmitPayForData builds, signs and submits a PayForData transaction. + SubmitPayForData( + ctx context.Context, + nID namespace.ID, + data []byte, + gasLimit uint64, + ) (*state.TxResponse, error) + // Transfer sends the given amount of coins from default wallet of the node + // to the given account address. + Transfer( + ctx context.Context, + to types.Address, + amount types.Int, + gasLimit uint64, + ) (*state.TxResponse, error) + + // StateModule also provides StakingModule + StakingModule + } +``` + +Ideally all the state modules below should be implemented on top of only +StateModule, but there is no way we can have an abstract state requesting method, +yet. + +##### Staking + +```go + type StakingModule interface { + // Delegate sends a user's liquid tokens to a validator for delegation. + Delegate( + ctx context.Context, + delAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) + // BeginRedelegate sends a user's delegated tokens to a new validator for redelegation. + BeginRedelegate( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) + // Undelegate undelegates a user's delegated tokens, unbonding them from the + // current validator. + Undelegate( + ctx context.Context, + delAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) + + // CancelUnbondingDelegation cancels a user's pending undelegation from a + // validator. + CancelUnbondingDelegation( + ctx context.Context, + valAddr state.ValAddress, + amount, + height state.Int, + gasLim uint64, + ) (*state.TxResponse, error) + + // QueryDelegation retrieves the delegation information between a delegator + // and a validator. + QueryDelegation( + ctx context.Context, + valAddr state.ValAddress, + ) (*types.QueryDelegationResponse, error) + // QueryRedelegations retrieves the status of the redelegations between a + // delegator and a validator. + QueryRedelegations( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + ) (*types.QueryRedelegationsResponse, error) + // QueryUnbonding retrieves the unbonding status between a delegator and a validator. + QueryUnbonding( + ctx context.Context, + valAddr state.ValAddress, + ) (*types.QueryUnbondingDelegationResponse, error) + + } +``` + +#### Fraud + +```go + type FraudModule interface { + // Subscribe subscribes to the given fraud proof type. + Subscribe(proof fraud.ProofType) error + // List lists all proof types to which the fraud module is currently + // subscribed. + List() []fraud.ProofType + + // Get returns any stored fraud proofs of the given type. + Get(proof fraud.ProofType) ([]Proof, error) + } +``` + +#### Metrics + +```go + type MetricsModule interface { + // List shows all the registered meters. + List(ctx) (string[], error) + + // Enable turns on the specific meter. + Enable(string) error + Disable(string) error + + // ExportTo sets the endpoint the metrics should be exported to + ExportTo(string) + } +``` + +### Nice to have (post-mainnet) + +#### State-related modules + +Eventually, it would be nice to break up `StateModule` into `StateModule`, +`BankModule` and `StakingModule`. + +##### State (general) + +```go +type StateModule interface { + // QueryABCI proxies a generic ABCI query to the core endpoint. + QueryABCI(ctx context.Context, request abci.RequestQuery) + (*coretypes.ResultABCIQuery, error) + // SubmitTx submits the given transaction/message to the Celestia network + // and blocks until the tx is included in a block. + SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) +} +``` + +##### Bank + +```go +type BankModule interface { + // Balance retrieves the Celestia coin balance for the node's account/signer + // and verifies it against the corresponding block's AppHash. + Balance(ctx context.Context) (*state.Balance, error) + // BalanceForAddress retrieves the Celestia coin balance for the given + // address and verifies the returned balance against the corresponding + // block's AppHash. + BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) + // SubmitPayForData builds, signs and submits a PayForData transaction. + SubmitPayForData( + ctx context.Context, + nID namespace.ID, + data []byte, + gasLimit uint64, + ) (*state.TxResponse, error) + // Transfer sends the given amount of coins from default wallet of the node + // to the given account address. + Transfer( + ctx context.Context, + to types.Address, + amount types.Int, + gasLimit uint64, + ) (*state.TxResponse, error) +} +``` + +##### Staking (same as pre-mainnet staking module) + +```go + type StakingModule interface { + Delegate + Redelegate + Unbond + CancelUnbond + + QueryDelegation + QueryRedelegation + QueryUnbondingDelegation + } +``` + +##### Account + +```go + type AccountModule interface { + Add + Delete + Show + List + Sign + Export + Import + } +``` + +## Status + +Proposed From 21bb00c0cb71cc2568c1a0ed5162eae0775c90c6 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:37:46 +0100 Subject: [PATCH 0186/1008] fix(header): Extend `ValidateBasic` on `ExtendedHeader` to include data root check (#1364) Resolves #1362 --- core/testing.go | 47 +++++++++++++++++++++++++++++++++++- header/core/exchange_test.go | 9 +++---- header/core/listener_test.go | 2 +- header/header.go | 7 ++++++ header/header_test.go | 12 ++++++++- header/testing.go | 25 +++++++++++++------ 6 files changed, 85 insertions(+), 17 deletions(-) diff --git a/core/testing.go b/core/testing.go index f0ddfd58bf..6253cf1447 100644 --- a/core/testing.go +++ b/core/testing.go @@ -14,10 +14,13 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/config" + tmrand "github.com/tendermint/tendermint/libs/rand" tmservice "github.com/tendermint/tendermint/libs/service" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" rpctest "github.com/tendermint/tendermint/rpc/test" tmtypes "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/testutil/testnode" ) // so that we never hit an issue where we request blocks that are removed @@ -57,15 +60,57 @@ func StartTestClient(ctx context.Context, t *testing.T) (tmservice.Service, Clie require.NoError(t, err) ip, port, err := net.SplitHostPort(endpoint) require.NoError(t, err) + client, err := NewRemote(ip, port) require.NoError(t, err) + + err = client.Start() + require.NoError(t, err) t.Cleanup(func() { err := client.Stop() require.NoError(t, err) }) + + return nd, client +} + +func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) { + // we create an arbitrary number of funded accounts + accounts := make([]string, 10) + for i := range accounts { + accounts[i] = tmrand.Str(9) + } + + tmNode, _, cctx, err := testnode.New( + t, + testnode.DefaultParams(), + testnode.DefaultTendermintConfig(), + true, + accounts..., + ) + require.NoError(t, err) + + _, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) + require.NoError(t, err) + t.Cleanup(cleanupCoreNode) + + endpoint, err := GetEndpoint(tmNode.Config()) + require.NoError(t, err) + + ip, port, err := net.SplitHostPort(endpoint) + require.NoError(t, err) + + client, err := NewRemote(ip, port) + require.NoError(t, err) + err = client.Start() require.NoError(t, err) - return nd, client + t.Cleanup(func() { + err := client.Stop() + require.NoError(t, err) + }) + + return tmNode, client } // GetEndpoint returns the remote node's RPC endpoint. diff --git a/header/core/exchange_test.go b/header/core/exchange_test.go index 4808771427..4c379abb64 100644 --- a/header/core/exchange_test.go +++ b/header/core/exchange_test.go @@ -14,10 +14,7 @@ import ( ) func TestCoreExchange_RequestHeaders(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - fetcher := createCoreFetcher(ctx, t) + fetcher := createCoreFetcher(t) store := mdutils.Bserv() // generate 10 blocks @@ -37,8 +34,8 @@ func Test_hashMatch(t *testing.T) { assert.False(t, bytes.Equal(expected, mismatch)) } -func createCoreFetcher(ctx context.Context, t *testing.T) *core.BlockFetcher { - _, client := core.StartTestClient(ctx, t) +func createCoreFetcher(t *testing.T) *core.BlockFetcher { + _, client := core.StartTestCoreWithApp(t) return core.NewBlockFetcher(client) } diff --git a/header/core/listener_test.go b/header/core/listener_test.go index b66ed2f4d4..fd879563c1 100644 --- a/header/core/listener_test.go +++ b/header/core/listener_test.go @@ -31,7 +31,7 @@ func TestListener(t *testing.T) { require.NoError(t, err) // create one block to store as Head in local store and then unsubscribe from block events - fetcher := createCoreFetcher(ctx, t) + fetcher := createCoreFetcher(t) // create Listener and start listening cl := createListener(ctx, t, fetcher, ps0) diff --git a/header/header.go b/header/header.go index 79ea4941b0..ac3ab6b8d8 100644 --- a/header/header.go +++ b/header/header.go @@ -16,6 +16,7 @@ import ( appshares "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" ) @@ -125,6 +126,12 @@ func (eh *ExtendedHeader) ValidateBasic() error { return err } + // ensure data root from raw header matches computed root + if !bytes.Equal(eh.DAH.Hash(), eh.DataHash) { + return fmt.Errorf("mismatch between data hash commitment from core header and computed data root: "+ + "data hash: %X, computed root: %X", eh.DataHash, eh.DAH.Hash()) + } + return eh.DAH.ValidateBasic() } diff --git a/header/header_test.go b/header/header_test.go index 419f6e93f9..11aac1a44f 100644 --- a/header/header_test.go +++ b/header/header_test.go @@ -7,6 +7,7 @@ import ( mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/rand" "github.com/celestiaorg/celestia-node/core" ) @@ -15,7 +16,7 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - _, client := core.StartTestClient(ctx, t) + _, client := core.StartTestCoreWithApp(t) fetcher := core.NewBlockFetcher(client) store := mdutils.Bserv() @@ -36,3 +37,12 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { assert.Equal(t, EmptyDAH(), *headerExt.DAH) } + +func TestMismatchedDataHash_ComputedRoot(t *testing.T) { + header := RandExtendedHeader(t) + + header.DataHash = rand.Bytes(32) + + err := header.ValidateBasic() + assert.ErrorContains(t, err, "mismatch between data hash") +} diff --git a/header/testing.go b/header/testing.go index cb55caff37..03880fcf28 100644 --- a/header/testing.go +++ b/header/testing.go @@ -50,14 +50,18 @@ func NewTestSuite(t *testing.T, num int) *TestSuite { } func (s *TestSuite) genesis() *ExtendedHeader { + dah := EmptyDAH() + gen := RandRawHeader(s.t) + + gen.DataHash = dah.Hash() gen.ValidatorsHash = s.valSet.Hash() gen.NextValidatorsHash = s.valSet.Hash() gen.Height = 1 voteSet := types.NewVoteSet(gen.ChainID, gen.Height, 0, tmproto.PrecommitType, s.valSet) commit, err := core.MakeCommit(RandBlockID(s.t), gen.Height, 0, voteSet, s.vals, time.Now()) require.NoError(s.t, err) - dah := EmptyDAH() + eh := &ExtendedHeader{ RawHeader: *gen, Commit: commit, @@ -155,13 +159,17 @@ func (s *TestSuite) nextProposer() *types.Validator { // RandExtendedHeader provides an ExtendedHeader fixture. func RandExtendedHeader(t *testing.T) *ExtendedHeader { + dah := EmptyDAH() + rh := RandRawHeader(t) + rh.DataHash = dah.Hash() + valSet, vals := core.RandValidatorSet(3, 1) rh.ValidatorsHash = valSet.Hash() voteSet := types.NewVoteSet(rh.ChainID, rh.Height, 0, tmproto.PrecommitType, valSet) commit, err := core.MakeCommit(RandBlockID(t), rh.Height, 0, voteSet, vals, time.Now()) require.NoError(t, err) - dah := EmptyDAH() + return &ExtendedHeader{ RawHeader: *rh, Commit: commit, @@ -212,13 +220,13 @@ func FraudMaker(t *testing.T, faultHeight int64) ConstructFn { comm *types.Commit, vals *types.ValidatorSet, bServ blockservice.BlockService) (*ExtendedHeader, error) { - eh := &ExtendedHeader{ - RawHeader: b.Header, - Commit: comm, - ValidatorSet: vals, - } - if b.Height == faultHeight { + eh := &ExtendedHeader{ + RawHeader: b.Header, + Commit: comm, + ValidatorSet: vals, + } + eh = CreateFraudExtHeader(t, eh, bServ) return eh, nil } @@ -234,6 +242,7 @@ func CreateFraudExtHeader(t *testing.T, eh *ExtendedHeader, dag blockservice.Blo require.NoError(t, err) dah := da.NewDataAvailabilityHeader(extended) eh.DAH = &dah + eh.RawHeader.DataHash = dah.Hash() return eh } From 4b4f64f546add60f7b8a13a6a0466e8669857000 Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Tue, 15 Nov 2022 11:00:17 +0100 Subject: [PATCH 0187/1008] fix(das): Correct `WithMetrics` fx Invocation (#1366) ## Overview This PR fixes the `das.WithMetrics` fx invokation which enables metrics collection in the DASer module. ## Impact Negligeable ## Owners @derrandz --- nodebuilder/das/opts.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nodebuilder/das/opts.go b/nodebuilder/das/opts.go index 88dad56db3..f9c76f1781 100644 --- a/nodebuilder/das/opts.go +++ b/nodebuilder/das/opts.go @@ -1,13 +1,11 @@ package das import ( - "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/das" ) -func WithMetrics() fx.Option { - return fx.Invoke(func(d *das.DASer) error { - return d.InitMetrics() - }) +// WithMetrics is a utility function that is expected to be +// "invoked" by the fx lifecycle. +func WithMetrics(d *das.DASer) error { + return d.InitMetrics() } From 60216cd567cd1f20c6af60a4812651380d19aa30 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:13:51 +0100 Subject: [PATCH 0188/1008] refactor(nodebuilder/p2p): Allow specifying network alias with `--p2p.network` flag (#1357) * Allows users to specify the network alias rather than the actual value of the network ID when passing `--p2p.network` * Fails out on init if invalid network specified via `--p2p.network` Supersedes https://github.com/celestiaorg/celestia-node/pull/1353 Ensures https://github.com/celestiaorg/docs/pull/304 does not need to happen. --- nodebuilder/p2p/flags.go | 15 ++++++-- nodebuilder/p2p/flags_test.go | 71 +++++++++++++++++++++++++++++++++++ nodebuilder/p2p/network.go | 9 +++++ 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 nodebuilder/p2p/flags_test.go diff --git a/nodebuilder/p2p/flags.go b/nodebuilder/p2p/flags.go index 5335a96330..82d03da697 100644 --- a/nodebuilder/p2p/flags.go +++ b/nodebuilder/p2p/flags.go @@ -67,9 +67,9 @@ func ParseFlags( // ParseNetwork tries to parse the network from the flags and environment, // and returns either the parsed network or the build's default network func ParseNetwork(cmd *cobra.Command) (Network, error) { - parsedNetwork := cmd.Flag(networkFlag).Value.String() + parsed := cmd.Flag(networkFlag).Value.String() // no network set through the flags, so check if there is an override in the env - if parsedNetwork == "" { + if parsed == "" { envNetwork, err := parseNetworkFromEnv() // no network found in env, so use the default network if envNetwork == "" { @@ -77,7 +77,16 @@ func ParseNetwork(cmd *cobra.Command) (Network, error) { } return envNetwork, err } - return Network(parsedNetwork), nil + // check if user provided an alias + parsedNetwork, ok := networkAliases[parsed] + if ok { + return parsedNetwork, nil + } + // check if user provided the actual network value + if err := Network(parsed).Validate(); err == nil { + return Network(parsed), nil + } + return "", fmt.Errorf("invalid network specified: %s", parsed) } // parseNetworkFromEnv tries to parse the network from the environment. diff --git a/nodebuilder/p2p/flags_test.go b/nodebuilder/p2p/flags_test.go new file mode 100644 index 0000000000..bec49f6074 --- /dev/null +++ b/nodebuilder/p2p/flags_test.go @@ -0,0 +1,71 @@ +package p2p + +import ( + "testing" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestParseNetwork_matchesByAlias checks to ensure flag parsing +// correctly matches the network's alias to the network name. +func TestParseNetwork_matchesByAlias(t *testing.T) { + cmd := createCmdWithNetworkFlag() + + err := cmd.Flags().Set(networkFlag, "arabica") + require.NoError(t, err) + + net, err := ParseNetwork(cmd) + require.NoError(t, err) + assert.Equal(t, Arabica, net) +} + +// TestParseNetwork_matchesByValue checks to ensure flag parsing +// correctly matches the network's actual value to the network name. +func TestParseNetwork_matchesByValue(t *testing.T) { + cmd := createCmdWithNetworkFlag() + + err := cmd.Flags().Set(networkFlag, string(Arabica)) + require.NoError(t, err) + + net, err := ParseNetwork(cmd) + require.NoError(t, err) + assert.Equal(t, Arabica, net) +} + +// TestParseNetwork_parsesFromEnv checks to ensure flag parsing +// correctly fetches the value from the environment variable. +func TestParseNetwork_parsesFromEnv(t *testing.T) { + cmd := createCmdWithNetworkFlag() + + t.Setenv(EnvCustomNetwork, "testing") + + net, err := ParseNetwork(cmd) + require.NoError(t, err) + assert.Equal(t, Network("testing"), net) +} + +func TestParsedNetwork_invalidNetwork(t *testing.T) { + cmd := createCmdWithNetworkFlag() + + err := cmd.Flags().Set(networkFlag, "invalid") + require.NoError(t, err) + + net, err := ParseNetwork(cmd) + assert.Error(t, err) + assert.Equal(t, Network(""), net) +} + +func createCmdWithNetworkFlag() *cobra.Command { + cmd := &cobra.Command{} + flags := &flag.FlagSet{} + flags.String( + networkFlag, + "", + "", + ) + cmd.Flags().AddFlagSet(flags) + return cmd +} diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 2289f6d635..ddeede30a0 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -46,6 +46,15 @@ var networksList = map[Network]struct{}{ Private: {}, } +// networkAliases is a strict list of all known long-standing networks +// mapped from the string representation of their *alias* (rather than +// their actual value) to the Network. +var networkAliases = map[string]Network{ + "arabica": Arabica, + "mamaki": Mamaki, + "private": Private, +} + // listProvidedNetworks provides a string listing all known long-standing networks for things like // command hints. func listProvidedNetworks() string { From 72a66f74d03632f0735850d3a40118dbc92887b6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 15 Nov 2022 14:16:37 +0100 Subject: [PATCH 0189/1008] fix(coreaccessor): pfd submission nil check (#1368) --- state/core_access.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/core_access.go b/state/core_access.go index a7fdfe5b3f..c4b3844317 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -153,7 +153,7 @@ func (ca *CoreAccessor) SubmitPayForData( ) (*TxResponse, error) { response, err := payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim) // metrics should only be counted on a successful PFD tx - if response.Code == 0 && err == nil { + if err == nil && response.Code == 0 { ca.lastPayForData = time.Now().UnixMilli() ca.payForDataCount++ } From 5f68932ac9ed17cc99d34fa5488ba4370f56f2e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:27:41 +0100 Subject: [PATCH 0190/1008] chore(deps): bump golangci/golangci-lint-action from 3.3.0 to 3.3.1 (#1360) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 5844a82796..1c7512a257 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -23,7 +23,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: golangci-lint - uses: golangci/golangci-lint-action@v3.3.0 + uses: golangci/golangci-lint-action@v3.3.1 with: version: v1.49.0 From 290cc65f68b14c3d5573579192634f3462ed6f6a Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:01:24 -0600 Subject: [PATCH 0191/1008] fix(eds/byzantine): trim `NMTWrapper`'s namespace during BEFP validation (#1354) Co-authored-by: Wondertan Fixes https://github.com/celestiaorg/celestia-node/issues/1355 --- share/eds/byzantine/bad_encoding.go | 5 +- share/eds/byzantine/bad_encoding_test.go | 63 ++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 share/eds/byzantine/bad_encoding_test.go diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 6632ac4972..f8670bfc9a 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -138,10 +138,13 @@ func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { if share == nil { continue } - shares[index] = share.Share + // validate inclusion of the share into one of the DAHeader roots if ok := share.Validate(ipld.MustCidFromNamespacedSha256(root)); !ok { return fmt.Errorf("fraud: invalid proof: incorrect share received at index %d", index) } + // NMTree commits the additional namespace while rsmt2d does not know about, so we trim it + // this is ugliness from NMTWrapper that we have to embrace ¯\_(ツ)_/¯ + shares[index] = share.Share[ipld.NamespaceSize:] } codec := appconsts.DefaultCodec() diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go new file mode 100644 index 0000000000..2e5a326c06 --- /dev/null +++ b/share/eds/byzantine/bad_encoding_test.go @@ -0,0 +1,63 @@ +package byzantine + +import ( + "context" + "testing" + + mdutils "github.com/ipfs/go-merkledag/test" + "github.com/stretchr/testify/require" + core "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/rsmt2d" +) + +// TestIncorrectBadEncodingFraudProof asserts that BEFP is not generated for the correct data +func TestIncorrectBadEncodingFraudProof(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + bServ := mdutils.Bserv() + + squareSize := 8 + shares := share.RandShares(t, squareSize*squareSize) + + eds, err := share.AddShares(ctx, shares, bServ) + require.NoError(t, err) + + dah := da.NewDataAvailabilityHeader(eds) + + // get an arbitrary row + row := uint(squareSize / 2) + rowShares := eds.Row(row) + rowRoot := dah.RowsRoots[row] + + shareProofs, err := GetProofsForShares(ctx, bServ, ipld.MustCidFromNamespacedSha256(rowRoot), rowShares) + require.NoError(t, err) + + // create a fake error for data that was encoded correctly + fakeError := ErrByzantine{ + Index: uint32(row), + Shares: shareProofs, + Axis: rsmt2d.Row, + } + + h := &header.ExtendedHeader{ + RawHeader: core.Header{ + Height: 420, + }, + DAH: &dah, + Commit: &core.Commit{ + BlockID: core.BlockID{ + Hash: []byte("made up hash"), + }, + }, + } + + proof := CreateBadEncodingProof(h.Hash(), uint64(h.Height), &fakeError) + err = proof.Validate(h) + require.Error(t, err) +} From 566daa03636fcb87b3bcca88ab791e807227f1f9 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:22:13 -0600 Subject: [PATCH 0192/1008] chore!: bump celestia-app to v0.10.0-rc1 (#1338) Co-authored-by: Vlad Co-authored-by: Wondertan --- go.mod | 2 +- go.sum | 4 ++-- share/eds/byzantine/bad_encoding.go | 11 ++++----- share/eds/testdata/example-root.json | 32 +++++++++++++-------------- share/eds/testdata/example.car | Bin 55235 -> 55235 bytes share/ipld/nmt_test.go | 6 ++--- 6 files changed, 27 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index a65e827288..6dabb0662d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/99designs/keyring v1.2.1 // indirect github.com/BurntSushi/toml v1.2.0 - github.com/celestiaorg/celestia-app v0.9.0 + github.com/celestiaorg/celestia-app v0.10.0-rc1 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.11.0 github.com/celestiaorg/rsmt2d v0.7.0 diff --git a/go.sum b/go.sum index aaf6f2bcc1..13303467bc 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.9.0 h1:c5Vlx9ajCn2qne2NpAojblicFRGq4gnXKLGDoJMXOPA= -github.com/celestiaorg/celestia-app v0.9.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= +github.com/celestiaorg/celestia-app v0.10.0-rc1 h1:Uj7wapqGCTBJ49Ub6luoUGwVtFxbEs0n5eDpGD1j/S0= +github.com/celestiaorg/celestia-app v0.10.0-rc1/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index f8670bfc9a..ceea531dcb 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -131,9 +131,8 @@ func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { root = merkleColRoots[p.Index] } - shares := make([][]byte, len(merkleRowRoots)) - // verify that Merkle proofs correspond to particular shares. + shares := make([][]byte, len(merkleRowRoots)) for index, share := range p.Shares { if share == nil { continue @@ -147,19 +146,21 @@ func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { shares[index] = share.Share[ipld.NamespaceSize:] } + odsWidth := uint64(len(merkleRowRoots) / 2) codec := appconsts.DefaultCodec() + // rebuild a row or col. rebuiltShares, err := codec.Decode(shares) if err != nil { return err } - rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0 : len(shares)/2]) + rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0:odsWidth]) if err != nil { return err } - rebuiltShares = append(rebuiltShares, rebuiltExtendedShares...) + copy(rebuiltShares[odsWidth:], rebuiltExtendedShares) - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(len(shares)/2), uint(p.Index)) + tree := wrapper.NewErasuredNamespacedMerkleTree(odsWidth, uint(p.Index)) for _, share := range rebuiltShares { tree.Push(share) } diff --git a/share/eds/testdata/example-root.json b/share/eds/testdata/example-root.json index f61ebaca37..b687e3cbeb 100644 --- a/share/eds/testdata/example-root.json +++ b/share/eds/testdata/example-root.json @@ -1,22 +1,22 @@ { "row_roots": [ -"IZBw0fNjDzw8rVzYOmsmzL8gIn4iUU1SGf9UheuvEWQYHd/SaarVD7SysSeeQ2R5", -"anU++0fzoDSJpADRWwUyTvYwZoJDLX0Lj1eeQ01vaSBCfKuRNNa/hfeMNJ7Oj+Z4", -"i4P8AN2Ze9vlGn5t8YJPxupnI+5/qgTFnuH029QqSg8oUrku7OJkmPRB6WK0yQ5g", -"6tR+3zc/C9T/WerT48XBj3FpEbOf0UTqjFVUMNmLkGGz2KNw99IVc6SCIyTPfKPD", -"/////////////////////0FmCIqkffiCKDgIwJoPfmrz40xqMzfHhDGeB+yGuDSc", -"/////////////////////1+BZ04UftGUSBZU96AuDIZrrI96rXiRppcLNT1177Ab", -"/////////////////////+v0FFPDkWPoUKpCiI+nbautXgZNGYOKpXWdRRCYdtkO", -"/////////////////////4EHD3dEkvimWgxs7dqy7fZBc5FRiqG0fyT+Jy1XTIM5" +"A3I8MUKAXaEf7JDjUxllL5CGD0/uS8k2l3KpbN+u5fa3tGL1h9FSqZhCJFcZhzAT", +"IA74YwcC1tFMkQfD9sE2NO7hwDtjo37O1KiiptYPCgAQWQnv/G7bxu6mUJ2s3Ljk", +"ZskuRtu6YfK9pTqLAOsfLXbvjrvDD+80xoj/850Z4YGvmAaRH5vFXraZNWgEQNyr", +"w9Tz/Q7t/JfX7McKTWvNjhqi784y+nGCYXrQtu4jy90kx0irPJcVTXTQ7v6Xnjdf", +"/////////////////////1MCHZjf0ySoNlpt+h8Vb6QFm7BHYJXI14SMiNRgwA0K", +"/////////////////////2dOgrZG2ZT+KIrfjoPmTcGpqXdsdFZE6ZHcF0l+fL6i", +"/////////////////////7OJEeOYA6VsagglTnpIysAtyK3m3u4d8Zd66FuqX0+M", +"/////////////////////+hYZyJ10gx7Brp05g/MLtSV4z7SLLwGcMsXZI/Fgw0J" ], "column_roots": [ -"IZBw0fNjDzzq1H7fNz8L1P9OTm5AI5Kr3tnL39TH7nEr+bJzDbprD9S2tHOf/bij", -"Lyod2pBlyrb3P4+cgtvBzqVhQJScQ1mk0hc5LvcZ8GEsGb7OF/rU3wqO+MZZfUGF", -"Mi47oqOs+zD9/Te8h3jnjCRLC5bTfVfoCkpFNpx//xRpRb+tzbIckhjHHa1XpD21", -"PK1c2DprJsz/WerT48XBj3fCvURegK/zpJY3i7rXm0C6QyL/8tat3/XH2MwrzDTL", -"/////////////////////yobAPxEVP+c1M5rGpjtYmajcC4MWSFizs9s42xpYsnw", -"/////////////////////7Amq619RgUhO6MmHywcubh5wGk6+5rDwn82IzyeqBoH", -"/////////////////////xtzAjExf9hu56xbwREDQQvJ0t3kzPvZNJmchRWWu9Ib", -"/////////////////////91tDCFn/64TQorwMFtFfyjA51UM+aH9xJmA3XRqIls+" +"A3I8MUKAXaHD1PP9Du38l5n9/iXzaDzWlVrLnsRE5zp+rEeyH2A0is+czwU/aHUn", +"D7uVznpBCw/S2nVDrzgGx9Ditcfzy/cL2Zdn+MBV31zeOgzCv+zXA0QP3SQRWXlb", +"FQDYNIesVPTUn9q9Ybq0VqDgprQbzKsMGrX+7kTlnbdOu434iOmx4P+vZVGjXAsm", +"H+yQ41MZZS/X7McKTWvNjoZId6AiHkxnGRsmYKuh4EvQCy+eEj19Q6Z5eQoMGWZL", +"/////////////////////1wS3guBpJRGNMLQMkEwgVOuXrTG3gUwJwI+nybYxTRF", +"/////////////////////1Eg48mqxsQejtwUIJkwrjEYCPvDYCRYm+dPXVitBCjl", +"/////////////////////zDz519rhl2FfQp6S3hR887dRSi6zN+Qs4PwIFdRqbxP", +"/////////////////////zARSBQBFxvGNpLt8I1qtbBOyEfFOXD4V/beDdU1kXPm" ] } \ No newline at end of file diff --git a/share/eds/testdata/example.car b/share/eds/testdata/example.car index 34ddb3731e3acbe5777d88f2233d1879974ea564..487bca4653922f3b43ea1e6639470f051c7a2534 100644 GIT binary patch literal 55235 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%0ATxWzIMRCk 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 dFr7!jboDOM*g1Mj} literal 55235 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%0A-D6xZJxg#rc!1=2^rcQ_%)n*zmK0T?(> z$meVBK^2EW0l@ijhwWXI3Oy^mI<-&OpG2P~XLxHyf!c-^^hgV6!rYyFGz51DFh|@> zUm{^v#12#6o7h0}Lu+`Y%@I(nk$UfJ^NXqYu^UCuUAO1>sCBuv1A?JGta|IYvmwg) zrf=t6vU9zBkbQmVL(X=>7}5mjb$UzST))Z8Bt}5|5Lz9e6@mC~@cNYBrb8oRYHT^L zaQmzKTr4xUFlzoWsQwU}sby`P@rRTs=g?h?UQ`n(Y%zNj6DUB#q)}~IXd0&<(A2Q; z{TV;G9|}vXR?_8~HR62%6Y#bO^RFiUsR*+i1W~`e-$mS8*+8u9&6H4mPP-WD@F($7 z#be}KW(-7$XC{9{!k9Pdx=nT2*H8cAy2#lbS-U_nsF?zjOM@SpHg$MJhLMo6uIbg# zp8@*ALvhUc=ATbPWV>SVkcj6$^m61<|d&_IvB3E z$$Y+2Rj=O}K8*Cc%|T_oJ<2_wq4>qQ4q6dnysn>B_};}^l})W;2v(u3>vI#;P@Gi1 zr`vrC)I4ze8!8E>hxQ5TljK#bGF@HZ^X4GyUIg4;ZvJRU4D7R(iYCb$Q+;7)dGDAW zuAeYTeu&;t>VlD0&xJkka9cNuNT!zva+Qzxv_?g8TiGWeyiL)T`&S-zK5+Kvm!9Sw zjmyO!A8yj>r*w9LF3}^SD#U12x+i#PZJ#05#A}OY_d=kx^*6a7WU6^kyKt4!Pk<(` zVA5PMOdyI!_j@zN_Vb#f;+PN-=)@PijffaWR`qT5OpSZ&LV4EOh0&ZbZ1+;De2ME3RQ$;htl8sDZUuvLK7`j?1w{4pEyl!WWo74mD6{Qud#EcFh5fDOE7k_`UmEoXTeRKS_Kg4V`T$YWLs|H8maecgfiLL&J2W&MzMh-nvbETjq08MNlV~ zB5l^-qVCkS|5lIDPzgeA7hG-yjdq_LF0>4J2S6MIIv6a-BUap%B&Q`#>GYk&asJX3 zycXNi#^*Ch(;n<4T;T4~Wd{(aVqPirvCV9T(vK(tGJ@2D|GkG%oK;gxm}8au*}nIF z5F8!o5lnsXb#USpsJw7J15-t-zUMYWGbRkraysWzMo0l)D)2hiD@EqLeqn(jIL~Y@ z#iOmW(?8qM3K^7E_6%{=Y{{*Y{#AHW9g+KwK|d#jKRoHJfK?p<(r*`uhaIinq2LPR;#8IE z@mUv4lEE%^to+ISP=6Ws$uxi-q4GAs5egQul5adaQr7-WkErPPxzDTSU1F85 z?QmeniN=V7ne%chS9QIt8Aye!No+gFap*(IQ@tZ#?VTXbsB$B9zvN5019yGi9#eM` zV4ulalDHr8BD;7g=^=b~=~#<7u=W@IQzG(d}MC$C6S_@5oU z_~!mI%k>YLHYgiAdoyw4^*Nrrl5VAoUsM+N1^7I$$WOhA>h($F<+REoC0s{vcYlP; z_;n-}%Kb@quD8E?;83B_kJ6zg{+f`2-B6-=Sq=6fs-||V(L4T?5Kz56p@^c7GmU0 zl34rihFMlUy%t$Y2Gdav>lX-Ov(<1mc>1&uNx;Q~x$D>aWpieNd>NrPDBtj+_M3IL zJfdcP0t>dHaO}29LyAI4)5w}O4)Ks&4bGT=#TH@^64GD=WaOOpmuCwuxfF3zZfKRR zo~owyjG&@DB3J<=9)ua6LF4hb?L~+hf@GjGRVmdc;r#;A{M92H)e@|MnFFS2g7=R> zKc}BRia84KyQhA)))8*=!^5up;8);8^T4s!1bcG=eF8R=u zo$Shab?!TJ1mcxB7MXJ+#5x&WwtP}+T4YLPf&xn~T`3lB%ZBv9!R4a;$FI;_G);O* z@-i9Zv()klI+MU_BbbD8xA2Riv)&W?yeC^@ORzv_r8C_hrrCTEGbF z;J$;`XPJ4N>SGnX@FyzHSS&?0e=jJ|`*yBBDqZtpk6Uy|Q&}~tg5=ILCUk+Nogh}1 z4l|#>;+3# zJ_l*`2)49s{z@n9850TLji>5me-3xxV>**b523*cvb;{Px=7O5SnP)qa$$-yAQCV4 zt$Qb3)5OV*Oh(FlGrs|93+;HqqNUU^|6|b45%>=k_bXg-kx9C_$keN<<`M%NOn3*V ztmaLQXJkP<7qH5Iaz9L;w95$A7zfwC^iP(zt0-*2N<)ioaz3?^p5e62HET2>9rSozchFf3^c)0xri!hj<_T)FSZG4yXaZUC&ip ztqQTA?o_ZXVXYfDuBy6Zcq997V%yTTWkn#-buAKL!bY3Z4548K;>XHZ2GOLtlABQC zhEcMM=!=z~SM%F#(O0nao?nVHX7Z|IDvZRntl0BB%~XSb^~U!A0YjwAA4wC*XOS7i z|MLP@5_-AoI}LD9i=l2fQ}souNXMWX`cj>Vxy{TPEMTL?wx5o|^F7pEYpdrWE-{tH zjc=FV#|$R@CbE6-()LH8u*SX^h4P|>8mmdR!%D0|_@PT`3sKL&0Qb0dc@mFHILcfL zPSlZNBBPmq71;(ziVU9UOzF3@wvrp%+pas^d>?!bAA>nix~-lGo_4U%9e~ZrB$e&t zKL-8GLHyAC&Ey;Kdvyb`mpq}nfntvD6_BML){^SZ0OSsa*DLua_d|zYhRB+zD=OlR zT33?C>z^|nOarpwlENsO$%5gKf7bT$q-T-if^QA-iQd((8`Og)9w*PH!~D~m^kw@o z`kkf0>@4Bs=5adIee0L!!AK&@=xh6o`#~UmBP5Y1*wpZCS7MtWDl=8(^{8(atm!)D z&i5ySj(2*{)_vx9p(Y!BeU*qJ7WOQ}`8eC`C;Ztxo$S&LNq`}b7tD5%rVvv&Yf-%; zC9bc=gv8!P_y(PbB#8k-$66YBjyo#*qZCA->drQ}TA(vg6#$JH`w!$@A=cWB$#wP(}U9nH^-tkF@RzhD3D?&~2&n z%kR(c$0&e!gnW;{KPw`g_=fshLL?kL&{V_l{`}QAd$d<|IG49}WpsIQOGsMxb}^Fu}T@wo)eH4X$EE=;*b4^_LTrTeB6S zLXpV{uXm()IPj@3O#sxd+k&1*Jf1~$jZ=|yeDnPBN57^5D#e_EK0}D6$guSbEB~Id zNU`+Z>NgTMm|W@Lo|Es_DQz02fUHD>%G6SjRj*Swgs$pkMF8>*)nC&zBi{SF%|<@R z2W^zZI$&3Iq3kh`VU@JXgu{%{b3w8nrQj$~5ijkL6C^z;$llfbt9J#myOYRm#+|%; z@2{IwwY!lq9FPE)V*ONzljK&w9lhl8D;!#gD_ak2)}WJW@W!KIbYO2FB^htNlV*&C zLNjKvsR>wCn=gjwb8=ryLW{Nw|GXq@eoAY#d}&`HB*NN&4@%Qfc7|V>LrfW%*9w-_ zozonpizt7JbcsH?vRZ#KD#T%%Q$i;%Mq7%1Re zB_pb58?HL;FOtwTQhLMb08NIN-c)ve$|2y=LYWCXES z4}0;`*9Nhsc2G?4hB_P&ClUP8n2l|TX~73)YUb}*%xX1uCm*k5=D`hqU+o=tDP<4=4#KT$3VTK$or zP?s}prr@N1@Sgu~f^7(G-bZDg8tOb)n%#OG5i{6cmJc2cos_Lt;2WSQ>JZeENk?b@ zCu8trS0uT&^+YNkuyF;543u*VXo$CN$Eb;iJS+4|@j7YX&H4ROoDjm7pdC|!mB}Y= zZ3*m*TDj|UvG83rUhC{xNf}(xi1YJO=$tuwuc=wvwr@Hk&Yr8=ka}YS?VQ|GFIZDc z%A|BQlsogwCB89+PJ)VZ3kj;$NUjRF$GNp@hi|l9z7eHZXnxI|8yR7*FbH~u8GiW! zh8QSA6taye<+xJBn{j_sh-TA(`=uEQr3VSpRE*OYZssF&u{&Wrez_&k3;6*sTENPh z&!<3b)H9yVbf@OFnMzqUA5U{HHS~uZ|?s8ly zg06jU_Y2Sb#_yupaTv#wqYlPezoNr%qti0bLUtj#oq9$V98~6ddT3dngkBxOW)ple z3WNJHL)4T*G)^C1A$S0Z{6PtxW=p2Y)R%Z#s{w!tk`9)CH>mX-r$I-q{MF*Xhtnf> z$mA%iPed6~?yFe;*$YOIP~^uT$q#lxXC}P(WzG)oGl0*^MB2gxgw!Z!yhVeum!Khq z@L{`Mm%PUq%CJQP5Dk!wuq{JHWqp(w;4(OdRQNuwsn2LSUt<7wN6$;U*&Y2y0^`%y zn3@ZgjT^qMUJCD;bXg;SlxP0PoBVv=tS88+L`H-H++Krvt&Jie{SJQc@B?i$(jAk! z&nFGJW`V~e^tw*dzAu$9h*Vb(U0fqO2sCb-Rh*mBWA$qh2+tpuU+G=A&0n$>gm#MwquKq7~F0btCZFSLT$MidW`tD{4kJ0nB#> zptkNfk%^NMUvlLQDD-(X$4t11?3ZJLO*o}y*&<2$$JGwJj!^1n@VwfFm?ApxiI306 zOc~Mu!~#Rs7BCCJ>z&oZHuuiy#!ugK=cZpRbvLbE^eKhzjJiA?aeWgMTe36PvrbhO< zYHXHDTb{n~hB)_D{@tJ{5!e)MKmoh2=x4s-sw0;ULa6<7#@Qe^>@8`CBGb+vgZekA z>u|Fd2ys|BS=RjB)HbLWXmXR|*!cL9S&!j@WzV2E3deDKkl3XBT;$xHh*Mz&JZ|Ot z8-R~Wr1C9TbtPW({iST<=3@_pBm%v<4&4mW%jd(=C*%N8UUx>;iYRGPMSTVF&&{K{ zSu?5BuDyU^rvtGMck%CLi11*g=p4y#8nMPp{Age$3kDhu~sAkA;>pjYQRj9Cak5?At2;z(Wlp3j~5 zi+JVt&V$tp+9om)&%%9GXK}*Aw4OvQQd?oyXC~cFAXxP{xFnW#Og53@F95~05YUvQ zO)Cbdj%B{qyaO2u5UHEjb4JJgrsS=V!DQ9q++jhzJ}+|1<-zG}-;z}N!l!So^<+X) zf=Gke@fpKy*EWg(X8VK8;U>Dph(ry#A)9&^EHzK~+L6kpd@t0)bZsPdxlNJLLRE(I z%LOF@%(i>XFN-Z{X1HANXsH{@DffY_HHQ3|X;Eqy+!&1Lf^32Sbym(27B>raS`IUv zPlp9!{-qK(=Naflg)#m2L3*OGbJgP)Bk_uq7H!+y7uQDvwVaB#d{X-C{%*K-}3{Z zRR`0>S!)WXcaj=d-3kp)1z^caD$vjyvv$-1 zSQ51dMv0jq5LVBTfsq-L%8j zX?<^i9mQ7~WSbk{@oPYZ(9XUx7ZaK(>BRV zHOWIg{MoNHRhmBioWrMLZQCT7NzmW|M~)UpjnuoA$Hyu5kyW=4OI2nS<`rJnu6|G) zv#ym43?$=4ygpzd_jDQQ8QizuL=cCj;;(*x6)eg|tIU&({@SVBf3q0m#M+xnb~DL{ zoDCV&*ay6t^pgTT1GO=4nDO@hyFn2$iH`VU(ZQtEr46^R(^y}Vk{ib!Zi!gf>J6asr4THris8bfux&aUw2kY_SICq ztCJj$erv{H%DQY*(T~+JWvBv86U#+KOcz1$msYtdHZWRPo|7sf4j!%8;vB}zpy(Jj zbfG>lkqeTNPCvMsD-tOpC9*g9DrcdCuCD~K!yr;GhMbVWq3n zdulC^(&_=D`;AbVYh_$#tU&UvbfWU{SI-KTKhPau^g(Aa%Ac3aS$@}IOhA5Alwp5I zfz!=Mq`_5z+jk(~LSl^q##ehp0+Z79Ya2d0-jS&IoD%J;=p)IjC0-*uhmzM@?K^Cq z`VL0$h=7iueUO5eYzE%#?mBk~2E^Y*a1?e*2^!Xocq#*u-y5T3p7s~M4{C}Lo_BDa zT3tL&&u}x(jKrPb&6PgA<}>(xOuFm`$I@(}M~y%3Q-GO%45xXNO9pAGqvWdgH3j>1e*oNPTp)(gukAsoz zD`FW9`JX}lUykUeg^7HH%S+fUVw#c3*2bd;ZsFawBPq#(xpe+DsW(xsIQp$sIowJ` z49YQRn%qPKLnNAUj_g_~ZE<5fmZ%104Ien02b{;sQzxjVJ&KWaE&vt)ZBEG@XZz1|Q?}k3j+FLw&tb1r$2;>+DUKa|ir+ z*{$=I(Yh9i@Cn{DCj-z5(*iyK(W>ZJNO#V8OHSUD;cAq{uC~cLx6qt~2+C8S4WM(m zoRT$KBHA3&K>_-Kq*CX`8iS(<;^-MGa{$N|eBE#vy}8#zr`hOH^A1aB<&cY;Ut79y zggoyYA+^}Jz)uh&;&fdt4*F6u=a?K)G9K>-&OXRYidNUN))Z;Ggc%T7!csGKL(7dtjw1Vw<>v{{I8lu~XPKx{`_EUS_99!#8B%lFO_jBq_+E0RY+QLAD=U~>=;hY-~!O7wES_vj^(7a z&^SKsby)>Fdz?>|_nIm<_Pyoqu@J5|teORRvaKYI*@ioP^|d49Hbw+K#@XPl;)qU` zI3A0*JTxF&wDr8+3;>PGWx0+s>)IuJcHkaMu%6FP4?*nbM5MF=j$DM&izIR3d}ax| zuYBGGNcH@^1E%@#Vb}{+w=R)3j8PgGCAr+PZ5Ccwp{729hZ9|Y<&-BXEOQ;rmsH9ew zT1$&q#J0>wX|!hmU!TJTA_>c-GAwFi{f`*|bIm7LVbDgD!g^|WD3bl6=Dc3b2LKGi z8dLdv1((d)Y@iT}F^G9NQz-2M%t+AQBdlXtH;;H}&VmWSa!(gJ{G<8hSEHUiB^BxyQ*By{8ZYC?qUy)|i` zPEUv3BLqcXUXQBiZwj8D2?|=`>@n~2vRPoVY9cBhs>K#m0ZROgyBh6&x__}cR@zO$ zEWYDfa?%u;#D_lXMj3>O`u1I+k&X7;%^S;T`sh3D zs!}HUJa=f;KgY72$ZWcj-`q)7h>XvRJEIeuF$wd{0()vp6W`e`W80xQyjff=DbDtz zuDk9d96_QmPe?K|)Ss3#(ER?s@G_3di)p8*&SqhCg{EM)vXh?OsaWtq{7OWi>@p#k z$3g5tUEd-P&i0<36M0eqI5f!X%jzM>_5Y}radk~VhYT;=@v%nnue_uksZf@8!Q@G(W zUS=QDLusfE3%(J$$2{8P;WvVt$)%^z1>%2&qr0XDD(C3FEisHbK`2t^`iT3`TbkQu zsrT=i(Yo4noqiQVSc;VU3N1sAZVl!$YvG)t5oQ4uC>zffJYQP}*xUL&Gi6lw-ac9$ zA?Vo9cwXUXPN=oS_3K#FB36V#H*b#1X4nqGt-e@(#B;jtcWugYs1)vRn4BBJb9|>S z@Dnq99uMICU9xKkb$|D@a86JHHEFfJwzzL*!nUR;9{M zIJtV&G9eD+(TZUYTWtzIsuEMzh!<}Oh$HquF2tL*Qy#p22s-K^!x>~S(TEbXy~-N( zmvYltj|hCUTmNnlWhkqZq4OeVp316ePryL9&kSZ&H1WWvDOWOy=0fo2 zk3qvK&$^-GJv{|?wP`|@;V0cRYTr-f(B0m(0AvJwlh9=&R{}K+@oaiZp7=X7G}?i(ef z=F!t^2?EX?&t6w;4@PAP){9|Q9B*$>@T2=;SEo6y+UKBNv}Xq1TC2ZwPpj^e!>S-~ z&0V3_S*^}Sog8@+dvxPg2;}36Zvn9cAMGC=NC2$NlM=qI>s)^=Q@XOttFw6pP(Jap z67iNkEBIocoKovMQU_tG(N~vtaCEZlb8H!HK=p(n|5;IL7U?Q2ss$@Ml|?kqf)}}~ zp1gYN`t|*r>kE3=g>eL7KJ@GwyfC&=6K`9l6*L472Rz*tT{3c*F{Nf7sUeZd{rilnH5(Vg-+Np6>CG-K;+SSWEH0 z=jUoe;uMFK4_n{^s8XHo>1N>=w<5kc>b(7EVBJ2E;mAwhQV4ER3AL#eo0z&2?qMVz zHNF1F13dsyL1%R-mwl2AMY23Gb65aDh`=xPTn&$FLw3yedRW^aVYTTq1p03OZcwHL zoP}8Rjpg7Ok2WU1`?jX-w4srXr*OYvY49=hjP#E|B=JDa9FohBQ zmbEIape-4MD@9;Q?!sZ)dzjyA?#r)Pc2Ry04OJ#1%C?mcFV0juBNi#$r8^c+_UKV) z(-}mhv*7lXh8DyTnOBncY+IRvfPSA<3@oz}QQLby%a}OrJ@D?2blFS|zIbKU=`sbt zB{q=`*#_)DSXBMOtn>A?CvqOX_Q@u437|qGBNO$T+f3|sjNsZb%Th0V`l7mc7&miL zqA;cb%tD?qo`P)jT4MVbFyh3ANe|CLiermqZ?V&H`?`*~TW|mJFE>E;6fq>=JEM7q znrkp=(Ans;x-^}%4a0V>T%_cipoTmOU?Y&kg zCLaIEcFZ7XW$|xDwSotVd8n^>>XLTzN<6Gj5G2m zv^BsSOKF(bjChXvDsX;cX}x?`@f)z*=QW$IAl(z;ogDpRU#ZjiXEwPa(PhhA?viSm zu6|+`lW4Eolk#lsk!l9u;J{ckx0!|pBdF+2*A{+k0vvTX>EDV6_Xd==SiBdP@Ry%0 zMOGtb@4$9SI$h6|YG1Q1rDh~?VYeS_?kF8A@BT1ZV{{k4sR){1T`t)Zl@{@tLzh>NLb`kIV}?(d3z-8UXG77GS5?O2cDZuIu zfiAufJLM|81_T$obvQw=VyX`S2pRDF&WjLsh)+~dew`t|_Z(!Sw?1%YGh(`;-Iede zV$tQgCUg8I8MAs5s?!>L`2AsUX+Kt&PZ~&FPqWT4_pB@x`NI0g1>8#@1W~<>?xjn} z*mxZETEvTwzD12++}VDK8T=UV8bi@zWj1Buaq(ojWuSu}WXQkYBfIhr{@L2&SLp5* z*&qgO=X63TeM=wh)mMS2jx|KbQa8TWjW)OrQ(K0>sqC18R0G4ZCpGJT#>b2P+*)vj zf8&?Y+WiUJ_oHqXx@63-0wy6?@Y+?qu!mN(;C++pupTId6fB<-YI7nqw4Jv@k!zbP zLKkgIRcZ6#HawLRGw_$IXcgzr4*~yD-+?#>&D8SX?%{z6hI!cuj7q{GEhGjb80H(n zGEIoI1&Y?p7|;7Ihuqy?KMD;XsK8ad+@QkCgj|$6*yM)wgbzUBUK#gxs+l+@r}K+# zo(!^(Jp=M*`HjFQCFj`zz?}~sNwKmLZ~5g%JZ9*BZ6g3WH0OTop&Dnn>u~>}Da&>+5v5cV-K&Ym zU9S)KD6PIz-8)Z!=4dlC`16mV*Ko@mujIfpIzI*=L0LGjLLuojk~lh6U7 za}oydaA;791v~tsZ8w<;zJbrg^)dDr3Jmu`XV{=7ePCqHX?0a34~@8=>hg1i%YO#e z;VJ9%_DPT`0XXky|{poj;f!Wc@#@a}5{GuFq+?y>w^UAMp<2eBI9b`0dkW$Wl z#|t|X^9L5gc^m;KYZOolpsyhDB!kj+qTVV z+jdXewr!i!wr$(CZR^fv{&P3)x5`bPQ=6wED=Q)+N%fMT^}7!^)RU9Z<-aV6U{=Ot z*u0l*;b05sa~L-iF@uBMw6KY~EqRbFIxIZb-~U?>hUg@IJrfwN6{b`D4xb6;Qx@6b z6`yf0D(#B8IAlBZKS4!plif@j$ctWZyhOQhAxf9PHo-Slgcc{di%(2WC<08O*`(UF zv^6}aXnwNd17xf;krL~w)P33YXQ%#FBA*1mOLmhdYO4K|=UE9Mt=l0wgl4E%l($f) zQJ}m$FZKr$vgt(0Wfr~`0Fy<~m}skmEf{j;6lf>yKX~v(8Z}!RgV_X1t7e<>TG1YJ z+RmkBC#}KJzh}t9E_+&eT9=8Ohbc=HODc{(Dxue@9yi&CxZiRoQQKDaYSvk1GhVW& zzr1)t#}EzIT!EBFjM>2lUA_aFkQ3cU%RUxqPn7kc^nqRA;HjY_M6y2M z=HJb^^wf`7pPQnY4zVS4IN6a4lVOcH7@%In&k{I^t2*#2Gdef>YW4Z()a=U@M*ddA z!efukJbV@QWDtDJQXcNLKqt$0EOcGI&RbxwsD;gtJ3%7em>F^Vi3#vv+#otf5O8P8 z)0KYR@gx~lYJcLDCQ@PFd&|OS#cC7yF4q z-Nb+m`*`I=x)b_nCuToXdy~X|GG%1nZq-tPG-79vQM%X_twosi=lSEnxW%|;O#j3N z-nP>`JZJgef(nC_A)te(fj$$kZ|LTYsT1+vhGApQ)_Jqf(>;~+PX7tA$f;J$la!w* zrl(t7^8c7)PE_=|ku$_}arjsu)0{5j<4+0@GU={Z+?=DS`eY7 zr~;Q`E?DO)xk~88Y2pf757+ROTg_h<3a$8Is+xy!+{`@LOwVND^kfj2{(+9r6hYR< z0X&qK#ZiaHNJ6v6+%gMSoAbwykodxz&Xl59KN-@(HZZ7x#3OYBPqi5=a?jCUEa9W@upNafVmYpi#`4`ncac*VA_=0@DMCiYaDOt zZQ>i5ddxNP1^Fvfe~9(azNbpqV>bO{$nUiNdR)6udN7j+5#4oIXQ_eRy((0O?(pp@ znFMwdyA2D((Rc+E-s-77ux>pXnNI*7Q=qQrfKP|T3Eusj0_}y6qsfaY#?l|%+ijFt zd```N%Fn@@Is-vEzpo6?bUlS)IzG_>pdgmxG?|Ft5q41?E_e3AAF98E z5VC$Z*UTz-Vss0CSJG*)^RKSJdo=)_@Ubez)kG`Cq_t-{{I{TfFUBHK9#3buvez8% zc{@+|(B{K_TLMBio+|8z3p@9Jf(i!mFE(=f>G>Jtyybkq@@7=o$8oCku9JD%c#U)U z9s61mZFK2Vd9AD!h5m5lq+sn=>9pkBLUH4QqW8hMDzIKX**V4YX;c*4`$j z%p1D}lyJbo5>&5WYRYxJhKTVNXb0WyX9`$gbt@=)aFE5^oXxq9$%vTub^Y@O*2A5=OhyD#Nz z4PwiSOa7itM91^dN+!tH)}bjGLB6FhNFs{`=K@k&gSNk-7@as}n1nm|?` z&$D@Zcpn%BJrqsZ*Wl{0(8d&e+yFkKCLdCF0lkr{Uw0-fO9TTMq=j?Ih%r!+O;t#R zEDwe?U|L;F55V5rb-mga2Z0FR`zR-ZNOy09;ASoUHA2;2h$g-P zf}@&W@GP+iRZEw;G+3}jhgMVMTN^HUm!_Tp9AJP0(t7xA^I*sf?{NUNZ)$jW=? zVoAm8TAgcgV2F5krv~0Llc@mCdzB0U>>6^BG0ND$JR8B7 zT=IO;%7{12?1)rLMfcx=rqW{psb1Tu8|5QQb3)T*o~0G((2E(RDxFI*$oV2;jVh?%cWi3Ptd9}nrPzLd%JoT}?exUILT7l>RiOSwwVL%)B@rX>UnBsm+GR>e);1?|V5^uA7J^T}rh2Nn1QFr7X zdz_HdYwBd{hU0uz;DqYgER}Y7U+3Z85)9V71VV8Q_WITJ@6w2O`Q9wnd0y+& z20X4mWVp;2P>5M0!1Xhf^=7`%Uinw;WKig8*hV@@;B8i)#roBU|Cm zPLavG%62C+XRF0_y_f+9K9)5rc+<94Bis&(<{F-9t@xPU9wx4ikX3yEZYdu?_n@29 z!flOfM1XoIsG$Vk&=&f-#k!P)Bo>BgVKn-?KP4aUEV*edYN;`_B5;SwX^ zFP|(3x)7ukiYB=L2P^A2JB0(o_N={I&iBqzjbLa*$Z~yQSn~{d0Xqb{eMeh}zm#cE zd+M}iHt)h&13;1b`O09GJyXT0IW>t51u0_GyII(|b0Padxh@6Mqa{w_r4Kg5ajHdj>7M*WFPht z8wzJf=q>RUeMDPn3w0rHFSNiNaezShl!2olfxb^mxH>Zw6@+c-@4`6)9y5N}oTI*Q zHx2ANWoB`dCCK4}G9a&~Lxlp1DxtRX_VW7$)$NGD?cm)?_Rw)P*bPdQbvQu^g2?XZ?KX46~<2VvM9Ly<<=22n&_i%9OB_ZNcC~276EeFP!lbFX94W zg~+LMHzY|W5i8nwre8n9!;w}JY3VOw`x?sNJDuiiUOC7W1{J}0&EIZt{Z57xE2dpx zJ2TGIvGV5Q;bC(Qgu$`6Haje7Tx>MBXd7}b74{!HyDfaGRFe5WX1};E?AAWevP9lqRdg*(wxUKHXk8`!^ z#ZsTtB9C?BQh;ecSe3t4g0p@Qae_+xtQJI1H*OWy{r)6^9U9>zTQ;U6)0Y*4p64D| z8_iEy!`^X6HkCzO4b-kC#9upBGxlxyydn1dgDz#nXNCWvt0u+~5$-8_5VE{qHu~R! ztlSYW?DvC7Mwu9$lac7UhKlBYX$l%RE2n`u`js9k{}V(>UD2iHpuLbFd8@y(J<^fE zS+`%qzXzS#2Uy4Ow=S@lY4zhG3_Cgd6xd68p_d-av6qk59Be8Apt*rp1Q{7Rsk)BwQWxsQ z*mJ&A5%-BV^e2`WX{e%YMx#eQ5Aon`h*-dZx+IV=+W4nDV*MVPki>}juv6YMa9$qu z2B5H{ZRUz=b;mnJ0}(U##$B|@J7&xuqD>0ZlAQB+l>Eh|PF2)omdUQrKrw!V(J4-A z{Xi~=Km{0=AwKTwjQdVP90!eItr0>|KQLY{c-aWv(iyzmzwK995(F`~%ML({6o{sS zkBY+m4u&^}@d%v!SFAs})s2?-Nqspnnh^84Setp0iPPbg3i=@y?4rKf)UbqFnO4TG zALu1D_9uYS=q=GXwLve9d59%nO4+Ct==%pXlj80;wAlJJHVEZ)Zm8W}XepjFN_Bub zV6@9Ah%veVRlzv1{Z(sIHf_*S)rFXTSdip@3rbqu!zyL;B8L@h)xchY;2iSM-VKDd zyRlU#bIy!snEWTG-*R{|)Vv3aQ2krqTzfI2y}mzj!#4uY3jLcn-PGKGIY~H1vr>ku zX#q$x#CEa56|49qc%ZJJ{tp~ktnnNPiYDPVTkLOa<_F2*T>lN!OcD>m%wxKDm@%A~ zRS^|vtg8YCoRM))SX4{ArSf3=e#h#lGU5RssRe(dV zz>6Iphblnx3qy0r(3sdy(Yh)DHm{IB-JqN<-nNVa%~ECHPm~eyM9!n-ap3*}Uwc~B z;wt!m1g)fP(t&&|{Ud#~4ch!O>Hqeeol;#N35ouYP`d5-Q~MHi>1d_1cqT{2CH;p=2Rb#qUAa+K`+?qqCOdCtu zq;TX6$<^a94#T2{VALb@4OCu`LSggRDrz_DeGONlq6C(B^s_&F(SWmO_V>}M1sjMqKkd}Q1$oxq{>csFhC!$MgQ;DE{UWLn9xJ$$^Hdwu)# z-)|NC4?(&j4#Sr>tZFHsq!qr@nhU{dxud&=UY?L?A%Y?K4MqP1on7?GxhD_6xPUL| z|KV0T*dlpw4TYCFP8ep1L{>bGD6Va+WPf#cV-Aw0Dg8Td&)-x;@4Ggq|+6a zIPpnRlas-jG=raH%r6`6kR3(IFMw z(BnGXp*#85M}2CB6gx+y86=}7NcJ`hIl>+;v5P5`S<7E{ZC~&iBHafxn;j8ZRf{jW*aXqbrAOByw}Wn@wNOX-PJW&m z(iiv!m1{15K6670jTBWooF?rBipRNbWqyNZt*tl)q?*#|3J{{&@-K{a&%Kqtyrt&68Bacqt@!|#%3@>MZSPl_Og2jk)I z%y(K~8w=C>;a=k^w`e9iE;ZY67$uTa`+7|dmc*eG1i|aR45)^wc>+`?WHx+A=q{%v zJ8uLVfR$Z3J)5jUfS<=lSNZo1yz3YTa)pY}3(qU9F|$i{K;tKtLa8NaP{lJ&>eiyP zDOsrf@iZlO)})CYO{4JVF=jv=;vc`#EQjRK$hhpL8}VTgo;EVx z)siRE{Pz1}APD!r1r;uQ75R>P3t=(8cX3}mA*c*~n_i3xDf;CKiM92d4*U~@__Hq# zY#2yHE+D985U@FL(;&hw)ac{Q3tKPMrGWj~!?)EwnZr!~R3k{c#rvE(64gK@g^O)q z{fJS#7TI>tcP0Pr-44XxBzsvDSPHIC`@WZiO@6u;SuiN1swv^5vO3zpx2TV~3{GoR zVcf*kaX4z%i^$LIs=X{(slOOf2p`!kb&Tv6XQ9t+OuS!QaRfUsZO&p}z-M_GfLOkM z{kKDFy;OzaRwyA*%8o0rVJCDe{8w47Z0MFm$mkP{J2ukG;y$3~Z-KiPzfbBj=W>di zgaao$U93o~9LxwOP4-Qf6wKoVmYyKZ60d(I;GN9nCy}EcYOn1VUpSy6U4#5)H3w3 zUtqEKHs6&muxxD?=eon^Rh=Tc-Mv}+)coLCF5mIRz^IibXRc%%n>Ih1{grOu|Xh8H5!16H7z1MSkb-yI#&+cEi;Gu10 zWa17OJCd26*_Up%Ih3HuRlvwzH7@uW?$^D^>P8t&&1?q%nx~ZC`fovaQREcP+R2tg zcS^4G6{P6J+a__s2bz0X(sAn_IF+>j1f9%Wnm1p=Y*`<}XyG;Dm_$OGr6h__b*HSi z-v>VTdyneFSni7U5MZQ9)DIH+glNpcWkGDLvQ9eg7&9y!Fx9pRrF~z!l-ne@k>HuY zZ1Pt7|2#Czv1WwGWkRYh!LWF{!)`?S8e7OJSz?R%FIrAY`jN(d-bV_+BeAEi6Zg(VXz421 zjN1`|VlX)JEjx0RHsco)dnDgPABsbe9BcC|aJPww9$($8V|N#U$!Q7TNAIv%aZApz zeB!_Ih8rr(SVSsDx$Ti^IF)fgT_q#Ru_mvrK;n5nVB%V}lwxCE0dqEaSg<*?$zk&t z#1X_wJIf{2Y*p2*2~=h*mHNGsa1U^M?uqUmXLc8h9QcJSIt1;n`m#HZg=ImV!%ter zXh45|@o3wT+9VmQRj35a-p#MTBNL^rZ2Km~QFDoTz>AW~R25flepf7Dl$%E}U}*D3 zE5S@N5PLao8yU$3>CQrqI%rYgULK}llSxcy-|JgYM#8px0Fyr2$ zFc~6iS;whdD#Yg|? z|154r{WmYKN&XwZ1XJTsp3c{>T_8ypUtlRL#xXBJ1Iry_9~Q<^tl*e)eR8uSotG77 zm7;4!Fs_CBu1Oq0Bky<;%LnOmVUzP{v#8Rj%2p}&Sz1%BFjW0x!tCuhq^T4`>s|a1Ke~a32aczL<2fn*}kJe8x z&+zCH-8=ar-`S5(Yu!N#w`EQ3&TeZAEEm+rNugSTJ3;mfdRx8zOvJ2s&_8=yh{*5@ z%10Ea0FrEvPSmUkwk7Y1h*)^x&0BkNx8bOU!WhpFHp8p;81CqZf+HrIb7z+zK@(QW zJG9bmX=5T&7e*09DEp2mvOr4G*}6N9IA+it%kBOGHuOE3?jTnQK0I8eS2wP@J!Fl2eqz)D)2UZkm z?+xf0dMd7s7Xgi2zq&%JIr^;w!oEpT$5JuivoV~AE1DbUsk$N6^Mv8CViECD!lZbd z;9w4`3^I}e)i26Clup$~u9HxVe$wkp<_Qpc!*VROF<&6X$EbB-JhkO0K5xlxp_R_7 zkL7P#3%t&BoyIPXLOf!ql=uPc?nEpOv?EYTBD{`3B+^11U=Raj^-gbCqj6GmI5fBa zjzPz{p?K;+i7#ie9L%S7N&eK zrR1D1b2qfxzw^)Rt$o&cpgE`;G2xSRVzlVZ)8=GmicM)v9wp&g7OO8nOKbs3J_Mv} zJIG-=(v}3LmE){W=7FHDAmPlp>(#@DOBnn*b357UGhqv%2}19ta&v3WEMu;vvBpFK z^@c|}l6Q5UV;|HNKL9D{*e|IFFM|*9sT6+HJB_oXU(^@AGytaM^+0lYIds%MOAf&L zHtvCX3cF_<=J`Yi+!-fM_!Bt@^JeB}#!bzRL@W>&Q++_;ipzp7_%7I*VBd#ehA z?(zX{^R;W*K_}R@^2;BcL`0`41=QMOS1T>P^&WCt-6nQOqTiRIqp485o?x@zFGV`V_2MS1?evwNq-=`AK~MMCKAX%`&#z6vrP5xkte8P; z*vor0&@h7>*6W!AXc&ca-ad|OZ^FTZmX>lFGiXtAJ&skL&jsw9D=~nVz zc9+3P+nmmxoiq8fDT%cWvtHg_fVad7JXnW;ok!h-xp6(INxNCEqx2S}THJkPgjYtX zsVA!YZj)(??PKsx4B-md2S@k&=}wOeq4w|dQt&=lZBW>J$GE)$C*g)2d8McXNH~uL zep`0w0t_#a^4V^*O%mIh*IeI)BGLk!`hZG(8e+-s2>se&AG5#0Ko24+1d(LM+;E@bdJ-SDX=)jK6j@m6@^z?tHPqH_{Ybs?OctH3j{F> zJE}PAasRJ^vJKkGxAD}if_}TI?8oddI(#rKx=K#wV3_r^6%&5Cy)!_Bla>$_R?bBw z2%*&TkCMb#MHZDv;uEe#uT^`dc%kT(qeLylzvd0=9tY7U3wMKGleqOR$vh5u$0J?mcWd_7X439yJzU$!#W36 zpQJtC&qVa8bVo*#!8c$z?^WZF9b$2oG*8yt;fnN+R6qm}Ss$8boxH5h_)%TA0(18k zij~bNVylM+g^0W_Zk^+y$t7k%yVM^G{?c9vIF>@BHUGs%_gOc(x9WWh02{5XH0 zN@Sx<`rXpuL;`bO4se^xga!uZaA$dYULQOMx*oQxNUt(9qk(>K++}f{RLmJB_lJEi zceqohn-#l~aVYuw@b!8kSbsRAn`PMWqdD3bI2InErHv~7M}3ej{d4Kfar@;R3ybe> z|NY(?fSg!fee6QHu{Z0ZMaBQC4}w5~JjX1??-tEEK4}oWc20myq zk5tFOGO6|j+*tH~+{+yTjkAq{lZ$EWV#_YJ^U3L<+bORw+rwzA{ip*A7`m`FyzSeNvALo%5=99`8nKGsn*Ad zE9$CJRhy8|5xBh|n?TGA#@owp%v*_3lD);%*(ByJIfstD1{w59wyHz;{vF^vLI(?< zLSn-$0Bp;Pd^0!)54y#Z;)GDbmSdrfbnCXF?cQ}xcMcvDrKDNmwJ)Z5gZ{?4OoBQA zFf&QU5(HHDc+6aJ|%E==-DwO)F7v z>s`peTtT0+K2;imhKGj>jcg)zkR-we1CGcPNx#%8HE!+76Ux)Vi=UL2n{w}%)b)9D z?$X}TvO(0)#(!}xh|P}W`g?J?*$p+uNv0U)8Kdum{_!Ha);(#zdHww-RjClL zj?$bj!(f-FFWjbx2EpgMoW)XPe+rpK(@I+QsitM9#jFp-%)pgzryzls4+F|OA~=&< zfBE|nWUrOwP;NfB6w_q_+iX8P+u(S_+n1?Ya4w~|~0q7V9Pe4K@BTigV z``3OJPl6rTB0q!i)O{9Hv&74oVa@cRIj%M~(lgLV7=$a!X_Rn@g>`N~FQLLZIRp=T z8Rp)WW08Xx1ca{|U?<|tk%%e4@HL$7(v!paF_TQST zl+5cKoZpJ$+2exxI8SVUjL}dWC1;F^ZXwQ!aD#xuwiwOVxq3k;Jt{7{H<07%gEJyFi%2L3-hEi8hh94u{IbbD9$ssJH8d3QHBM zG?^MTGwi67%@ssJfAl3o)l~d25r`V<&a?bYMQa2*ZJx)@){zK##BkV{|3_vuMVK0S z_f6&*W)E`Mj$qqUDCZc5>Ez`KN#N?S!yU*2=?+fodJaqsv^xX9e(J zh)%UBkw9Jqtj=SS3w@U4RRbQFMkeEB>ie_vrKp z@{*tmQ6TANSbmcmMGqv)QlBxJ$pHAx#0SdmwOBj)g8Tl|lJ-mmGg+=kW|81pefEf^ z|79VT*Y?Ct#@tLXV70pWEZ7&5P7pCh^g^Ev+~3;8ZCxmsbb{JHbJbbtla8lua@qO% z_Y~uEryaK*Gh^>Odj#o%;W}s=)7v^QxE^~tni_MP#iExvqwJwv|3pwSkJVseU0C;e zII5$JzZ0Hc<%8o`4aIA`4QL8O8`hM=pf=&rLv+Gp1wKLNPo)p%os~7*<&^L>o<2V~ zUR_->^^o`%fJ|b{X!3MzNQ@Fm)71ZWGt0l2@^7pO!O9QXh-!#f2KU|NoFM3e+^QF< z=eeLaEqR#tOx*l8@5uI{c#**f|NEm#=K)Y^g*u5VL+sn$snFeZ(c`qJP-ZLZSBlP! zs~%-riz7k?o%hhj$uo<{V=(BCqs22>3Y1n`0S7w+F zCOfJ6IjFX;DV2YJ_yS2BkH?7So1f;CCc6Oi{t`1P){b9ZlYta|5HqNi zrhaddS|m>5!X(jyC^N@$`DlXNR~x?0BJpCtRvJzD}cN!E1$2rRDQ$JLNU`3Gqwe&>#w|o}n#S)(m6S zlr1ue%@~&Y_q2>ZEm^Mh@;N@;Tz+T<<2YAVfL=Z&#QNnPqQAoEi55_An#-Mm#l;6g z(AX>(VM8x_`b{yJh;F4?J!$7&pV?3Mh}Q7q)+mHI8PwB+0Z;Wo-{&%}KX_kK5}m1^ z&eRVUx%RuUCjLy%1~@u(dO7I=0|gPiC44z{?R#b_Xzt={(QF(>X=YmyU#JyFKvvst zl+=~{aPUD7y6zZl73XfvJQ{{j73JT3XEsUW(SQM{|1#f_WkQcuABP>zz$&bIK{!8M5y6lo&I+ip zE4Y*0_u&Fa2{HLJP6WBfQt-GrJ6#zs=jJjoOBvm^kihRDDmg#0X{qL`)1{?xMJrnC z4+$h7((pGBun|6;C)Bg}$4wBsM@$ecP`_m>6f;S+WSPtk0Y%x3fw0r0epEd61$=|= z?Zx)?LuGw5F#*>t&&%1zA!ly1h;FfW4Ur{HT*Fn+zc=;)vAz8=3xFy8<%7Kp{=Kz~ zc0M_9?R_8I`ZsY-HxpEP8yB+&-?EnX2BzLaT@{ovW_TTnl$1G# zjk9R?coc9EYUD1&2W>RD3D`PBjoGt8U*KhPk<jWE}Qrwr5&(0 zq3-ekG%uzPEZV`eeDOmACEsgzfrShVNb65)94#(!^JaA;M-lZWC16(+?u)Z<+JoG? z|8M_$mI+N_g?qEK;KXdy5rJJn;61z=QT)~Sn%uYfUtAsIi~lW%vCo%>mGd*ZDK%we zeH+=DznL$|y0bHITHqD9jpIt~pCGO_a+vORxLT#kIW}THD?GkIPq`qE177jc@&pgS zeD97yl|Na|Xp%|qvd7eZ7&>I%9`RUTD=@+lmQW^{6 z!|=ZgJBspaiM1RuXg${@ZmD8SfW zZwoOi#Svl}B^0S5=55;(^hpnGoc+3Y%pUJgW|-{Ow0xXqEMYaie%D-Di-2O@)4;mc zuI7P7%5wU}rX*Nu!0%y<2m3(A9RIp`uA5{regqBT9xE}+kTPXdZdi@mA(8!Fii@_k z2y3w%1bQJ?KG!p?zN^M!%za2)&mMez^k?YzWIE_Zb`omPf*0QA`(xRv`_?n?r%mgm zh!&c~nkl^Y>7&Row(x^z>@i(@39iM%7&8z0?k3Z16Jqfy0CoLRJotwTg`r|JB&|fe z_x`~2Rlq{&B7OBZJDJaa3&K)DM>EEXuM*%5V_65j*++u@=L!b_5=8`WGFo^v@=Qr(Jw-{b++qZ#Z=hyMZvqZ=QeO(G*FMSz!?g3 zV4k0Ii3Q5fhIzPa1sZ_YSvL6CMts2Vzu5T^h+qHiUoER#1Zqy*y;&eYKtoap@N2|X z`xy7XC^R8*u5P* z!6?F>u>0YkU9KX$iZ{F3e!V-RAOS8TM}vQ*F)I^aC{Q58f>l>|Pc3+-@ZW;?rLZi5 zS5*T9$-OyMv*46#p9UoT@E3$ZCZ5>b%~hxW#ULm~Cs^g|H>k1z%c$shvu;GDOuyz& z7FP`UVC*0n!HA5#!||O?KoV8KMJ^yF=-jNxZq3f36OWMUEUiw3b@vivCY~4~Gv46N zCeI{&jP;j1r1|+oUDS(BoJP9wy1d81^`Wx~uY6$4f_`A}ptqN>%N3Dn6)j8acMABp zw+P*Y)H|2AWT5FACT(f@6mdO(I{2__lV73)U#qfFo!!cfKb4T@3e5upgLgJ{GDx;5 zO11o;>WwkHzB*?PGo1}%z@Gb5+prV^96%?9i0ek1eI1c*00A)wgBe_zC;uX4Fz zIEQ2Rov+{K3PKbRJoYLDpw(y!%;~5e{RE15{5;YHJuT_N<{L$OEuCM z@7_?KRR0OuTC-S-Bz>TZ+t4*HM7f>N9YjY0Jy7vFTlDX~>qUvx&%41Kf}>wIu#nrd zbJxD>$PqkwDHtgJ%nsei;nL`(b*I)R zR)|(1V{Vk!B+1lV6CNx9!in9jhHY}-L460fip*z%y-reyPOO?D+esWRuRnmyFB9M- zVV*I4c9^J!^8kqUu693;K?B760O4P>`s@Vn2H;uZ$|2MxEj^Tj(3&xTKKaaoQ`xxR zFai=$j-`VLO<0!t1#%~e)bh8jYlzJlFnxaCZ7&Q93M>$ZY%Pn5Qf^`Wk6e<&K9~_g zANH|{LR@C`-$lT5t|aa~U-IopN6;U->X!UZM+sBJuG0B;kSrzWihKfT-&Xc3_V2}5$CfHIez~o@ zE$RZBAW$1w%$>&H6IzgYhhYuz_jZa^`giyx52FBk)9ad1IiO{ZRLkCxSN9Vk&|-Pn ztll=JR&HExwifh=#)}9Vp&r5)mpR2J3Lwy!8Sf{KUe8)4uxbkQm7J;W{N3ye@{as1 z<3?1{MBQu}%-U>^HPXN*=?Tu)K*2G(e75Ahj50_)nw!u=_5nI1Y<(1Z>3@k zPAk5tqkzPX{4O7&R1R}|cjM6~wTg`#(k;_4WJ+dnf~Zsk6!SH^-UA1B_^|!t#Nw)C zpO6qEL|nO(NU7d!02{;gV~{J550=q7lh}Uqv!1a}W4TJECwL?L{exULNg$b%TA9Z- ze;(JMQ`=mz3DPPgl3JcAyH1%V5TgA_3A`Ht(kn15a76Xe$Mi&nVL-ICKsGx-2WKM- zu4QV{yt?7;Y9#>zKgp`raY+A#F%+K3i8r56==I-%#B_MV0Q;6$-a~t@fstN$Z-0pp z%yo3_@|Vv@SLp5r{1YVWXZx{_YE5^z)s4aS?XGJm@u_lW*uG+K{{??Val-gyvEkZ6Y`%>Ug2N(yzb+_##7^(trlJz)Mfq=qbH^UnN^RG)PPjc z1iI)XZm69hv9{q`h&H+jsnJ zb_1<>SS3Gs@rB3vSv*3(6jfXUV2WUHx5zWL1z~s^+(e9e_Yl4!Y~=Mh9UPbz{T!N}Y6REoZLlPhpk z7QZ;W1)!x^Gw)>;kjkGfoZSyGlO%{pCc!_GRV#H$n^b0ly(Z*+S#KRJ-j&JCMPK$^ zAYkmr*zrHAT&L$Xl_INsURZPF2+Xd$Ml60eG)x{3!2O&m>PS`aD*O}FjCj0HBcmG9 zTp6$D{ABx^lNl_fw7~m|27Q5bRuB}V$ipXW99Ed!aejzRW338hrX%1b`E$L$mW1I* z2wwQto1?nMeF_dE8~8jw%2NE;GpS%|e?^x%zRiY2H%a7Brpq+>stES@*QQt_doLYX z*|jqX6nPAwO@fw;UT6kRz37W}%HtsY5(K)BVu1%;+%XR$#sSbv0Fhuti4Nx!4T=(S zo%Ap=mfqO#9J9?s)t9F+#;-j;aw+Ox+O0If*{F74t_39<69&y(jys(7@a|RHM*S5WBWki*$ID1y=$cngpl=grtDwS9 zP)NTx79F;Ju@kUD+irNnAB!6djgvMwmJI8J>m>x(mM6mmP>BR|Mt?k{dSj08F5mOG z2q4eo?f!;BaJI)ZjS9yLUGU!wF)sFN*F$%1`@yb2L4;x*o$SDQ2xssh>RCXIfHl-p zb}3YSBvrcdRa&T+CwA*ZUX$3EACZO=O0(dJZ|#oPwKrC?7ZRuE5k-vasvaHmk9`s0KS4L83`Q;SWBVCXY%A>ii%)V+Jl)+%XUQ+ z>@zrM0abh_j3OYLt}$fiV~np}Q?Kwsh+yW8X%&VlT6XROb^ zv#+~=2#kIbO33fssUhA;ijFeP4$*({F?lU^lO6g@GH7vM?k(Y zOT8eSyxf_)Gw1pkf-r`z0Ro}Uv@a?}cv8_0kuV++y3=A&UWR^@%r8t3l~nfe9Q|_s z0Gdpza3*|$QKP|JZ2;4G^T6SRcZ-u}PN|ZFM*v;E;m`zUtS|vQ-XQ~%zf+b@k&n(P z0dfEG_ZHKw{;o`Li~+BrUd$5_&UB@TwTVCQ27-kqPk7R@$25=?$0yDfFN=&}4kDN$ zlZNIb1D6(|7T0>6Dgpd&K?)j%#zf1Wgh%fr*bYq;{#oGQgm28k{&jWq<&}7Fi2np3 z)IVQ6)lNaOrRB3@DWCp0XAtHFh-0s8)VX2LaYBrmjtc?1fYdOcA=$A0s`;oxqepi& ze~5(*wbGAZ!g(27PYG2JR})bTXxJEOZ6oPIbBuqI@jr>NyrEP~bjbBi6NM3TG#%c- z+6+&4xDiZIs#Xa=)|{6+7f^kuMu+(|;{T%Mo-_*E8(R@m077p0C-N|zq?bXY#b)R0 zO@Sk*NS4~qE(qn>E2cGo`b((yV;ZqJT2ZbqR4}IGt~w;_1=fmvN!$$Xk}yS@*+^IIb61g4){ zRf{?he*{N-P3=;cHz$l-@y-xYPyf^|j7m&o)dZ0`=FIqSMz-Y{_@F)98dbF9jx67> zkx=HPoIZ*Y~)4tu=fxAo!NxQPG4Zr+2`y%AoNB%y&`YMR=U6 zqym0?H9X!$HEY_YsZvTnmz4Zp?l*b76_%W*c3V+S?q=RkyyGUB6hOAVjh*qUR93sU%I*=2Hy7zTVK$U<32sR^pm<4J z#`#AQF*_6HE`2EY`xUAM_Ke+vk$&^HF_UN(t(=H2Z<2WKmK8MVZlvnr|1IdKh0r2O z=NZpl00r&;b#|WdTzCH;w<0^M?7fne9kNq)BC;iWm1Km>P_il!**m+8ME2ez$p}Se z$SRffzwW#*|Hswi`}p|&Zawe3KIgp7`K&YE=NuQ-4OSX#2_<=gzA;WZhGEgK2d(e~ z&3pB#yi?Bq`lELJL^*Z50k?UaPnY6C#j@nR)5?`6?EQLd5UN;>ybHr?NK@0!bxe{|Gn=UPe{9G?NkK@ElV%KHz@^39uz$<1X}!zk`Xaxr)r znQ{&>xi#u$Jxk&dm&f(4>u>0@CTmGKr>G)bak9eU7In_uTkoF@EKWaBD(1bc_ssM} z{e^&##_-u%G2+{wx5&98Wo4+{l;;x*@dQ#{mP~h(+;6ffr(TdL_`EpF8c%m_tGipr z2&0Ey*dx>Kc9wb}u10R~sl<&9-W zrSor}R=G}}zA1N^bo)6=cIdE2{;><>al;+%`jVvYv(tLaS9-X*f;0R?>QW0=YrlC} z|KV!-{wm_j#~sZ_wtbxgq29V-_u1@suXfM95f1)#?|1K0`ktcS)j@d2#{}a;1NTki ztVkX&eN2c{U>e;tU9-h8h>)>R+*fi;+}({{{y^F*M>nJ7)cS=+#mN1NWbcW2B7HTY zbi*umY{$eWAHcYh4=F+0!}q0x3723?`XOI`1{#s#7~=ktgBfFmJ{i1t0?6EyMK_LhNjB3{>v0A zDmB&2tcI`I?$xt4PdVaQ6K-BgKO-13nNgn{8Y$dZJkp+S zsgm$GX61*8hI0ScPceFx+3&(A26qjGB`#iMN}TqnirnMan+o-8S_rY}pzHc4s{3JZ z%jFhtZ1t*ay1e17P44#DjCYlWPWTdB=jz%-<;1>8{&dsj=rND~zIE{X-uR~-ZCbAQ zFPkB9k%7ak zX820uTqSHw zb#9bPI?p*Ab?+2DRTTY(g7~UOz5rc=OJe0v$dEt3Po#_a&_XW1*coZO&8M1-eRpTD zV|Gfi#cfZE-(hYFt7Dk;(Y@|T7Nqh(hC+8dq@CV`NH9X$QvbV{5#Lag?vZ%-l>%B57e*d8DO(JEvhb1<6Q`hO2Be!YKt1PqSys+7GukW-IpS>X!gU$ z1CCSY_^&GU{E5l#FW{MHI_2PPb}Y&dlezG=-~fDs3~N4DCo{3E@SX8)wBTuFH6kpQ zbUwgw40WH3(5>mKF^o2mF2S4(oae}8zZW`=@u;6x*0+QM%W$G)S>Bo^TIXdXoq>!T z0pmf$V$7`riJ6rcw#Zhg*fmcQBdPcAJgR^qg?-p)f;$(KUk_Y~r+(nsOxkFg z)}O)uahPrXQ(vz$@eLK7AmY=I-RA~iOc)^155}b`(U#l(s z`X7|??!%8q z$yHPh^))f($}BNFa4yF&YybGF-`s((`9A)-K)#yk6ub1ev0~AB=bd*FbXJv?i9u1? zQ53PK-$ZJ%=;XE#wCvJKJ!MP5v!N#8IB|Qvhi*-r*;9ug=D4w9Nxf>AV7~j0of1lp zqL-;7%rXmVfdT)VOuQBOrhS=ZqDJ=DYps)x)wBwDb!~R2pCI)a{{o_JlF(o4MU^vRcQKLL)rv9L5ny~{au;Qa5*?X|DN{7d2dzd_pHLXngVjSQ6`t| z5J!7gDj^*Hg4CPY+?Gj&ubIr{{NMXx#%mvY#diAb=(O_!!-@l4S~wxRyEhi{~(_3`g~=`Ez64EAhVAE~Tm&*x!v-VUCNuv=d4Ux00Xxn8;odQ`F8%8|6z7qT` z`{X}AwO)(YZF>>!WL)XDF^PH;8RH=NO0J*uE`7yw7Hy$QNh85JvNQ`)3Nf5Q@^mv9 zjOa5w%6xjA*3aurY7-2j>(-h0IBCp3;RO?wMf3zFHBj4?O zPWR00=Beee#pvHJ&v0Yle{Z?+0+&x=6%6= zq@ya|B`jDvM$G9+QD@9Z1dE; z;XeC$ivymZ-U*?9cI%8QaZ8`8Z(WZ~Pd~SRUA3DHr!PN($m3>)oyXQ^lja0M14h9U z3+xkI)q>jsS6w%V**bgV^mKWZr1HGYOR3m-g~Sr=ni}gfyMFs0#6GN!>aB|W-Xcs@ zy2c%H-04(+;}w$0j%FII{1w(Im33y1p0NgpG!>k$E7qlTzOB7E*s^1N0g9)tN`?$s zO88eibS9J{!rFdSO>43trNuJaZ`5jIHX2-({Dv*B;aiv3aIp~)w`G+z){wz|08TYt z+v9mR&+>o~8505nEq-5)6B-#@zR`)JvyS*h?`>nqta&KgPJUO#a}Yf(DV9V58yq?`Y+h|hsO~KbvYVnxPc!^Rudv0ED(IYd_ zy&CLG^;hNR{OEQ}=d+9*?<~Atf8aGWo7ak&N}9(fCFdBxb$_aWzLVn8m*yPPteirT z2%EL+b5>*%5)5bE>A8dI_s`R%U3=B2lCn?&v=uPTf#rnX}Fd-|*@I0ydMUfs;%6Fl3yvb3W;UDbmoSgXQ|j*n@unLNt8S!8y7_%Y3gYqanm?MXQno> za&NM6N@#)M?Q={_|5eO@RZKy6B7jpF__OWp%EGwL{Kvm!iQj2*$F01n=3KjD;W*!E z!&?kG;8E9?fgULi`_LIn^sRx-8>G?#JPc#eR-F}iuGYuJJo?QC6KyxQMQd&*wY|P= z5pPSlFdOIezU|v7&;zqOCb>>5GEjZ_=#TZH`?KFaaT${~Pw>rJ1HaNs$shNCbb&@q z?zzzqHfBz_u(@{qQL66jGM(wOC|J1oI=RyUc)iK2DPi&i=TAn)Mthf$=(Kp*;q#|(ZImZ~ z4tUhF0zr>-rVeUnb3FBHkuJEAy=7T6*3uQZbjgmcujmPWZRhalTT&1G^#*vg3ZHHq z9lkEtSlgp|eXNUPuP%w|^^EWg*QK*7F}$_l2q=>9QCu`^W9wo*@1CDlSM>17=YN9a zGx3^UKEiyZ8QO%}8M-;4`vxG5ejYlrcazzD?L}og@mXej2Cigo9f}|O# zye%8n{I|b`Wqx$^L8kvT7EzE6CkZYKpoZ)<8r)4zjAEjGgUt+WGr54bSi+r@KyaIY) zu-ELcTm$=+-sPz_62Jc4P1uH)==9-Cd^iFsOzkS-AET|RUMe;GJ${6JIFS&r>V z{^V%a)KJ^1%bDM=U5Tq4NQWxQTvPTcGybb5AEcX%ioeMgmh58aV|F!fg+GXrFvjMx zV%7R-ajR)!s4HM+^~~p}#qV?G`ZkTJqX{3blc;&d67Ny^C7hJEmL<7$=nSp^;%Ddo zkj`3jZGS7yF@U8I9>HUL1J`|kqWNPwhEe7AP0#_4dOjrRkCwzaL8d}{Z7tVT4r*lqLo z)Oe!~oxyVhUjyw6;o|-*9RCSN_4ms2P6Bjim zIBeHZJNw!=JH^Zp$Pg&;FwTU@dAU(^r$d*__D2eaYFNR5h$lD#9`!r}&?D_$kJ?$< zGwf*M60eghLBW64#3fRB#hN4tullpftP}PuPkjHY6=Wk@IEYW?9&IkGTeuMw{h|Mb z=YCQC4T;Zg5nRTW?p@#rh*Waa&Ki2oRJfF6kSeS&m>zeb+WIN#CD$!NZD7GolHm32XME#M;IEj`Bm z%H@0&jvWOA?Qc#uD9Uxo*1XQmPxt3dqrmAujGb*n+(0YaA_0Y6mkF}wd+9# z{NMlR=+?neS0jNlq_ro9b@qCeUii&em(aFsRAS(GLvO5R6xW0#%|;eoNZ+{!eOok= zm0h3L(#nP(hiZx1Z@mCLkca9UQO$>YXS%7xqoz-HQ(?7nM!ZcrWw5wQ#7pv|=>?<< zyfTyTkxI!?E5&uKuy8QueyNR2^BJf2HuL?OI6gykd56y6orHF}z3!7}Eluwlzqrz$ zUi2-qD-t#AX1k@?FO}ck37(Z}2R$&uB{;HIThlM{;m!m$Gi~0l^8~jaJ_$0|n{t$S zh8b}N(gjL5{d0&2WgacX@ChBvu-C_W(gSF(lT9vH*$_soAPk>gyxEb2IzoCjmif-QuG>XXTIAsQNnUcjP!DsnTLs* z-?mDxzbFYb2|n4)Ta16YT^Vu!qWi|g6_6IR2Iq8g=D%1s_g|SgB}DjJ{uB8fR`IV{P^NFSfKWe6G<)sq?@sS57N!}8eqA5~Jy2@%l4yqb z$B5jhoo9k~-|6PRoScxtlu4or77_h{i}4=P1s?r5gR%pS%X)vthWZP}ls2*TmU0G! zSVnet+ni?=UstP@tv8Ja9q_2TwtyaKb@O4JJqp^zn27fthj$I*-phUr2J?tDyiczL z0nO|>yW$8BS$IcZ5df?J_tCXP18KZAigpayiTMgYmU|=+gxobA6R*mE+ z>C!;DK%{s1Z)dZ#zE10bTCGbqY^#2GI4#Lvw#J8eR7tt7yyah>wtT)Nzi3g`L1GS$ zK%Z?$X@L7j`M07JTe{WKoV7L;$9L}7EppkUYv=d{6V#xtfJp7XLuYUWuvo%)#gqpb`uDe6-^?Ui$;@PTtsKjol>r^_sCzPl9%%<&)Xr>t&aKCgrW`YGi~duq zzBn#6!!$r}wKtYkBc%!_qqh-q03LP6de9^7PmbEzkiT+fBH0sddz*J;(%}ZYOE}Ey z-p`Irdf83%w^d!pg&crLe;d#NkyZqtb|&vKCRX#cdwN{ua<;Tyyv(euY3OpKAoXRV zP6L9<_BzM`h;(;A2SnOO@X#5$EkV%3C_2(8y%4`tdPc#HApFhrMxC=~*j!EMeTiOw zi@1{hJ~>9OXD({1P3CjUu0&kI|0k(Z1pp z^jaibQgU~Fyn1kw{KS^xm$E?*V~Owkk`%VR&+1l`lSSAO(?>+qfDHHLf zOxTN`uo5;h3KLKHX4sK}4(RacnSLJ+6{|DIIL(+Al0!ZW-zto+*7dWTRu zL+aZ=4nTBg#E@E=sGTW&*CnU)Ax@OzB%vqcHmoJpAmuA3j$pTJV%5Ala`N9ZyTKW{ z|BN|~I-h2*Xt99AJFIwuMck_4z7+4+pO8G7o3(8D`7#%^v!&TrbToudtrh6I8PxBn z8Ex8>T+^6ajwK8>4leRsx(1Gb7gi3A)2}v@+;er$Z=)t^~z6q*T>pnX=Q%w`&@kb>Otqg5fI(6aHQFzLuc@} zfxid48e#1Fd-@9msu#o({SGMYF4PtFyrzFb-NsO;pLxFgzj~y<8I%Cn>S8lPM|%sa zzVVG&glXT4!pw2syh1)aDyfUum7#p4htA*%;F*uPtbKP{4J=q1BOPwD(e3tJUkkh6 z_F)Y_zU6(uV@A;Z%OkGr1O5Nr6E5M*FTAIGj72JglZlu9ZROp1JIS4drba(8HtFPC zT0F=B*y^;P&ez5u^USBo;6RIN^PBMtgCXYu8a%n?^l^cvzrKG+-%HeWhVHB&QY-6*Xsl5(^)KGcj1_-W_3?#Vl_KlG#&F@(kG7HSGubPH8 zb_VgTbVL`l70;;>EO=gsGr@28V6SftIv`SX&!IDD&%-$C*q7OXOByHYKJ~6JSMu(q zUM3mbBBZ~xFQc?LXGB^n0(u})G!3K+{NKlEk^Z<)b|BJ>J?MZ(f6m|vAkuRHIv~2e zK%|+F!@CJN0Jk=15~|7i*F)^tq8k^U+jVCCTu@?hwk0yz8B%%`YX&+Xx=~(8dqy8R zgKs*#b4dL`&;yN>%7uSgYbH+#{F*zU`kHh@C1~?*+6{6EDH{UK`3L%tE)Z$-^w1fU z9Vl;oJS_9xuRv~zh{9oZTHV6<1L3&iX?&#SZ9>~mEsne<@^HP3=<_+ zP)FdmAI8OnXJ+!g2}CxE{uO(LG~a;Q*@P1ji_Jk1g+kbxpwR_C?)v$wM8DJan&bT^ z?%1;${WTUOqNEWdE!6b=TtS2OO%nzWr_XpRIB_ZdYUXPUjiaCc-9-*cas!J_Ll0ZP z?23T0!A_aIomkD8@mrFz@{ilU7p8|AKSJ&7k33h!Npq?=+AZz!n=FqQg?_WDc^I?m zc73|dnCq4B*O(*HOe#169(_g(t^lIjdPaAq0%?B{)OCjL-I1fe`e9j_!t{G^gF(A` zc*q+L+WARGoQZ{fK_y4l(|^qgBi$3!&Il36J_Qm;Jh1e_arT?NHOu@ zi+CL`a3AUx*n5E_&ID85Wj}ITHoI`nqned)=9|z!Fe&|{I)0YzJJ12uAD{Y(t1zKa zRx0-gyJ#$_@})ZF(%$5q`P1$-x}R-^Q9DB#uZJ9fEp2~3P%m-6U2}0}rF}3rxpJ}9 zK&$3s=pNRv1YTU_U#%dbv&&=A3-$&%H|bt{8&I(#WbZ4JG+aw`io_;mE^%W!bO!%F z7_SfY*m~xsG0EG4KNoRs^Fg_v4_%CL1KU4`pYQ}Sd(@YJ9{z(!|5+H*hV+r)aSoHQe&N$jxbgY36cr06JS|(!VS7XLbb&peN zsL|=gi=LSBl!HcHC+~8fX9FD&sXvR_8Mvg z7iWiGn*{2$_nZiFMeS@#19xdQSB8F!on9y!!zIJ1!o-GX9}C05-ATYLzyIIURiO^p zo}H>I)W|il2>HZJanVD?meG;8RzE(gG}7|7hV^-B&;gP98K|8Rl|Q8^PksNn{945u z;p2fyd0P%)Qp?X-niMa}4)VuOLk>W6TL2^3KSvqbowFjI+P=SKi(x1?NWA!`D!J8i z;PO35k!|NgXV74KwAxESUUeE)PVh;OfuO6wT33nZc^oAWegV{hB-$zy;lcU0MYFWp}TSmY0epSoqgM))M3PbKWGrnv>#dJjWFChX3(mRDcNqS?1Md zyk^ex`UJ_So&D+Cf086c6=^pi&B&VeF{G5@!9%UNS)1aMWMAm5*creP5Z#!V+j544 zR2CX921ZwUZtE9}o#t4HFAL50cvOH_H2>H!=MDfqsLM~D;U}WNVCbH z2O_neAzk4AzJ3$k)f?zWk|6c9P~T0Y)-c=^Akz3g=zvIPC!uym=Zbaqtlx<9e1TB| z8MZuO$Kb@*?wwJQ_{3nU*y=$#$N`9MObpUG7u3$sjiy0&G!$u!616j=w;JjSh!h(I zI^fZx3E>K$S%8furSCw@rIWhbsr*DC4h2zOg`SVI;|N91yL~VD>u#d^{X-g`LtSU+ zuHZx3#{fzUJo*~|Gh5JlMvB1$JrHR|4AKQ6wa!u38PcjBC_50{768(G18QeTe_U_` OM2e;X9S~_{>Hh&*(5NB+ diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index 2b75436987..edc8824d04 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -21,10 +21,8 @@ func TestNamespaceFromCID(t *testing.T) { var tests = []struct { randData [][]byte }{ - {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize)}, - {randData: generateRandNamespacedRawData(16, 16, appconsts.ShareSize)}, - {randData: generateRandNamespacedRawData(4, 4, appconsts.ShareSize)}, - {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize/2)}, + {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize-appconsts.NamespaceSize)}, + {randData: generateRandNamespacedRawData(16, appconsts.NamespaceSize, appconsts.ShareSize-appconsts.NamespaceSize)}, } for i, tt := range tests { From 0a4013ccd71887de9b8d06b33d6b7bb3c7b28c4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 16:38:33 +0100 Subject: [PATCH 0193/1008] chore(deps): bump go.opentelemetry.io/otel/metric from 0.32.1 to 0.33.0 (#1267) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 6dabb0662d..ddf4017c43 100644 --- a/go.mod +++ b/go.mod @@ -52,13 +52,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 - go.opentelemetry.io/otel v1.10.0 + go.opentelemetry.io/otel v1.11.1 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 - go.opentelemetry.io/otel/metric v0.32.1 + go.opentelemetry.io/otel/metric v0.33.0 go.opentelemetry.io/otel/sdk v1.10.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 - go.opentelemetry.io/otel/trace v1.10.0 + go.opentelemetry.io/otel/trace v1.11.1 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.8.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e diff --git a/go.sum b/go.sum index 13303467bc..3f64ba0e16 100644 --- a/go.sum +++ b/go.sum @@ -563,8 +563,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -1741,8 +1741,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= @@ -1753,14 +1753,14 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXo go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 h1:S8DedULB3gp93Rh+9Z+7NTEv+6Id/KYS7LDyipZ9iCE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0/go.mod h1:5WV40MLWwvWlGP7Xm8g3pMcg0pKOUY609qxJn8y7LmM= -go.opentelemetry.io/otel/metric v0.32.1 h1:ftff5LSBCIDwL0UkhBuDg8j9NNxx2IusvJ18q9h6RC4= -go.opentelemetry.io/otel/metric v0.32.1/go.mod h1:iLPP7FaKMAD5BIxJ2VX7f2KTuz//0QK2hEUyti5psqQ= +go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= +go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= -go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= From 0d74d458d35f54e5a05ee574a42cc98cfb982df7 Mon Sep 17 00:00:00 2001 From: Josh Bowen Date: Tue, 15 Nov 2022 09:02:57 -0700 Subject: [PATCH 0194/1008] Update Docker image build pipelines (#1337) --- .github/workflows/docker-build.yml | 34 ++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index c844acc5a4..fbf85b0a8e 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -2,12 +2,9 @@ name: "docker-build" on: push: - branches: - - "**" workflow_dispatch: env: - GO_VERSION: 1.19 IMAGE_NAME: ${{ github.repository }} REGISTRY: ghcr.io @@ -21,15 +18,8 @@ jobs: permissions: contents: write packages: write - steps: - uses: "actions/checkout@v3" - - - name: set up go - uses: actions/setup-go@v3 - with: - go-version: ${{ env.GO_VERSION }} - - name: Docker meta id: meta uses: docker/metadata-action@v4 @@ -37,24 +27,32 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=sha - type=semver,pattern={{major}}.{{minor}}.{{patch}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - + type=semver,pattern={{version}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Login to GHCR uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push + - name: Build and Push amd64 Image + if: | + github.ref != 'refs/heads/main' && + !startsWith(github.ref, 'refs/tags/v') + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + file: docker/Dockerfile + - name: Build and Push amd64+arm64 Images + if: | + github.ref == 'refs/heads/main' || + startsWith(github.ref, 'refs/tags/v') uses: docker/build-push-action@v3 with: - platforms: linux/amd64, linux/arm64 + platforms: linux/arm64, linux/amd64 push: true tags: ${{ steps.meta.outputs.tags }} file: docker/Dockerfile From 7787c6bcb1b32a9f2016bb504215bc18cd59a873 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:50:03 -0600 Subject: [PATCH 0195/1008] chore!(nodebuilder/p2p): increment the chain-id for arabica (#1370) --- nodebuilder/p2p/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index ddeede30a0..e184afc15f 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Arabica // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-1" + Arabica Network = "arabica-2" // Mamaki testnet. See: celestiaorg/networks. Mamaki Network = "mamaki" // Private can be used to set up any private network, including local testing setups. From 039facd94104c741f8fa995e80f1321776ae4ad3 Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Tue, 15 Nov 2022 20:07:17 +0200 Subject: [PATCH 0196/1008] chore!(nodebuilder/p2p): change genesis hash for upgraded arabica-2 (#1371) --- nodebuilder/p2p/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 47cac600f0..32d3e6a87e 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -21,7 +21,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "FAAA4B939173B4F8567E812FDC570FF186355326879017A611C58267A5D6EF17", + Arabica: "04EE55B212745B88F29943D7B9528C415473A211F12EEF6E9333EF32E4DEAF3C", Mamaki: "41BBFD05779719E826C4D68C4CCBBC84B2B761EB52BC04CFDE0FF8603C9AA3CA", Private: "", } From e21de2c9447ea353fc6e5e1e8c8b6a9a1c8e344f Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 16 Nov 2022 11:24:50 +0100 Subject: [PATCH 0197/1008] feat(rpc): tooling for openrpc spec autogeneration (#1283) Closes https://github.com/celestiaorg/celestia-node/issues/1188 --- Makefile | 8 ++ api/docgen/examples.go | 94 +++++++++++++ api/docgen/openrpc.go | 232 ++++++++++++++++++++++++++++++++ cmd/docgen/docgen.go | 59 ++++++++ go.mod | 17 ++- go.sum | 50 +++++++ nodebuilder/default_services.go | 19 +++ nodebuilder/header/service.go | 16 +-- nodebuilder/share/service.go | 7 +- 9 files changed, 491 insertions(+), 11 deletions(-) create mode 100644 api/docgen/examples.go create mode 100644 api/docgen/openrpc.go create mode 100644 cmd/docgen/docgen.go create mode 100644 nodebuilder/default_services.go diff --git a/Makefile b/Makefile index cb03b81be6..5360ce5930 100644 --- a/Makefile +++ b/Makefile @@ -146,3 +146,11 @@ pb-gen: done; \ done; .PHONY: pb-gen + + +## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api +openrpc-gen: + @echo "--> Generating OpenRPC spec" + @go run ./api/docgen/cmd fraud header state share daser +.PHONY: openrpc-gen + diff --git a/api/docgen/examples.go b/api/docgen/examples.go new file mode 100644 index 0000000000..b4c9f7e551 --- /dev/null +++ b/api/docgen/examples.go @@ -0,0 +1,94 @@ +package docgen + +import ( + "fmt" + "reflect" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/celestiaorg/celestia-node/fraud" +) + +var ExampleValues = map[reflect.Type]interface{}{ + reflect.TypeOf(""): "string value", + reflect.TypeOf(uint64(42)): uint64(42), + reflect.TypeOf(uint32(42)): uint32(42), + // reflect.TypeOf(int32(42)): int32(42), + reflect.TypeOf(int64(42)): int64(42), + reflect.TypeOf(byte(7)): byte(7), + reflect.TypeOf(float64(42)): float64(42), + reflect.TypeOf(true): true, + reflect.TypeOf([]byte{}): []byte("byte array"), + reflect.TypeOf(fraud.BadEncoding): fraud.BadEncoding, +} + +func ExampleValue(t, parent reflect.Type) (interface{}, error) { + v, ok := ExampleValues[t] + if ok { + return v, nil + } + + switch t.Kind() { + case reflect.Slice: + out := reflect.New(t).Elem() + val, err := ExampleValue(t.Elem(), t) + if err != nil { + return nil, err + } + out = reflect.Append(out, reflect.ValueOf(val)) + return out.Interface(), nil + case reflect.Chan: + return ExampleValue(t.Elem(), nil) + case reflect.Struct: + es, err := exampleStruct(t, parent) + if err != nil { + return nil, err + } + v := reflect.ValueOf(es).Elem().Interface() + ExampleValues[t] = v + return v, nil + case reflect.Array: + out := reflect.New(t).Elem() + for i := 0; i < t.Len(); i++ { + val, err := ExampleValue(t.Elem(), t) + if err != nil { + return nil, err + } + out.Index(i).Set(reflect.ValueOf(val)) + } + return out.Interface(), nil + + case reflect.Ptr: + if t.Elem().Kind() == reflect.Struct { + es, err := exampleStruct(t.Elem(), t) + if err != nil { + return nil, err + } + return es, err + } + case reflect.Interface: + return struct{}{}, nil + } + + return nil, fmt.Errorf("failed to retrieve example value for type: %s on parent '%s')", t, parent) +} + +func exampleStruct(t, parent reflect.Type) (interface{}, error) { + ns := reflect.New(t) + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Type == parent { + continue + } + if cases.Title(language.Und, cases.NoLower).String(f.Name) == f.Name { + val, err := ExampleValue(f.Type, t) + if err != nil { + return nil, err + } + ns.Elem().Field(i).Set(reflect.ValueOf(val)) + } + } + + return ns.Interface(), nil +} diff --git a/api/docgen/openrpc.go b/api/docgen/openrpc.go new file mode 100644 index 0000000000..84af262db4 --- /dev/null +++ b/api/docgen/openrpc.go @@ -0,0 +1,232 @@ +// Package docgen generates an OpenRPC spec for the Celestia Node. It has been inspired by and adapted from Filecoin's +// Lotus API implementation. +package docgen + +import ( + "encoding/json" + "fmt" + "go/ast" + "go/parser" + "go/token" + "net" + "reflect" + "strings" + + "github.com/alecthomas/jsonschema" + go_openrpc_reflect "github.com/etclabscore/go-openrpc-reflect" + meta_schema "github.com/open-rpc/meta-schema" +) + +const ( + APIVersion = "v0.0.1" + APIDescription = "The Celestia Node API is the collection of RPC methods that " + + "can be used to interact with the services provided by Celestia Data Availability Nodes." + APIName = "Celestia Node API" + DocsURL = "https://github.com/celestiaorg/celestia-node" + DocsName = "Celestia Node GitHub" +) + +type Visitor struct { + Methods map[string]ast.Node +} + +func (v *Visitor) Visit(node ast.Node) ast.Visitor { + st, ok := node.(*ast.TypeSpec) + if !ok { + return v + } + + if st.Name.Name != "Module" { + return nil + } + + iface := st.Type.(*ast.InterfaceType) + for _, m := range iface.Methods.List { + if len(m.Names) > 0 { + v.Methods[m.Names[0].Name] = m + } + } + + return v +} + +type Comments = map[string]string + +func ParseCommentsFromNodebuilderModules(moduleNames ...string) Comments { + fset := token.NewFileSet() + nodeComments := make(Comments) + for _, moduleName := range moduleNames { + f, err := parser.ParseFile(fset, "nodebuilder/"+moduleName+"/service.go", nil, parser.AllErrors|parser.ParseComments) + if err != nil { + panic(err) + } + + cmap := ast.NewCommentMap(fset, f, f.Comments) + + v := &Visitor{make(map[string]ast.Node)} + ast.Walk(v, f) + + // TODO(@distractedm1nd): An issue with this could be two methods with the same name in different modules + for mn, node := range v.Methods { + filteredComments := cmap.Filter(node).Comments() + if len(filteredComments) == 0 { + nodeComments[mn] = "No comment exists yet for this method." + } else { + nodeComments[mn] = filteredComments[0].Text() + } + } + } + return nodeComments +} + +func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { + d := &go_openrpc_reflect.Document{} + + d.WithMeta(&go_openrpc_reflect.MetaT{ + GetServersFn: func() func(listeners []net.Listener) (*meta_schema.Servers, error) { + return func(listeners []net.Listener) (*meta_schema.Servers, error) { + return nil, nil + } + }, + GetInfoFn: func() (info *meta_schema.InfoObject) { + info = &meta_schema.InfoObject{} + title := APIName + info.Title = (*meta_schema.InfoObjectProperties)(&title) + + version := APIVersion + info.Version = (*meta_schema.InfoObjectVersion)(&version) + + description := APIDescription + info.Description = (*meta_schema.InfoObjectDescription)(&description) + + return info + }, + GetExternalDocsFn: func() (exdocs *meta_schema.ExternalDocumentationObject) { + url, description := DocsURL, DocsName + + return &meta_schema.ExternalDocumentationObject{ + Url: (*meta_schema.ExternalDocumentationObjectUrl)(&url), + Description: (*meta_schema.ExternalDocumentationObjectDescription)(&description), + } + }, + }) + + appReflector := &go_openrpc_reflect.EthereumReflectorT{} + + appReflector.FnSchemaTypeMap = func() func(ty reflect.Type) *jsonschema.Type { + return OpenRPCSchemaTypeMapper + } + + appReflector.FnGetMethodExternalDocs = func( + r reflect.Value, + m reflect.Method, + funcDecl *ast.FuncDecl, + ) (*meta_schema.ExternalDocumentationObject, error) { + extDocs, err := go_openrpc_reflect.EthereumReflector.GetMethodExternalDocs(r, m, funcDecl) + if err != nil { + return nil, err + } + + desc := "Source of the default service's implementation of this method." + extDocs.Description = (*meta_schema.ExternalDocumentationObjectDescription)(&desc) + + url := strings.Replace(string(*extDocs.Url), "/master/", "/main/", 1) + extDocs.Url = (*meta_schema.ExternalDocumentationObjectUrl)(&url) + // + return extDocs, nil + } + + appReflector.FnIsMethodEligible = func(m reflect.Method) bool { + // methods are only eligible if they were found in the Module interface + _, ok := comments[m.Name] + if !ok { + return false + } + + /* TODO(@distractedm1nd): find out why chans are excluded in lotus. is this a must? + for i := 0; i < m.Func.Type().NumOut(); i++ { + if m.Func.Type().Out(i).Kind() == reflect.Chan { + return false + } + } + */ + return go_openrpc_reflect.EthereumReflector.IsMethodEligible(m) + } + + // remove the default implementation from the method descriptions + appReflector.FnGetMethodDescription = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + return "", nil // noComment + } + + appReflector.FnGetMethodName = func( + moduleName string, + r reflect.Value, + m reflect.Method, + funcDecl *ast.FuncDecl, + ) (string, error) { + return moduleName + "." + m.Name, nil + } + + appReflector.FnGetMethodSummary = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + if v, ok := comments[m.Name]; ok { + return v, nil + } + return "", nil // noComment + } + + appReflector.FnSchemaExamples = func(ty reflect.Type) (examples *meta_schema.Examples, err error) { + v, err := ExampleValue(ty, ty) // This isn't ideal, but seems to work well enough. + if err != nil { + fmt.Println(err) + } + return &meta_schema.Examples{ + meta_schema.AlwaysTrue(v), + }, nil + } + + d.WithReflector(appReflector) + return d +} + +const integerD = `{ "title": "number", "type": "number", "description": "Number is a number" }` + +func OpenRPCSchemaTypeMapper(ty reflect.Type) *jsonschema.Type { + unmarshalJSONToJSONSchemaType := func(input string) *jsonschema.Type { + var js jsonschema.Type + err := json.Unmarshal([]byte(input), &js) + if err != nil { + panic(err) + } + return &js + } + + if ty.Kind() == reflect.Ptr { + ty = ty.Elem() + } + + if ty == reflect.TypeOf((*interface{})(nil)).Elem() { + return &jsonschema.Type{Type: "object", AdditionalProperties: []byte("true")} + } + + // Handle primitive types in case there are generic cases + // specific to our services. + switch ty.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // Return all integer types as the hex representation integer schemea. + ret := unmarshalJSONToJSONSchemaType(integerD) + return ret + case reflect.Uintptr: + return &jsonschema.Type{Type: "number", Title: "uintptr-title"} + case reflect.Struct: + case reflect.Map: + case reflect.Slice, reflect.Array: + case reflect.Float32, reflect.Float64: + case reflect.Bool: + case reflect.String: + case reflect.Ptr, reflect.Interface: + default: + } + + return nil +} diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go new file mode 100644 index 0000000000..4414955cda --- /dev/null +++ b/cmd/docgen/docgen.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "encoding/json" + "log" + "os" + + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/api/docgen" + "github.com/celestiaorg/celestia-node/nodebuilder" +) + +var rootCmd = &cobra.Command{ + Use: "docgen [packages]", + Short: "docgen generates the openrpc documentation for Celestia Node packages", + RunE: func(cmd *cobra.Command, moduleNames []string) error { + // 1. Open the respective nodebuilder/X/service.go files for AST parsing + nodeComments := docgen.ParseCommentsFromNodebuilderModules(moduleNames...) + + // 2. Create an OpenRPC document from the map of comments + hardcoded metadata + doc := docgen.NewOpenRPCDocument(nodeComments) + + // 3. Register the client wrapper interface on the document + for moduleName, module := range nodebuilder.PackageToDefaultImpl { + doc.RegisterReceiverName(moduleName, module) + } + + // 4. Call doc.Discover() + d, err := doc.Discover() + if err != nil { + return err + } + + // 5. Print to Stdout + jsonOut, err := json.MarshalIndent(d, "", " ") + if err != nil { + log.Fatalln(err) + } + if err != nil { + return err + } + + _, err = os.Stdout.Write(jsonOut) + return err + }, +} + +func main() { + err := run() + if err != nil { + os.Exit(1) + } +} + +func run() error { + return rootCmd.ExecuteContext(context.Background()) +} diff --git a/go.mod b/go.mod index ddf4017c43..0d1cb3c960 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/math v1.0.0-beta.3 - github.com/99designs/keyring v1.2.1 // indirect github.com/BurntSushi/toml v1.2.0 + github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/celestiaorg/celestia-app v0.10.0-rc1 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.11.0 @@ -15,6 +15,7 @@ require ( github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/dgraph-io/badger/v2 v2.2007.4 + github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/go-jsonrpc v0.1.8 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 @@ -48,6 +49,7 @@ require ( github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.7.0 github.com/multiformats/go-multihash v0.2.0 + github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/spf13/cobra v1.6.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 @@ -63,6 +65,7 @@ require ( go.uber.org/multierr v1.8.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 + golang.org/x/text v0.3.7 google.golang.org/grpc v1.49.0 ) @@ -74,7 +77,10 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.1 // indirect github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.0 // indirect @@ -118,6 +124,7 @@ require ( github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect + github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect github.com/ethereum/go-ethereum v1.10.17 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -130,6 +137,10 @@ require ( github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.4 // indirect + github.com/go-openapi/spec v0.19.11 // indirect + github.com/go-openapi/swag v0.19.11 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect @@ -162,6 +173,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/huin/goupnp v1.0.3 // indirect + github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/ipfs/bbloom v0.0.4 // indirect @@ -183,6 +195,7 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.15.6 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect @@ -204,6 +217,7 @@ require ( github.com/libp2p/go-yamux/v3 v3.1.2 // indirect github.com/lucas-clemente/quic-go v0.28.0 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect @@ -288,7 +302,6 @@ require ( golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect - golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.81.0 // indirect diff --git a/go.sum b/go.sum index 3f64ba0e16..dc72bca24a 100644 --- a/go.sum +++ b/go.sum @@ -100,10 +100,17 @@ github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -120,6 +127,8 @@ github.com/adlio/schema v1.1.13 h1:LeNMVg5Z1FX+Qgz8tJUijBLRdcpbFUElz+d1489On98= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 h1:T3+cD5fYvuH36h7EZq+TDpm+d8a6FSD4pQsbmuGGQ8o= +github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -262,6 +271,7 @@ github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b80 github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= @@ -286,6 +296,7 @@ github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= github.com/cosmos/cosmos-proto v1.0.0-alpha7 h1:yqYUOHF2jopwZh4dVQp3xgqwftE5/2hkrwIV6vkUbO0= @@ -392,6 +403,10 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakRE22OTI12k+2LkyY= +github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= +github.com/etclabscore/go-openrpc-reflect v0.0.37 h1:IH0e7JqIvR9OhbbFWi/BHIkXrqbR3Zyia3RJ733eT6c= +github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= @@ -465,8 +480,22 @@ github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dT github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.11 h1:ogU5q8dtp3MMPn59a9VRrPKVxvJHEs5P7yNMR5sNnis= +github.com/go-openapi/spec v0.19.11/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.8/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc= +github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -716,8 +745,12 @@ github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= +github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= +github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -867,6 +900,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -1186,6 +1221,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= @@ -1377,6 +1415,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= +github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -1406,6 +1445,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= +github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= @@ -1630,6 +1671,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1653,9 +1695,15 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -1909,6 +1957,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1930,6 +1979,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go new file mode 100644 index 0000000000..ea62a9e10d --- /dev/null +++ b/nodebuilder/default_services.go @@ -0,0 +1,19 @@ +package nodebuilder + +import ( + "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/state" +) + +// PackageToDefaultImpl maps a package to its default implementation. Currently only used for method discovery for +// openrpc spec generation +var PackageToDefaultImpl = map[string]interface{}{ + "fraud": &fraud.ProofService{}, + "state": &state.CoreAccessor{}, + "share": &light.ShareAvailability{}, + "header": &header.Service{}, + "daser": &das.DASer{}, +} diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 42d9e4e002..55dd283651 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -30,10 +30,10 @@ type API struct { IsSyncing func() bool } -// service represents the header service that can be started / stopped on a node. -// service's main function is to manage its sub-services. service can contain several +// Service represents the header service that can be started / stopped on a node. +// Service's main function is to manage its sub-services. Service can contain several // sub-services, such as Exchange, ExchangeServer, Syncer, and so forth. -type service struct { +type Service struct { ex header.Exchange syncer *sync.Syncer @@ -42,14 +42,14 @@ type service struct { store header.Store } -// NewHeaderService creates a new instance of header service. +// NewHeaderService creates a new instance of header Service. func NewHeaderService( syncer *sync.Syncer, sub header.Subscriber, p2pServer *p2p.ExchangeServer, ex header.Exchange, store header.Store) Module { - return &service{ + return &Service{ syncer: syncer, sub: sub, p2pServer: p2pServer, @@ -58,14 +58,14 @@ func NewHeaderService( } } -func (s *service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { +func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { return s.store.GetByHeight(ctx, height) } -func (s *service) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Service) Head(ctx context.Context) (*header.ExtendedHeader, error) { return s.store.Head(ctx) } -func (s *service) IsSyncing() bool { +func (s *Service) IsSyncing() bool { return !s.syncer.State().Finished() } diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go index 9da51e1ab4..3b020bcf7c 100644 --- a/nodebuilder/share/service.go +++ b/nodebuilder/share/service.go @@ -29,9 +29,14 @@ import ( // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - share.Availability + // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. + SharesAvailable(context.Context, *share.Root) error + // ProbabilityOfAvailability calculates the probability of the data square + // being available based on the number of samples collected. + ProbabilityOfAvailability() float64 GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) + // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) } From f8bef40b19434b9f143d969e52af2e9a4af5db7a Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:19:35 +0300 Subject: [PATCH 0198/1008] feat(das): make WaitCatchUp public (#1380) ## Overview Exposes WaitCatchUp from DASer to subscribe to catchup event from external caller. Requested for https://github.com/celestiaorg/celestia-node/pull/1341 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing --- das/daser.go | 5 +++++ das/daser_test.go | 12 ++++++++++-- das/state.go | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/das/daser.go b/das/daser.go index 92859ed9f7..ce4b41225e 100644 --- a/das/daser.go +++ b/das/daser.go @@ -169,3 +169,8 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { func (d *DASer) SamplingStats(ctx context.Context) (SamplingStats, error) { return d.sampler.stats(ctx) } + +// WaitCatchUp waits for DASer to indicate catchup is done +func (d *DASer) WaitCatchUp(ctx context.Context) error { + return d.sampler.state.waitCatchUp(ctx) +} diff --git a/das/daser_test.go b/das/daser_test.go index ca4b244b72..ae43d28fbb 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -54,12 +54,17 @@ func TestDASerLifecycle(t *testing.T) { // ensure checkpoint is stored at 30 assert.EqualValues(t, 30, checkpoint.SampleFrom-1) }() - // wait for dasing catch-up routine to indicateDone + + // wait for mock to indicate that catchup is done select { case <-ctx.Done(): t.Fatal(ctx.Err()) case <-mockGet.doneCh: } + + // wait for DASer to indicate done + assert.NoError(t, daser.WaitCatchUp(ctx)) + // give catch-up routine a second to finish up sampling last header assert.NoError(t, daser.sampler.state.waitCatchUp(ctx)) } @@ -80,13 +85,16 @@ func TestDASer_Restart(t *testing.T) { err = daser.Start(ctx) require.NoError(t, err) - // wait for dasing catch-up routine to indicateDone + // wait for mock to indicate that catchup is done select { case <-ctx.Done(): t.Fatal(ctx.Err()) case <-mockGet.doneCh: } + // wait for DASer to indicate done + assert.NoError(t, daser.WaitCatchUp(ctx)) + err = daser.Stop(ctx) require.NoError(t, err) diff --git a/das/state.go b/das/state.go index 1f96560dc9..970433b8bc 100644 --- a/das/state.go +++ b/das/state.go @@ -221,7 +221,7 @@ func (s *coordinatorState) checkDone() { } } -// waitCatchUp waits for sampling process to indicateDone catchup +// waitCatchUp waits for sampling process to indicate catchup is done func (s *coordinatorState) waitCatchUp(ctx context.Context) error { select { case <-s.catchUpDoneCh: From 93b4474c2c11722d2a0f6986680bf6fc8f1e212e Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 21 Nov 2022 09:46:26 +0100 Subject: [PATCH 0199/1008] fix: allowing hostnames in gateway/rpc addr field (#1378) Closes https://github.com/celestiaorg/celestia-node/issues/1374 --- libs/utils/address.go | 40 ++++++++++++++++++++++++++++ libs/utils/address_test.go | 49 +++++++++++++++++++++++++++++++++++ nodebuilder/core/config.go | 20 +++----------- nodebuilder/gateway/config.go | 12 ++++++--- nodebuilder/rpc/config.go | 12 ++++++--- 5 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 libs/utils/address.go create mode 100644 libs/utils/address_test.go diff --git a/libs/utils/address.go b/libs/utils/address.go new file mode 100644 index 0000000000..5971bf5a98 --- /dev/null +++ b/libs/utils/address.go @@ -0,0 +1,40 @@ +package utils + +import ( + "fmt" + "net" + "strings" +) + +// SanitizeAddr trims leading protocol scheme and port from the given +// IP address or hostname if present. +func SanitizeAddr(addr string) (string, error) { + original := addr + addr = strings.TrimPrefix(addr, "http://") + addr = strings.TrimPrefix(addr, "https://") + addr = strings.TrimPrefix(addr, "tcp://") + addr = strings.TrimSuffix(addr, "/") + addr = strings.Split(addr, ":")[0] + if addr == "" { + return "", fmt.Errorf("invalid IP address or hostname given: %s", original) + } + return addr, nil +} + +// ValidateAddr sanitizes the given address and verifies that it is a valid IP or hostname. The +// sanitized address is returned. +func ValidateAddr(addr string) (string, error) { + addr, err := SanitizeAddr(addr) + if err != nil { + return addr, err + } + + if ip := net.ParseIP(addr); ip == nil { + _, err = net.LookupHost(addr) + if err != nil { + return addr, fmt.Errorf("could not resolve hostname or ip: %w", err) + } + } + + return addr, nil +} diff --git a/libs/utils/address_test.go b/libs/utils/address_test.go new file mode 100644 index 0000000000..c914a7d853 --- /dev/null +++ b/libs/utils/address_test.go @@ -0,0 +1,49 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSanitizeAddr(t *testing.T) { + var tests = []struct { + addr string + want string + }{ + // Testcase: trims protocol prefix + {addr: "http://celestia.org", want: "celestia.org"}, + // Testcase: trims protocol prefix, and trims port and trailing slash suffix + {addr: "tcp://192.168.42.42:5050/", want: "192.168.42.42"}, + } + + for _, tt := range tests { + t.Run(tt.addr, func(t *testing.T) { + got, err := SanitizeAddr(tt.addr) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestValidateAddr(t *testing.T) { + var tests = []struct { + addr string + want string + }{ + // Testcase: ip is valid + {addr: "192.168.42.42:5050", want: "192.168.42.42"}, + // Testcase: hostname is valid + {addr: "https://celestia.org", want: "celestia.org"}, + // Testcase: resolves localhost + {addr: "http://localhost:8080/", want: "localhost"}, + } + + for _, tt := range tests { + t.Run(tt.addr, func(t *testing.T) { + got, err := ValidateAddr(tt.addr) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/nodebuilder/core/config.go b/nodebuilder/core/config.go index d46703db40..c0261c7c86 100644 --- a/nodebuilder/core/config.go +++ b/nodebuilder/core/config.go @@ -3,7 +3,8 @@ package core import ( "fmt" "strconv" - "strings" + + "github.com/celestiaorg/celestia-node/libs/utils" ) // Config combines all configuration fields for managing the relationship with a Core node. @@ -25,7 +26,7 @@ func DefaultConfig() Config { // Validate performs basic validation of the config. func (cfg *Config) Validate() error { - ip, err := sanitizeIP(cfg.IP) + ip, err := utils.ValidateAddr(cfg.IP) if err != nil { return err } @@ -40,18 +41,3 @@ func (cfg *Config) Validate() error { } return nil } - -// sanitizeIP trims leading protocol scheme and port from the given -// IP address if present. -func sanitizeIP(ip string) (string, error) { - original := ip - ip = strings.TrimPrefix(ip, "http://") - ip = strings.TrimPrefix(ip, "https://") - ip = strings.TrimPrefix(ip, "tcp://") - ip = strings.TrimSuffix(ip, "/") - ip = strings.Split(ip, ":")[0] - if ip == "" { - return "", fmt.Errorf("nodebuilder/core: invalid IP addr given: %s", original) - } - return ip, nil -} diff --git a/nodebuilder/gateway/config.go b/nodebuilder/gateway/config.go index a148d2068c..903a27489a 100644 --- a/nodebuilder/gateway/config.go +++ b/nodebuilder/gateway/config.go @@ -2,8 +2,9 @@ package gateway import ( "fmt" - "net" "strconv" + + "github.com/celestiaorg/celestia-node/libs/utils" ) type Config struct { @@ -22,10 +23,13 @@ func DefaultConfig() Config { } func (cfg *Config) Validate() error { - if ip := net.ParseIP(cfg.Address); ip == nil { - return fmt.Errorf("gateway: invalid listen address format: %s", cfg.Address) + sanitizedAddress, err := utils.ValidateAddr(cfg.Address) + if err != nil { + return fmt.Errorf("gateway: invalid address: %w", err) } - _, err := strconv.Atoi(cfg.Port) + cfg.Address = sanitizedAddress + + _, err = strconv.Atoi(cfg.Port) if err != nil { return fmt.Errorf("gateway: invalid port: %s", err.Error()) } diff --git a/nodebuilder/rpc/config.go b/nodebuilder/rpc/config.go index 39a94cdc4f..306dd562e3 100644 --- a/nodebuilder/rpc/config.go +++ b/nodebuilder/rpc/config.go @@ -2,8 +2,9 @@ package rpc import ( "fmt" - "net" "strconv" + + "github.com/celestiaorg/celestia-node/libs/utils" ) type Config struct { @@ -20,10 +21,13 @@ func DefaultConfig() Config { } func (cfg *Config) Validate() error { - if ip := net.ParseIP(cfg.Address); ip == nil { - return fmt.Errorf("service/rpc: invalid listen address format: %s", cfg.Address) + sanitizedAddress, err := utils.ValidateAddr(cfg.Address) + if err != nil { + return fmt.Errorf("service/rpc: invalid address: %w", err) } - _, err := strconv.Atoi(cfg.Port) + cfg.Address = sanitizedAddress + + _, err = strconv.Atoi(cfg.Port) if err != nil { return fmt.Errorf("service/rpc: invalid port: %s", err.Error()) } From a93a75342178069c88dc2ada8a086d9768b38839 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 21 Nov 2022 14:48:54 +0100 Subject: [PATCH 0200/1008] feat(module/das): add WaitCatchUp method to the module (#1383) Also moves the stub test into the das module --- nodebuilder/das/daser.go | 8 +++++++- nodebuilder/das/module_test.go | 31 +++++++++++++++++++++++++++++++ nodebuilder/das/service.go | 3 +++ nodebuilder/node_bridge_test.go | 22 ---------------------- 4 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 nodebuilder/das/module_test.go diff --git a/nodebuilder/das/daser.go b/nodebuilder/das/daser.go index 49ef9f37c6..d6fa19f57c 100644 --- a/nodebuilder/das/daser.go +++ b/nodebuilder/das/daser.go @@ -14,12 +14,18 @@ import ( var _ Module = (*daserStub)(nil) +var errStub = fmt.Errorf("module/das: stubbed: dasing is not available on bridge nodes") + // daserStub is a stub implementation of the DASer that is used on bridge nodes, so that we can // provide a friendlier error when users try to access the daser over the API. type daserStub struct{} func (d daserStub) SamplingStats(context.Context) (das.SamplingStats, error) { - return das.SamplingStats{}, fmt.Errorf("moddas: dasing is not available on bridge nodes") + return das.SamplingStats{}, errStub +} + +func (d daserStub) WaitCatchUp(context.Context) error { + return errStub } func newDaserStub() Module { diff --git a/nodebuilder/das/module_test.go b/nodebuilder/das/module_test.go new file mode 100644 index 0000000000..2883b6adb1 --- /dev/null +++ b/nodebuilder/das/module_test.go @@ -0,0 +1,31 @@ +package das + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +// TestConstructModule_DASBridgeStub verifies that a bridge node implements a stub daser that +// returns an error and empty das.SamplingStats +func TestConstructModule_DASBridgeStub(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + var mod Module + + cfg := DefaultConfig() + app := fxtest.New(t, + ConstructModule(node.Bridge, &cfg), + fx.Populate(&mod)). + RequireStart() + defer app.RequireStop() + + _, err := mod.SamplingStats(ctx) + assert.ErrorIs(t, err, errStub) +} diff --git a/nodebuilder/das/service.go b/nodebuilder/das/service.go index 45ef2e10cb..5d2b58b113 100644 --- a/nodebuilder/das/service.go +++ b/nodebuilder/das/service.go @@ -10,10 +10,13 @@ import ( type Module interface { // SamplingStats returns the current statistics over the DA sampling process. SamplingStats(ctx context.Context) (das.SamplingStats, error) + // WaitCatchUp blocks until DASer finishes catching up to the network head. + WaitCatchUp(ctx context.Context) error } // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { SamplingStats func(ctx context.Context) (das.SamplingStats, error) + WaitCatchUp func(ctx context.Context) error } diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index 95b3e0f143..56f1bfad56 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -4,11 +4,9 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-node/das" coremodule "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -31,23 +29,3 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { err = node.Stop(ctx) require.NoError(t, err) } - -// TestBridge_HasStubDaser verifies that a bridge node implements a stub daser that returns an -// error and empty das.SamplingStats -func TestBridge_HasStubDaser(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - _, client := core.StartTestClient(ctx, t) - nd := TestNode(t, node.Bridge, coremodule.WithClient(client)) - require.NotNil(t, nd) - err := nd.Start(ctx) - require.NoError(t, err) - - stats, err := nd.DASer.SamplingStats(ctx) - assert.EqualError(t, err, "moddas: dasing is not available on bridge nodes") - assert.Equal(t, stats, das.SamplingStats{}) - - err = nd.Stop(ctx) - require.NoError(t, err) -} From f59d69bd0514d35ba84f343a6bc5736a50469a1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:07:19 +0100 Subject: [PATCH 0201/1008] chore(deps): bump google.golang.org/grpc from 1.49.0 to 1.51.0 (#1387) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 0d1cb3c960..1cc75fa793 100644 --- a/go.mod +++ b/go.mod @@ -65,8 +65,8 @@ require ( go.uber.org/multierr v1.8.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 - golang.org/x/text v0.3.7 - google.golang.org/grpc v1.49.0 + golang.org/x/text v0.4.0 + google.golang.org/grpc v1.51.0 ) require ( diff --git a/go.sum b/go.sum index dc72bca24a..8d131311c6 100644 --- a/go.sum +++ b/go.sum @@ -2181,8 +2181,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2457,8 +2458,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 06b9c0c1f79c8114b9e0ab4661c7614af60aad7f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 21 Nov 2022 15:11:52 +0100 Subject: [PATCH 0202/1008] tests(nodebuilder): sync and das over non-empty blocks (#1341) --- nodebuilder/tests/sync_test.go | 46 ++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 18e2c1b8be..a4d2591e95 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -3,6 +3,7 @@ package tests import ( "context" "testing" + "time" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" @@ -14,8 +15,15 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) +// Common consts for tests producing filled blocks +const ( + blocks = 20 + bsize = 16 + btime = time.Millisecond * 300 +) + /* -Test-Case: Sync a Light Node with a Bridge Node +Test-Case: Sync a Light Node with a Bridge Node(includes DASing of non-empty blocks) Steps: 1. Create a Bridge Node(BN) 2. Start a BN @@ -25,13 +33,14 @@ Steps: 6. Check LN is synced to height 30 */ func TestSyncLightWithBridge(t *testing.T) { - sw := swamp.NewSwamp(t) - - bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + fillDn := sw.FillBlocks(ctx, bsize, blocks) + + bridge := sw.NewBridgeNode() + sw.WaitTillHeight(ctx, 20) err := bridge.Start(ctx) @@ -55,7 +64,14 @@ func TestSyncLightWithBridge(t *testing.T) { h, err = light.HeaderServ.GetByHeight(ctx, 30) require.NoError(t, err) + err = light.ShareServ.SharesAvailable(ctx, h.DAH) + assert.NoError(t, err) + + err = light.DASer.WaitCatchUp(ctx) + require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) + require.NoError(t, <-fillDn) } /* @@ -120,7 +136,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { } /* -Test-Case: Sync a Full Node with a Bridge Node +Test-Case: Sync a Full Node with a Bridge Node(includes DASing of non-empty blocks) Steps: 1. Create a Bridge Node(BN) 2. Start a BN @@ -130,13 +146,14 @@ Steps: 6. Check FN is synced to height 30 */ func TestSyncFullWithBridge(t *testing.T) { - sw := swamp.NewSwamp(t) - - bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + fillDn := sw.FillBlocks(ctx, bsize, blocks) + + bridge := sw.NewBridgeNode() + sw.WaitTillHeight(ctx, 20) err := bridge.Start(ctx) @@ -159,6 +176,15 @@ func TestSyncFullWithBridge(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) + + err = full.ShareServ.SharesAvailable(ctx, h.DAH) + assert.NoError(t, err) + + err = full.DASer.WaitCatchUp(ctx) + require.NoError(t, err) + + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) + require.NoError(t, <-fillDn) } /* From e7ea1f10420d57fa911f8e0e0809f4fb41bd9d0e Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 23 Nov 2022 15:39:50 +0100 Subject: [PATCH 0203/1008] feat(share): EDSStore scaffolding (#1232) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov Closes https://github.com/celestiaorg/celestia-node/issues/1107 Closes https://github.com/celestiaorg/celestia-node/issues/1108 Closes https://github.com/celestiaorg/celestia-node/issues/1110 Closes https://github.com/celestiaorg/celestia-node/issues/1111 Closes https://github.com/celestiaorg/celestia-node/issues/1112 --- api/docgen/openrpc.go | 7 +- api/rpc_test.go | 4 +- go.mod | 5 + go.sum | 67 ++++++++++ nodebuilder/default_services.go | 4 +- nodebuilder/share/service.go | 6 +- share/eds/retriever.go | 3 +- share/eds/store.go | 230 ++++++++++++++++++++++++++++++++ share/eds/store_test.go | 172 ++++++++++++++++++++++++ 9 files changed, 488 insertions(+), 10 deletions(-) create mode 100644 share/eds/store.go create mode 100644 share/eds/store_test.go diff --git a/api/docgen/openrpc.go b/api/docgen/openrpc.go index 84af262db4..52871fc189 100644 --- a/api/docgen/openrpc.go +++ b/api/docgen/openrpc.go @@ -1,5 +1,5 @@ -// Package docgen generates an OpenRPC spec for the Celestia Node. It has been inspired by and adapted from Filecoin's -// Lotus API implementation. +// Package docgen generates an OpenRPC spec for the Celestia Node. It has been inspired by and +// adapted from Filecoin's Lotus API implementation. package docgen import ( @@ -66,7 +66,8 @@ func ParseCommentsFromNodebuilderModules(moduleNames ...string) Comments { v := &Visitor{make(map[string]ast.Node)} ast.Walk(v, f) - // TODO(@distractedm1nd): An issue with this could be two methods with the same name in different modules + // TODO(@distractedm1nd): An issue with this could be two methods with the same name in different + // modules for mn, node := range v.Methods { filteredComments := cmap.Filter(node).Comments() if len(filteredComments) == 0 { diff --git a/api/rpc_test.go b/api/rpc_test.go index db53f81460..80f3546c1f 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -91,8 +91,8 @@ func implementsMarshaler(t *testing.T, typ reflect.Type) { switch typ.Kind() { case reflect.Struct: - // a user defined struct could implement json.Marshaler on the pointer receiver, so check there first. - // note that the "non-pointer" receiver is checked before the switch. + // a user defined struct could implement json.Marshaler on the pointer receiver, so check there + // first. note that the "non-pointer" receiver is checked before the switch. pointerType := reflect.TypeOf(reflect.New(typ).Elem().Addr().Interface()) if pointerType.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()) { return diff --git a/go.mod b/go.mod index 1cc75fa793..f941508db4 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/dgraph-io/badger/v2 v2.2007.4 github.com/etclabscore/go-openrpc-reflect v0.0.37 + github.com/filecoin-project/dagstore v0.5.6 github.com/filecoin-project/go-jsonrpc v0.1.8 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 @@ -188,6 +189,7 @@ require ( github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-peertaskqueue v0.7.0 // indirect github.com/ipfs/go-verifcid v0.0.1 // indirect + github.com/ipld/go-car/v2 v2.4.1 // indirect github.com/ipld/go-codec-dagpb v1.3.1 // indirect github.com/ipld/go-ipld-prime v0.16.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect @@ -249,6 +251,7 @@ require ( github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -283,6 +286,7 @@ require ( github.com/tklauser/numcpus v0.4.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect + github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect @@ -320,6 +324,7 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 + github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 diff --git a/go.sum b/go.sum index 8d131311c6..62e2c512d6 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,7 @@ cosmossdk.io/math v1.0.0-beta.3 h1:TbZxSopz2LqjJ7aXYfn7nJSb8vNaBklW6BLpcei1qwM= cosmossdk.io/math v1.0.0-beta.3/go.mod h1:3LYasri3Zna4XpbrTNdKsWmD5fHHkaNAod/mNT9XdE4= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= @@ -116,6 +117,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= @@ -184,6 +186,7 @@ github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dm github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= @@ -219,6 +222,8 @@ github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOL github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0/go.mod h1:ByQ2rOrZs7s2OnPfeaiTMC8IOlcrT195xIRPgevydCI= +github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= +github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= github.com/celestiaorg/go-libp2p-messenger v0.1.0/go.mod h1:XzNksXrH0VxuNRGOnjPL9Ck4UyQlbmMpCYg9YwSBerI= github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 h1:Tb1lPVAGSJvBjRCM7YpC+VaITzdZjjno4+KEnbPT6tU= @@ -292,6 +297,7 @@ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmf github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -430,6 +436,7 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -471,6 +478,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -534,6 +542,7 @@ github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -770,11 +779,14 @@ github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mq github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= +github.com/ipfs/go-bitfield v1.0.0 h1:y/XHm2GEmD9wKngheWNNCNL0pzrWXZwCdQGv1ikXknQ= +github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= github.com/ipfs/go-bitswap v0.8.0/go.mod h1:/h8sBij8UVEaNWl8ABzpLRA5Y1cttdNUnpeGo2AA/LQ= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= +github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= github.com/ipfs/go-blockservice v0.4.0 h1:7MUijAW5SqdsqEW/EhnNFRJXVF8mGU5aGhZ3CQaCWbY= github.com/ipfs/go-blockservice v0.4.0/go.mod h1:kRjO3wlGW9mS1aKuiCeGhx9K1DagQ10ACpVO59qgAx4= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -811,18 +823,25 @@ github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+Sd github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= +github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= +github.com/ipfs/go-ipfs-blockstore v1.1.2/go.mod h1:w51tNR9y5+QXB0wkNcHt4O2aSZjTdqaEWaQdSxEyUOY= github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= +github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= +github.com/ipfs/go-ipfs-chunker v0.0.1 h1:cHUUxKFQ99pozdahi+uSC/3Y6HeRpi9oTeUHbE27SEw= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= +github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= +github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= @@ -859,20 +878,30 @@ github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72g github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= github.com/ipfs/go-merkledag v0.7.0 h1:PHdWOGwx+J2uRAuP9Mu+bz89ulmf3W2QmbSS/N6O29U= github.com/ipfs/go-merkledag v0.7.0/go.mod h1:/1cuN4VbcDn/xbVMAqjPUwejJYr8W9SvizmyYLU/B7k= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.7.0 h1:VyO6G4sbzX80K58N60cCaHsSsypbUNs1GjO5seGNsQ0= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= +github.com/ipfs/go-unixfsnode v1.4.0 h1:9BUxHBXrbNi8mWHc6j+5C580WJqtVw9uoeEKn4tMhwA= github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= +github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= +github.com/ipld/go-car/v2 v2.4.1 h1:9S+FYbQzQJ/XzsdiOV13W5Iu/i+gUnr6csbSD9laFEg= +github.com/ipld/go-car/v2 v2.4.1/go.mod h1:zjpRf0Jew9gHqSvjsKVyoq9OY9SWoEKdYCQUKVaaPT0= +github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.3.1 h1:yVNlWRQexCa54ln3MSIiUN++ItH7pdhBFhh0hSgZu1w= github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= +github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= +github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= github.com/ipld/go-ipld-prime v0.16.0 h1:RS5hhjB/mcpeEPJvfyj0qbOj/QL+/j05heZ0qa97dVo= github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= +github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= +github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -938,6 +967,7 @@ github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRs github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -996,6 +1026,7 @@ github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZk github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= github.com/libp2p/go-libp2p v0.20.3/go.mod h1:I+vndVanE/p/SjFbnA+BEmmfAUEpWxrdXZeyQ1Dus5c= github.com/libp2p/go-libp2p v0.21.0 h1:s9yYScuIFY33FOOzwTXbc8QqbvsRyKIWFf0FCSJKrfM= @@ -1040,6 +1071,7 @@ github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJB github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= github.com/libp2p/go-libp2p-core v0.10.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= @@ -1052,6 +1084,7 @@ github.com/libp2p/go-libp2p-core v0.19.1/go.mod h1:2uLhmmqDiFY+dw+70KkBLeKvvsJHG github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= +github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.17.0 h1:HWEjqjNVDuf8yuccuswGy1vYGzB0v4Z+yQ4DMDMSIqk= github.com/libp2p/go-libp2p-kad-dht v0.17.0/go.mod h1:zeE26Xo+PY7sS2AgkBQQcBnJEazMT26KGZLUFttl+rk= @@ -1063,12 +1096,14 @@ github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3 github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= +github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5BcKwrrb2VmOYb3M= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= @@ -1077,11 +1112,13 @@ github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnq github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= github.com/libp2p/go-libp2p-peerstore v0.7.1 h1:7FpALlqR+3+oOBXdzm3AVt0vjMYLW1b7jM03E4iEHlw= github.com/libp2p/go-libp2p-peerstore v0.7.1/go.mod h1:cdUWTHro83vpg6unCpGUr8qJoX3e93Vy8o97u5ppIM0= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= +github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= @@ -1102,6 +1139,7 @@ github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaT github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= github.com/libp2p/go-libp2p-swarm v0.10.0/go.mod h1:71ceMcV6Rg/0rIQ97rsZWMzto1l9LnNquef+efcRbmA= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -1123,6 +1161,7 @@ github.com/libp2p/go-libp2p-tls v0.3.0/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5o github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= github.com/libp2p/go-libp2p-transport-upgrader v0.7.0/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= @@ -1142,6 +1181,7 @@ github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTW github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= @@ -1173,6 +1213,7 @@ github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7z github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= +github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= github.com/libp2p/go-reuseport-transport v0.1.0/go.mod h1:vev0C0uMkzriDY59yFHD9v+ujJvYmDQVLowvAjEOmfw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= @@ -1185,11 +1226,13 @@ github.com/libp2p/go-stream-muxer-multistream v0.4.0/go.mod h1:nb+dGViZleRP4XcyH github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= +github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= github.com/libp2p/go-tcp-transport v0.5.0/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= +github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= @@ -1206,6 +1249,7 @@ github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enE github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= @@ -1225,6 +1269,8 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= +github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= @@ -1374,6 +1420,7 @@ github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyD github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.2.0/go.mod h1:/y4YVwkfMyry5kFbMTbLJKErhycTIftytRV+llXdyS4= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= +github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.5.0 h1:EgU6cBe/D7WRwQb1KmnBvU7lrcFGMggZVTPtOW9dDHs= github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= @@ -1480,6 +1527,8 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= +github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= @@ -1576,7 +1625,9 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1622,6 +1673,7 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= @@ -1744,8 +1796,11 @@ github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2r github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= +github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 h1:WXhVOwj2USAXB5oMDwRl3piOux2XMV9TANaYxXHdkoE= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= +github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= @@ -1789,6 +1844,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= @@ -1801,12 +1857,16 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXo go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 h1:S8DedULB3gp93Rh+9Z+7NTEv+6Id/KYS7LDyipZ9iCE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0/go.mod h1:5WV40MLWwvWlGP7Xm8g3pMcg0pKOUY609qxJn8y7LmM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -1880,6 +1940,7 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1892,6 +1953,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -1900,6 +1962,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1920,10 +1984,12 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -2224,6 +2290,7 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go index ea62a9e10d..c41af05076 100644 --- a/nodebuilder/default_services.go +++ b/nodebuilder/default_services.go @@ -8,8 +8,8 @@ import ( "github.com/celestiaorg/celestia-node/state" ) -// PackageToDefaultImpl maps a package to its default implementation. Currently only used for method discovery for -// openrpc spec generation +// PackageToDefaultImpl maps a package to its default implementation. Currently only used for +// method discovery for openrpc spec generation var PackageToDefaultImpl = map[string]interface{}{ "fraud": &fraud.ProofService{}, "state": &state.CoreAccessor{}, diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go index 3b020bcf7c..097604a648 100644 --- a/nodebuilder/share/service.go +++ b/nodebuilder/share/service.go @@ -29,14 +29,16 @@ import ( // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. + // SharesAvailable subjectively validates if Shares committed to the given Root are available on + // the Network. SharesAvailable(context.Context, *share.Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. ProbabilityOfAvailability() float64 GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) - // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. + // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the + // given namespace.ID. GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) } diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 2e8713c72f..5a4ac6932c 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -108,7 +108,8 @@ type retrievalSession struct { bget blockservice.BlockGetter adder *ipld.NmtNodeAdder - // TODO(@Wondertan): Extract into a separate data structure https://github.com/celestiaorg/rsmt2d/issues/135 + // TODO(@Wondertan): Extract into a separate data structure + // https://github.com/celestiaorg/rsmt2d/issues/135 squareQuadrants []*quadrant squareCellsLks [][]sync.Mutex squareCellsCount uint32 diff --git a/share/eds/store.go b/share/eds/store.go new file mode 100644 index 0000000000..ddfef04b03 --- /dev/null +++ b/share/eds/store.go @@ -0,0 +1,230 @@ +package eds + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/index" + "github.com/filecoin-project/dagstore/mount" + "github.com/filecoin-project/dagstore/shard" + "github.com/ipfs/go-datastore" + + "github.com/celestiaorg/celestia-node/share" + + "github.com/celestiaorg/rsmt2d" +) + +const ( + blocksPath = "/blocks/" + indexPath = "/index/" + transientsPath = "/transients/" +) + +// Store maintains (via DAGStore) a top-level index enabling granular and efficient random access to +// every share and/or Merkle proof over every registered CARv1 file. The EDSStore provides a custom +// Blockstore interface implementation to achieve access. The main use-case is randomized sampling +// over the whole chain of EDS block data and getting data by namespace. +type Store struct { + dgstr *dagstore.DAGStore + mounts *mount.Registry + + topIdx index.Inverted + carIdx index.FullIndexRepo + + basepath string +} + +// NewStore creates a new EDS Store under the given basepath and datastore. +func NewStore(basepath string, ds datastore.Batching) (*Store, error) { + err := setupPath(basepath) + if err != nil { + return nil, fmt.Errorf("failed to setup eds.Store directories: %w", err) + } + + r := mount.NewRegistry() + err = r.Register("fs", &mount.FSMount{FS: os.DirFS(basepath + blocksPath)}) + if err != nil { + return nil, fmt.Errorf("failed to register FS mount on the registry: %w", err) + } + + fsRepo, err := index.NewFSRepo(basepath + indexPath) + if err != nil { + return nil, fmt.Errorf("failed to create index repository: %w", err) + } + + invertedRepo := index.NewInverted(ds) + dagStore, err := dagstore.NewDAGStore( + dagstore.Config{ + TransientsDir: basepath + transientsPath, + IndexRepo: fsRepo, + Datastore: ds, + MountRegistry: r, + TopLevelIndex: invertedRepo, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to create DAGStore: %w", err) + } + + return &Store{ + basepath: basepath, + dgstr: dagStore, + topIdx: invertedRepo, + carIdx: fsRepo, + mounts: r, + }, nil +} + +// Start starts the underlying DAGStore. +func (s *Store) Start(ctx context.Context) error { + return s.dgstr.Start(ctx) +} + +// Stop stops the underlying DAGStore. +func (s *Store) Stop() error { + return s.dgstr.Close() +} + +// Put stores the given data square with DataRoot's hash as a key. +// +// The square is verified on the Exchange level, and Put only stores the square, trusting it. +// The resulting file stores all the shares and NMT Merkle Proofs of the EDS. +// Additionally, the file gets indexed s.t. store.Blockstore can access them. +func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.ExtendedDataSquare) error { + key := root.String() + f, err := os.OpenFile(s.basepath+blocksPath+key, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return err + } + + err = WriteEDS(ctx, square, f) + if err != nil { + return fmt.Errorf("failed to write EDS to file: %w", err) + } + + ch := make(chan dagstore.ShardResult, 1) + err = s.dgstr.RegisterShard(ctx, shard.KeyFromString(key), &mount.FSMount{ + FS: os.DirFS(s.basepath + blocksPath), + Path: key, + }, ch, dagstore.RegisterOpts{}) + if err != nil { + return fmt.Errorf("failed to initiate shard registration: %w", err) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case result := <-ch: + if result.Error != nil { + return fmt.Errorf("failed to register shard: %w", result.Error) + } + return nil + } +} + +// GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as a +// CARv1 file. +// The Reader strictly reads the CAR header and first quadrant (1/4) of the EDS, omitting all the +// NMT Merkle proofs. Integrity of the store data is not verified. +// +// Caller must Close returned reader after reading. +func (s *Store) GetCAR(ctx context.Context, root share.Root) (io.ReadCloser, error) { + key := root.String() + + ch := make(chan dagstore.ShardResult, 1) + err := s.dgstr.AcquireShard(ctx, shard.KeyFromString(key), ch, dagstore.AcquireOpts{}) + if err != nil { + return nil, fmt.Errorf("failed to initiate shard acquisition: %w", err) + } + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case result := <-ch: + if result.Error != nil { + return nil, fmt.Errorf("failed to acquire shard: %w", result.Error) + } + return result.Accessor, nil + } +} + +// Remove removes EDS from Store by the given share.Root and cleans up all the indexing. +func (s *Store) Remove(ctx context.Context, root share.Root) error { + key := root.String() + + ch := make(chan dagstore.ShardResult, 1) + err := s.dgstr.DestroyShard(ctx, shard.KeyFromString(key), ch, dagstore.DestroyOpts{}) + if err != nil { + return fmt.Errorf("failed to initiate shard destruction: %w", err) + } + + select { + case result := <-ch: + if result.Error != nil { + return fmt.Errorf("failed to destroy shard: %w", result.Error) + } + case <-ctx.Done(): + return ctx.Err() + } + + dropped, err := s.carIdx.DropFullIndex(shard.KeyFromString(key)) + if !dropped { + log.Warnf("failed to drop index for %s", key) + } + if err != nil { + return fmt.Errorf("failed to drop index for %s: %w", key, err) + } + + err = os.Remove(s.basepath + blocksPath + key) + if err != nil { + return fmt.Errorf("failed to remove CAR file: %w", err) + } + return nil +} + +// Get reads EDS out of Store by given DataRoot. +// +// It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by +// recomputing it. +func (s *Store) Get(ctx context.Context, root share.Root) (*rsmt2d.ExtendedDataSquare, error) { + f, err := s.GetCAR(ctx, root) + if err != nil { + return nil, fmt.Errorf("failed to get CAR file: %w", err) + } + eds, err := ReadEDS(ctx, f, root) + if err != nil { + return nil, fmt.Errorf("failed to read EDS from CAR file: %w", err) + } + return eds, nil +} + +// Has checks if EDS exists by the given share.Root. +func (s *Store) Has(ctx context.Context, root share.Root) (bool, error) { + key := root.String() + info, err := s.dgstr.GetShardInfo(shard.KeyFromString(key)) + if err == dagstore.ErrShardUnknown { + return false, err + } + + return true, info.Error +} + +func setupPath(basepath string) error { + perms := os.FileMode(0755) + err := os.Mkdir(basepath+blocksPath, perms) + if err != nil { + return fmt.Errorf("failed to create blocks directory: %w", err) + } + err = os.Mkdir(basepath+transientsPath, perms) + if err != nil { + return fmt.Errorf("failed to create transients directory: %w", err) + } + err = os.Mkdir(basepath+indexPath, perms) + if err != nil { + return fmt.Errorf("failed to create index directory: %w", err) + } + return nil +} diff --git a/share/eds/store_test.go b/share/eds/store_test.go new file mode 100644 index 0000000000..e68fc880c9 --- /dev/null +++ b/share/eds/store_test.go @@ -0,0 +1,172 @@ +package eds + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/dagstore/shard" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/ipld/go-car" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" +) + +// TestEDSStore_PutRegistersShard tests if Put registers the shard on the underlying DAGStore +func TestEDSStore_PutRegistersShard(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + + // shard hasn't been registered yet + has, err := edsStore.Has(ctx, dah) + assert.False(t, has) + assert.Error(t, err, "shard not found") + + err = edsStore.Put(ctx, dah, eds) + assert.NoError(t, err) + + _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) + assert.NoError(t, err) +} + +// TestEDSStore_PutIndexesEDS ensures that Putting an EDS indexes it into the car index +func TestEDSStore_PutIndexesEDS(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + stat, _ := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) + assert.False(t, stat.Exists) + + err = edsStore.Put(ctx, dah, eds) + assert.NoError(t, err) + + stat, err = edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) + assert.True(t, stat.Exists) + assert.NoError(t, err) +} + +// TestEDSStore_GetCAR ensures that the reader returned from GetCAR is capable of reading the CAR +// header and ODS. +func TestEDSStore_GetCAR(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + r, err := edsStore.GetCAR(ctx, dah) + assert.NoError(t, err) + carReader, err := car.NewCarReader(r) + + fmt.Println(car.HeaderSize(carReader.Header)) + assert.NoError(t, err) + + for i := 0; i < 4; i++ { + for j := 0; j < 4; j++ { + original := eds.GetCell(uint(i), uint(j)) + block, err := carReader.Next() + assert.NoError(t, err) + assert.Equal(t, original, block.RawData()[share.NamespaceSize:]) + } + } +} + +func TestEDSStore_Remove(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + // assert that file now exists + _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) + assert.NoError(t, err) + + err = edsStore.Remove(ctx, dah) + assert.NoError(t, err) + + // shard should no longer be registered on the dagstore + _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) + assert.Error(t, err, "shard not found") + + // shard should have been dropped from the index, which also removes the file under /index/ + indexStat, err := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) + assert.NoError(t, err) + assert.False(t, indexStat.Exists) + + // file no longer exists + _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) + assert.ErrorContains(t, err, "no such file or directory") +} + +func TestEDSStore_Has(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + + ok, err := edsStore.Has(ctx, dah) + assert.Error(t, err, "shard not found") + assert.False(t, ok) + + err = edsStore.Put(ctx, dah, eds) + assert.NoError(t, err) + + ok, err = edsStore.Has(ctx, dah) + assert.NoError(t, err) + assert.True(t, ok) +} + +func newStore(t *testing.T) (*Store, error) { + t.Helper() + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + return NewStore(tmpDir, ds) +} + +func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + + return eds, dah +} From b4a81459f68427d52ede9397749d9c8d2299571f Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 23 Nov 2022 20:31:15 +0100 Subject: [PATCH 0204/1008] fix: pointing `make openrpc-gen` to the correct cmd (#1390) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5360ce5930..e148abf613 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,6 @@ pb-gen: ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api openrpc-gen: @echo "--> Generating OpenRPC spec" - @go run ./api/docgen/cmd fraud header state share daser + @go run ./cmd/docgen fraud header state share das .PHONY: openrpc-gen From 3d8817a5ad2500631d18f4eb827e3bd70d6e5aba Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 23 Nov 2022 22:11:14 +0100 Subject: [PATCH 0205/1008] feat(nodebuilder/p2p): Implement `Module` (#1285) --- api/rpc/client/client.go | 4 + docs/adr/adr-009-public-api.md | 6 +- go.mod | 2 +- nodebuilder/p2p/flags.go | 2 +- nodebuilder/p2p/host.go | 15 ++- nodebuilder/p2p/module.go | 8 ++ nodebuilder/p2p/p2p.go | 209 ++++++++++++++++++++++++++++++ nodebuilder/p2p/p2p_test.go | 214 +++++++++++++++++++++++++++++++ nodebuilder/rpc/rpc.go | 3 + nodebuilder/tests/swamp/swamp.go | 1 + 10 files changed, 455 insertions(+), 9 deletions(-) create mode 100644 nodebuilder/p2p/p2p.go create mode 100644 nodebuilder/p2p/p2p_test.go diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 8e328f600c..c22d1beca1 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -8,6 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" ) @@ -18,6 +19,7 @@ type API interface { state.Module share.Module das.Module + p2p.Module } type Client struct { @@ -26,6 +28,7 @@ type Client struct { State state.API Share share.API DAS das.API + P2P p2p.API closer multiClientCloser } @@ -64,6 +67,7 @@ func NewClient(ctx context.Context, addr string) (*Client, error) { "header": &client.Header, "fraud": &client.Fraud, "das": &client.DAS, + "p2p": &client.P2P, } for name, module := range modules { closer, err := jsonrpc.NewClient(ctx, addr, name, module, nil) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 575ad3b9fd..56ae47a14e 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -140,10 +140,10 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) ```go type P2PModule interface { - // Info returns basic information about the node's p2p host/operations. - Info() p2p.Info + // Info returns address information about the host. + Info() peer.AddrInfo // Peers returns all peer IDs used across all inner stores. - Peers() peer.IDSlice + Peers() []peer.ID // PeerInfo returns a small slice of information Peerstore has on the // given peer. PeerInfo(id peer.ID) peer.AddrInfo diff --git a/go.mod b/go.mod index f941508db4..005baad5d3 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/libp2p/go-libp2p-peerstore v0.7.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 github.com/libp2p/go-libp2p-record v0.1.3 + github.com/libp2p/go-libp2p-resource-manager v0.5.1 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 @@ -210,7 +211,6 @@ require ( github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect github.com/libp2p/go-libp2p-loggables v0.1.0 // indirect - github.com/libp2p/go-libp2p-resource-manager v0.5.1 // indirect github.com/libp2p/go-msgio v0.2.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-netroute v0.2.0 // indirect diff --git a/nodebuilder/p2p/flags.go b/nodebuilder/p2p/flags.go index 82d03da697..f7a3d9c463 100644 --- a/nodebuilder/p2p/flags.go +++ b/nodebuilder/p2p/flags.go @@ -26,7 +26,7 @@ func Flags() *flag.FlagSet { mutualFlag, nil, `Comma-separated multiaddresses of mutual peers to keep a prioritized connection with. -Such connection is immune to peer scoring slashing and connection manager trimming. +Such connection is immune to peer scoring slashing and connection module trimming. Peers must bidirectionally point to each other. (Format: multiformats.io/multiaddr) `, ) diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index 23dfce9845..3c4f4bda15 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -8,6 +8,8 @@ import ( "github.com/libp2p/go-libp2p-core/connmgr" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" "github.com/libp2p/go-libp2p-core/routing" @@ -15,6 +17,8 @@ import ( routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // RoutedHost constructs a wrapped Host that may fallback to address discovery, @@ -24,7 +28,7 @@ func RoutedHost(base HostBase, r routing.PeerRouting) host.Host { } // Host returns constructor for Host. -func Host(cfg Config, params hostParams) (HostBase, error) { +func Host(cfg Config, params hostParams, bw *metrics.BandwidthCounter, rm network.ResourceManager) (HostBase, error) { opts := []libp2p.Option{ libp2p.NoListenAddrs, // do not listen automatically libp2p.AddrsFactory(params.AddrF), @@ -35,15 +39,16 @@ func Host(cfg Config, params hostParams) (HostBase, error) { libp2p.UserAgent(fmt.Sprintf("celestia-%s", params.Net)), libp2p.NATPortMap(), // enables upnp libp2p.DisableRelay(), + libp2p.BandwidthReporter(bw), + libp2p.ResourceManager(rm), // to clearly define what defaults we rely upon libp2p.DefaultSecurity, libp2p.DefaultTransports, libp2p.DefaultMuxers, } - // TODO(@Wondertan): Other, non Celestia bootstrapper may also enable NATService to contribute the - // network. - if cfg.Bootstrapper { + // All node types except light (bridge, full) will enable NATService + if params.Tp != node.Light { opts = append(opts, libp2p.EnableNATService()) } @@ -72,4 +77,6 @@ type hostParams struct { PStore peerstore.Peerstore ConnMngr connmgr.ConnManager ConnGater *conngater.BasicConnectionGater + + Tp node.Type } diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index f02c9a0bf1..524e4d44b7 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -2,6 +2,9 @@ package p2p import ( logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" + rcmgr "github.com/libp2p/go-libp2p-resource-manager" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -30,6 +33,11 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(PeerRouting), fx.Provide(ContentRouting), fx.Provide(AddrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), + fx.Provide(metrics.NewBandwidthCounter), + fx.Provide(func() (network.ResourceManager, error) { + return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale())) + }), + fx.Provide(newModule), fx.Invoke(Listen(cfg.ListenAddresses)), ) diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go new file mode 100644 index 0000000000..e27e30034a --- /dev/null +++ b/nodebuilder/p2p/p2p.go @@ -0,0 +1,209 @@ +package p2p + +import ( + "context" + "fmt" + "reflect" + + libhost "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + pubsub "github.com/libp2p/go-libp2p-pubsub" + rcmgr "github.com/libp2p/go-libp2p-resource-manager" + basichost "github.com/libp2p/go-libp2p/p2p/host/basic" + "github.com/libp2p/go-libp2p/p2p/net/conngater" +) + +// Module represents all accessible methods related to the node's p2p +// host / operations. +// +//nolint:dupl +type Module interface { + // Info returns address information about the host. + Info() peer.AddrInfo + // Peers returns all peer IDs used across all inner stores. + Peers() []peer.ID + // PeerInfo returns a small slice of information Peerstore has on the + // given peer. + PeerInfo(id peer.ID) peer.AddrInfo + + // Connect ensures there is a connection between this host and the peer with + // given peer. + Connect(ctx context.Context, pi peer.AddrInfo) error + // ClosePeer closes the connection to a given peer. + ClosePeer(id peer.ID) error + // Connectedness returns a state signaling connection capabilities. + Connectedness(id peer.ID) network.Connectedness + // NATStatus returns the current NAT status. + NATStatus() (network.Reachability, error) + + // BlockPeer adds a peer to the set of blocked peers. + BlockPeer(p peer.ID) error + // UnblockPeer removes a peer from the set of blocked peers. + UnblockPeer(p peer.ID) error + // ListBlockedPeers returns a list of blocked peers. + ListBlockedPeers() []peer.ID + // Protect adds a peer to the list of peers who have a bidirectional + // peering agreement that they are protected from being trimmed, dropped + // or negatively scored. + Protect(id peer.ID, tag string) + // Unprotect removes a peer from the list of peers who have a bidirectional + // peering agreement that they are protected from being trimmed, dropped + // or negatively scored, returning a bool representing whether the given + // peer is protected or not. + Unprotect(id peer.ID, tag string) bool + // IsProtected returns whether the given peer is protected. + IsProtected(id peer.ID, tag string) bool + + // BandwidthStats returns a Stats struct with bandwidth metrics for all + // data sent/received by the local peer, regardless of protocol or remote + // peer IDs. + BandwidthStats() metrics.Stats + // BandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID. + // The metrics returned include all traffic sent / received for the peer, regardless of protocol. + BandwidthForPeer(id peer.ID) metrics.Stats + // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID. + BandwidthForProtocol(proto protocol.ID) metrics.Stats + + // ResourceState returns the state of the resource manager. + ResourceState() (rcmgr.ResourceManagerStat, error) + + // PubSubPeers returns the peer IDs of the peers joined on + // the given topic. + PubSubPeers(topic string) []peer.ID +} + +// module contains all components necessary to access information and +// perform actions related to the node's p2p Host / operations. +type module struct { + host HostBase + ps *pubsub.PubSub + connGater *conngater.BasicConnectionGater + bw *metrics.BandwidthCounter + rm network.ResourceManager +} + +func newModule( + host HostBase, + ps *pubsub.PubSub, + cg *conngater.BasicConnectionGater, + bw *metrics.BandwidthCounter, + rm network.ResourceManager, +) Module { + return &module{ + host: host, + ps: ps, + connGater: cg, + bw: bw, + rm: rm, + } +} + +func (m *module) Info() peer.AddrInfo { + return *libhost.InfoFromHost(m.host) +} + +func (m *module) Peers() []peer.ID { + return m.host.Peerstore().Peers() +} + +func (m *module) PeerInfo(id peer.ID) peer.AddrInfo { + return m.host.Peerstore().PeerInfo(id) +} + +func (m *module) Connect(ctx context.Context, pi peer.AddrInfo) error { + return m.host.Connect(ctx, pi) +} + +func (m *module) ClosePeer(id peer.ID) error { + return m.host.Network().ClosePeer(id) +} + +func (m *module) Connectedness(id peer.ID) network.Connectedness { + return m.host.Network().Connectedness(id) +} + +func (m *module) NATStatus() (network.Reachability, error) { + basic, ok := m.host.(*basichost.BasicHost) + if !ok { + return 0, fmt.Errorf("unexpected implementation of host.Host, expected %s, got %T", + reflect.TypeOf(&basichost.BasicHost{}).String(), m.host) + } + return basic.GetAutoNat().Status(), nil +} + +func (m *module) BlockPeer(p peer.ID) error { + return m.connGater.BlockPeer(p) +} + +func (m *module) UnblockPeer(p peer.ID) error { + return m.connGater.UnblockPeer(p) +} + +func (m *module) ListBlockedPeers() []peer.ID { + return m.connGater.ListBlockedPeers() +} + +func (m *module) Protect(id peer.ID, tag string) { + m.host.ConnManager().Protect(id, tag) +} + +func (m *module) Unprotect(id peer.ID, tag string) bool { + return m.host.ConnManager().Unprotect(id, tag) +} + +func (m *module) IsProtected(id peer.ID, tag string) bool { + return m.host.ConnManager().IsProtected(id, tag) +} + +func (m *module) BandwidthStats() metrics.Stats { + return m.bw.GetBandwidthTotals() +} + +func (m *module) BandwidthForPeer(id peer.ID) metrics.Stats { + return m.bw.GetBandwidthForPeer(id) +} + +func (m *module) BandwidthForProtocol(proto protocol.ID) metrics.Stats { + return m.bw.GetBandwidthForProtocol(proto) +} + +func (m *module) ResourceState() (rcmgr.ResourceManagerStat, error) { + rms, ok := m.rm.(rcmgr.ResourceManagerState) + if !ok { + return rcmgr.ResourceManagerStat{}, fmt.Errorf("network.ResourceManager does not implement " + + "rcmgr.ResourceManagerState") + } + return rms.Stat(), nil +} + +func (m *module) PubSubPeers(topic string) []peer.ID { + return m.ps.ListPeers(topic) +} + +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +// +//nolint:dupl +type API struct { + Info func() peer.AddrInfo + Peers func() []peer.ID + PeerInfo func(id peer.ID) peer.AddrInfo + Connect func(ctx context.Context, pi peer.AddrInfo) error + ClosePeer func(id peer.ID) error + Connectedness func(id peer.ID) network.Connectedness + NATStatus func() (network.Reachability, error) + BlockPeer func(p peer.ID) error + UnblockPeer func(p peer.ID) error + ListBlockedPeers func() []peer.ID + Protect func(id peer.ID, tag string) + Unprotect func(id peer.ID, tag string) bool + IsProtected func(id peer.ID, tag string) bool + BandwidthStats func() metrics.Stats + BandwidthForPeer func(id peer.ID) metrics.Stats + BandwidthForProtocol func(proto protocol.ID) metrics.Stats + ResourceState func() (rcmgr.ResourceManagerStat, error) + PubSubPeers func(topic string) []peer.ID +} diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go new file mode 100644 index 0000000000..da77ff4562 --- /dev/null +++ b/nodebuilder/p2p/p2p_test.go @@ -0,0 +1,214 @@ +package p2p + +import ( + "context" + "math/rand" + "testing" + "time" + + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p" + libhost "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p-core/network" + libpeer "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + pubsub "github.com/libp2p/go-libp2p-pubsub" + rcmgr "github.com/libp2p/go-libp2p-resource-manager" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestP2PModule_Host tests P2P Module methods on +// the instance of Host. +func TestP2PModule_Host(t *testing.T) { + net, err := mocknet.FullMeshConnected(2) + require.NoError(t, err) + host, peer := net.Hosts()[0], net.Hosts()[1] + + mgr := newModule(host, nil, nil, nil, nil) + + // test all methods on `manager.host` + assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), mgr.Peers()) + assert.Equal(t, libhost.InfoFromHost(peer).ID, mgr.PeerInfo(peer.ID()).ID) + + assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(peer.ID())) + // now disconnect using manager and check for connectedness match again + assert.NoError(t, mgr.ClosePeer(peer.ID())) + assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(peer.ID())) +} + +// TestP2PModule_ConnManager tests P2P Module methods on +// the Host's ConnManager. Note that this test is constructed differently +// than the one above because mocknet does not provide a ConnManager to its +// mock peers. +func TestP2PModule_ConnManager(t *testing.T) { + // make two full peers and connect them + host, err := libp2p.New() + require.NoError(t, err) + + peer, err := libp2p.New() + require.NoError(t, err) + + mgr := newModule(host, nil, nil, nil, nil) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err = mgr.Connect(ctx, *libhost.InfoFromHost(peer)) + require.NoError(t, err) + + mgr.Protect(peer.ID(), "test") + assert.True(t, mgr.IsProtected(peer.ID(), "test")) + mgr.Unprotect(peer.ID(), "test") + assert.False(t, mgr.IsProtected(peer.ID(), "test")) +} + +// TestP2PModule_Autonat tests P2P Module methods on +// the node's instance of AutoNAT. +func TestP2PModule_Autonat(t *testing.T) { + host, err := libp2p.New(libp2p.EnableNATService()) + require.NoError(t, err) + + mgr := newModule(host, nil, nil, nil, nil) + + status, err := mgr.NATStatus() + assert.NoError(t, err) + assert.Equal(t, network.ReachabilityUnknown, status) +} + +// TestP2PModule_Bandwidth tests P2P Module methods on +// the Host's bandwidth reporter. +func TestP2PModule_Bandwidth(t *testing.T) { + bw := metrics.NewBandwidthCounter() + host, err := libp2p.New(libp2p.BandwidthReporter(bw)) + require.NoError(t, err) + + protoID := protocol.ID("test") + // define a buf size, so we know how many bytes to read + bufSize := 1000 + + // create a peer to connect to + peer, err := libp2p.New(libp2p.BandwidthReporter(bw)) + require.NoError(t, err) + + // set stream handler on the host + host.SetStreamHandler(protoID, func(stream network.Stream) { + buf := make([]byte, bufSize) + _, err := stream.Read(buf) + require.NoError(t, err) + + _, err = stream.Write(buf) + require.NoError(t, err) + }) + + mgr := newModule(host, nil, nil, bw, nil) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // connect to the peer + err = mgr.Connect(ctx, *libhost.InfoFromHost(peer)) + require.NoError(t, err) + // check to ensure they're actually connected + require.Equal(t, network.Connected, mgr.Connectedness(peer.ID())) + + // open stream with host + stream, err := peer.NewStream(ctx, mgr.Info().ID, protoID) + require.NoError(t, err) + + // write to stream to increase bandwidth usage get some substantive + // data to read from the bandwidth counter + buf := make([]byte, bufSize) + _, err = rand.Read(buf) + require.NoError(t, err) + _, err = stream.Write(buf) + require.NoError(t, err) + + _, err = stream.Read(buf) + require.NoError(t, err) + + // has to be ~2 seconds for the metrics reporter to collect the stats + // in the background process + time.Sleep(time.Second * 2) + + stats := mgr.BandwidthStats() + assert.NotNil(t, stats) + peerStat := mgr.BandwidthForPeer(peer.ID()) + assert.NotZero(t, peerStat.TotalIn) + assert.Greater(t, int(peerStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc + protoStat := mgr.BandwidthForProtocol(protoID) + assert.NotZero(t, protoStat.TotalIn) + assert.Greater(t, int(protoStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc +} + +// TestP2PModule_Pubsub tests P2P Module methods on +// the instance of pubsub. +func TestP2PModule_Pubsub(t *testing.T) { + net, err := mocknet.FullMeshConnected(5) + require.NoError(t, err) + + host := net.Hosts()[0] + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + gs, err := pubsub.NewGossipSub(ctx, host) + require.NoError(t, err) + + mgr := newModule(host, gs, nil, nil, nil) + + topicStr := "test-topic" + + topic, err := gs.Join(topicStr) + require.NoError(t, err) + + // also join all peers on mocknet to topic + for _, p := range net.Hosts()[1:] { + newGs, err := pubsub.NewGossipSub(ctx, p) + require.NoError(t, err) + + tp, err := newGs.Join(topicStr) + require.NoError(t, err) + _, err = tp.Subscribe() + require.NoError(t, err) + } + + err = topic.Publish(ctx, []byte("test")) + require.NoError(t, err) + + // give for some peers to properly join the topic (this is necessary + // anywhere where gossipsub is used in tests) + time.Sleep(1 * time.Second) + + assert.Equal(t, len(topic.ListPeers()), len(mgr.PubSubPeers(topicStr))) +} + +// TestP2PModule_ConnGater tests P2P Module methods on +// the instance of ConnectionGater. +func TestP2PModule_ConnGater(t *testing.T) { + gater, err := ConnectionGater(datastore.NewMapDatastore()) + require.NoError(t, err) + + mgr := newModule(nil, nil, gater, nil, nil) + + assert.NoError(t, mgr.BlockPeer("badpeer")) + assert.Len(t, mgr.ListBlockedPeers(), 1) + assert.NoError(t, mgr.UnblockPeer("badpeer")) + assert.Len(t, mgr.ListBlockedPeers(), 0) +} + +// TestP2PModule_ResourceManager tests P2P Module methods on +// the ResourceManager. +func TestP2PModule_ResourceManager(t *testing.T) { + rm, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale())) + require.NoError(t, err) + + mgr := newModule(nil, nil, nil, nil, rm) + + state, err := mgr.ResourceState() + require.NoError(t, err) + + assert.NotNil(t, state) +} diff --git a/nodebuilder/rpc/rpc.go b/nodebuilder/rpc/rpc.go index 8a1b0e1f85..7b46e39c8f 100644 --- a/nodebuilder/rpc/rpc.go +++ b/nodebuilder/rpc/rpc.go @@ -5,6 +5,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" ) @@ -16,6 +17,7 @@ func RegisterEndpoints( fraud fraud.Module, header header.Module, daser das.Module, + p2p p2p.Module, serv *rpc.Server, ) { serv.RegisterService("state", state) @@ -23,6 +25,7 @@ func RegisterEndpoints( serv.RegisterService("fraud", fraud) serv.RegisterService("header", header) serv.RegisterService("das", daser) + serv.RegisterService("p2p", p2p) } func Server(cfg *Config) *rpc.Server { diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 6351c8f2c8..59325c1d69 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -18,6 +18,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" From 95907f47fe477237a3a19701d0f108833f40f055 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 25 Nov 2022 13:01:05 +0100 Subject: [PATCH 0206/1008] refactor: changing `nodebuilder//service.go` to `.go` (#1301) Closes https://github.com/celestiaorg/celestia-node/issues/1278 --- api/docgen/openrpc.go | 3 +- nodebuilder/core/{core.go => constructors.go} | 0 nodebuilder/das/{daser.go => constructors.go} | 0 nodebuilder/das/{service.go => das.go} | 0 .../gateway/{gateway.go => constructors.go} | 0 nodebuilder/header/constructors.go | 79 +++++++++++++++++ nodebuilder/header/header.go | 86 ++++--------------- nodebuilder/header/service.go | 24 +----- nodebuilder/rpc/{rpc.go => constructors.go} | 0 nodebuilder/share/constructors.go | 54 ++++++++++++ nodebuilder/share/module.go | 16 ++-- nodebuilder/share/service.go | 66 -------------- nodebuilder/share/share.go | 64 ++++++++------ nodebuilder/state/{service.go => state.go} | 0 share/empty.go | 2 +- 15 files changed, 199 insertions(+), 195 deletions(-) rename nodebuilder/core/{core.go => constructors.go} (100%) rename nodebuilder/das/{daser.go => constructors.go} (100%) rename nodebuilder/das/{service.go => das.go} (100%) rename nodebuilder/gateway/{gateway.go => constructors.go} (100%) create mode 100644 nodebuilder/header/constructors.go rename nodebuilder/rpc/{rpc.go => constructors.go} (100%) create mode 100644 nodebuilder/share/constructors.go delete mode 100644 nodebuilder/share/service.go rename nodebuilder/state/{service.go => state.go} (100%) diff --git a/api/docgen/openrpc.go b/api/docgen/openrpc.go index 52871fc189..9c05137952 100644 --- a/api/docgen/openrpc.go +++ b/api/docgen/openrpc.go @@ -56,7 +56,8 @@ func ParseCommentsFromNodebuilderModules(moduleNames ...string) Comments { fset := token.NewFileSet() nodeComments := make(Comments) for _, moduleName := range moduleNames { - f, err := parser.ParseFile(fset, "nodebuilder/"+moduleName+"/service.go", nil, parser.AllErrors|parser.ParseComments) + fileName := fmt.Sprintf("nodebuilder/%s/%s.go", moduleName, moduleName) + f, err := parser.ParseFile(fset, fileName, nil, parser.AllErrors|parser.ParseComments) if err != nil { panic(err) } diff --git a/nodebuilder/core/core.go b/nodebuilder/core/constructors.go similarity index 100% rename from nodebuilder/core/core.go rename to nodebuilder/core/constructors.go diff --git a/nodebuilder/das/daser.go b/nodebuilder/das/constructors.go similarity index 100% rename from nodebuilder/das/daser.go rename to nodebuilder/das/constructors.go diff --git a/nodebuilder/das/service.go b/nodebuilder/das/das.go similarity index 100% rename from nodebuilder/das/service.go rename to nodebuilder/das/das.go diff --git a/nodebuilder/gateway/gateway.go b/nodebuilder/gateway/constructors.go similarity index 100% rename from nodebuilder/gateway/gateway.go rename to nodebuilder/gateway/constructors.go diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go new file mode 100644 index 0000000000..a776429c1b --- /dev/null +++ b/nodebuilder/header/constructors.go @@ -0,0 +1,79 @@ +package header + +import ( + "context" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/p2p" + "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/header/sync" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" +) + +// newP2PServer constructs a new ExchangeServer using the given Network as a protocolID suffix. +func newP2PServer(host host.Host, store header.Store, network modp2p.Network) *p2p.ExchangeServer { + return p2p.NewExchangeServer(host, store, string(network)) +} + +// newP2PExchange constructs a new Exchange for headers. +func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, modp2p.Network, host.Host) (header.Exchange, error) { + return func(bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host) (header.Exchange, error) { + peers, err := cfg.trustedPeers(bpeers) + if err != nil { + return nil, err + } + ids := make([]peer.ID, len(peers)) + for index, peer := range peers { + ids[index] = peer.ID + host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) + } + return p2p.NewExchange(host, ids, string(network)), nil + } +} + +// newSyncer constructs new Syncer for headers. +func newSyncer(ex header.Exchange, store initStore, sub header.Subscriber, duration time.Duration) *sync.Syncer { + return sync.NewSyncer(ex, store, sub, duration) +} + +// initStore is a type representing initialized header store. +// NOTE: It is needed to ensure that Store is always initialized before Syncer is started. +type initStore header.Store + +// newInitStore constructs an initialized store +func newInitStore( + lc fx.Lifecycle, + cfg Config, + net modp2p.Network, + s header.Store, + ex header.Exchange, +) (initStore, error) { + trustedHash, err := cfg.trustedHash(net) + if err != nil { + return nil, err + } + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + err = store.Init(ctx, s, ex, trustedHash) + if err != nil { + // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. + // This is due to requesting step of initialization, which fetches initial Header by trusted hash from + // the network. The step can't be done during unit tests and fixing it would require either + // * Having some test/dev/offline mode for Node that mocks out all the networking + // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step + // * Or removing explicit initialization in favor of automated initialization by Syncer + log.Errorf("initializing header store failed: %s", err) + } + return nil + }, + }) + + return s, nil +} diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 7b6392c32d..cc85faecfc 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -2,78 +2,28 @@ package header import ( "context" - "time" - - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" - "go.uber.org/fx" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" - modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -// newP2PServer constructs a new ExchangeServer using the given Network as a protocolID suffix. -func newP2PServer(host host.Host, store header.Store, network modp2p.Network) *p2p.ExchangeServer { - return p2p.NewExchangeServer(host, store, string(network)) -} - -// newP2PExchange constructs a new Exchange for headers. -func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, modp2p.Network, host.Host) (header.Exchange, error) { - return func(bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host) (header.Exchange, error) { - peers, err := cfg.trustedPeers(bpeers) - if err != nil { - return nil, err - } - ids := make([]peer.ID, len(peers)) - for index, peer := range peers { - ids[index] = peer.ID - host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) - } - return p2p.NewExchange(host, ids, string(network)), nil - } -} - -// newSyncer constructs new Syncer for headers. -func newSyncer(ex header.Exchange, store initStore, sub header.Subscriber, duration time.Duration) *sync.Syncer { - return sync.NewSyncer(ex, store, sub, duration) +// Module exposes the functionality needed for querying headers from the network. +// Any method signature changed here needs to also be changed in the API struct. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + // GetByHeight returns the ExtendedHeader at the given height, blocking + // until header has been processed by the store or context deadline is exceeded. + GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) + // Head returns the ExtendedHeader of the chain head. + Head(context.Context) (*header.ExtendedHeader, error) + // IsSyncing returns the status of sync + IsSyncing() bool } -// initStore is a type representing initialized header store. -// NOTE: It is needed to ensure that Store is always initialized before Syncer is started. -type initStore header.Store - -// newInitStore constructs an initialized store -func newInitStore( - lc fx.Lifecycle, - cfg Config, - net modp2p.Network, - s header.Store, - ex header.Exchange, -) (initStore, error) { - trustedHash, err := cfg.trustedHash(net) - if err != nil { - return nil, err - } - - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - err = store.Init(ctx, s, ex, trustedHash) - if err != nil { - // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. - // This is due to requesting step of initialization, which fetches initial Header by trusted hash - // from the network. The step can't be done during unit tests and fixing it would require either - // * Having some test/dev/offline mode for Node that mocks out all the networking - // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step - // * Or removing explicit initialization in favor of automated initialization by Syncer - log.Errorf("initializing header store failed: %s", err) - } - return nil - }, - }) - - return s, nil +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +type API struct { + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) + Head func(context.Context) (*header.ExtendedHeader, error) + IsSyncing func() bool } diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 55dd283651..b569df00d3 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -8,29 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/header/sync" ) -// Module exposes the functionality needed for querying headers from the network. -// Any method signature changed here needs to also be changed in the API struct. -// -//go:generate mockgen -destination=mocks/api.go -package=mocks . Module -type Module interface { - // GetByHeight returns the ExtendedHeader at the given height, blocking - // until header has been processed by the store or context deadline is exceeded. - GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) - // Head returns the ExtendedHeader of the chain head. - Head(context.Context) (*header.ExtendedHeader, error) - // IsSyncing returns the status of sync - IsSyncing() bool -} - -// API is a wrapper around Module for the RPC. -// TODO(@distractedm1nd): These structs need to be autogenerated. -type API struct { - GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) - Head func(context.Context) (*header.ExtendedHeader, error) - IsSyncing func() bool -} - -// Service represents the header service that can be started / stopped on a node. +// Service represents the header Service that can be started / stopped on a node. // Service's main function is to manage its sub-services. Service can contain several // sub-services, such as Exchange, ExchangeServer, Syncer, and so forth. type Service struct { diff --git a/nodebuilder/rpc/rpc.go b/nodebuilder/rpc/constructors.go similarity index 100% rename from nodebuilder/rpc/rpc.go rename to nodebuilder/rpc/constructors.go diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go new file mode 100644 index 0000000000..45277ab0e1 --- /dev/null +++ b/nodebuilder/share/constructors.go @@ -0,0 +1,54 @@ +package share + +import ( + "context" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/routing" + routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/cache" + disc "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/service" +) + +func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { + return func( + r routing.ContentRouting, + h host.Host, + ) *disc.Discovery { + return disc.NewDiscovery( + h, + routingdisc.NewRoutingDiscovery(r), + cfg.PeersLimit, + cfg.DiscoveryInterval, + cfg.AdvertiseInterval, + ) + } +} + +// cacheAvailability wraps either Full or Light availability with a cache for result sampling. +func cacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batching, avail A) share.Availability { + ca := cache.NewShareAvailability(avail, ds) + lc.Append(fx.Hook{ + OnStop: ca.Close, + }) + return ca +} + +func newModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Availability) Module { + serv := service.NewShareService(bServ, avail) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return serv.Start(ctx) + }, + OnStop: func(ctx context.Context) error { + return serv.Stop(ctx) + }, + }) + return serv +} diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 53ffa972e9..a882953985 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -21,8 +21,8 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Error(cfgErr), fx.Options(options...), fx.Invoke(share.EnsureEmptySquareExists), - fx.Provide(Discovery(*cfg)), - fx.Provide(NewModule), + fx.Provide(discovery(*cfg)), + fx.Provide(newModule), ) switch tp { @@ -39,9 +39,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return avail.Stop(ctx) }), )), - // CacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a CacheAvailability but the constructor returns a share.Availability - fx.Provide(CacheAvailability[*light.ShareAvailability]), + // cacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a cacheAvailability but the constructor returns a share.Availability + fx.Provide(cacheAvailability[*light.ShareAvailability]), ) case node.Bridge, node.Full: return fx.Module( @@ -56,9 +56,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return avail.Stop(ctx) }), )), - // CacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a CacheAvailability but the constructor returns a share.Availability - fx.Provide(CacheAvailability[*full.ShareAvailability]), + // cacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a cacheAvailability but the constructor returns a share.Availability + fx.Provide(cacheAvailability[*full.ShareAvailability]), ) default: panic("invalid node type") diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go deleted file mode 100644 index 097604a648..0000000000 --- a/nodebuilder/share/service.go +++ /dev/null @@ -1,66 +0,0 @@ -package share - -import ( - "context" - - "github.com/celestiaorg/celestia-node/share/service" - - "github.com/ipfs/go-blockservice" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/nmt/namespace" -) - -// Module provides access to any data square or block share on the network. -// -// All Get methods provided on Module follow the following flow: -// 1. Check local storage for the requested Share. -// 2. If exists -// * Load from disk -// * Return -// 3. If not -// * Find provider on the network -// * Fetch the Share from the provider -// * Store the Share -// * Return -// -// Any method signature changed here needs to also be changed in the API struct. -// -//go:generate mockgen -destination=mocks/api.go -package=mocks . Module -type Module interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on - // the Network. - SharesAvailable(context.Context, *share.Root) error - // ProbabilityOfAvailability calculates the probability of the data square - // being available based on the number of samples collected. - ProbabilityOfAvailability() float64 - GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) - GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) - // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the - // given namespace.ID. - GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) -} - -func NewModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Availability) Module { - serv := service.NewShareService(bServ, avail) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return serv.Start(ctx) - }, - OnStop: func(ctx context.Context) error { - return serv.Stop(ctx) - }, - }) - return serv -} - -// API is a wrapper around Module for the RPC. -// TODO(@distractedm1nd): These structs need to be autogenerated. -type API struct { - SharesAvailable func(context.Context, *share.Root) error - ProbabilityOfAvailability func() float64 - GetShare func(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) - GetShares func(ctx context.Context, root *share.Root) ([][]share.Share, error) - GetSharesByNamespace func(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) -} diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index 2803a29d96..9c51ac5f60 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -1,38 +1,46 @@ package share import ( - "go.uber.org/fx" - - "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/routing" - routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "context" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/cache" - "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/nmt/namespace" ) -func Discovery(cfg Config) func(routing.ContentRouting, host.Host) *discovery.Discovery { - return func( - r routing.ContentRouting, - h host.Host, - ) *discovery.Discovery { - return discovery.NewDiscovery( - h, - routingdisc.NewRoutingDiscovery(r), - cfg.PeersLimit, - cfg.DiscoveryInterval, - cfg.AdvertiseInterval, - ) - } +// Module provides access to any data square or block share on the network. +// +// All Get methods provided on Module follow the following flow: +// 1. Check local storage for the requested Share. +// 2. If exists +// * Load from disk +// * Return +// 3. If not +// * Find provider on the network +// * Fetch the Share from the provider +// * Store the Share +// * Return +// +// Any method signature changed here needs to also be changed in the API struct. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. + SharesAvailable(context.Context, *share.Root) error + // ProbabilityOfAvailability calculates the probability of the data square + // being available based on the number of samples collected. + ProbabilityOfAvailability() float64 + GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) + GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) + // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. + GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) } -// CacheAvailability wraps either Full or Light availability with a cache for result sampling. -func CacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batching, avail A) share.Availability { - ca := cache.NewShareAvailability(avail, ds) - lc.Append(fx.Hook{ - OnStop: ca.Close, - }) - return ca +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +type API struct { + SharesAvailable func(context.Context, *share.Root) error + ProbabilityOfAvailability func() float64 + GetShare func(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) + GetShares func(ctx context.Context, root *share.Root) ([][]share.Share, error) + GetSharesByNamespace func(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) } diff --git a/nodebuilder/state/service.go b/nodebuilder/state/state.go similarity index 100% rename from nodebuilder/state/service.go rename to nodebuilder/state/state.go diff --git a/share/empty.go b/share/empty.go index 6116307399..08c3f3f278 100644 --- a/share/empty.go +++ b/share/empty.go @@ -12,7 +12,7 @@ import ( // EnsureEmptySquareExists checks if the given DAG contains an empty block data square. // If it does not, it stores an empty block. This optimization exists to prevent // redundant storing of empty block data so that it is only stored once and returned -// upon request for a block with an empty data square. Ref: header/header.go#L56 +// upon request for a block with an empty data square. Ref: header/constructors.go#L56 func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockService) error { shares := make([][]byte, appconsts.MinShareCount) for i := 0; i < appconsts.MinShareCount; i++ { From 7df5cf85a3a6813a1a4e58775b365deac22b04ed Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 25 Nov 2022 15:51:31 +0200 Subject: [PATCH 0207/1008] improvement(header/store): make all global variables configurable (#1388) Related to [#709](https://github.com/celestiaorg/celestia-node/issues/709) --- header/store/height_indexer.go | 4 +- header/store/options.go | 70 ++++++++++++++++++++++++ header/store/store.go | 90 ++++++++++++++++--------------- header/store/store_test.go | 7 +-- nodebuilder/header/config.go | 9 ++++ nodebuilder/header/module.go | 14 ++++- nodebuilder/header/module_test.go | 40 ++++++++++++++ 7 files changed, 185 insertions(+), 49 deletions(-) create mode 100644 header/store/options.go create mode 100644 nodebuilder/header/module_test.go diff --git a/header/store/height_indexer.go b/header/store/height_indexer.go index a1e64986a4..f8e0697a22 100644 --- a/header/store/height_indexer.go +++ b/header/store/height_indexer.go @@ -19,8 +19,8 @@ type heightIndexer struct { } // newHeightIndexer creates new heightIndexer. -func newHeightIndexer(ds datastore.Batching) (*heightIndexer, error) { - cache, err := lru.NewARC(DefaultIndexCacheSize) +func newHeightIndexer(ds datastore.Batching, indexCacheSize int) (*heightIndexer, error) { + cache, err := lru.NewARC(indexCacheSize) if err != nil { return nil, err } diff --git a/header/store/options.go b/header/store/options.go new file mode 100644 index 0000000000..d9c3246665 --- /dev/null +++ b/header/store/options.go @@ -0,0 +1,70 @@ +package store + +import ( + "fmt" +) + +// Option is the functional option that is applied to the store instance +// to configure store parameters. +type Option func(*Parameters) + +// Parameters is the set of parameters that must be configured for the store. +type Parameters struct { + // StoreCacheSize defines the maximum amount of entries in the Header Store cache. + StoreCacheSize int + + // IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. + IndexCacheSize int + + // WriteBatchSize defines the size of the batched header write. + // Headers are written in batches not to thrash the underlying Datastore with writes. + WriteBatchSize int +} + +// DefaultParameters returns the default params to configure the store. +func DefaultParameters() *Parameters { + return &Parameters{ + StoreCacheSize: 4096, + IndexCacheSize: 16384, + WriteBatchSize: 2048, + } +} + +const errSuffix = "value should be positive and non-zero" + +func (p *Parameters) Validate() error { + if p.StoreCacheSize <= 0 { + return fmt.Errorf("invalid store cache size:%s", errSuffix) + } + if p.IndexCacheSize <= 0 { + return fmt.Errorf("invalid indexer cache size:%s", errSuffix) + } + if p.WriteBatchSize <= 0 { + return fmt.Errorf("invalid batch size:%s", errSuffix) + } + return nil +} + +// WithStoreCacheSize is a functional option that configures the +// `StoreCacheSize` parameter. +func WithStoreCacheSize(size int) Option { + return func(p *Parameters) { + p.StoreCacheSize = size + } +} + +// WithIndexCacheSize is a functional option that configures the +// `IndexCacheSize` parameter. +func WithIndexCacheSize(size int) Option { + return func(p *Parameters) { + p.IndexCacheSize = size + } +} + +// WithWriteBatchSize is a functional option that configures the +// `WriteBatchSize` parameter. +func WithWriteBatchSize(size int) Option { + return func(p *Parameters) { + p.WriteBatchSize = size + } +} diff --git a/header/store/store.go b/header/store/store.go index 8331e03034..9587fa79f7 100644 --- a/header/store/store.go +++ b/header/store/store.go @@ -18,26 +18,13 @@ import ( var log = logging.Logger("header/store") -// TODO(@Wondertan): Those values must be configurable and proper defaults should be set for -// specific node type. (#709) -var ( - // DefaultStoreCacheSize defines the amount of max entries allowed in the Header Store cache. - DefaultStoreCacheSize = 4096 - // DefaultIndexCacheSize defines the amount of max entries allowed in the Height to Hash index - // cache. - DefaultIndexCacheSize = 16384 - // DefaultWriteBatchSize defines the size of the batched header write. - // Headers are written in batches not to thrash the underlying Datastore with writes. - DefaultWriteBatchSize = 2048 -) - var ( // errStoppedStore is returned for attempted operations on a stopped store errStoppedStore = errors.New("stopped store") ) // store implements the Store interface for ExtendedHeaders over Datastore. -type store struct { +type Store struct { // header storing // // underlying KV store @@ -63,18 +50,25 @@ type store struct { writeHead atomic.Pointer[header.ExtendedHeader] // pending keeps headers pending to be written in one batch pending *batch + + Params *Parameters } // NewStore constructs a Store over datastore. // The datastore must have a head there otherwise Start will error. // For first initialization of Store use NewStoreWithHead. -func NewStore(ds datastore.Batching) (header.Store, error) { - return newStore(ds) +func NewStore(ds datastore.Batching, opts ...Option) (*Store, error) { + return newStore(ds, opts...) } // NewStoreWithHead initiates a new Store and forcefully sets a given trusted header as head. -func NewStoreWithHead(ctx context.Context, ds datastore.Batching, head *header.ExtendedHeader) (header.Store, error) { - store, err := newStore(ds) +func NewStoreWithHead( + ctx context.Context, + ds datastore.Batching, + head *header.ExtendedHeader, + opts ...Option, +) (*Store, error) { + store, err := newStore(ds, opts...) if err != nil { return nil, err } @@ -82,30 +76,40 @@ func NewStoreWithHead(ctx context.Context, ds datastore.Batching, head *header.E return store, store.Init(ctx, head) } -func newStore(ds datastore.Batching) (*store, error) { - ds = namespace.Wrap(ds, storePrefix) - cache, err := lru.NewARC(DefaultStoreCacheSize) +func newStore(ds datastore.Batching, opts ...Option) (*Store, error) { + params := DefaultParameters() + for _, opt := range opts { + opt(params) + } + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("header/store: store creation failed: %w", err) + } + + cache, err := lru.NewARC(params.StoreCacheSize) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create index cache: %w", err) } - index, err := newHeightIndexer(ds) + wrappedStore := namespace.Wrap(ds, storePrefix) + index, err := newHeightIndexer(wrappedStore, params.IndexCacheSize) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create height indexer: %w", err) } - return &store{ - ds: ds, - cache: cache, - heightIndex: index, + return &Store{ + Params: params, + ds: wrappedStore, heightSub: newHeightSub(), writes: make(chan []*header.ExtendedHeader, 16), writesDn: make(chan struct{}), - pending: newBatch(DefaultWriteBatchSize), + cache: cache, + heightIndex: index, + pending: newBatch(params.WriteBatchSize), }, nil } -func (s *store) Init(ctx context.Context, initial *header.ExtendedHeader) error { +func (s *Store) Init(ctx context.Context, initial *header.ExtendedHeader) error { // trust the given header as the initial head err := s.flush(ctx, initial) if err != nil { @@ -116,12 +120,12 @@ func (s *store) Init(ctx context.Context, initial *header.ExtendedHeader) error return nil } -func (s *store) Start(context.Context) error { +func (s *Store) Start(context.Context) error { go s.flushLoop() return nil } -func (s *store) Stop(ctx context.Context) error { +func (s *Store) Stop(ctx context.Context) error { select { case <-s.writesDn: return errStoppedStore @@ -141,11 +145,11 @@ func (s *store) Stop(ctx context.Context) error { return nil } -func (s *store) Height() uint64 { +func (s *Store) Height() uint64 { return s.heightSub.Height() } -func (s *store) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Store) Head(ctx context.Context) (*header.ExtendedHeader, error) { head, err := s.GetByHeight(ctx, s.heightSub.Height()) if err == nil { return head, nil @@ -164,7 +168,7 @@ func (s *store) Head(ctx context.Context) (*header.ExtendedHeader, error) { } } -func (s *store) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { +func (s *Store) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { if v, ok := s.cache.Get(hash.String()); ok { return v.(*header.ExtendedHeader), nil } @@ -191,7 +195,7 @@ func (s *store) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.Extende return h, nil } -func (s *store) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { +func (s *Store) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { if height == 0 { return nil, fmt.Errorf("header/store: height must be bigger than zero") } @@ -221,7 +225,7 @@ func (s *store) GetByHeight(ctx context.Context, height uint64) (*header.Extende return s.Get(ctx, hash) } -func (s *store) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { +func (s *Store) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { h, err := s.GetByHeight(ctx, to-1) if err != nil { return nil, err @@ -241,7 +245,7 @@ func (s *store) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*heade return headers, nil } -func (s *store) Has(ctx context.Context, hash tmbytes.HexBytes) (bool, error) { +func (s *Store) Has(ctx context.Context, hash tmbytes.HexBytes) (bool, error) { if ok := s.cache.Contains(hash.String()); ok { return ok, nil } @@ -253,7 +257,7 @@ func (s *store) Has(ctx context.Context, hash tmbytes.HexBytes) (bool, error) { return s.ds.Has(ctx, datastore.NewKey(hash.String())) } -func (s *store) Append(ctx context.Context, headers ...*header.ExtendedHeader) (int, error) { +func (s *Store) Append(ctx context.Context, headers ...*header.ExtendedHeader) (int, error) { lh := len(headers) if lh == 0 { return 0, nil @@ -315,7 +319,7 @@ func (s *store) Append(ctx context.Context, headers ...*header.ExtendedHeader) ( // This way writes are controlled and manageable from one place allowing // (1) Appends not to be blocked on long disk IO writes and underlying DB compactions // (2) Batching header writes -func (s *store) flushLoop() { +func (s *Store) flushLoop() { defer close(s.writesDn) ctx := context.Background() for headers := range s.writes { @@ -327,7 +331,7 @@ func (s *store) flushLoop() { s.heightSub.Pub(headers...) // don't flush and continue if pending batch is not grown enough, // and Store is not stopping(headers == nil) - if s.pending.Len() < DefaultWriteBatchSize && headers != nil { + if s.pending.Len() < s.Params.WriteBatchSize && headers != nil { continue } @@ -349,7 +353,7 @@ func (s *store) flushLoop() { } // flush writes the given batch to datastore. -func (s *store) flush(ctx context.Context, headers ...*header.ExtendedHeader) error { +func (s *Store) flush(ctx context.Context, headers ...*header.ExtendedHeader) error { ln := len(headers) if ln == 0 { return nil @@ -395,7 +399,7 @@ func (s *store) flush(ctx context.Context, headers ...*header.ExtendedHeader) er } // readHead loads the head from the datastore. -func (s *store) readHead(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Store) readHead(ctx context.Context) (*header.ExtendedHeader, error) { b, err := s.ds.Get(ctx, headKey) if err != nil { return nil, err diff --git a/header/store/store_test.go b/header/store/store_test.go index e6bc3ad50a..460c5e78cd 100644 --- a/header/store/store_test.go +++ b/header/store/store_test.go @@ -96,9 +96,10 @@ func TestStorePendingCacheMiss(t *testing.T) { ds := sync.MutexWrap(datastore.NewMapDatastore()) - DefaultWriteBatchSize = 100 - DefaultStoreCacheSize = 100 - store, err := NewStoreWithHead(ctx, ds, suite.Head()) + store, err := NewStoreWithHead(ctx, ds, suite.Head(), + WithWriteBatchSize(100), + WithStoreCacheSize(100), + ) require.NoError(t, err) err = store.Start(ctx) diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 3f9318dba3..08701311c4 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -2,11 +2,13 @@ package header import ( "encoding/hex" + "fmt" "github.com/libp2p/go-libp2p-core/peer" "github.com/multiformats/go-multiaddr" tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -19,12 +21,15 @@ type Config struct { // Note: The trusted does *not* imply Headers are not verified, but trusted as reliable to fetch // headers at any moment. TrustedPeers []string + + Store *store.Parameters } func DefaultConfig() Config { return Config{ TrustedHash: "", TrustedPeers: make([]string, 0), + Store: store.DefaultParameters(), } } @@ -62,5 +67,9 @@ func (cfg *Config) trustedHash(net p2p.Network) (tmbytes.HexBytes, error) { // Validate performs basic validation of the config. func (cfg *Config) Validate() error { + err := cfg.Store.Validate() + if err != nil { + return fmt.Errorf("module/header: misconfiguration of store: %w", err) + } return nil } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index e465e1374a..416d4a03a6 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -3,6 +3,7 @@ package header import ( "context" + "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" @@ -26,9 +27,20 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Supply(*cfg), fx.Error(cfgErr), fx.Supply(modp2p.BlockTime), + fx.Provide( + func(cfg Config) []store.Option { + return []store.Option{ + store.WithStoreCacheSize(cfg.Store.StoreCacheSize), + store.WithIndexCacheSize(cfg.Store.IndexCacheSize), + store.WithWriteBatchSize(cfg.Store.WriteBatchSize), + } + }, + ), fx.Provide(NewHeaderService), fx.Provide(fx.Annotate( - store.NewStore, + func(ds datastore.Batching, opts []store.Option) (header.Store, error) { + return store.NewStore(ds, opts...) + }, fx.OnStart(func(ctx context.Context, store header.Store) error { return store.Start(ctx) }), diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go new file mode 100644 index 0000000000..68378ac6b3 --- /dev/null +++ b/nodebuilder/header/module_test.go @@ -0,0 +1,40 @@ +package header + +import ( + "testing" + + "github.com/ipfs/go-datastore" + "github.com/stretchr/testify/require" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +// TestConstructModule_StoreParams ensures that all passed via functional options +// params are set in store correctly. +func TestConstructModule_StoreParams(t *testing.T) { + cfg := DefaultConfig() + cfg.Store.StoreCacheSize = 15 + cfg.Store.IndexCacheSize = 25 + cfg.Store.WriteBatchSize = 35 + var headerStore *store.Store + + app := fxtest.New(t, + fx.Provide(func() datastore.Batching { + return datastore.NewMapDatastore() + }), + ConstructModule(node.Light, &cfg), + fx.Invoke( + func(s header.Store) { + ss := s.(*store.Store) + headerStore = ss + }), + ) + require.NoError(t, app.Err()) + require.Equal(t, headerStore.Params.StoreCacheSize, cfg.Store.StoreCacheSize) + require.Equal(t, headerStore.Params.IndexCacheSize, cfg.Store.IndexCacheSize) + require.Equal(t, headerStore.Params.WriteBatchSize, cfg.Store.WriteBatchSize) +} From 0849c194c3009a6a94e191064811f2fe67c020b8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 25 Nov 2022 15:15:34 +0100 Subject: [PATCH 0208/1008] fix(nodebuilder/das): Skip config validation for bridge node (#1373) Related to #1372 Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 4 ++-- nodebuilder/config.go | 28 ++++++++++++++++------------ nodebuilder/config_test.go | 27 +++++++++++++++++++-------- nodebuilder/das/module.go | 7 ++++++- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 005baad5d3..3b3c4546e7 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/math v1.0.0-beta.3 - github.com/BurntSushi/toml v1.2.0 + github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/celestiaorg/celestia-app v0.10.0-rc1 github.com/celestiaorg/go-libp2p-messenger v0.1.0 diff --git a/go.sum b/go.sum index 62e2c512d6..efd319f8c3 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= diff --git a/nodebuilder/config.go b/nodebuilder/config.go index 56a90fe4c5..b18c179592 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -30,24 +30,28 @@ type Config struct { Gateway gateway.Config Share share.Config Header header.Config - DASer das.Config + DASer das.Config `toml:",omitempty"` } // DefaultConfig provides a default Config for a given Node Type 'tp'. // NOTE: Currently, configs are identical, but this will change. func DefaultConfig(tp node.Type) *Config { + commonConfig := &Config{ + Core: core.DefaultConfig(), + State: state.DefaultConfig(), + P2P: p2p.DefaultConfig(), + RPC: rpc.DefaultConfig(), + Gateway: gateway.DefaultConfig(), + Share: share.DefaultConfig(), + Header: header.DefaultConfig(), + } + switch tp { - case node.Bridge, node.Light, node.Full: - return &Config{ - Core: core.DefaultConfig(), - State: state.DefaultConfig(), - P2P: p2p.DefaultConfig(), - RPC: rpc.DefaultConfig(), - Gateway: gateway.DefaultConfig(), - Share: share.DefaultConfig(), - Header: header.DefaultConfig(), - DASer: das.DefaultConfig(), - } + case node.Bridge: + return commonConfig + case node.Light, node.Full: + commonConfig.DASer = das.DefaultConfig() + return commonConfig default: panic("node: invalid node type") } diff --git a/nodebuilder/config_test.go b/nodebuilder/config_test.go index 0a603137a8..202a54a848 100644 --- a/nodebuilder/config_test.go +++ b/nodebuilder/config_test.go @@ -10,15 +10,26 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) +// TestConfigWriteRead tests that the configs for all node types can be encoded to and from TOML. func TestConfigWriteRead(t *testing.T) { - buf := bytes.NewBuffer(nil) - in := DefaultConfig(node.Bridge) + tests := []node.Type{ + node.Full, + node.Light, + node.Bridge, + } - err := in.Encode(buf) - require.NoError(t, err) + for _, tp := range tests { + t.Run(tp.String(), func(t *testing.T) { + buf := bytes.NewBuffer(nil) + in := DefaultConfig(tp) - var out Config - err = out.Decode(buf) - require.NoError(t, err) - assert.EqualValues(t, in, &out) + err := in.Encode(buf) + require.NoError(t, err) + + var out Config + err = out.Decode(buf) + require.NoError(t, err) + assert.EqualValues(t, in, &out) + }) + } } diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index a2b22cf195..6110cf4a53 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -12,7 +12,12 @@ import ( ) func ConstructModule(tp node.Type, cfg *Config) fx.Option { - err := cfg.Validate() + var err error + // do not validate daser config for bridge node as it + // does not need it + if tp != node.Bridge { + err = cfg.Validate() + } baseComponents := fx.Options( fx.Supply(*cfg), From f39129f9df60d37eabbe49773ef2487bd83f6392 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 25 Nov 2022 15:34:37 +0100 Subject: [PATCH 0209/1008] Add `kind:docs` as an oprtion for required labels (#1393) --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 63543b258b..8c92eed914 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -16,4 +16,4 @@ jobs: with: mode: minimum count: 1 - labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:improvement, kind:feature, kind:dependencies" # yamllint disable-line rule:line-length + labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:improvement, kind:feature, kind:dependencies, kind:docs" # yamllint disable-line rule:line-length From c71c036db1442d8455200f2eab6f4dd4153242ed Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:44:20 +0300 Subject: [PATCH 0210/1008] feat(share): Implement ODSreader (#1377) Implements ODSReader Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- share/eds/ods.go | 98 +++++++++++++++++++++++++++++++++++++++++++ share/eds/ods_test.go | 94 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 share/eds/ods.go create mode 100644 share/eds/ods_test.go diff --git a/go.mod b/go.mod index 3b3c4546e7..da67d170e6 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/ipfs/go-ipfs-exchange-interface v0.2.0 github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-ipfs-routing v0.2.1 + github.com/ipfs/go-ipld-cbor v0.0.5 github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.7.0 @@ -183,7 +184,6 @@ require ( github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect github.com/ipfs/go-ipfs-pq v0.0.2 // indirect github.com/ipfs/go-ipfs-util v0.0.2 // indirect - github.com/ipfs/go-ipld-cbor v0.0.5 // indirect github.com/ipfs/go-ipld-legacy v0.1.0 // indirect github.com/ipfs/go-ipns v0.1.2 // indirect github.com/ipfs/go-log v1.0.5 // indirect diff --git a/share/eds/ods.go b/share/eds/ods.go new file mode 100644 index 0000000000..aa1219d41a --- /dev/null +++ b/share/eds/ods.go @@ -0,0 +1,98 @@ +package eds + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/ipld/go-car" + "github.com/ipld/go-car/util" +) + +// bufferedODSReader will read odsSquareSize amount of leaves from reader into the buffer. +// It exposes the buffer to be read by io.Reader interface implementation +type bufferedODSReader struct { + carReader *bufio.Reader + // current is the amount of CARv1 encoded leaves that have been read from reader. When current + // reaches odsSquareSize, bufferedODSReader will prevent further reads by returning io.EOF + current, odsSquareSize int + buf *bytes.Buffer +} + +// ODSReader reads CARv1 encoded data from io.ReadCloser and limits the reader to the CAR header +// and first quadrant (ODS) +func ODSReader(carReader io.Reader) (io.Reader, error) { + if carReader == nil { + return nil, errors.New("eds: can't create ODSReader over nil reader") + } + + odsR := &bufferedODSReader{ + carReader: bufio.NewReader(carReader), + buf: new(bytes.Buffer), + } + + // first LdRead reads the full CAR header to determine amount of shares in the ODS + data, err := util.LdRead(odsR.carReader) + if err != nil { + return nil, fmt.Errorf("reading header: %v", err) + } + + var header car.CarHeader + err = cbor.DecodeInto(data, &header) + if err != nil { + return nil, fmt.Errorf("invalid header: %w", err) + } + + // car header contains both row roots and col roots which is why + // we divide by 4 to get the ODSWidth + odsWidth := len(header.Roots) / 4 + odsR.odsSquareSize = odsWidth * odsWidth + + // NewCarReader will expect to read the header first, so write it first + return odsR, util.LdWrite(odsR.buf, data) +} + +func (r *bufferedODSReader) Read(p []byte) (n int, err error) { + // read leafs to the buffer until it has sufficient data to fill provided container or full ods is + // read + for r.current < r.odsSquareSize && r.buf.Len() < len(p) { + if err := r.readLeaf(); err != nil { + return 0, err + } + + r.current++ + } + + // read buffer to slice + return r.buf.Read(p) +} + +// readLeaf reads one leaf from reader into bufferedODSReader buffer +func (r *bufferedODSReader) readLeaf() error { + if _, err := r.carReader.Peek(1); err != nil { // no more blocks, likely clean io.EOF + return err + } + + l, err := binary.ReadUvarint(r.carReader) + if err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF + } + return err + } + + if l > uint64(util.MaxAllowedSectionSize) { // Don't OOM + return fmt.Errorf("malformed car; header `length`: %v is bigger than %v", l, util.MaxAllowedSectionSize) + } + + buf := make([]byte, 8) + n := binary.PutUvarint(buf, l) + r.buf.Write(buf[:n]) + + _, err = r.buf.ReadFrom(io.LimitReader(r.carReader, int64(l))) + return err +} diff --git a/share/eds/ods_test.go b/share/eds/ods_test.go new file mode 100644 index 0000000000..8509fc04b2 --- /dev/null +++ b/share/eds/ods_test.go @@ -0,0 +1,94 @@ +package eds + +import ( + "context" + "io" + "testing" + + "github.com/ipld/go-car" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" +) + +// TestODSReader ensures that the reader returned from ODSReader is capable of reading the CAR +// header and ODS. +func TestODSReader(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // launch eds store + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + // generate random eds data and put it into the store + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + // get CAR reader from store + r, err := edsStore.GetCAR(ctx, dah) + assert.NoError(t, err) + + // create ODSReader wrapper based on car reader to limit reads to ODS only + odsR, err := ODSReader(r) + assert.NoError(t, err) + + // create CAR reader from ODSReader + carReader, err := car.NewCarReader(odsR) + assert.NoError(t, err) + + // validate ODS could be obtained from reader + for i := 0; i < 4; i++ { + for j := 0; j < 4; j++ { + // pick share from original eds + original := eds.GetCell(uint(i), uint(j)) + + // read block from odsReader based reader + block, err := carReader.Next() + assert.NoError(t, err) + + // check that original data from eds is same as data from reader + assert.Equal(t, original, block.RawData()[share.NamespaceSize:]) + } + } + + // Make sure no excess data is available to get from reader + _, err = carReader.Next() + assert.Error(t, io.EOF, err) +} + +// TestODSReaderReconstruction ensures that the reader returned from ODSReader provides sufficient +// data for EDS reconstruction +func TestODSReaderReconstruction(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // launch eds store + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + // generate random eds data and put it into the store + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + // get CAR reader from store + r, err := edsStore.GetCAR(ctx, dah) + assert.NoError(t, err) + + // create ODSReader wrapper based on car reader to limit reads to ODS only + odsR, err := ODSReader(r) + assert.NoError(t, err) + + // reconstruct EDS from ODSReader + loaded, err := ReadEDS(ctx, odsR, dah) + assert.NoError(t, err) + require.Equal(t, eds.RowRoots(), loaded.RowRoots()) + require.Equal(t, eds.ColRoots(), loaded.ColRoots()) +} From b3d019dc100b3f627f1ae363c9021244797b607c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 28 Nov 2022 15:20:01 +0200 Subject: [PATCH 0211/1008] improvement(header/p2p): parallelize GetRangeByHeight request (#1206) Resolves * #1020 * https://github.com/celestiaorg/celestia-node/issues/636 Co-authored-by: Ryan --- header/p2p/exchange.go | 30 +++- header/p2p/exchange_test.go | 69 ++++++--- header/p2p/peer_stats.go | 130 ++++++++++++++++ header/p2p/peer_stats_test.go | 101 ++++++++++++ header/p2p/peer_tracker.go | 104 +++++++++++++ header/p2p/server_test.go | 4 +- header/p2p/session.go | 237 +++++++++++++++++++++++++++++ header/p2p/session_test.go | 16 ++ nodebuilder/header/constructors.go | 25 ++- 9 files changed, 681 insertions(+), 35 deletions(-) create mode 100644 header/p2p/peer_stats.go create mode 100644 header/p2p/peer_stats_test.go create mode 100644 header/p2p/peer_tracker.go create mode 100644 header/p2p/session.go create mode 100644 header/p2p/session_test.go diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 11a803f541..721fc1500d 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -40,11 +40,14 @@ const PubSubTopic = "header-sub" // Exchange enables sending outbound ExtendedHeaderRequests to the network as well as // handling inbound ExtendedHeaderRequests from the network. type Exchange struct { - protocolID protocol.ID + ctx context.Context + cancel context.CancelFunc - host host.Host + protocolID protocol.ID + host host.Host trustedPeers peer.IDSlice + peerTracker *peerTracker } func protocolID(protocolSuffix string) protocol.ID { @@ -56,9 +59,21 @@ func NewExchange(host host.Host, peers peer.IDSlice, protocolSuffix string) *Exc host: host, protocolID: protocolID(protocolSuffix), trustedPeers: peers, + peerTracker: newPeerTracker(host), } } +func (ex *Exchange) Start(context.Context) error { + ex.ctx, ex.cancel = context.WithCancel(context.Background()) + go ex.peerTracker.track(ex.ctx) + return nil +} + +func (ex *Exchange) Stop(context.Context) error { + ex.cancel() + return nil +} + // Head requests the latest ExtendedHeader. Note that the ExtendedHeader // must be verified thereafter. // NOTE: @@ -128,13 +143,12 @@ func (ex *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.Ext // GetRangeByHeight performs a request for the given range of ExtendedHeaders // to the network. Note that the ExtendedHeaders must be verified thereafter. func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { - log.Debugw("requesting headers", "from", from, "to", from+amount) - // create request - req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: from}, - Amount: amount, + if amount > maxRequestSize { + return nil, header.ErrHeadersLimitExceeded } - return ex.performRequest(ctx, req) + session := newSession(ex.ctx, ex.host, ex.peerTracker.peers(), ex.protocolID) + defer session.close() + return session.getRangeByHeight(ctx, from, amount) } // Get performs a request for the ExtendedHeader by the given hash corresponding diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 3fab82df70..8dfb8c3139 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "testing" + "time" libhost "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" @@ -22,8 +23,8 @@ import ( var privateProtocolID = protocolID("private") func TestExchange_RequestHead(t *testing.T) { - host, peer := createMocknet(t) - exchg, store := createP2PExAndServer(t, host, peer) + hosts := createMocknet(t, 2) + exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) // perform header request header, err := exchg.Head(context.Background()) require.NoError(t, err) @@ -33,8 +34,8 @@ func TestExchange_RequestHead(t *testing.T) { } func TestExchange_RequestHeader(t *testing.T) { - host, peer := createMocknet(t) - exchg, store := createP2PExAndServer(t, host, peer) + hosts := createMocknet(t, 2) + exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) // perform expected request header, err := exchg.GetByHeight(context.Background(), 5) require.NoError(t, err) @@ -43,8 +44,8 @@ func TestExchange_RequestHeader(t *testing.T) { } func TestExchange_RequestHeaders(t *testing.T) { - host, peer := createMocknet(t) - exchg, store := createP2PExAndServer(t, host, peer) + hosts := createMocknet(t, 2) + exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) // perform expected request gotHeaders, err := exchg.GetRangeByHeight(context.Background(), 1, 5) require.NoError(t, err) @@ -54,11 +55,40 @@ func TestExchange_RequestHeaders(t *testing.T) { } } +// TestExchange_RequestFullRangeHeaders requests max amount of headers +// to verify how session will parallelize all requests. +func TestExchange_RequestFullRangeHeaders(t *testing.T) { + // create mocknet with 5 peers + hosts := createMocknet(t, 5) + // set max amount of headers per 1 peer + headersPerPeer = 10 + totalAmount := 80 + store := createStore(t, totalAmount) + protocolSuffix := "private" + // create new exchange + exchange := NewExchange(hosts[len(hosts)-1], []peer.ID{}, protocolSuffix) + exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) + t.Cleanup(exchange.cancel) + servers := make([]*ExchangeServer, len(hosts)-1) // amount of servers is len(hosts)-1 because one peer acts as a client + for index := range servers { + servers[index] = NewExchangeServer(hosts[index], store, protocolSuffix) + servers[index].Start(context.Background()) //nolint:errcheck + exchange.peerTracker.connectedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()} + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + // request headers from 1 to totalAmount(80) + headers, err := exchange.GetRangeByHeight(ctx, 1, uint64(totalAmount)) + require.NoError(t, err) + require.Len(t, headers, 80) +} + // TestExchange_RequestHeadersFails tests that the Exchange instance will return // header.ErrNotFound if it will not have requested header. func TestExchange_RequestHeadersFails(t *testing.T) { - host, peer := createMocknet(t) - exchg, _ := createP2PExAndServer(t, host, peer) + hosts := createMocknet(t, 2) + exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) tt := []struct { amount uint64 expectedErr *error @@ -80,15 +110,6 @@ func TestExchange_RequestHeadersFails(t *testing.T) { } } -func TestExchange_RequestHeadersLimitExceed(t *testing.T) { - host, peer := createMocknet(t) - exchg, _ := createP2PExAndServer(t, host, peer) - // perform expected request - _, err := exchg.GetRangeByHeight(context.Background(), 1, 600) - require.Error(t, err) - require.ErrorAs(t, err, &header.ErrHeadersLimitExceeded) -} - // TestExchange_RequestByHash tests that the Exchange instance can // respond to an ExtendedHeaderRequest for a hash instead of a height. func TestExchange_RequestByHash(t *testing.T) { @@ -230,11 +251,11 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.Equal(t, resp.StatusCode, p2p_pb.StatusCode_NOT_FOUND) } -func createMocknet(t *testing.T) (libhost.Host, libhost.Host) { - net, err := mocknet.FullMeshConnected(2) +func createMocknet(t *testing.T, amount int) []libhost.Host { + net, err := mocknet.FullMeshConnected(amount) require.NoError(t, err) // get host and peer - return net.Hosts()[0], net.Hosts()[1] + return net.Hosts() } // createP2PExAndServer creates a Exchange with 5 headers already in its store. @@ -244,11 +265,15 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan err := serverSideEx.Start(context.Background()) require.NoError(t, err) + exchange := NewExchange(host, []peer.ID{tpeer.ID()}, "private") + exchange.peerTracker.connectedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} + exchange.Start(context.Background()) //nolint:errcheck + t.Cleanup(func() { serverSideEx.Stop(context.Background()) //nolint:errcheck + exchange.Stop(context.Background()) //nolint:errcheck }) - - return NewExchange(host, []peer.ID{tpeer.ID()}, "private"), store + return exchange, store } type mockStore struct { diff --git a/header/p2p/peer_stats.go b/header/p2p/peer_stats.go new file mode 100644 index 0000000000..37f905e3ed --- /dev/null +++ b/header/p2p/peer_stats.go @@ -0,0 +1,130 @@ +package p2p + +import ( + "container/heap" + "context" + "sync" + + "github.com/libp2p/go-libp2p-core/peer" +) + +// peerStat represents a peer's average statistics. +type peerStat struct { + sync.RWMutex + peerID peer.ID + // score is the average speed per single request + peerScore float32 +} + +// updateStats recalculates peer.score by averaging the last score +// updateStats takes the total amount of bytes that were requested from the peer +// and the total request duration(in milliseconds). The final score is calculated +// by dividing the amount by time, so the result score will represent how many bytes +// were retrieved in 1 millisecond. This value will then be averaged relative to the +// previous peerScore. +func (p *peerStat) updateStats(amount uint64, time uint64) { + p.Lock() + defer p.Unlock() + var averageSpeed float32 + if time != 0 { + averageSpeed = float32(amount / time) + } + if p.peerScore == 0.0 { + p.peerScore = averageSpeed + return + } + p.peerScore = (p.peerScore + averageSpeed) / 2 +} + +// score reads a peer's latest score from the queue +func (p *peerStat) score() float32 { + p.RLock() + defer p.RUnlock() + return p.peerScore +} + +// peerStats implements heap.Interface, so we can be sure that we are getting the peer +// with the highest score, each time we call Pop. +type peerStats []*peerStat + +func newPeerStats() peerStats { + ps := make(peerStats, 0) + heap.Init(&ps) + return ps +} + +func (ps peerStats) Len() int { return len(ps) } + +// Less compares two peerScores. +// Less is used by heap.Interface to build the queue in a decreasing order. +func (ps peerStats) Less(i, j int) bool { + return ps[i].score() > ps[j].score() +} + +func (ps peerStats) Swap(i, j int) { + ps[i], ps[j] = ps[j], ps[i] +} + +// Push adds peerStat to the queue. +func (ps *peerStats) Push(x any) { + item := x.(*peerStat) + *ps = append(*ps, item) +} + +// Pop returns the peer with the highest score from the queue. +func (ps *peerStats) Pop() any { + old := *ps + n := len(old) + item := old[n-1] + old[n-1] = nil + *ps = old[:n-1] + return item +} + +// peerQueue wraps peerStats and guards it with the mutex. +type peerQueue struct { + ctx context.Context + + statsLk sync.RWMutex + stats peerStats + + havePeer chan struct{} +} + +func newPeerQueue(ctx context.Context, stats []*peerStat) *peerQueue { + statsCh := make(chan struct{}, len(stats)) + pq := &peerQueue{ + ctx: ctx, + stats: newPeerStats(), + havePeer: statsCh, + } + for _, stat := range stats { + pq.push(stat) + } + return pq +} + +// waitPop pops the peer with the biggest score. +// in case if there are no peer available in current session, it blocks until +// the peer will be pushed in. +func (p *peerQueue) waitPop(ctx context.Context) *peerStat { + select { + case <-ctx.Done(): + return &peerStat{} + case <-p.ctx.Done(): + return &peerStat{} + case <-p.havePeer: + } + p.statsLk.Lock() + defer p.statsLk.Unlock() + return heap.Pop(&p.stats).(*peerStat) +} + +// push adds the peer to the queue. +func (p *peerQueue) push(stat *peerStat) { + p.statsLk.Lock() + heap.Push(&p.stats, stat) + p.statsLk.Unlock() + // notify that the peer is available in the queue, so it can be popped out + p.havePeer <- struct{}{} +} diff --git a/header/p2p/peer_stats_test.go b/header/p2p/peer_stats_test.go new file mode 100644 index 0000000000..1ac8b741de --- /dev/null +++ b/header/p2p/peer_stats_test.go @@ -0,0 +1,101 @@ +package p2p + +import ( + "container/heap" + "context" + "testing" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/require" +) + +func Test_PeerStatsPush(t *testing.T) { + pQueue := newPeerStats() + pQueue.Push(&peerStat{peerID: "peerID"}) + require.True(t, pQueue.Len() == 1) +} + +func Test_PeerStatsPop(t *testing.T) { + pQueue := newPeerStats() + pQueue.Push(&peerStat{peerID: "peerID"}) + stats := heap.Pop(&pQueue).(*peerStat) + require.Equal(t, stats.peerID, peer.ID("peerID")) +} + +func Test_PeerQueuePopBestPeer(t *testing.T) { + peersStat := peerStats{ + {peerID: "peerID1", peerScore: 1}, + {peerID: "peerID2", peerScore: 2}, + {peerID: "peerID3", peerScore: 4}, + {peerID: "peerID4"}, // score = 0 + } + wantStat := peerStats{ + {peerID: "peerID3", peerScore: 4}, + {peerID: "peerID2", peerScore: 2}, + {peerID: "peerID1", peerScore: 1}, + {peerID: "peerID4"}, // score = 0 + } + + // we do not need timeout/cancel functionality here + pQueue := newPeerQueue(context.Background(), peersStat) + for index := 0; index < pQueue.stats.Len(); index++ { + stats := heap.Pop(&pQueue.stats).(*peerStat) + require.Equal(t, stats, wantStat[index]) + } +} + +func Test_PeerQueueRemovePeer(t *testing.T) { + peersStat := []*peerStat{ + {peerID: "peerID1", peerScore: 1}, + {peerID: "peerID2", peerScore: 2}, + {peerID: "peerID3", peerScore: 4}, + {peerID: "peerID4"}, // score = 0 + } + + // we do not need timeout/cancel functionality here + pQueue := newPeerQueue(context.Background(), peersStat) + + _ = heap.Pop(&pQueue.stats) + stat := heap.Pop(&pQueue.stats).(*peerStat) + require.Equal(t, stat.peerID, peer.ID("peerID2")) +} + +func Test_StatsUpdateStats(t *testing.T) { + // we do not need timeout/cancel functionality here + pQueue := newPeerQueue(context.Background(), []*peerStat{}) + stat := &peerStat{peerID: "peerID", peerScore: 0} + heap.Push(&pQueue.stats, stat) + testCases := []struct { + inputTime uint64 + inputBytes uint64 + resultScore float32 + }{ + // common case, where time and bytes is not equal to 0 + { + inputTime: 16, + inputBytes: 4, + resultScore: 4, + }, + // in case if bytes is equal to 0, + // then the request was failed and previous score will be + // decreased + { + inputTime: 10, + inputBytes: 0, + resultScore: 2, + }, + // testing case with time=0, to ensure that dividing by 0 is handled properly + { + inputTime: 0, + inputBytes: 0, + resultScore: 1, + }, + } + + for _, tt := range testCases { + stat.updateStats(tt.inputBytes, tt.inputTime) + updatedStat := heap.Pop(&pQueue.stats).(*peerStat) + require.Equal(t, updatedStat.score(), stat.score()) + heap.Push(&pQueue.stats, updatedStat) + } +} diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go new file mode 100644 index 0000000000..9451b6f929 --- /dev/null +++ b/header/p2p/peer_tracker.go @@ -0,0 +1,104 @@ +package p2p + +import ( + "context" + "sync" + + "github.com/libp2p/go-libp2p-core/event" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" +) + +type peerTracker struct { + sync.RWMutex + connectedPeers map[peer.ID]*peerStat + // we cache the peer once they disconnect, + // so we can guarantee that peerQueue will only contain active peers + disconnectedPeers map[peer.ID]*peerStat + + host host.Host +} + +func newPeerTracker(h host.Host) *peerTracker { + return &peerTracker{ + disconnectedPeers: make(map[peer.ID]*peerStat), + connectedPeers: make(map[peer.ID]*peerStat), + host: h, + } +} + +func (p *peerTracker) track(ctx context.Context) { + // store peers that have been already connected + for _, peer := range p.host.Peerstore().Peers() { + p.connected(peer) + } + + subs, err := p.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) + if err != nil { + log.Errorw("subscribing to EvtPeerConnectednessChanged", "err", err) + return + } + for { + select { + case <-ctx.Done(): + err = subs.Close() + if err != nil { + log.Errorw("closing subscription", "err", err) + } + return + case subscription := <-subs.Out(): + ev := subscription.(event.EvtPeerConnectednessChanged) + switch ev.Connectedness { + case network.Connected: + p.connected(ev.Peer) + case network.NotConnected: + p.disconnected(ev.Peer) + } + } + } +} + +func (p *peerTracker) connected(pID peer.ID) { + if p.host.ID() == pID { + return + } + for _, c := range p.host.Network().ConnsToPeer(pID) { + // check if connection is short-termed and skip this peer + if c.Stat().Transient { + return + } + } + p.Lock() + defer p.Unlock() + // additional check in p.connectedPeers should be done, + // because libp2p does not emit multiple Connected events per 1 peer + stats, ok := p.disconnectedPeers[pID] + if !ok { + stats = &peerStat{peerID: pID} + } else { + delete(p.disconnectedPeers, pID) + } + p.connectedPeers[pID] = stats +} + +func (p *peerTracker) disconnected(pID peer.ID) { + p.Lock() + defer p.Unlock() + stats, ok := p.connectedPeers[pID] + if !ok { + return + } + p.disconnectedPeers[pID] = stats + delete(p.connectedPeers, pID) +} + +func (p *peerTracker) peers() []*peerStat { + p.RLock() + defer p.RUnlock() + peers := make([]*peerStat, 0, len(p.connectedPeers)) + for _, stat := range p.connectedPeers { + peers = append(peers, stat) + } + return peers +} diff --git a/header/p2p/server_test.go b/header/p2p/server_test.go index 43d0ad0a64..9bb50136ec 100644 --- a/header/p2p/server_test.go +++ b/header/p2p/server_test.go @@ -11,10 +11,10 @@ import ( ) func TestExchangeServer_handleRequestTimeout(t *testing.T) { - _, peer := createMocknet(t) + peer := createMocknet(t, 1) s, err := store.NewStore(datastore.NewMapDatastore()) require.NoError(t, err) - server := NewExchangeServer(peer, s, "private") + server := NewExchangeServer(peer[0], s, "private") err = server.Start(context.Background()) require.NoError(t, err) t.Cleanup(func() { diff --git a/header/p2p/session.go b/header/p2p/session.go new file mode 100644 index 0000000000..3f64f71173 --- /dev/null +++ b/header/p2p/session.go @@ -0,0 +1,237 @@ +package p2p + +import ( + "context" + "errors" + "io" + "sort" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/celestiaorg/go-libp2p-messenger/serde" + + "github.com/celestiaorg/celestia-node/header" + p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" +) + +// TODO(@vgonkivs): make it configurable +var ( + // headersPerPeer is a maximum amount of headers that will be requested per peer. + headersPerPeer uint64 = 64 +) + +// session aims to divide a range of headers +// into several smaller requests among different peers. +type session struct { + ctx context.Context + cancel context.CancelFunc + host host.Host + protocolID protocol.ID + // peerTracker contains discovered peers with records that describes their activity. + queue *peerQueue + + reqCh chan *p2p_pb.ExtendedHeaderRequest + errCh chan error +} + +func newSession(ctx context.Context, h host.Host, peerTracker []*peerStat, protocolID protocol.ID) *session { + ctx, cancel := context.WithCancel(ctx) + return &session{ + ctx: ctx, + cancel: cancel, + protocolID: protocolID, + host: h, + queue: newPeerQueue(ctx, peerTracker), + errCh: make(chan error), + } +} + +// GetRangeByHeight requests headers from different peers. +func (s *session) getRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { + log.Debugw("requesting headers", "from", from, "to", from+amount) + + requests := prepareRequests(from, amount, headersPerPeer) + result := make(chan []*header.ExtendedHeader, len(requests)) + s.reqCh = make(chan *p2p_pb.ExtendedHeaderRequest, len(requests)) + + go s.handleOutgoingRequests(ctx, result) + for _, req := range requests { + s.reqCh <- req + } + + headers := make([]*header.ExtendedHeader, 0, amount) + for i := 0; i < len(requests); i++ { + select { + case <-s.ctx.Done(): + return nil, errors.New("header/p2p: exchange is closed") + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-s.errCh: + return nil, err + case res := <-result: + headers = append(headers, res...) + } + } + sort.Slice(headers, func(i, j int) bool { + return headers[i].Height < headers[j].Height + }) + return headers, nil +} + +// close stops the session. +func (s *session) close() { + if s.cancel != nil { + s.cancel() + s.cancel = nil + } +} + +// handleOutgoingRequests pops a peer from the queue and sends a prepared request to the peer. +// Will exit via canceled session context or when all request are processed. +func (s *session) handleOutgoingRequests(ctx context.Context, result chan []*header.ExtendedHeader) { + for { + select { + case <-ctx.Done(): + return + case <-s.ctx.Done(): + return + case req := <-s.reqCh: + // select peer with the highest score among the available ones for the request + stats := s.queue.waitPop(ctx) + if stats.peerID == "" { + return + } + go s.doRequest(ctx, stats, req, result) + } + } +} + +// doRequest chooses the best peer to fetch headers and sends a request in range of available maxRetryAttempts. +func (s *session) doRequest( + ctx context.Context, + stat *peerStat, + req *p2p_pb.ExtendedHeaderRequest, + headers chan []*header.ExtendedHeader, +) { + r, size, duration, err := s.requestHeaders(ctx, stat.peerID, req) + if err != nil { + if err == context.Canceled || err == context.DeadlineExceeded { + return + } + log.Errorw("requesting headers from peer failed."+ + "Retrying the request from different peer", "failed peer", stat.peerID, "err", err) + select { + case <-ctx.Done(): + case <-s.ctx.Done(): + case s.reqCh <- req: + } + return + } + + h, err := s.processResponse(r) + if err != nil { + s.errCh <- err + return + } + log.Debugw("request headers from peer succeed ", "from", s.host.ID(), "pID", stat.peerID, "amount", req.Amount) + // send headers to the channel, update peer stats and return peer to the queue, so it can be re-used in case + // if there are other requests awaiting + headers <- h + stat.updateStats(size, duration) + s.queue.push(stat) +} + +// requestHeaders sends the ExtendedHeaderRequest to a remote peer. +func (s *session) requestHeaders( + ctx context.Context, + to peer.ID, + req *p2p_pb.ExtendedHeaderRequest, +) ([]*p2p_pb.ExtendedHeaderResponse, uint64, uint64, error) { + startTime := time.Now() + stream, err := s.host.NewStream(ctx, to, s.protocolID) + if err != nil { + return nil, 0, 0, err + } + + // set stream deadline from the context deadline. + // if it is empty, then we assume that it will + // hang until the server will close the stream by the timeout. + if dl, ok := ctx.Deadline(); ok { + if err = stream.SetDeadline(dl); err != nil { + log.Debugw("error setting deadline: %s", err) + } + } + // send request + _, err = serde.Write(stream, req) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, 0, 0, err + } + err = stream.CloseWrite() + if err != nil { + log.Error(err) + } + headers := make([]*p2p_pb.ExtendedHeaderResponse, 0) + totalRequestSize := uint64(0) + for i := 0; i < int(req.Amount); i++ { + resp := new(p2p_pb.ExtendedHeaderResponse) + msgSize, err := serde.Read(stream, resp) + if err != nil { + if err == io.EOF { + break + } + stream.Reset() //nolint:errcheck + return nil, 0, 0, err + } + totalRequestSize += uint64(msgSize) + headers = append(headers, resp) + } + duration := time.Since(startTime).Milliseconds() + if err = stream.Close(); err != nil { + log.Errorw("closing stream", "err", err) + } + return headers, totalRequestSize, uint64(duration), nil +} + +// processResponse converts ExtendedHeaderResponse to ExtendedHeader. +func (s *session) processResponse(responses []*p2p_pb.ExtendedHeaderResponse) ([]*header.ExtendedHeader, error) { + headers := make([]*header.ExtendedHeader, 0) + for _, resp := range responses { + err := convertStatusCodeToError(resp.StatusCode) + if err != nil { + return nil, err + } + header, err := header.UnmarshalExtendedHeader(resp.Body) + if err != nil { + return nil, err + } + headers = append(headers, header) + } + + return headers, nil +} + +// prepareRequests converts incoming range into separate ExtendedHeaderRequest. +func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.ExtendedHeaderRequest { + requests := make([]*p2p_pb.ExtendedHeaderRequest, 0, amount/headersPerPeer) + for amount > uint64(0) { + var requestSize uint64 + request := &p2p_pb.ExtendedHeaderRequest{ + Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: from}, + } + if amount < headersPerPeer { + requestSize = amount + amount = 0 + } else { + amount -= headersPerPeer + from += headersPerPeer + requestSize = headersPerPeer + } + request.Amount = requestSize + requests = append(requests, request) + } + return requests +} diff --git a/header/p2p/session_test.go b/header/p2p/session_test.go new file mode 100644 index 0000000000..cc6a3667a1 --- /dev/null +++ b/header/p2p/session_test.go @@ -0,0 +1,16 @@ +package p2p + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_PrepareRequests(t *testing.T) { + // from : 1, amount: 10, headersPerPeer: 5 + // result -> {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}} + requests := prepareRequests(1, 10, 5) + require.Len(t, requests, 2) + require.Equal(t, requests[0].GetOrigin(), uint64(1)) + require.Equal(t, requests[1].GetOrigin(), uint64(6)) +} diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index a776429c1b..7d4d69f68a 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -22,8 +22,18 @@ func newP2PServer(host host.Host, store header.Store, network modp2p.Network) *p } // newP2PExchange constructs a new Exchange for headers. -func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, modp2p.Network, host.Host) (header.Exchange, error) { - return func(bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host) (header.Exchange, error) { +func newP2PExchange(cfg Config) func( + fx.Lifecycle, + modp2p.Bootstrappers, + modp2p.Network, + host.Host, +) (header.Exchange, error) { + return func( + lc fx.Lifecycle, + bpeers modp2p.Bootstrappers, + network modp2p.Network, + host host.Host, + ) (header.Exchange, error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { return nil, err @@ -33,7 +43,16 @@ func newP2PExchange(cfg Config) func(modp2p.Bootstrappers, modp2p.Network, host. ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - return p2p.NewExchange(host, ids, string(network)), nil + exchange := p2p.NewExchange(host, ids, string(network)) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return exchange.Start(ctx) + }, + OnStop: func(ctx context.Context) error { + return exchange.Stop(ctx) + }, + }) + return exchange, nil } } From c05065e3b8fdefe613d71b389c66af8e8e016392 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 29 Nov 2022 14:31:22 +0300 Subject: [PATCH 0212/1008] feat(share): add get proof by namespace (#1339) Resolves https://github.com/celestiaorg/celestia-node/issues/1117 Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Ryan --- share/eds/byzantine/share_proof.go | 2 +- share/get.go | 3 +- share/get_test.go | 102 ++++++++++++++-- share/ipld/get.go | 186 +++++++++++++++++------------ share/ipld/nmt.go | 5 + share/ipld/proof.go | 50 ++++++++ share/service/service.go | 2 +- 7 files changed, 263 insertions(+), 87 deletions(-) create mode 100644 share/ipld/proof.go diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 2d360f2aee..c2476d123c 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -108,5 +108,5 @@ func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { } func ProtoToProof(protoProof *pb.MerkleProof) nmt.Proof { - return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, true) + return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, ipld.NMTIgnoreMaxNamespace) } diff --git a/share/get.go b/share/get.go index d452378926..31385a281a 100644 --- a/share/get.go +++ b/share/get.go @@ -49,11 +49,12 @@ func GetSharesByNamespace( root cid.Cid, nID namespace.ID, maxShares int, + proofContainer *ipld.Proof, ) ([]Share, error) { ctx, span := tracer.Start(ctx, "get-shares-by-namespace") defer span.End() - leaves, err := ipld.GetLeavesByNamespace(ctx, bGetter, root, nID, maxShares) + leaves, err := ipld.GetLeavesByNamespace(ctx, bGetter, root, nID, maxShares, proofContainer) if err != nil && leaves == nil { return nil, err } diff --git a/share/get_test.go b/share/get_test.go index 6b76923554..fdf882c569 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -15,11 +15,13 @@ import ( blockstore "github.com/ipfs/go-ipfs-blockstore" offline "github.com/ipfs/go-ipfs-exchange-offline" mdutils "github.com/ipfs/go-merkledag/test" + "github.com/minio/sha256-simd" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" ) @@ -165,7 +167,7 @@ func TestGetSharesByNamespace(t *testing.T) { var shares []Share for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) - rowShares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) + rowShares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots()), nil) require.NoError(t, err) shares = append(shares, rowShares...) @@ -219,10 +221,9 @@ func TestGetLeavesByNamespace_IncompleteData(t *testing.T) { err = bServ.DeleteBlock(ctx, r.Cid()) require.NoError(t, err) - nodes, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) - assert.Equal(t, nil, nodes[1]) - // TODO(distractedm1nd): Decide if we should return an array containing nil - assert.Equal(t, 4, len(nodes)) + leaves, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares), nil) + assert.Nil(t, leaves[1]) + assert.Equal(t, 4, len(leaves)) require.Error(t, err) } @@ -301,10 +302,10 @@ func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) - nodes, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) + leaves, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares), nil) assert.Nil(t, err) - for _, node := range nodes { + for _, node := range leaves { // test that the data returned by getLeavesByNamespace for nid // matches the commonNamespaceData that was copied across almost all data assert.Equal(t, commonNamespaceData, node.RawData()[NamespaceSize:]) @@ -312,6 +313,89 @@ func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T } } +func TestGetSharesWithProofsByNamespace(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + bServ := mdutils.Bserv() + + var tests = []struct { + rawData []Share + }{ + {rawData: RandShares(t, 4)}, + {rawData: RandShares(t, 16)}, + {rawData: RandShares(t, 64)}, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + // choose random range in shares slice + from := rand.Intn(len(tt.rawData)) + to := rand.Intn(len(tt.rawData)) + + if to < from { + from, to = to, from + } + + expected := tt.rawData[from] + nID := expected[:NamespaceSize] + + // change rawData to contain several shares with same nID + for i := from; i <= to; i++ { + tt.rawData[i] = expected + } + + // put raw data in BlockService + eds, err := AddShares(ctx, tt.rawData, bServ) + require.NoError(t, err) + + var shares []Share + for _, row := range eds.RowRoots() { + rcid := ipld.MustCidFromNamespacedSha256(row) + proof := new(ipld.Proof) + rowShares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots()), proof) + require.NoError(t, err) + if rowShares != nil { + // append shares to check integrity later + shares = append(shares, rowShares...) + + // construct nodes from shares by prepending namespace + var leaves [][]byte + for _, sh := range rowShares { + leaves = append(leaves, append(sh[:NamespaceSize], sh...)) + } + + proofNodes := make([][]byte, 0, len(proof.Nodes)) + for _, n := range proof.Nodes { + proofNodes = append(proofNodes, ipld.NamespacedSha256FromCID(n)) + } + + // construct new proof + inclusionProof := nmt.NewInclusionProof( + proof.Start, + proof.End, + proofNodes, + ipld.NMTIgnoreMaxNamespace) + + // verify inclusion + verified := inclusionProof.VerifyNamespace( + sha256.New(), + nID, + leaves, + ipld.NamespacedSha256FromCID(rcid)) + require.True(t, verified) + } + } + + // validate shares + assert.Equal(t, to-from+1, len(shares)) + for _, share := range shares { + assert.Equal(t, expected, share) + } + }) + } +} + func TestBatchSize(t *testing.T) { tests := []struct { name string @@ -363,8 +447,8 @@ func assertNoRowContainsNID( // for each row root cid check if the minNID exists for _, rowCID := range rowRootCIDs { - data, err := ipld.GetLeavesByNamespace(context.Background(), bServ, rowCID, nID, rowRootCount) - assert.Nil(t, data) + leaves, err := ipld.GetLeavesByNamespace(context.Background(), bServ, rowCID, nID, rowRootCount, nil) + assert.Nil(t, leaves) assert.Nil(t, err) } } diff --git a/share/ipld/get.go b/share/ipld/get.go index e0590de7d5..eb477a2cfc 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -124,7 +124,7 @@ func GetLeaves(ctx context.Context, span.SetAttributes( attribute.String("cid", j.id.String()), - attribute.Int("pos", j.pos), + attribute.Int("pos", j.sharePos), ) nd, err := GetNode(ctx, bGetter, j.id) @@ -141,7 +141,7 @@ func GetLeaves(ctx context.Context, // successfully fetched a share/leaf // ladies and gentlemen, we got em! span.SetStatus(codes.Ok, "") - put(j.pos, nd) + put(j.sharePos, nd) return } // ok, we found more links @@ -152,7 +152,7 @@ func GetLeaves(ctx context.Context, id: lnk.Cid, // calc position for children nodes (bin-tree-feat), // s.t. 'if' above knows where to put a share - pos: j.pos*2 + i, + sharePos: j.sharePos*2 + i, // we pass the context to job so that spans are tracked in a tree // structure ctx: ctx, @@ -170,16 +170,19 @@ func GetLeaves(ctx context.Context, wg.Wait() } -// GetLeavesByNamespace returns as many leaves from the given root with the given namespace.ID as -// it can retrieve. If no shares are found, it returns both data and error as nil. -// A non-nil error means that only partial data is returned, because at least one share retrieval -// failed The following implementation is based on `GetShares`. +// GetLeavesByNamespace returns leaves and corresponding proof that could be used to verify leaves +// inclusion. It returns as many leaves from the given root with the given namespace.ID as it can +// retrieve. If no shares are found, it returns both data and error as nil. If non-nil +// proofContainer param passed, it will be filled with data required for inclusion verification. A +// non-nil error means that only partial data is returned, because at least one share retrieval +// failed. The following implementation is based on `GetShares`. func GetLeavesByNamespace( ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, nID namespace.ID, maxShares int, + proofContainer *Proof, ) ([]ipld.Node, error) { if len(nID) != NamespaceSize { return nil, fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(nID)) @@ -216,84 +219,116 @@ func GetLeavesByNamespace( // on the level above, the length of the Row is passed in as maxShares leaves := make([]ipld.Node, maxShares) + // if non-nil proof container provided, collect proofs while traversing the tree and fill put them + // into container after + var collectProofs = proofContainer != nil + var proofs *proofCollector + if collectProofs { + proofs = newProofCollector(maxShares) + } + for { + var j *job + var ok bool select { - case j, ok := <-jobs: - if !ok { - // if there were no leaves under the given root in the given namespace, - // both return values are nil. otherwise, the error will also be non-nil. - if bounds.lowest == int64(maxShares) { - return nil, retrievalErr - } + case j, ok = <-jobs: + case <-ctx.Done(): + return nil, ctx.Err() + } - return leaves[bounds.lowest : bounds.highest+1], retrievalErr + if !ok { + // if there were no leaves under the given root in the given namespace, + // both return values are nil. otherwise, the error will also be non-nil. + if bounds.lowest == int64(maxShares) { + return nil, retrievalErr } - pool.Submit(func() { - ctx, span := tracer.Start(j.ctx, "process-job") - defer span.End() - defer wg.done() - span.SetAttributes( - attribute.String("cid", j.id.String()), - attribute.Int("pos", j.pos), - ) + if collectProofs { + proofContainer.Start = int(bounds.lowest) + proofContainer.End = int(bounds.highest) + 1 + proofContainer.Nodes = proofs.Nodes() + } - // if an error is likely to be returned or not depends on - // the underlying impl of the blockservice, currently it is not a realistic probability - nd, err := GetNode(ctx, bGetter, j.id) - if err != nil { - singleErr.Do(func() { - retrievalErr = err - }) - log.Errorw("getSharesByNamespace: could not retrieve node", "nID", nID, "pos", j.pos, "err", err) - span.SetStatus(codes.Error, err.Error()) - // we still need to update the bounds - bounds.update(int64(j.pos)) - return - } + return leaves[bounds.lowest : bounds.highest+1], retrievalErr + } + pool.Submit(func() { + ctx, span := tracer.Start(j.ctx, "process-job") + defer span.End() + defer wg.done() + + span.SetAttributes( + attribute.String("cid", j.id.String()), + attribute.Int("pos", j.sharePos), + ) + + // if an error is likely to be returned or not depends on + // the underlying impl of the blockservice, currently it is not a realistic probability + nd, err := GetNode(ctx, bGetter, j.id) + if err != nil { + singleErr.Do(func() { + retrievalErr = err + }) + log.Errorw("getLeavesWithProofsByNamespace: could not retrieve node", "nID", nID, "pos", j.sharePos, "err", err) + span.SetStatus(codes.Error, err.Error()) + // we still need to update the bounds + bounds.update(int64(j.sharePos)) + return + } - links := nd.Links() - if len(links) == 0 { - // successfully fetched a leaf belonging to the namespace - span.SetStatus(codes.Ok, "") - leaves[j.pos] = nd - // we found a leaf, so we update the bounds - // the update routine is repeated until the atomic swap is successful - bounds.update(int64(j.pos)) - return - } + links := nd.Links() + if len(links) == 0 { + // successfully fetched a leaf belonging to the namespace + span.SetStatus(codes.Ok, "") + leaves[j.sharePos] = nd + // we found a leaf, so we update the bounds + // the update routine is repeated until the atomic swap is successful + bounds.update(int64(j.sharePos)) + return + } - // this node has links in the namespace, so keep walking - for i, lnk := range links { - newJob := &job{ - id: lnk.Cid, - // position represents the index in a flattened binary tree, - // so we can return a slice of leaves in order - pos: j.pos*2 + i, - // we pass the context to job so that spans are tracked in a tree - // structure - ctx: ctx, + // this node has links in the namespace, so keep walking + for i, lnk := range links { + newJob := &job{ + id: lnk.Cid, + // sharePos represents potential share position in share slice + sharePos: j.sharePos*2 + i, + // position represents the index in a flattened binary tree, + // so we can return a slice of leaves in order + depth: j.depth + 1, + // we pass the context to job so that spans are tracked in a tree + // structure + ctx: ctx, + } + // if the link's nID isn't in range we don't need to create a new job for it, + // but need to collect a proof + jobNid := NamespacedSha256FromCID(newJob.id) + + // proof is on the right side, if the nID is less than min namespace of jobNid + if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) { + if collectProofs { + proofs.addRight(lnk.Cid) } + continue + } - // if the link's nID isn't in range we don't need to create a new job for it - jobNid := NamespacedSha256FromCID(newJob.id) - if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) || !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { - continue + // proof is on the left side, if the nID is bigger than max namespace of jobNid + if !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { + if collectProofs { + proofs.addLeft(lnk.Cid) } + continue + } - // by passing the previous check, we know we will have one more node to process - // note: it is important to increase the counter before sending to the channel - wg.add(1) - select { - case jobs <- newJob: - case <-ctx.Done(): - return - } + // by passing the previous check, we know we will have one more node to process + // note: it is important to increase the counter before sending to the channel + wg.add(1) + select { + case jobs <- newJob: + case <-ctx.Done(): + return } - }) - case <-ctx.Done(): - return nil, ctx.Err() - } + } + }) } } @@ -383,7 +418,8 @@ func (b *fetchedBounds) update(index int64) { // job represents an encountered node to investigate during the `GetLeaves` // and `GetLeavesByNamespace` routines. type job struct { - id cid.Cid - pos int - ctx context.Context + id cid.Cid + sharePos int + depth int + ctx context.Context } diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index cd881b10cd..096a7f97f3 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -55,6 +55,11 @@ const ( // for NamespacedSha256. For more information, see: // https://multiformats.io/multihash/#the-multihash-format cidPrefixSize = 4 + + // NMTIgnoreMaxNamespace is currently used value for IgnoreMaxNamespace option in NMT. + // IgnoreMaxNamespace defines whether the largest possible namespace.ID MAX_NID should be 'ignored'. + // If set to true, this allows for shorter proofs in particular use-cases. + NMTIgnoreMaxNamespace = true ) func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid) (ipld.Node, error) { diff --git a/share/ipld/proof.go b/share/ipld/proof.go new file mode 100644 index 0000000000..7dae321240 --- /dev/null +++ b/share/ipld/proof.go @@ -0,0 +1,50 @@ +package ipld + +import ( + "math" + + "github.com/ipfs/go-cid" +) + +// Proof contains information required for Leaves inclusion validation. +type Proof struct { + Nodes []cid.Cid + Start, End int +} + +// proofCollector collects proof nodes' CIDs for the construction of a shares inclusion validation nmt.Proof. +type proofCollector struct { + left, right []cid.Cid +} + +func newProofCollector(maxShares int) *proofCollector { + // maximum possible amount of required proofs from each side is equal to tree height. + height := int(math.Log2(float64(maxShares))) + return &proofCollector{ + left: make([]cid.Cid, 0, height), + right: make([]cid.Cid, 0, height), + } +} + +func (c *proofCollector) addLeft(node cid.Cid) { + c.left = append(c.left, node) +} + +func (c *proofCollector) addRight(node cid.Cid) { + c.right = append(c.right, node) +} + +// Nodes returns nodes collected by proofCollector in the order that nmt.Proof validator will use +// to traverse the tree. +func (c *proofCollector) Nodes() []cid.Cid { + nodes := make([]cid.Cid, 0, len(c.left)+len(c.left)) + // left side will be traversed in bottom-up order + nodes = append(nodes, c.left...) + + // right side of the tree will be traversed from top to bottom, + // so sort in reversed order + for i := len(c.right) - 1; i >= 0; i-- { + nodes = append(nodes, c.right[i]) + } + return nodes +} diff --git a/share/service/service.go b/share/service/service.go index 6688159c2b..190e68c4ea 100644 --- a/share/service/service.go +++ b/share/service/service.go @@ -118,7 +118,7 @@ func (s *ShareService) GetSharesByNamespace( // shadow loop variables, to ensure correct values are captured i, rootCID := i, rootCID errGroup.Go(func() (err error) { - shares[i], err = share.GetSharesByNamespace(ctx, s.bServ, rootCID, nID, len(root.RowsRoots)) + shares[i], err = share.GetSharesByNamespace(ctx, s.bServ, rootCID, nID, len(root.RowsRoots), nil) return }) } From 3444fdf4a9c257569e7f89cd85a2461ed0b1d806 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 29 Nov 2022 23:20:55 +0200 Subject: [PATCH 0213/1008] improvement(header/p2p): unify sendMessage method to request headers (#1405) Resolves #1404 --- header/p2p/exchange.go | 66 ++++------------------------ header/p2p/helpers.go | 97 ++++++++++++++++++++++++++++++++++++++++++ header/p2p/session.go | 59 +------------------------ 3 files changed, 106 insertions(+), 116 deletions(-) create mode 100644 header/p2p/helpers.go diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 721fc1500d..216637e210 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -14,8 +14,6 @@ import ( "github.com/libp2p/go-libp2p-core/protocol" tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/go-libp2p-messenger/serde" - "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) @@ -50,10 +48,6 @@ type Exchange struct { peerTracker *peerTracker } -func protocolID(protocolSuffix string) protocol.ID { - return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) -} - func NewExchange(host host.Host, peers peer.IDSlice, protocolSuffix string) *Exchange { return &Exchange{ host: host, @@ -194,53 +188,23 @@ func (ex *Exchange) request( to peer.ID, req *p2p_pb.ExtendedHeaderRequest, ) ([]*header.ExtendedHeader, error) { - stream, err := ex.host.NewStream(ctx, to, ex.protocolID) + responses, _, _, err := sendMessage(ctx, ex.host, to, ex.protocolID, req) if err != nil { return nil, err } - if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { - log.Debugf("error setting deadline: %s", err) - } - // send request - _, err = serde.Write(stream, req) - if err != nil { - stream.Reset() //nolint:errcheck - return nil, err - } - err = stream.CloseWrite() - if err != nil { - log.Error(err) + if len(responses) == 0 { + return nil, header.ErrNotFound } - // read responses - headers := make([]*header.ExtendedHeader, req.Amount) - for i := 0; i < int(req.Amount); i++ { - resp := new(p2p_pb.ExtendedHeaderResponse) - if err = stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { - log.Debugf("error setting deadline: %s", err) - } - _, err := serde.Read(stream, resp) - if err != nil { - stream.Reset() //nolint:errcheck + headers := make([]*header.ExtendedHeader, 0, len(responses)) + for _, response := range responses { + if err = convertStatusCodeToError(response.StatusCode); err != nil { return nil, err } - - if err = convertStatusCodeToError(resp.StatusCode); err != nil { - stream.Reset() //nolint:errcheck - return nil, err - } - header, err := header.UnmarshalExtendedHeader(resp.Body) + header, err := header.UnmarshalExtendedHeader(response.Body) if err != nil { - stream.Reset() //nolint:errcheck return nil, err } - - headers[i] = header - } - if err = stream.Close(); err != nil { - log.Errorw("closing stream", "err", err) - } - if len(headers) == 0 { - return nil, header.ErrNotFound + headers = append(headers, header) } return headers, nil } @@ -274,17 +238,3 @@ func bestHead(result []*header.ExtendedHeader) (*header.ExtendedHeader, error) { // otherwise return header with the max height return result[0], nil } - -// convertStatusCodeToError converts passed status code into an error. -func convertStatusCodeToError(code p2p_pb.StatusCode) error { - switch code { - case p2p_pb.StatusCode_OK: - return nil - case p2p_pb.StatusCode_NOT_FOUND: - return header.ErrNotFound - case p2p_pb.StatusCode_LIMIT_EXCEEDED: - return header.ErrHeadersLimitExceeded - default: - return fmt.Errorf("unknown status code %d", code) - } -} diff --git a/header/p2p/helpers.go b/header/p2p/helpers.go new file mode 100644 index 0000000000..93f328eb93 --- /dev/null +++ b/header/p2p/helpers.go @@ -0,0 +1,97 @@ +package p2p + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/celestiaorg/go-libp2p-messenger/serde" + + "github.com/celestiaorg/celestia-node/header" + p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" +) + +func protocolID(protocolSuffix string) protocol.ID { + return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) +} + +// sendMessage opens the stream to the given peers and sends ExtendedHeaderRequest to fetch ExtendedHeaders. +// As a result sendMessage returns ExtendedHeaderResponse, the size of fetched data, +// the duration of the request and an error. +func sendMessage( + ctx context.Context, + host host.Host, + to peer.ID, + protocol protocol.ID, + req *p2p_pb.ExtendedHeaderRequest, +) ([]*p2p_pb.ExtendedHeaderResponse, uint64, uint64, error) { + startTime := time.Now() + stream, err := host.NewStream(ctx, to, protocol) + if err != nil { + return nil, 0, 0, err + } + + // set stream deadline from the context deadline. + // if it is empty, then we assume that it will + // hang until the server will close the stream by the timeout. + if dl, ok := ctx.Deadline(); ok { + if err = stream.SetDeadline(dl); err != nil { + log.Debugw("error setting deadline: %s", err) + } + } + + // send request + _, err = serde.Write(stream, req) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, 0, 0, err + } + + err = stream.CloseWrite() + if err != nil { + return nil, 0, 0, nil + } + + headers := make([]*p2p_pb.ExtendedHeaderResponse, 0) + totalRequestSize := uint64(0) + for i := 0; i < int(req.Amount); i++ { + resp := new(p2p_pb.ExtendedHeaderResponse) + msgSize, err := serde.Read(stream, resp) + if err != nil { + if err == io.EOF { + break + } + stream.Reset() //nolint:errcheck + return nil, 0, 0, err + } + + totalRequestSize += uint64(msgSize) + headers = append(headers, resp) + } + + duration := time.Since(startTime).Milliseconds() + if err = stream.Close(); err != nil { + log.Errorw("closing stream", "err", err) + } + + return headers, totalRequestSize, uint64(duration), nil +} + +// convertStatusCodeToError converts passed status code into an error. +func convertStatusCodeToError(code p2p_pb.StatusCode) error { + switch code { + case p2p_pb.StatusCode_OK: + return nil + case p2p_pb.StatusCode_NOT_FOUND: + return header.ErrNotFound + case p2p_pb.StatusCode_LIMIT_EXCEEDED: + return header.ErrHeadersLimitExceeded + default: + return fmt.Errorf("unknown status code %d", code) + } +} diff --git a/header/p2p/session.go b/header/p2p/session.go index 3f64f71173..9630380d22 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -3,16 +3,11 @@ package p2p import ( "context" "errors" - "io" "sort" - "time" "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" - "github.com/celestiaorg/go-libp2p-messenger/serde" - "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) @@ -116,7 +111,7 @@ func (s *session) doRequest( req *p2p_pb.ExtendedHeaderRequest, headers chan []*header.ExtendedHeader, ) { - r, size, duration, err := s.requestHeaders(ctx, stat.peerID, req) + r, size, duration, err := sendMessage(ctx, s.host, stat.peerID, s.protocolID, req) if err != nil { if err == context.Canceled || err == context.DeadlineExceeded { return @@ -144,58 +139,6 @@ func (s *session) doRequest( s.queue.push(stat) } -// requestHeaders sends the ExtendedHeaderRequest to a remote peer. -func (s *session) requestHeaders( - ctx context.Context, - to peer.ID, - req *p2p_pb.ExtendedHeaderRequest, -) ([]*p2p_pb.ExtendedHeaderResponse, uint64, uint64, error) { - startTime := time.Now() - stream, err := s.host.NewStream(ctx, to, s.protocolID) - if err != nil { - return nil, 0, 0, err - } - - // set stream deadline from the context deadline. - // if it is empty, then we assume that it will - // hang until the server will close the stream by the timeout. - if dl, ok := ctx.Deadline(); ok { - if err = stream.SetDeadline(dl); err != nil { - log.Debugw("error setting deadline: %s", err) - } - } - // send request - _, err = serde.Write(stream, req) - if err != nil { - stream.Reset() //nolint:errcheck - return nil, 0, 0, err - } - err = stream.CloseWrite() - if err != nil { - log.Error(err) - } - headers := make([]*p2p_pb.ExtendedHeaderResponse, 0) - totalRequestSize := uint64(0) - for i := 0; i < int(req.Amount); i++ { - resp := new(p2p_pb.ExtendedHeaderResponse) - msgSize, err := serde.Read(stream, resp) - if err != nil { - if err == io.EOF { - break - } - stream.Reset() //nolint:errcheck - return nil, 0, 0, err - } - totalRequestSize += uint64(msgSize) - headers = append(headers, resp) - } - duration := time.Since(startTime).Milliseconds() - if err = stream.Close(); err != nil { - log.Errorw("closing stream", "err", err) - } - return headers, totalRequestSize, uint64(duration), nil -} - // processResponse converts ExtendedHeaderResponse to ExtendedHeader. func (s *session) processResponse(responses []*p2p_pb.ExtendedHeaderResponse) ([]*header.ExtendedHeader, error) { headers := make([]*header.ExtendedHeader, 0) From 8a6de42904377e7d4fefb56faec204e3df673921 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 30 Nov 2022 19:38:33 +0200 Subject: [PATCH 0214/1008] feat(header/p2p): implement GetVerifiedRangeByHeight (#1305) Resolves: * https://github.com/celestiaorg/celestia-node/issues/1201 * https://github.com/celestiaorg/celestia-node/issues/1078 Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- das/daser_test.go | 10 +++++++++- header/core/exchange.go | 17 +++++++++++++++++ header/interface.go | 8 ++++++-- header/local/exchange.go | 5 +++++ header/p2p/exchange.go | 25 +++++++++++++++++++++++++ header/p2p/exchange_test.go | 27 +++++++++++++++++++++++++++ header/p2p/session.go | 4 +++- header/store/store.go | 23 +++++++++++++++++++++++ header/sync/sync_test.go | 10 +++++++++- 9 files changed, 124 insertions(+), 5 deletions(-) diff --git a/das/daser_test.go b/das/daser_test.go index ae43d28fbb..fac628418b 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -319,7 +319,15 @@ func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.Exten DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}, nil } -func (m getterStub) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { +func (m getterStub) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { + return nil, nil +} + +func (m getterStub) GetVerifiedRange( + context.Context, + *header.ExtendedHeader, + uint64, +) ([]*header.ExtendedHeader, error) { return nil, nil } diff --git a/header/core/exchange.go b/header/core/exchange.go index b3c6d917d0..9689f84f00 100644 --- a/header/core/exchange.go +++ b/header/core/exchange.go @@ -55,6 +55,23 @@ func (ce *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( return headers, nil } +func (ce *Exchange) GetVerifiedRange(ctx context.Context, from *header.ExtendedHeader, amount uint64, +) ([]*header.ExtendedHeader, error) { + headers, err := ce.GetRangeByHeight(ctx, uint64(from.Height)+1, amount) + if err != nil { + return nil, err + } + + for _, h := range headers { + err := from.VerifyAdjacent(h) + if err != nil { + return nil, err + } + from = h + } + return headers, nil +} + func (ce *Exchange) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { log.Debugw("requesting header", "hash", hash.String()) block, err := ce.fetcher.GetBlockByHash(ctx, hash) diff --git a/header/interface.go b/header/interface.go index 1273dea103..b6a439f4f1 100644 --- a/header/interface.go +++ b/header/interface.go @@ -123,8 +123,12 @@ type Getter interface { // GetByHeight returns the ExtendedHeader corresponding to the given block height. GetByHeight(context.Context, uint64) (*ExtendedHeader, error) - // GetRangeByHeight returns the given range [from:to) of ExtendedHeaders. - GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) + // GetRangeByHeight returns the given range of ExtendedHeaders. + GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*ExtendedHeader, error) + + // GetVerifiedRange requests the header range from the provided ExtendedHeader and + // verifies that the returned headers are adjacent to each other. + GetVerifiedRange(ctx context.Context, from *ExtendedHeader, amount uint64) ([]*ExtendedHeader, error) } // Head contains the behavior necessary for a component to retrieve diff --git a/header/local/exchange.go b/header/local/exchange.go index 9b284930a4..3bd978f483 100644 --- a/header/local/exchange.go +++ b/header/local/exchange.go @@ -43,6 +43,11 @@ func (l *Exchange) GetRangeByHeight(ctx context.Context, origin, amount uint64) return l.store.GetRangeByHeight(ctx, origin, origin+amount) } +func (l *Exchange) GetVerifiedRange(ctx context.Context, from *header.ExtendedHeader, amount uint64, +) ([]*header.ExtendedHeader, error) { + return l.store.GetVerifiedRange(ctx, from, uint64(from.Height)+amount) +} + func (l *Exchange) Get(ctx context.Context, hash bytes.HexBytes) (*header.ExtendedHeader, error) { return l.store.Get(ctx, hash) } diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 216637e210..9fc111e360 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -145,6 +145,31 @@ func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( return session.getRangeByHeight(ctx, from, amount) } +// GetVerifiedRange performs a request for the given range of ExtendedHeaders to the network and ensures +// that returned headers are correct against the passed one. +func (ex *Exchange) GetVerifiedRange( + ctx context.Context, + from *header.ExtendedHeader, + amount uint64, +) ([]*header.ExtendedHeader, error) { + session := newSession(ex.ctx, ex.host, ex.peerTracker.peers(), ex.protocolID) + defer session.close() + + headers, err := session.getRangeByHeight(ctx, uint64(from.Height)+1, amount) + if err != nil { + return nil, err + } + + for _, h := range headers { + err := from.VerifyAdjacent(h) + if err != nil { + return nil, err + } + from = h + } + return headers, nil +} + // Get performs a request for the ExtendedHeader by the given hash corresponding // to the RawHeader. Note that the ExtendedHeader must be verified thereafter. func (ex *Exchange) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 8dfb8c3139..e35a36d897 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -55,6 +55,25 @@ func TestExchange_RequestHeaders(t *testing.T) { } } +func TestExchange_RequestVerifiedHeaders(t *testing.T) { + hosts := createMocknet(t, 2) + exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) + // perform expected request + h := store.headers[1] + _, err := exchg.GetVerifiedRange(context.Background(), h, 3) + require.NoError(t, err) +} + +func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { + hosts := createMocknet(t, 2) + exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) + store.headers[2] = store.headers[3] + // perform expected request + h := store.headers[1] + _, err := exchg.GetVerifiedRange(context.Background(), h, 3) + require.Error(t, err) +} + // TestExchange_RequestFullRangeHeaders requests max amount of headers // to verify how session will parallelize all requests. func TestExchange_RequestFullRangeHeaders(t *testing.T) { @@ -342,6 +361,14 @@ func (m *mockStore) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*h return headers, nil } +func (m *mockStore) GetVerifiedRange( + ctx context.Context, + h *header.ExtendedHeader, + to uint64, +) ([]*header.ExtendedHeader, error) { + return m.GetRangeByHeight(ctx, uint64(h.Height)+1, to) +} + func (m *mockStore) Has(context.Context, tmbytes.HexBytes) (bool, error) { return false, nil } diff --git a/header/p2p/session.go b/header/p2p/session.go index 9630380d22..e43b654450 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -153,7 +153,9 @@ func (s *session) processResponse(responses []*p2p_pb.ExtendedHeaderResponse) ([ } headers = append(headers, header) } - + if len(headers) == 0 { + return nil, header.ErrNotFound + } return headers, nil } diff --git a/header/store/store.go b/header/store/store.go index 9587fa79f7..42be50e955 100644 --- a/header/store/store.go +++ b/header/store/store.go @@ -245,6 +245,29 @@ func (s *Store) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*heade return headers, nil } +func (s *Store) GetVerifiedRange( + ctx context.Context, + from *header.ExtendedHeader, + to uint64, +) ([]*header.ExtendedHeader, error) { + if uint64(from.Height) >= to { + return nil, fmt.Errorf("header/store: invalid range(%d,%d)", from.Height, to) + } + headers, err := s.GetRangeByHeight(ctx, uint64(from.Height)+1, to) + if err != nil { + return nil, err + } + + for _, h := range headers { + err := from.VerifyAdjacent(h) + if err != nil { + return nil, err + } + from = h + } + return headers, nil +} + func (s *Store) Has(ctx context.Context, hash tmbytes.HexBytes) (bool, error) { if ok := s.cache.Contains(hash.String()); ok { return ok, nil diff --git a/header/sync/sync_test.go b/header/sync/sync_test.go index 4d65a576da..4324925f56 100644 --- a/header/sync/sync_test.go +++ b/header/sync/sync_test.go @@ -212,6 +212,14 @@ func (e *exchangeCountingHead) GetByHeight(ctx context.Context, u uint64) (*head panic("implement me") } -func (e *exchangeCountingHead) GetRangeByHeight(c context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { +func (e *exchangeCountingHead) GetRangeByHeight( + c context.Context, + from, amount uint64, +) ([]*header.ExtendedHeader, error) { + panic("implement me") +} + +func (e *exchangeCountingHead) GetVerifiedRange(c context.Context, from *header.ExtendedHeader, amount uint64, +) ([]*header.ExtendedHeader, error) { panic("implement me") } From 3c82504634fa471f052d6278a3801e0a705cf073 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 30 Nov 2022 19:46:24 +0200 Subject: [PATCH 0215/1008] feat(header/p2p): add functional params for header/p2p package (#1398) Closes to #709 Co-authored-by: Ryan --- header/p2p/exchange.go | 41 +++++---- header/p2p/exchange_test.go | 34 ++++--- header/p2p/options.go | 140 +++++++++++++++++++++++++++++ header/p2p/server.go | 26 ++++-- header/p2p/server_test.go | 3 +- header/p2p/session.go | 11 +-- nodebuilder/config.go | 2 +- nodebuilder/header/config.go | 32 ++++++- nodebuilder/header/constructors.go | 16 +++- nodebuilder/header/module.go | 17 ++++ nodebuilder/header/module_test.go | 40 ++++++++- 11 files changed, 310 insertions(+), 52 deletions(-) create mode 100644 header/p2p/options.go diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 9fc111e360..357954707a 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -6,7 +6,6 @@ import ( "fmt" "math/rand" "sort" - "time" logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/host" @@ -20,17 +19,6 @@ import ( var log = logging.Logger("header/p2p") -const ( - // writeDeadline sets timeout for sending messages to the stream - writeDeadline = time.Second * 5 - // readDeadline sets timeout for reading messages from the stream - readDeadline = time.Minute - // the target minimum amount of responses with the same chain head - minResponses = 2 - // requestSize defines the max amount of headers that can be requested/handled at once. - maxRequestSize uint64 = 512 -) - // PubSubTopic hardcodes the name of the ExtendedHeader // gossipsub topic. const PubSubTopic = "header-sub" @@ -46,15 +34,32 @@ type Exchange struct { trustedPeers peer.IDSlice peerTracker *peerTracker + + Params *ClientParameters } -func NewExchange(host host.Host, peers peer.IDSlice, protocolSuffix string) *Exchange { +func NewExchange( + host host.Host, + peers peer.IDSlice, + protocolSuffix string, + opts ...Option[ClientParameters], +) (*Exchange, error) { + params := DefaultClientParameters() + for _, opt := range opts { + opt(params) + } + + err := params.Validate() + if err != nil { + return nil, err + } return &Exchange{ host: host, protocolID: protocolID(protocolSuffix), trustedPeers: peers, peerTracker: newPeerTracker(host), - } + Params: params, + }, nil } func (ex *Exchange) Start(context.Context) error { @@ -110,7 +115,7 @@ LOOP: } } - return bestHead(result) + return bestHead(result, ex.Params.MinResponses) } // GetByHeight performs a request for the ExtendedHeader at the given @@ -137,12 +142,12 @@ func (ex *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.Ext // GetRangeByHeight performs a request for the given range of ExtendedHeaders // to the network. Note that the ExtendedHeaders must be verified thereafter. func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { - if amount > maxRequestSize { + if amount > ex.Params.MaxRequestSize { return nil, header.ErrHeadersLimitExceeded } session := newSession(ex.ctx, ex.host, ex.peerTracker.peers(), ex.protocolID) defer session.close() - return session.getRangeByHeight(ctx, from, amount) + return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRequest) } // GetVerifiedRange performs a request for the given range of ExtendedHeaders to the network and ensures @@ -239,7 +244,7 @@ func (ex *Exchange) request( // * should be received at least from 2 peers; // If neither condition is met, then latest ExtendedHeader will be returned (header of the highest // height). -func bestHead(result []*header.ExtendedHeader) (*header.ExtendedHeader, error) { +func bestHead(result []*header.ExtendedHeader, minResponses int) (*header.ExtendedHeader, error) { if len(result) == 0 { return nil, header.ErrNotFound } diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index e35a36d897..7d3bb81bfa 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -79,18 +79,19 @@ func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { func TestExchange_RequestFullRangeHeaders(t *testing.T) { // create mocknet with 5 peers hosts := createMocknet(t, 5) - // set max amount of headers per 1 peer - headersPerPeer = 10 totalAmount := 80 store := createStore(t, totalAmount) protocolSuffix := "private" // create new exchange - exchange := NewExchange(hosts[len(hosts)-1], []peer.ID{}, protocolSuffix) + exchange, err := NewExchange(hosts[len(hosts)-1], []peer.ID{}, protocolSuffix) + require.NoError(t, err) + exchange.Params.MaxHeadersPerRequest = 10 exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) t.Cleanup(exchange.cancel) servers := make([]*ExchangeServer, len(hosts)-1) // amount of servers is len(hosts)-1 because one peer acts as a client for index := range servers { - servers[index] = NewExchangeServer(hosts[index], store, protocolSuffix) + servers[index], err = NewExchangeServer(hosts[index], store, protocolSuffix) + require.NoError(t, err) servers[index].Start(context.Background()) //nolint:errcheck exchange.peerTracker.connectedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()} } @@ -141,7 +142,8 @@ func TestExchange_RequestByHash(t *testing.T) { host, peer := net.Hosts()[0], net.Hosts()[1] // create and start the ExchangeServer store := createStore(t, 5) - serv := NewExchangeServer(host, store, "private") + serv, err := NewExchangeServer(host, store, "private") + require.NoError(t, err) err = serv.Start(ctx) require.NoError(t, err) t.Cleanup(func() { @@ -173,6 +175,7 @@ func TestExchange_RequestByHash(t *testing.T) { } func Test_bestHead(t *testing.T) { + params := DefaultClientParameters() gen := func() []*header.ExtendedHeader { suite := header.NewTestSuite(t, 3) res := make([]*header.ExtendedHeader, 0) @@ -231,7 +234,7 @@ func Test_bestHead(t *testing.T) { } for _, tt := range testCases { res := tt.precondition() - header, err := bestHead(res) + header, err := bestHead(res, params.MinResponses) require.NoError(t, err) require.True(t, header.Height == tt.expectedHeight) } @@ -247,7 +250,8 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.NoError(t, err) // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] - serv := NewExchangeServer(host, createStore(t, 0), "private") + serv, err := NewExchangeServer(host, createStore(t, 0), "private") + require.NoError(t, err) err = serv.Start(ctx) require.NoError(t, err) t.Cleanup(func() { @@ -280,19 +284,21 @@ func createMocknet(t *testing.T, amount int) []libhost.Host { // createP2PExAndServer creates a Exchange with 5 headers already in its store. func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchange, *mockStore) { store := createStore(t, 5) - serverSideEx := NewExchangeServer(tpeer, store, "private") - err := serverSideEx.Start(context.Background()) + serverSideEx, err := NewExchangeServer(tpeer, store, "private") + require.NoError(t, err) + err = serverSideEx.Start(context.Background()) require.NoError(t, err) - exchange := NewExchange(host, []peer.ID{tpeer.ID()}, "private") - exchange.peerTracker.connectedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} - exchange.Start(context.Background()) //nolint:errcheck + ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private") + require.NoError(t, err) + ex.peerTracker.connectedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} + require.NoError(t, ex.Start(context.Background())) t.Cleanup(func() { serverSideEx.Stop(context.Background()) //nolint:errcheck - exchange.Stop(context.Background()) //nolint:errcheck + ex.Stop(context.Background()) //nolint:errcheck }) - return exchange, store + return ex, store } type mockStore struct { diff --git a/header/p2p/options.go b/header/p2p/options.go new file mode 100644 index 0000000000..27f089aab5 --- /dev/null +++ b/header/p2p/options.go @@ -0,0 +1,140 @@ +package p2p + +import ( + "fmt" + "time" +) + +// parameters is an interface that encompasses all params needed for +// client and server parameters to protect `optional functions` from this package. +type parameters interface { + ServerParameters | ClientParameters +} + +// Option is the functional option that is applied to the exchange instance +// to configure parameters. +type Option[T parameters] func(*T) + +// ServerParameters is the set of parameters that must be configured for the exchange. +type ServerParameters struct { + // WriteDeadline sets the timeout for sending messages to the stream + WriteDeadline time.Duration + // ReadDeadline sets the timeout for reading messages from the stream + ReadDeadline time.Duration + // MaxRequestSize defines the max amount of headers that can be handled at once. + MaxRequestSize uint64 +} + +// DefaultServerParameters returns the default params to configure the store. +func DefaultServerParameters() *ServerParameters { + return &ServerParameters{ + WriteDeadline: time.Second * 5, + ReadDeadline: time.Minute, + MaxRequestSize: 512, + } +} + +const errSuffix = "value should be positive and non-zero" + +func (p *ServerParameters) Validate() error { + if p.WriteDeadline == 0 { + return fmt.Errorf("invalid write time duration: %s", errSuffix) + } + if p.ReadDeadline == 0 { + return fmt.Errorf("invalid read time duration: %s", errSuffix) + } + if p.MaxRequestSize == 0 { + return fmt.Errorf("invalid max request size: %s", errSuffix) + } + return nil +} + +// WithWriteDeadline is a functional option that configures the +// `WriteDeadline` parameter. +func WithWriteDeadline[T ServerParameters](deadline time.Duration) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ServerParameters: + t.WriteDeadline = deadline + } + } +} + +// WithReadDeadline is a functional option that configures the +// `WithReadDeadline` parameter. +func WithReadDeadline[T ServerParameters](deadline time.Duration) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ServerParameters: + t.ReadDeadline = deadline + } + } +} + +// WithMaxRequestSize is a functional option that configures the +// `MaxRequestSize` parameter. +func WithMaxRequestSize[T parameters](size uint64) Option[T] { + return func(p *T) { + switch t := any(p).(type) { + case *ClientParameters: + t.MaxRequestSize = size + case *ServerParameters: + t.MaxRequestSize = size + } + } +} + +// ClientParameters is the set of parameters that must be configured for the exchange. +type ClientParameters struct { + // the target minimum amount of responses with the same chain head + MinResponses int + // MaxRequestSize defines the max amount of headers that can be handled at once. + MaxRequestSize uint64 + // MaxHeadersPerRequest defines the max amount of headers that can be requested per 1 request. + MaxHeadersPerRequest uint64 +} + +// DefaultClientParameters returns the default params to configure the store. +func DefaultClientParameters() *ClientParameters { + return &ClientParameters{ + MinResponses: 2, + MaxRequestSize: 512, + MaxHeadersPerRequest: 64, + } +} + +func (p *ClientParameters) Validate() error { + if p.MinResponses <= 0 { + return fmt.Errorf("invalid minimum amount of responses: %s", errSuffix) + } + if p.MaxRequestSize == 0 { + return fmt.Errorf("invalid max request size: %s", errSuffix) + } + if p.MaxHeadersPerRequest == 0 || p.MaxHeadersPerRequest > p.MaxRequestSize { + return fmt.Errorf("invalid max headers per request: %s", errSuffix) + } + return nil +} + +// WithMinResponses is a functional option that configures the +// `MinResponses` parameter. +func WithMinResponses[T ClientParameters](responses int) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ClientParameters: + t.MinResponses = responses + } + } +} + +// WithMaxHeadersPerRequest is a functional option that configures the +// // `MaxRequestSize` parameter. +func WithMaxHeadersPerRequest[T ClientParameters](amount uint64) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ClientParameters: + t.MaxHeadersPerRequest = amount + } + + } +} diff --git a/header/p2p/server.go b/header/p2p/server.go index 5d5fd7e39b..a4b08eabd7 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -25,16 +25,32 @@ type ExchangeServer struct { ctx context.Context cancel context.CancelFunc + + Params *ServerParameters } // NewExchangeServer returns a new P2P server that handles inbound // header-related requests. -func NewExchangeServer(host host.Host, store header.Store, protocolSuffix string) *ExchangeServer { +func NewExchangeServer( + host host.Host, + store header.Store, + protocolSuffix string, + opts ...Option[ServerParameters], +) (*ExchangeServer, error) { + params := DefaultServerParameters() + for _, opt := range opts { + opt(params) + } + if err := params.Validate(); err != nil { + return nil, err + } + return &ExchangeServer{ protocolID: protocolID(protocolSuffix), host: host, store: store, - } + Params: params, + }, nil } // Start sets the stream handler for inbound header-related requests. @@ -57,7 +73,7 @@ func (serv *ExchangeServer) Stop(context.Context) error { // requestHandler handles inbound ExtendedHeaderRequests. func (serv *ExchangeServer) requestHandler(stream network.Stream) { - err := stream.SetReadDeadline(time.Now().Add(readDeadline)) + err := stream.SetReadDeadline(time.Now().Add(serv.Params.ReadDeadline)) if err != nil { log.Debugf("error setting deadline: %s", err) } @@ -104,7 +120,7 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { } // write all headers to stream for _, h := range headers { - if err := stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + if err := stream.SetWriteDeadline(time.Now().Add(serv.Params.ReadDeadline)); err != nil { log.Debugf("error setting deadline: %s", err) } var bin []byte @@ -158,7 +174,7 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe return []*header.ExtendedHeader{head}, nil } - if to-from > maxRequestSize { + if to-from > serv.Params.MaxRequestSize { log.Errorw("server: skip request for too many headers.", "amount", to-from) return nil, header.ErrHeadersLimitExceeded } diff --git a/header/p2p/server_test.go b/header/p2p/server_test.go index 9bb50136ec..a1bdbaf51a 100644 --- a/header/p2p/server_test.go +++ b/header/p2p/server_test.go @@ -14,7 +14,8 @@ func TestExchangeServer_handleRequestTimeout(t *testing.T) { peer := createMocknet(t, 1) s, err := store.NewStore(datastore.NewMapDatastore()) require.NoError(t, err) - server := NewExchangeServer(peer[0], s, "private") + server, err := NewExchangeServer(peer[0], s, "private") + require.NoError(t, err) err = server.Start(context.Background()) require.NoError(t, err) t.Cleanup(func() { diff --git a/header/p2p/session.go b/header/p2p/session.go index e43b654450..73dc644b14 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -12,12 +12,6 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) -// TODO(@vgonkivs): make it configurable -var ( - // headersPerPeer is a maximum amount of headers that will be requested per peer. - headersPerPeer uint64 = 64 -) - // session aims to divide a range of headers // into several smaller requests among different peers. type session struct { @@ -45,7 +39,10 @@ func newSession(ctx context.Context, h host.Host, peerTracker []*peerStat, proto } // GetRangeByHeight requests headers from different peers. -func (s *session) getRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { +func (s *session) getRangeByHeight( + ctx context.Context, + from, amount, headersPerPeer uint64, +) ([]*header.ExtendedHeader, error) { log.Debugw("requesting headers", "from", from, "to", from+amount) requests := prepareRequests(from, amount, headersPerPeer) diff --git a/nodebuilder/config.go b/nodebuilder/config.go index b18c179592..e034a303b5 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -43,7 +43,7 @@ func DefaultConfig(tp node.Type) *Config { RPC: rpc.DefaultConfig(), Gateway: gateway.DefaultConfig(), Share: share.DefaultConfig(), - Header: header.DefaultConfig(), + Header: header.DefaultConfig(tp), } switch tp { diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 08701311c4..e0dd16deaa 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -8,7 +8,9 @@ import ( "github.com/multiformats/go-multiaddr" tmbytes "github.com/tendermint/tendermint/libs/bytes" + p2p_exchange "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -23,13 +25,27 @@ type Config struct { TrustedPeers []string Store *store.Parameters + + Server *p2p_exchange.ServerParameters + Client *p2p_exchange.ClientParameters `toml:",omitempty"` } -func DefaultConfig() Config { - return Config{ +func DefaultConfig(tp node.Type) Config { + cfg := Config{ TrustedHash: "", TrustedPeers: make([]string, 0), Store: store.DefaultParameters(), + Server: p2p_exchange.DefaultServerParameters(), + } + + switch tp { + case node.Bridge: + return cfg + case node.Light, node.Full: + cfg.Client = p2p_exchange.DefaultClientParameters() + return cfg + default: + panic("header: invalid node type") } } @@ -71,5 +87,17 @@ func (cfg *Config) Validate() error { if err != nil { return fmt.Errorf("module/header: misconfiguration of store: %w", err) } + + err = cfg.Server.Validate() + if err != nil { + return fmt.Errorf("module/header: misconfiguration of p2p exchange server: %w", err) + } + + if cfg.Client != nil { + err = cfg.Client.Validate() + if err != nil { + return fmt.Errorf("module/header: misconfiguration of p2p exchange client: %w", err) + } + } return nil } diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 7d4d69f68a..6c7ddfd6e6 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -17,8 +17,13 @@ import ( ) // newP2PServer constructs a new ExchangeServer using the given Network as a protocolID suffix. -func newP2PServer(host host.Host, store header.Store, network modp2p.Network) *p2p.ExchangeServer { - return p2p.NewExchangeServer(host, store, string(network)) +func newP2PServer( + host host.Host, + store header.Store, + network modp2p.Network, + opts []p2p.Option[p2p.ServerParameters], +) (*p2p.ExchangeServer, error) { + return p2p.NewExchangeServer(host, store, string(network), opts...) } // newP2PExchange constructs a new Exchange for headers. @@ -27,12 +32,14 @@ func newP2PExchange(cfg Config) func( modp2p.Bootstrappers, modp2p.Network, host.Host, + []p2p.Option[p2p.ClientParameters], ) (header.Exchange, error) { return func( lc fx.Lifecycle, bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host, + opts []p2p.Option[p2p.ClientParameters], ) (header.Exchange, error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { @@ -43,7 +50,10 @@ func newP2PExchange(cfg Config) func( ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - exchange := p2p.NewExchange(host, ids, string(network)) + exchange, err := p2p.NewExchange(host, ids, string(network), opts...) + if err != nil { + return nil, err + } lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { return exchange.Start(ctx) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 416d4a03a6..6f1399793c 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -36,6 +36,14 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { } }, ), + fx.Provide( + func(cfg Config) []p2p.Option[p2p.ServerParameters] { + return []p2p.Option[p2p.ServerParameters]{ + p2p.WithWriteDeadline(cfg.Server.WriteDeadline), + p2p.WithReadDeadline(cfg.Server.ReadDeadline), + p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), + } + }), fx.Provide(NewHeaderService), fx.Provide(fx.Annotate( func(ds datastore.Batching, opts []store.Option) (header.Store, error) { @@ -98,6 +106,15 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "header", baseComponents, + fx.Provide( + func(cfg Config) []p2p.Option[p2p.ClientParameters] { + return []p2p.Option[p2p.ClientParameters]{ + p2p.WithMinResponses(cfg.Client.MinResponses), + p2p.WithMaxRequestSize[p2p.ClientParameters](cfg.Client.MaxRequestSize), + p2p.WithMaxHeadersPerRequest(cfg.Client.MaxHeadersPerRequest), + } + }, + ), fx.Provide(newP2PExchange(*cfg)), ) case node.Bridge: diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 68378ac6b3..3e834b4717 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -4,19 +4,22 @@ import ( "testing" "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/nodebuilder/node" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // TestConstructModule_StoreParams ensures that all passed via functional options // params are set in store correctly. func TestConstructModule_StoreParams(t *testing.T) { - cfg := DefaultConfig() + cfg := DefaultConfig(node.Light) cfg.Store.StoreCacheSize = 15 cfg.Store.IndexCacheSize = 25 cfg.Store.WriteBatchSize = 35 @@ -38,3 +41,38 @@ func TestConstructModule_StoreParams(t *testing.T) { require.Equal(t, headerStore.Params.IndexCacheSize, cfg.Store.IndexCacheSize) require.Equal(t, headerStore.Params.WriteBatchSize, cfg.Store.WriteBatchSize) } + +// TestConstructModule_ExchangeParams ensures that all passed via functional options +// params are set in store correctly. +func TestConstructModule_ExchangeParams(t *testing.T) { + cfg := DefaultConfig(node.Light) + cfg.Client.MinResponses = 10 + cfg.Client.MaxRequestSize = 200 + cfg.Client.MaxHeadersPerRequest = 15 + var exchange *p2p.Exchange + var exchangeServer *p2p.ExchangeServer + + app := fxtest.New(t, + fx.Supply(modp2p.Private), + fx.Supply(modp2p.Bootstrappers{}), + fx.Provide(libp2p.New), + fx.Provide(func() datastore.Batching { + return datastore.NewMapDatastore() + }), + ConstructModule(node.Light, &cfg), + fx.Invoke( + func(e header.Exchange, server *p2p.ExchangeServer) { + ex := e.(*p2p.Exchange) + exchange = ex + exchangeServer = server + }), + ) + require.NoError(t, app.Err()) + require.Equal(t, exchange.Params.MinResponses, cfg.Client.MinResponses) + require.Equal(t, exchange.Params.MaxRequestSize, cfg.Client.MaxRequestSize) + require.Equal(t, exchange.Params.MaxHeadersPerRequest, cfg.Client.MaxHeadersPerRequest) + + require.Equal(t, exchangeServer.Params.WriteDeadline, cfg.Server.WriteDeadline) + require.Equal(t, exchangeServer.Params.ReadDeadline, cfg.Server.ReadDeadline) + require.Equal(t, exchangeServer.Params.MaxRequestSize, cfg.Server.MaxRequestSize) +} From da4f54bca1bfef86f53880ced569d37ffb4b8b84 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Thu, 1 Dec 2022 01:48:13 -0600 Subject: [PATCH 0216/1008] fix(header): add extra arg to fix build (#1410) ## Overview main isn't building due to a change in the api of a dependency and a recent PR. this PR adds an arbitrary number to fix that. NOTE: I don't actually know what number we should put here. 3 seemed like a good number, so I used it. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords Co-authored-by: Viacheslav --- header/p2p/exchange.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 357954707a..e816a81531 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -160,7 +160,7 @@ func (ex *Exchange) GetVerifiedRange( session := newSession(ex.ctx, ex.host, ex.peerTracker.peers(), ex.protocolID) defer session.close() - headers, err := session.getRangeByHeight(ctx, uint64(from.Height)+1, amount) + headers, err := session.getRangeByHeight(ctx, uint64(from.Height)+1, amount, ex.Params.MaxHeadersPerRequest) if err != nil { return nil, err } From 31434cb8e882bbc9fdd7bc8c7c8000b6721b2d67 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 5 Dec 2022 16:59:05 +0200 Subject: [PATCH 0217/1008] improvement(header): remove pubsub topic from p2p.subscription (#1430) ## Overview Topic was removed because we did not use it anywhere. ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords Checklist is not relevant here as this is a removal of the dead code. --- header/p2p/subscription.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/header/p2p/subscription.go b/header/p2p/subscription.go index c419acc661..a3ac7d1454 100644 --- a/header/p2p/subscription.go +++ b/header/p2p/subscription.go @@ -12,7 +12,6 @@ import ( // subscription handles retrieving ExtendedHeaders from the header pubsub topic. type subscription struct { - topic *pubsub.Topic subscription *pubsub.Subscription } @@ -25,7 +24,6 @@ func newSubscription(topic *pubsub.Topic) (*subscription, error) { } return &subscription{ - topic: topic, subscription: sub, }, nil } From 0fc1d0b821524171245d67dd40b2adfe472ebd69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 17:06:15 +0100 Subject: [PATCH 0218/1008] chore(deps): bump github.com/filecoin-project/go-jsonrpc from 0.1.8 to 0.1.9 (#1403) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index da67d170e6..3da051da14 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.4 github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/dagstore v0.5.6 - github.com/filecoin-project/go-jsonrpc v0.1.8 + github.com/filecoin-project/go-jsonrpc v0.1.9 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 diff --git a/go.sum b/go.sum index efd319f8c3..032c08b6bf 100644 --- a/go.sum +++ b/go.sum @@ -423,8 +423,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/filecoin-project/go-jsonrpc v0.1.8 h1:uXX/ikAk3Q4f/k8DRd9Zw+fWnfiYb5I+UI1tzlQgHog= -github.com/filecoin-project/go-jsonrpc v0.1.8/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= +github.com/filecoin-project/go-jsonrpc v0.1.9 h1:HRWLxo7HAWzI3xZGeFG4LZJoYpms+Q+8kwmMTLnyS3A= +github.com/filecoin-project/go-jsonrpc v0.1.9/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= From 352d8e3e5b65506bacfe38711071014a2ec44b72 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 6 Dec 2022 12:28:32 +0300 Subject: [PATCH 0219/1008] fix(share): update proto import path (#1435) ## Overview Generated pb file had different pkg name from one that used by gogoprotobuf. Regenerated the file and updated improrts ## Checklist - [x] Required CI checks are passing --- share/eds/byzantine/bad_encoding.go | 2 +- share/eds/byzantine/pb/share.pb.go | 50 ++++++++++++++--------------- share/eds/byzantine/share_proof.go | 2 +- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index ceea531dcb..5734745e67 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -9,7 +9,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" + pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/rsmt2d" ) diff --git a/share/eds/byzantine/pb/share.pb.go b/share/eds/byzantine/pb/share.pb.go index dc89a62621..33b9cdd1ab 100644 --- a/share/eds/byzantine/pb/share.pb.go +++ b/share/eds/byzantine/pb/share.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: share/eds/byzantine/pb/share.proto -package pb +package share_eds_byzantine_pb import ( fmt "fmt" @@ -255,31 +255,29 @@ func init() { } var fileDescriptor_d28ce8f160a920d1 = []byte{ - // 380 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x41, 0x6b, 0xdb, 0x30, - 0x18, 0xb5, 0x66, 0x3b, 0xdb, 0x3e, 0x67, 0x23, 0x88, 0x11, 0x0c, 0xdb, 0x8c, 0xf1, 0x2e, 0x66, - 0x30, 0x7b, 0x64, 0xec, 0x30, 0x76, 0x6a, 0xda, 0x42, 0x0a, 0x2d, 0x29, 0x2a, 0x6d, 0xa1, 0x97, - 0x22, 0x47, 0x8a, 0x6d, 0x9a, 0x5a, 0xc1, 0x52, 0x21, 0xed, 0xaf, 0xe8, 0x8f, 0xea, 0xa1, 0xc7, - 0x1c, 0x7b, 0x2c, 0xc9, 0x1f, 0x29, 0x92, 0x43, 0xc8, 0x21, 0xb9, 0xbd, 0xf7, 0x78, 0xd2, 0x7b, - 0x9f, 0xf4, 0x41, 0x24, 0x0b, 0x5a, 0xf3, 0x94, 0x33, 0x99, 0x66, 0xf7, 0x0f, 0xb4, 0x52, 0x65, - 0xc5, 0xd3, 0x69, 0x96, 0x1a, 0x39, 0x99, 0xd6, 0x42, 0x09, 0xdc, 0x6d, 0x08, 0x67, 0x32, 0x59, - 0x7b, 0x92, 0x69, 0x16, 0x15, 0xe0, 0x9d, 0xf0, 0xfa, 0x66, 0xc2, 0x4f, 0x6b, 0x21, 0xc6, 0xf8, - 0x0b, 0xb8, 0x52, 0xd1, 0x5a, 0xf9, 0x28, 0x44, 0xb1, 0x4d, 0x1a, 0x82, 0x3b, 0x60, 0xf3, 0x8a, - 0xf9, 0xef, 0x8c, 0xa6, 0xa1, 0xf6, 0x55, 0x82, 0x71, 0xe9, 0xdb, 0xa1, 0x1d, 0xb7, 0x49, 0x43, - 0xf0, 0x57, 0xf8, 0x38, 0xe1, 0x74, 0x7c, 0x5d, 0x50, 0x59, 0xf8, 0x4e, 0x88, 0xe2, 0x36, 0xf9, - 0xa0, 0x85, 0x01, 0x95, 0x45, 0x74, 0x01, 0xee, 0x99, 0xee, 0x80, 0x31, 0x38, 0x07, 0x54, 0x51, - 0x13, 0xd1, 0x26, 0x06, 0xe3, 0x7f, 0xe0, 0x9a, 0x02, 0x26, 0xc3, 0xeb, 0xfd, 0x48, 0xb6, 0xd7, - 0x4d, 0x36, 0xba, 0x92, 0xe6, 0x44, 0xf4, 0x84, 0xc0, 0xeb, 0x53, 0x76, 0x58, 0x8d, 0x04, 0x2b, - 0xab, 0x1c, 0x07, 0x00, 0x03, 0x4e, 0x19, 0xaf, 0x75, 0xea, 0x2a, 0x64, 0x43, 0xc1, 0x5d, 0x68, - 0x0d, 0x78, 0x99, 0x17, 0xca, 0x64, 0x39, 0x64, 0xc5, 0xf0, 0x5f, 0x68, 0x99, 0x7e, 0xcd, 0x4c, - 0x5e, 0xef, 0xfb, 0xae, 0x0e, 0xc6, 0x45, 0x56, 0x66, 0xfd, 0x12, 0x47, 0x15, 0xe3, 0x33, 0x33, - 0xef, 0x27, 0xd2, 0x10, 0xfc, 0x1b, 0x9c, 0xbd, 0x59, 0x29, 0x7d, 0x37, 0x44, 0xf1, 0xe7, 0xde, - 0xb7, 0x5d, 0x57, 0xd1, 0x59, 0x29, 0x89, 0x71, 0xfe, 0xf4, 0xc1, 0xd1, 0x0c, 0xbf, 0x07, 0x9b, - 0x0c, 0x2f, 0x3b, 0x96, 0x06, 0xfb, 0xc3, 0xe3, 0x0e, 0xea, 0x9f, 0x3f, 0x2f, 0x02, 0x34, 0x5f, - 0x04, 0xe8, 0x75, 0x11, 0xa0, 0xc7, 0x65, 0x60, 0xcd, 0x97, 0x81, 0xf5, 0xb2, 0x0c, 0xac, 0xab, - 0xff, 0x79, 0xa9, 0x8a, 0xbb, 0x2c, 0x19, 0x89, 0xdb, 0x74, 0xc4, 0x27, 0x5c, 0xaa, 0x92, 0x8a, - 0x3a, 0x5f, 0xe3, 0x5f, 0xfa, 0x5b, 0xd2, 0xed, 0xdb, 0x91, 0xb5, 0xcc, 0x62, 0xfc, 0x79, 0x0b, - 0x00, 0x00, 0xff, 0xff, 0x2d, 0xf3, 0x01, 0x31, 0x3e, 0x02, 0x00, 0x00, + // 347 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x41, 0x4b, 0xf3, 0x30, + 0x1c, 0xc6, 0x9b, 0xb7, 0xed, 0xde, 0xf7, 0xfd, 0x77, 0xca, 0x08, 0x32, 0x02, 0x6a, 0x28, 0xf5, + 0x52, 0x3c, 0xb4, 0x32, 0xf1, 0xe0, 0xd1, 0xa9, 0x30, 0x41, 0x99, 0x44, 0xd0, 0xa3, 0xa4, 0x26, + 0x5b, 0x8b, 0x23, 0x1d, 0x4d, 0x0f, 0xd3, 0x4f, 0xe1, 0x87, 0xf2, 0xe0, 0x71, 0x47, 0x8f, 0xb2, + 0x7d, 0x11, 0x49, 0x3a, 0x64, 0x07, 0x77, 0xfb, 0x3f, 0x0f, 0x4f, 0xf2, 0xfc, 0xfe, 0x09, 0x44, + 0x3a, 0xe7, 0x95, 0x4c, 0xa5, 0xd0, 0x69, 0xf6, 0xf2, 0xca, 0x55, 0x5d, 0x28, 0x99, 0x4e, 0xb3, + 0xd4, 0xda, 0xc9, 0xb4, 0x2a, 0xeb, 0x12, 0x77, 0x1b, 0x21, 0x85, 0x4e, 0x7e, 0x32, 0xc9, 0x34, + 0x8b, 0x72, 0x08, 0x6e, 0x64, 0xf5, 0x3c, 0x91, 0xb7, 0x55, 0x59, 0x8e, 0xf0, 0x0e, 0xf8, 0xba, + 0xe6, 0x55, 0x4d, 0x50, 0x88, 0x62, 0x97, 0x35, 0x02, 0x77, 0xc0, 0x95, 0x4a, 0x90, 0x3f, 0xd6, + 0x33, 0xa3, 0xc9, 0xa9, 0x52, 0x48, 0x4d, 0xdc, 0xd0, 0x8d, 0xdb, 0xac, 0x11, 0x78, 0x17, 0xfe, + 0x4f, 0x24, 0x1f, 0x3d, 0xe6, 0x5c, 0xe7, 0xc4, 0x0b, 0x51, 0xdc, 0x66, 0xff, 0x8c, 0x31, 0xe0, + 0x3a, 0x8f, 0xee, 0xc1, 0xbf, 0x33, 0x0c, 0x18, 0x83, 0x77, 0xc1, 0x6b, 0x6e, 0x2b, 0xda, 0xcc, + 0xce, 0xf8, 0x14, 0x7c, 0x0b, 0x60, 0x3b, 0x82, 0xde, 0x41, 0xf2, 0x3b, 0x6e, 0xb2, 0xc6, 0xca, + 0x9a, 0x13, 0xd1, 0x3b, 0x82, 0xa0, 0xcf, 0xc5, 0xa5, 0x7a, 0x2a, 0x45, 0xa1, 0xc6, 0x98, 0x02, + 0x0c, 0x24, 0x17, 0xb2, 0x32, 0xad, 0xab, 0x92, 0x35, 0x07, 0x77, 0xa1, 0x35, 0x90, 0xc5, 0x38, + 0xaf, 0x6d, 0x97, 0xc7, 0x56, 0x0a, 0x9f, 0x40, 0xcb, 0xf2, 0x35, 0x3b, 0x05, 0xbd, 0xfd, 0x4d, + 0x0c, 0x36, 0xc5, 0x56, 0x61, 0xf3, 0x12, 0x57, 0x4a, 0xc8, 0x99, 0xdd, 0x77, 0x8b, 0x35, 0x02, + 0x1f, 0x81, 0x77, 0x36, 0x2b, 0x34, 0xf1, 0x43, 0x14, 0x6f, 0xf7, 0xf6, 0x36, 0x5d, 0xc5, 0x67, + 0x85, 0x66, 0x36, 0x79, 0x48, 0xc0, 0x33, 0x0a, 0xff, 0x05, 0x97, 0x0d, 0x1f, 0x3a, 0x8e, 0x19, + 0xce, 0x87, 0xd7, 0x1d, 0xd4, 0x27, 0x1f, 0x0b, 0x8a, 0xe6, 0x0b, 0x8a, 0xbe, 0x16, 0x14, 0xbd, + 0x2d, 0xa9, 0x33, 0x5f, 0x52, 0xe7, 0x73, 0x49, 0x9d, 0xac, 0x65, 0xff, 0xf6, 0xf8, 0x3b, 0x00, + 0x00, 0xff, 0xff, 0x0f, 0xa3, 0x91, 0xb4, 0x01, 0x02, 0x00, 0x00, } func (m *MerkleProof) Marshal() (dAtA []byte, err error) { diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index c2476d123c..2c3dcb175d 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -9,7 +9,7 @@ import ( "github.com/minio/sha256-simd" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" + pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" ) From c8413c2472c65900b6ea885d57f18bcb0e76bd6b Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 6 Dec 2022 14:03:59 +0100 Subject: [PATCH 0220/1008] feat(share): Supplying `eds.Store` to bridge and full nodes (#1363) closes https://github.com/celestiaorg/celestia-node/issues/1114 --- nodebuilder/module.go | 1 + nodebuilder/node/type.go | 3 +++ nodebuilder/share/module.go | 20 ++++++++++++++++---- nodebuilder/testing.go | 3 +++ nodebuilder/tests/swamp/swamp.go | 4 ++++ share/eds/store.go | 12 ++++++------ 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 7a72e3bbcf..5f1f3e0cbf 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -30,6 +30,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store fx.Supply(store.Config), fx.Provide(store.Datastore), fx.Provide(store.Keystore), + fx.Supply(node.StorePath(store.Path())), // modules provided by the node p2p.ConstructModule(tp, &cfg.P2P), state.ConstructModule(tp, &cfg.State), diff --git a/nodebuilder/node/type.go b/nodebuilder/node/type.go index 6ba304871e..7af1c56b7a 100644 --- a/nodebuilder/node/type.go +++ b/nodebuilder/node/type.go @@ -4,6 +4,9 @@ package node // The zero value for Type is invalid. type Type uint8 +// StorePath is an alias used in order to pass the base path of the node store to nodebuilder modules. +type StorePath string + const ( // Bridge is a Celestia Node that bridges the Celestia consensus network and data availability // network. It maintains a trusted channel/connection to a Celestia Core node via the core.Client diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index a882953985..bfa6961732 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -3,13 +3,14 @@ package share import ( "context" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/full" - "github.com/celestiaorg/celestia-node/share/availability/light" - + "github.com/ipfs/go-datastore" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/full" + "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/share/eds" ) func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { @@ -47,6 +48,17 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, + fx.Provide(fx.Annotate( + func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { + return eds.NewStore(string(path), ds) + }, + fx.OnStart(func(ctx context.Context, eds *eds.Store) error { + return eds.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, eds *eds.Store) error { + return eds.Stop(ctx) + }), + )), fx.Provide(fx.Annotate( full.NewShareAvailability, fx.OnStart(func(ctx context.Context, avail *full.ShareAvailability) error { diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index a7fb1e7bde..0ea3684294 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -48,8 +48,11 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti cfg.Core.RPCPort = port cfg.RPC.Port = "0" + // storePath is used for the eds blockstore + storePath := t.TempDir() opts = append(opts, state.WithKeyringSigner(TestKeyringSigner(t)), + fx.Replace(node.StorePath(storePath)), ) nd, err := New(tp, p2p.Private, store, opts...) require.NoError(t, err) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 59325c1d69..0aefb2376d 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -259,8 +259,12 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti cfg, _ := store.Config() cfg.Header.TrustedHash = s.trustedHash cfg.RPC.Port = "0" + + // tempDir is used for the eds.Store + tempDir := s.t.TempDir() options = append(options, p2p.WithHost(s.createPeer(ks)), + fx.Replace(node.StorePath(tempDir)), ) node, err := nodebuilder.New(t, p2p.Private, store, options...) diff --git a/share/eds/store.go b/share/eds/store.go index ddfef04b03..172d018f5a 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -79,12 +79,13 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } // Start starts the underlying DAGStore. -func (s *Store) Start(ctx context.Context) error { +func (s *Store) Start(context.Context) error { + ctx := context.Background() return s.dgstr.Start(ctx) } // Stop stops the underlying DAGStore. -func (s *Store) Stop() error { +func (s *Store) Stop(context.Context) error { return s.dgstr.Close() } @@ -213,16 +214,15 @@ func (s *Store) Has(ctx context.Context, root share.Root) (bool, error) { } func setupPath(basepath string) error { - perms := os.FileMode(0755) - err := os.Mkdir(basepath+blocksPath, perms) + err := os.MkdirAll(basepath+blocksPath, os.ModePerm) if err != nil { return fmt.Errorf("failed to create blocks directory: %w", err) } - err = os.Mkdir(basepath+transientsPath, perms) + err = os.MkdirAll(basepath+transientsPath, os.ModePerm) if err != nil { return fmt.Errorf("failed to create transients directory: %w", err) } - err = os.Mkdir(basepath+indexPath, perms) + err = os.MkdirAll(basepath+indexPath, os.ModePerm) if err != nil { return fmt.Errorf("failed to create index directory: %w", err) } From 5080aba04a78cd6a0515c2238300cb24b8c98ebf Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 6 Dec 2022 14:06:36 +0100 Subject: [PATCH 0221/1008] feat(rpc): creating scaffolding for JWT authentication (#1325) --- Makefile | 2 +- api/gateway/availability.go | 2 +- api/gateway/middleware.go | 2 +- api/rpc/client/client.go | 12 +- api/rpc/server.go | 28 +- api/rpc_test.go | 19 +- cmd/docgen/docgen.go | 2 +- nodebuilder/das/constructors.go | 4 +- nodebuilder/das/das.go | 16 +- nodebuilder/default_services.go | 24 +- nodebuilder/fraud/fraud.go | 20 +- nodebuilder/header/header.go | 22 +- nodebuilder/header/service.go | 2 +- nodebuilder/node_test.go | 4 +- nodebuilder/p2p/mocks/api.go | 292 ++++++++++++++++++ nodebuilder/p2p/p2p.go | 113 +++++-- nodebuilder/rpc/constructors.go | 25 +- nodebuilder/share/share.go | 57 +++- nodebuilder/state/state.go | 197 +++++++++--- share/availability.go | 2 +- share/availability/cache/availability.go | 4 +- share/availability/cache/availability_test.go | 2 +- share/availability/full/availability.go | 2 +- share/availability/light/availability.go | 2 +- state/core_access.go | 2 +- state/core_access_test.go | 6 +- 26 files changed, 728 insertions(+), 135 deletions(-) create mode 100644 nodebuilder/p2p/mocks/api.go diff --git a/Makefile b/Makefile index e148abf613..b3755e68b7 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,6 @@ pb-gen: ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api openrpc-gen: @echo "--> Generating OpenRPC spec" - @go run ./cmd/docgen fraud header state share das + @go run ./cmd/docgen fraud header state share das p2p .PHONY: openrpc-gen diff --git a/api/gateway/availability.go b/api/gateway/availability.go index 37d61ee4b7..e5e53e0dba 100644 --- a/api/gateway/availability.go +++ b/api/gateway/availability.go @@ -35,7 +35,7 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http availResp := &AvailabilityResponse{ Probability: strconv.FormatFloat( - h.share.ProbabilityOfAvailability(), 'g', -1, 64), + h.share.ProbabilityOfAvailability(r.Context()), 'g', -1, 64), } err = h.share.SharesAvailable(r.Context(), header.DAH) diff --git a/api/gateway/middleware.go b/api/gateway/middleware.go index f71d3c3d23..498b9c5d64 100644 --- a/api/gateway/middleware.go +++ b/api/gateway/middleware.go @@ -33,7 +33,7 @@ func checkPostDisabled(state state.Module) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check if state service was halted and deny the transaction - if r.Method == http.MethodPost && state.IsStopped() { + if r.Method == http.MethodPost && state.IsStopped(r.Context()) { writeError(w, http.StatusMethodNotAllowed, r.URL.Path, errors.New("not possible to submit data")) return } diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index c22d1beca1..2e8c74d254 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -62,12 +62,12 @@ func NewClient(ctx context.Context, addr string) (*Client, error) { // TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 var modules = map[string]interface{}{ - "share": &client.Share, - "state": &client.State, - "header": &client.Header, - "fraud": &client.Fraud, - "das": &client.DAS, - "p2p": &client.P2P, + "share": &client.Share.Internal, + "state": &client.State.Internal, + "header": &client.Header.Internal, + "fraud": &client.Fraud.Internal, + "das": &client.DAS.Internal, + "p2p": &client.P2P.Internal, } for name, module := range modules { closer, err := jsonrpc.NewClient(ctx, addr, name, module, nil) diff --git a/api/rpc/server.go b/api/rpc/server.go index 85d59ccab0..684894162e 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -4,15 +4,22 @@ import ( "context" "net" "net/http" + "reflect" "sync/atomic" "time" "github.com/filecoin-project/go-jsonrpc" + "github.com/filecoin-project/go-jsonrpc/auth" logging "github.com/ipfs/go-log/v2" ) var log = logging.Logger("rpc") +var ( + AllPerms = []auth.Permission{"public", "read", "write", "admin"} + DefaultPerms = []auth.Permission{"public"} +) + type Server struct { srv *http.Server rpc *jsonrpc.RPCServer @@ -23,11 +30,19 @@ type Server struct { func NewServer(address, port string) *Server { rpc := jsonrpc.NewServer() + authHandler := &auth.Handler{ + Verify: func(ctx context.Context, token string) ([]auth.Permission, error) { + // TODO(distractedm1nd/renaynay): implement auth + log.Warn("auth not implemented, token: ", token) + return DefaultPerms, nil + }, + Next: rpc.ServeHTTP, + } return &Server{ rpc: rpc, srv: &http.Server{ Addr: address + ":" + port, - Handler: rpc, + Handler: authHandler, // the amount of time allowed to read request headers. set to the default 2 seconds ReadHeaderTimeout: 2 * time.Second, }, @@ -40,6 +55,17 @@ func (s *Server) RegisterService(namespace string, service interface{}) { s.rpc.Register(namespace, service) } +// RegisterAuthedService registers a service onto the RPC server. All methods on the service will +// then be exposed over the RPC. +func (s *Server) RegisterAuthedService(namespace string, service interface{}, out interface{}) { + auth.PermissionedProxy(AllPerms, DefaultPerms, service, getInternalStruct(out)) + s.RegisterService(namespace, out) +} + +func getInternalStruct(api interface{}) interface{} { + return reflect.ValueOf(api).Elem().FieldByName("Internal").Addr().Interface() +} + // Start starts the RPC Server. func (s *Server) Start(context.Context) error { couldStart := s.started.CompareAndSwap(false, true) diff --git a/api/rpc_test.go b/api/rpc_test.go index 80f3546c1f..b06fa3a3fc 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -19,6 +19,7 @@ import ( fraudMock "github.com/celestiaorg/celestia-node/nodebuilder/fraud/mocks" headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/node" + p2pMock "github.com/celestiaorg/celestia-node/nodebuilder/p2p/mocks" shareMock "github.com/celestiaorg/celestia-node/nodebuilder/share/mocks" stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks" "github.com/celestiaorg/celestia-node/state" @@ -62,13 +63,16 @@ func TestModulesImplementFullAPI(t *testing.T) { client := reflect.TypeOf(new(client.Client)).Elem() for i := 0; i < client.NumField(); i++ { module := client.Field(i) - for j := 0; j < module.Type.NumField(); j++ { - impl := module.Type.Field(j) + // the "closers" field is not an actual module + if module.Name == "closer" { + continue + } + internal, ok := module.Type.FieldByName("Internal") + require.True(t, ok, "module %s's API does not have an Internal field", module.Name) + for j := 0; j < internal.Type.NumField(); j++ { + impl := internal.Type.Field(j) method, _ := api.MethodByName(impl.Name) - // closers is the only thing on the Client struct that doesn't exist in the API - if impl.Name != "closers" { - require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) - } + require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) } } } @@ -140,6 +144,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { fraudMock.NewMockModule(ctrl), headerMock.NewMockModule(ctrl), dasMock.NewMockModule(ctrl), + p2pMock.NewMockModule(ctrl), } // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root @@ -150,6 +155,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { srv.RegisterService("fraud", mockAPI.Fraud) srv.RegisterService("header", mockAPI.Header) srv.RegisterService("das", mockAPI.Das) + srv.RegisterService("p2p", mockAPI.P2P) }) nd := nodebuilder.TestNode(t, node.Full, invokeRPC) // start node @@ -168,4 +174,5 @@ type mockAPI struct { Fraud *fraudMock.MockModule Header *headerMock.MockModule Das *dasMock.MockModule + P2P *p2pMock.MockModule } diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go index 4414955cda..2367ee8b90 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/docgen.go @@ -23,7 +23,7 @@ var rootCmd = &cobra.Command{ doc := docgen.NewOpenRPCDocument(nodeComments) // 3. Register the client wrapper interface on the document - for moduleName, module := range nodebuilder.PackageToDefaultImpl { + for moduleName, module := range nodebuilder.PackageToAPI { doc.RegisterReceiverName(moduleName, module) } diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index d6fa19f57c..b6b44758b6 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -37,8 +37,8 @@ func NewDASer( hsub header.Subscriber, store header.Store, batching datastore.Batching, - fraudService fraud.Service, + fraudServ fraud.Service, options ...das.Option, ) (*das.DASer, error) { - return das.NewDASer(da, hsub, store, batching, fraudService, options...) + return das.NewDASer(da, hsub, store, batching, fraudServ, options...) } diff --git a/nodebuilder/das/das.go b/nodebuilder/das/das.go index 5d2b58b113..f1dddbc4df 100644 --- a/nodebuilder/das/das.go +++ b/nodebuilder/das/das.go @@ -6,6 +6,8 @@ import ( "github.com/celestiaorg/celestia-node/das" ) +var _ Module = (*API)(nil) + //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // SamplingStats returns the current statistics over the DA sampling process. @@ -17,6 +19,16 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { - SamplingStats func(ctx context.Context) (das.SamplingStats, error) - WaitCatchUp func(ctx context.Context) error + Internal struct { + SamplingStats func(ctx context.Context) (das.SamplingStats, error) `perm:"read"` + WaitCatchUp func(ctx context.Context) error `perm:"read"` + } +} + +func (api *API) SamplingStats(ctx context.Context) (das.SamplingStats, error) { + return api.Internal.SamplingStats(ctx) +} + +func (api *API) WaitCatchUp(ctx context.Context) error { + return api.Internal.WaitCatchUp(ctx) } diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go index c41af05076..969688cead 100644 --- a/nodebuilder/default_services.go +++ b/nodebuilder/default_services.go @@ -1,19 +1,21 @@ package nodebuilder import ( - "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/das" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" - "github.com/celestiaorg/celestia-node/share/availability/light" - "github.com/celestiaorg/celestia-node/state" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -// PackageToDefaultImpl maps a package to its default implementation. Currently only used for +// PackageToAPI maps a package to its API struct. Currently only used for // method discovery for openrpc spec generation -var PackageToDefaultImpl = map[string]interface{}{ - "fraud": &fraud.ProofService{}, - "state": &state.CoreAccessor{}, - "share": &light.ShareAvailability{}, - "header": &header.Service{}, - "daser": &das.DASer{}, +var PackageToAPI = map[string]interface{}{ + "fraud": &fraud.API{}, + "state": &state.API{}, + "share": &share.API{}, + "header": &header.API{}, + "daser": &das.API{}, + "p2p": &p2p.API{}, } diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 5eacd0dba3..fd9cb3edd8 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -6,20 +6,32 @@ import ( "github.com/celestiaorg/celestia-node/fraud" ) +var _ Module = (*API)(nil) + // Module encompasses the behavior necessary to subscribe and broadcast fraud proofs within the // network. Any method signature changed here needs to also be changed in the API struct. // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - // Get fetches fraud proofs from the disk by its type. - Get(context.Context, fraud.ProofType) ([]Proof, error) // Subscribe allows to subscribe on a Proof pub sub topic by its type. Subscribe(context.Context, fraud.ProofType) (<-chan Proof, error) + // Get fetches fraud proofs from the disk by its type. + Get(context.Context, fraud.ProofType) ([]Proof, error) } // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { - Subscribe func(context.Context, fraud.ProofType) (<-chan Proof, error) - Get func(context.Context, fraud.ProofType) ([]Proof, error) + Internal struct { + Subscribe func(context.Context, fraud.ProofType) (<-chan Proof, error) `perm:"public"` + Get func(context.Context, fraud.ProofType) ([]Proof, error) `perm:"public"` + } +} + +func (api *API) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) { + return api.Internal.Subscribe(ctx, proofType) +} + +func (api *API) Get(ctx context.Context, proofType fraud.ProofType) ([]Proof, error) { + return api.Internal.Get(ctx, proofType) } diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index cc85faecfc..082285ddb3 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -17,13 +17,27 @@ type Module interface { // Head returns the ExtendedHeader of the chain head. Head(context.Context) (*header.ExtendedHeader, error) // IsSyncing returns the status of sync - IsSyncing() bool + IsSyncing(context.Context) bool } // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { - GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) - Head func(context.Context) (*header.ExtendedHeader, error) - IsSyncing func() bool + Internal struct { + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` + Head func(context.Context) (*header.ExtendedHeader, error) `perm:"public"` + IsSyncing func(context.Context) bool `perm:"public"` + } +} + +func (api *API) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { + return api.Internal.GetByHeight(ctx, u) +} + +func (api *API) Head(ctx context.Context) (*header.ExtendedHeader, error) { + return api.Internal.Head(ctx) +} + +func (api *API) IsSyncing(ctx context.Context) bool { + return api.Internal.IsSyncing(ctx) } diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index b569df00d3..1cdaaf17d3 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -44,6 +44,6 @@ func (s *Service) Head(ctx context.Context) (*header.ExtendedHeader, error) { return s.store.Head(ctx) } -func (s *Service) IsSyncing() bool { +func (s *Service) IsSyncing(context.Context) bool { return !s.syncer.State().Finished() } diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index f37540349d..51e0becc4d 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -37,13 +37,13 @@ func TestLifecycle(t *testing.T) { require.NoError(t, err) // ensure the state service is running - require.False(t, node.StateServ.IsStopped()) + require.False(t, node.StateServ.IsStopped(ctx)) err = node.Stop(ctx) require.NoError(t, err) // ensure the state service is stopped - require.True(t, node.StateServ.IsStopped()) + require.True(t, node.StateServ.IsStopped(ctx)) }) } } diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go new file mode 100644 index 0000000000..85310c5e27 --- /dev/null +++ b/nodebuilder/p2p/mocks/api.go @@ -0,0 +1,292 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/p2p (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + metrics "github.com/libp2p/go-libp2p-core/metrics" + network "github.com/libp2p/go-libp2p-core/network" + peer "github.com/libp2p/go-libp2p-core/peer" + protocol "github.com/libp2p/go-libp2p-core/protocol" + rcmgr "github.com/libp2p/go-libp2p-resource-manager" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// BandwidthForPeer mocks base method. +func (m *MockModule) BandwidthForPeer(arg0 peer.ID) metrics.Stats { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BandwidthForPeer", arg0) + ret0, _ := ret[0].(metrics.Stats) + return ret0 +} + +// BandwidthForPeer indicates an expected call of BandwidthForPeer. +func (mr *MockModuleMockRecorder) BandwidthForPeer(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForPeer", reflect.TypeOf((*MockModule)(nil).BandwidthForPeer), arg0) +} + +// BandwidthForProtocol mocks base method. +func (m *MockModule) BandwidthForProtocol(arg0 protocol.ID) metrics.Stats { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BandwidthForProtocol", arg0) + ret0, _ := ret[0].(metrics.Stats) + return ret0 +} + +// BandwidthForProtocol indicates an expected call of BandwidthForProtocol. +func (mr *MockModuleMockRecorder) BandwidthForProtocol(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForProtocol", reflect.TypeOf((*MockModule)(nil).BandwidthForProtocol), arg0) +} + +// BandwidthStats mocks base method. +func (m *MockModule) BandwidthStats() metrics.Stats { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BandwidthStats") + ret0, _ := ret[0].(metrics.Stats) + return ret0 +} + +// BandwidthStats indicates an expected call of BandwidthStats. +func (mr *MockModuleMockRecorder) BandwidthStats() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthStats", reflect.TypeOf((*MockModule)(nil).BandwidthStats)) +} + +// BlockPeer mocks base method. +func (m *MockModule) BlockPeer(arg0 peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BlockPeer", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// BlockPeer indicates an expected call of BlockPeer. +func (mr *MockModuleMockRecorder) BlockPeer(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockPeer", reflect.TypeOf((*MockModule)(nil).BlockPeer), arg0) +} + +// ClosePeer mocks base method. +func (m *MockModule) ClosePeer(arg0 peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClosePeer", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClosePeer indicates an expected call of ClosePeer. +func (mr *MockModuleMockRecorder) ClosePeer(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClosePeer", reflect.TypeOf((*MockModule)(nil).ClosePeer), arg0) +} + +// Connect mocks base method. +func (m *MockModule) Connect(arg0 context.Context, arg1 peer.AddrInfo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Connect", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Connect indicates an expected call of Connect. +func (mr *MockModuleMockRecorder) Connect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockModule)(nil).Connect), arg0, arg1) +} + +// Connectedness mocks base method. +func (m *MockModule) Connectedness(arg0 peer.ID) network.Connectedness { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Connectedness", arg0) + ret0, _ := ret[0].(network.Connectedness) + return ret0 +} + +// Connectedness indicates an expected call of Connectedness. +func (mr *MockModuleMockRecorder) Connectedness(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connectedness", reflect.TypeOf((*MockModule)(nil).Connectedness), arg0) +} + +// Info mocks base method. +func (m *MockModule) Info() peer.AddrInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Info") + ret0, _ := ret[0].(peer.AddrInfo) + return ret0 +} + +// Info indicates an expected call of Info. +func (mr *MockModuleMockRecorder) Info() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockModule)(nil).Info)) +} + +// IsProtected mocks base method. +func (m *MockModule) IsProtected(arg0 peer.ID, arg1 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsProtected", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsProtected indicates an expected call of IsProtected. +func (mr *MockModuleMockRecorder) IsProtected(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsProtected", reflect.TypeOf((*MockModule)(nil).IsProtected), arg0, arg1) +} + +// ListBlockedPeers mocks base method. +func (m *MockModule) ListBlockedPeers() []peer.ID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListBlockedPeers") + ret0, _ := ret[0].([]peer.ID) + return ret0 +} + +// ListBlockedPeers indicates an expected call of ListBlockedPeers. +func (mr *MockModuleMockRecorder) ListBlockedPeers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBlockedPeers", reflect.TypeOf((*MockModule)(nil).ListBlockedPeers)) +} + +// NATStatus mocks base method. +func (m *MockModule) NATStatus() (network.Reachability, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NATStatus") + ret0, _ := ret[0].(network.Reachability) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NATStatus indicates an expected call of NATStatus. +func (mr *MockModuleMockRecorder) NATStatus() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NATStatus", reflect.TypeOf((*MockModule)(nil).NATStatus)) +} + +// PeerInfo mocks base method. +func (m *MockModule) PeerInfo(arg0 peer.ID) peer.AddrInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PeerInfo", arg0) + ret0, _ := ret[0].(peer.AddrInfo) + return ret0 +} + +// PeerInfo indicates an expected call of PeerInfo. +func (mr *MockModuleMockRecorder) PeerInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerInfo", reflect.TypeOf((*MockModule)(nil).PeerInfo), arg0) +} + +// Peers mocks base method. +func (m *MockModule) Peers() []peer.ID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Peers") + ret0, _ := ret[0].([]peer.ID) + return ret0 +} + +// Peers indicates an expected call of Peers. +func (mr *MockModuleMockRecorder) Peers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockModule)(nil).Peers)) +} + +// Protect mocks base method. +func (m *MockModule) Protect(arg0 peer.ID, arg1 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Protect", arg0, arg1) +} + +// Protect indicates an expected call of Protect. +func (mr *MockModuleMockRecorder) Protect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protect", reflect.TypeOf((*MockModule)(nil).Protect), arg0, arg1) +} + +// PubSubPeers mocks base method. +func (m *MockModule) PubSubPeers(arg0 string) []peer.ID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PubSubPeers", arg0) + ret0, _ := ret[0].([]peer.ID) + return ret0 +} + +// PubSubPeers indicates an expected call of PubSubPeers. +func (mr *MockModuleMockRecorder) PubSubPeers(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubSubPeers", reflect.TypeOf((*MockModule)(nil).PubSubPeers), arg0) +} + +// ResourceState mocks base method. +func (m *MockModule) ResourceState() (rcmgr.ResourceManagerStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ResourceState") + ret0, _ := ret[0].(rcmgr.ResourceManagerStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResourceState indicates an expected call of ResourceState. +func (mr *MockModuleMockRecorder) ResourceState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceState", reflect.TypeOf((*MockModule)(nil).ResourceState)) +} + +// UnblockPeer mocks base method. +func (m *MockModule) UnblockPeer(arg0 peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnblockPeer", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnblockPeer indicates an expected call of UnblockPeer. +func (mr *MockModuleMockRecorder) UnblockPeer(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnblockPeer", reflect.TypeOf((*MockModule)(nil).UnblockPeer), arg0) +} + +// Unprotect mocks base method. +func (m *MockModule) Unprotect(arg0 peer.ID, arg1 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Unprotect", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Unprotect indicates an expected call of Unprotect. +func (mr *MockModuleMockRecorder) Unprotect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unprotect", reflect.TypeOf((*MockModule)(nil).Unprotect), arg0, arg1) +} diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index e27e30034a..5e9ac52fb5 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -16,10 +16,13 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" ) +var _ Module = (*API)(nil) + // Module represents all accessible methods related to the node's p2p // host / operations. // //nolint:dupl +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // Info returns address information about the host. Info() peer.AddrInfo @@ -188,22 +191,96 @@ func (m *module) PubSubPeers(topic string) []peer.ID { // //nolint:dupl type API struct { - Info func() peer.AddrInfo - Peers func() []peer.ID - PeerInfo func(id peer.ID) peer.AddrInfo - Connect func(ctx context.Context, pi peer.AddrInfo) error - ClosePeer func(id peer.ID) error - Connectedness func(id peer.ID) network.Connectedness - NATStatus func() (network.Reachability, error) - BlockPeer func(p peer.ID) error - UnblockPeer func(p peer.ID) error - ListBlockedPeers func() []peer.ID - Protect func(id peer.ID, tag string) - Unprotect func(id peer.ID, tag string) bool - IsProtected func(id peer.ID, tag string) bool - BandwidthStats func() metrics.Stats - BandwidthForPeer func(id peer.ID) metrics.Stats - BandwidthForProtocol func(proto protocol.ID) metrics.Stats - ResourceState func() (rcmgr.ResourceManagerStat, error) - PubSubPeers func(topic string) []peer.ID + Internal struct { + Info func() peer.AddrInfo `perm:"admin"` + Peers func() []peer.ID `perm:"admin"` + PeerInfo func(id peer.ID) peer.AddrInfo `perm:"admin"` + Connect func(ctx context.Context, pi peer.AddrInfo) error `perm:"admin"` + ClosePeer func(id peer.ID) error `perm:"admin"` + Connectedness func(id peer.ID) network.Connectedness `perm:"admin"` + NATStatus func() (network.Reachability, error) `perm:"admin"` + BlockPeer func(p peer.ID) error `perm:"admin"` + UnblockPeer func(p peer.ID) error `perm:"admin"` + ListBlockedPeers func() []peer.ID `perm:"admin"` + Protect func(id peer.ID, tag string) `perm:"admin"` + Unprotect func(id peer.ID, tag string) bool `perm:"admin"` + IsProtected func(id peer.ID, tag string) bool `perm:"admin"` + BandwidthStats func() metrics.Stats `perm:"admin"` + BandwidthForPeer func(id peer.ID) metrics.Stats `perm:"admin"` + BandwidthForProtocol func(proto protocol.ID) metrics.Stats `perm:"admin"` + ResourceState func() (rcmgr.ResourceManagerStat, error) `perm:"admin"` + PubSubPeers func(topic string) []peer.ID `perm:"admin"` + } +} + +func (api *API) Info() peer.AddrInfo { + return api.Internal.Info() +} + +func (api *API) Peers() []peer.ID { + return api.Internal.Peers() +} + +func (api *API) PeerInfo(id peer.ID) peer.AddrInfo { + return api.Internal.PeerInfo(id) +} + +func (api *API) Connect(ctx context.Context, pi peer.AddrInfo) error { + return api.Internal.Connect(ctx, pi) +} + +func (api *API) ClosePeer(id peer.ID) error { + return api.Internal.ClosePeer(id) +} + +func (api *API) Connectedness(id peer.ID) network.Connectedness { + return api.Internal.Connectedness(id) +} + +func (api *API) NATStatus() (network.Reachability, error) { + return api.Internal.NATStatus() +} + +func (api *API) BlockPeer(p peer.ID) error { + return api.Internal.BlockPeer(p) +} + +func (api *API) UnblockPeer(p peer.ID) error { + return api.Internal.UnblockPeer(p) +} + +func (api *API) ListBlockedPeers() []peer.ID { + return api.Internal.ListBlockedPeers() +} + +func (api *API) Protect(id peer.ID, tag string) { + api.Internal.Protect(id, tag) +} + +func (api *API) Unprotect(id peer.ID, tag string) bool { + return api.Internal.Unprotect(id, tag) +} + +func (api *API) IsProtected(id peer.ID, tag string) bool { + return api.Internal.IsProtected(id, tag) +} + +func (api *API) BandwidthStats() metrics.Stats { + return api.Internal.BandwidthStats() +} + +func (api *API) BandwidthForPeer(id peer.ID) metrics.Stats { + return api.Internal.BandwidthForPeer(id) +} + +func (api *API) BandwidthForProtocol(proto protocol.ID) metrics.Stats { + return api.Internal.BandwidthForProtocol(proto) +} + +func (api *API) ResourceState() (rcmgr.ResourceManagerStat, error) { + return api.Internal.ResourceState() +} + +func (api *API) PubSubPeers(topic string) []peer.ID { + return api.Internal.PubSubPeers(topic) } diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index 7b46e39c8f..6d6c337226 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -12,20 +12,21 @@ import ( // RegisterEndpoints registers the given services on the rpc. func RegisterEndpoints( - state state.Module, - share share.Module, - fraud fraud.Module, - header header.Module, - daser das.Module, - p2p p2p.Module, + stateMod state.Module, + shareMod share.Module, + fraudMod fraud.Module, + headerMod header.Module, + daserMod das.Module, + p2pMod p2p.Module, serv *rpc.Server, ) { - serv.RegisterService("state", state) - serv.RegisterService("share", share) - serv.RegisterService("fraud", fraud) - serv.RegisterService("header", header) - serv.RegisterService("das", daser) - serv.RegisterService("p2p", p2p) + serv.RegisterAuthedService("fraud", fraudMod, &fraud.API{}) + serv.RegisterAuthedService("das", daserMod, &das.API{}) + serv.RegisterAuthedService("header", headerMod, &header.API{}) + serv.RegisterAuthedService("state", stateMod, &state.API{}) + serv.RegisterAuthedService("share", shareMod, &share.API{}) + // @TODO(renaynay): add context to all p2p methods so we can activate auth + serv.RegisterService("p2p", p2pMod) } func Server(cfg *Config) *rpc.Server { diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index 9c51ac5f60..eaf049aedc 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -7,6 +7,8 @@ import ( "github.com/celestiaorg/nmt/namespace" ) +var _ Module = (*API)(nil) + // Module provides access to any data square or block share on the network. // // All Get methods provided on Module follow the following flow: @@ -24,23 +26,62 @@ import ( // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. + // SharesAvailable subjectively validates if Shares committed to the given Root are available on + // the Network. SharesAvailable(context.Context, *share.Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. - ProbabilityOfAvailability() float64 + ProbabilityOfAvailability(context.Context) float64 GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) - // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. + // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the + // given namespace.ID. GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) } // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { - SharesAvailable func(context.Context, *share.Root) error - ProbabilityOfAvailability func() float64 - GetShare func(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) - GetShares func(ctx context.Context, root *share.Root) ([][]share.Share, error) - GetSharesByNamespace func(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) + Internal struct { + SharesAvailable func(context.Context, *share.Root) error `perm:"public"` + ProbabilityOfAvailability func(context.Context) float64 `perm:"public"` + GetShare func( + ctx context.Context, + dah *share.Root, + row, col int, + ) (share.Share, error) `perm:"public"` + GetShares func( + ctx context.Context, + root *share.Root, + ) ([][]share.Share, error) `perm:"public"` + GetSharesByNamespace func( + ctx context.Context, + root *share.Root, + namespace namespace.ID, + ) ([]share.Share, error) `perm:"public"` + } +} + +func (api *API) SharesAvailable(ctx context.Context, root *share.Root) error { + return api.Internal.SharesAvailable(ctx, root) +} + +func (api *API) ProbabilityOfAvailability(ctx context.Context) float64 { + return api.Internal.ProbabilityOfAvailability(ctx) +} + +func (api *API) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { + return api.Internal.GetShare(ctx, dah, row, col) +} + +func (api *API) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) { + return api.Internal.GetShares(ctx, root) +} + +func (api *API) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + namespace namespace.ID, +) ([]share.Share, error) { + return api.Internal.GetSharesByNamespace(ctx, root, namespace) } diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 0b2a95f2d1..6058f5ecf4 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -11,6 +11,8 @@ import ( "github.com/celestiaorg/nmt/namespace" ) +var _ Module = (*API)(nil) + // Module represents the behaviors necessary for a user to // query for state-related information and submit transactions/ // messages to the Celestia network. @@ -18,7 +20,7 @@ import ( //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // IsStopped checks if the Module's context has been stopped - IsStopped() bool + IsStopped(ctx context.Context) bool // AccountAddress retrieves the address of the node's account/signer AccountAddress(ctx context.Context) (state.Address, error) @@ -79,47 +81,154 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { - IsStopped func() bool - Balance func(ctx context.Context) (*state.Balance, error) - BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) - Transfer func( - ctx context.Context, - to state.AccAddress, - amount math.Int, - gasLimit uint64, - ) (*state.TxResponse, error) - SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) - SubmitPayForData func(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) ( - *state.TxResponse, - error, - ) - CancelUnbondingDelegation func( - ctx context.Context, - valAddr state.ValAddress, - amount, - height state.Int, - gasLim uint64, - ) (*state.TxResponse, error) - BeginRedelegate func( - ctx context.Context, - srcValAddr, - dstValAddr state.ValAddress, - amount state.Int, - gasLim uint64, - ) (*state.TxResponse, error) - Undelegate func(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) ( - *state.TxResponse, - error, - ) - Delegate func(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) ( - *state.TxResponse, - error, - ) - QueryDelegation func(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) - QueryUnbonding func(ctx context.Context, valAddr state.ValAddress) (*types.QueryUnbondingDelegationResponse, error) - QueryRedelegations func( - ctx context.Context, - srcValAddr, - dstValAddr state.ValAddress, - ) (*types.QueryRedelegationsResponse, error) + Internal struct { + AccountAddress func(ctx context.Context) (state.Address, error) `perm:"read"` + IsStopped func(ctx context.Context) bool `perm:"public"` + Balance func(ctx context.Context) (*state.Balance, error) `perm:"read"` + BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) `perm:"public"` + Transfer func( + ctx context.Context, + to state.AccAddress, + amount math.Int, + gasLimit uint64, + ) (*state.TxResponse, error) `perm:"write"` + SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"write"` + SubmitPayForData func( + ctx context.Context, + nID namespace.ID, + data []byte, + gasLim uint64, + ) (*state.TxResponse, error) `perm:"write"` + CancelUnbondingDelegation func( + ctx context.Context, + valAddr state.ValAddress, + amount, + height state.Int, + gasLim uint64, + ) (*state.TxResponse, error) `perm:"write"` + BeginRedelegate func( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) `perm:"write"` + Undelegate func( + ctx context.Context, + delAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) `perm:"write"` + Delegate func( + ctx context.Context, + delAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) `perm:"write"` + QueryDelegation func( + ctx context.Context, + valAddr state.ValAddress, + ) (*types.QueryDelegationResponse, error) `perm:"public"` + QueryUnbonding func( + ctx context.Context, + valAddr state.ValAddress, + ) (*types.QueryUnbondingDelegationResponse, error) `perm:"public"` + QueryRedelegations func( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + ) (*types.QueryRedelegationsResponse, error) `perm:"public"` + } +} + +func (api *API) AccountAddress(ctx context.Context) (state.Address, error) { + return api.Internal.AccountAddress(ctx) +} + +func (api *API) IsStopped(ctx context.Context) bool { + return api.Internal.IsStopped(ctx) +} + +func (api *API) BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) { + return api.Internal.BalanceForAddress(ctx, addr) +} + +func (api *API) Transfer( + ctx context.Context, + to state.AccAddress, + amount math.Int, + gasLimit uint64, +) (*state.TxResponse, error) { + return api.Internal.Transfer(ctx, to, amount, gasLimit) +} + +func (api *API) SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) { + return api.Internal.SubmitTx(ctx, tx) +} + +func (api *API) SubmitPayForData( + ctx context.Context, + nID namespace.ID, + data []byte, + gasLim uint64, +) (*state.TxResponse, error) { + return api.Internal.SubmitPayForData(ctx, nID, data, gasLim) +} + +func (api *API) CancelUnbondingDelegation( + ctx context.Context, + valAddr state.ValAddress, + amount, height state.Int, + gasLim uint64, +) (*state.TxResponse, error) { + return api.Internal.CancelUnbondingDelegation(ctx, valAddr, amount, height, gasLim) +} + +func (api *API) BeginRedelegate( + ctx context.Context, + srcValAddr, dstValAddr state.ValAddress, + amount state.Int, + gasLim uint64, +) (*state.TxResponse, error) { + return api.Internal.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, gasLim) +} + +func (api *API) Undelegate( + ctx context.Context, + delAddr state.ValAddress, + amount state.Int, + gasLim uint64, +) (*state.TxResponse, error) { + return api.Internal.Undelegate(ctx, delAddr, amount, gasLim) +} + +func (api *API) Delegate( + ctx context.Context, + delAddr state.ValAddress, + amount state.Int, + gasLim uint64, +) (*state.TxResponse, error) { + return api.Internal.Delegate(ctx, delAddr, amount, gasLim) +} + +func (api *API) QueryDelegation(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) { + return api.Internal.QueryDelegation(ctx, valAddr) +} + +func (api *API) QueryUnbonding( + ctx context.Context, + valAddr state.ValAddress, +) (*types.QueryUnbondingDelegationResponse, error) { + return api.Internal.QueryUnbonding(ctx, valAddr) +} + +func (api *API) QueryRedelegations( + ctx context.Context, + srcValAddr, dstValAddr state.ValAddress, +) (*types.QueryRedelegationsResponse, error) { + return api.Internal.QueryRedelegations(ctx, srcValAddr, dstValAddr) +} + +func (api *API) Balance(ctx context.Context) (*state.Balance, error) { + return api.Internal.Balance(ctx) } diff --git a/share/availability.go b/share/availability.go index 7bd52109ec..712bf97456 100644 --- a/share/availability.go +++ b/share/availability.go @@ -28,5 +28,5 @@ type Availability interface { // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. // TODO(@Wondertan): Merge with SharesAvailable method, eventually - ProbabilityOfAvailability() float64 + ProbabilityOfAvailability(context.Context) float64 } diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index f8f3d8760c..e9ee88f867 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -81,8 +81,8 @@ func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro return err } -func (ca *ShareAvailability) ProbabilityOfAvailability() float64 { - return ca.avail.ProbabilityOfAvailability() +func (ca *ShareAvailability) ProbabilityOfAvailability(ctx context.Context) float64 { + return ca.avail.ProbabilityOfAvailability(ctx) } // Close flushes all queued writes to disk. diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index 438d63924e..6b3e96229f 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -134,6 +134,6 @@ func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root return nil } -func (da *dummyAvailability) ProbabilityOfAvailability() float64 { +func (da *dummyAvailability) ProbabilityOfAvailability(context.Context) float64 { return 0 } diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 1a5c57606c..68ebb56491 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -72,6 +72,6 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro return err } -func (fa *ShareAvailability) ProbabilityOfAvailability() float64 { +func (fa *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { return 1 } diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index ac8c805df8..71247417c4 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -122,6 +122,6 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo // (DefaultSampleAmount). // // Formula: 1 - (0.75 ** amount of samples) -func (la *ShareAvailability) ProbabilityOfAvailability() float64 { +func (la *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { return 1 - math.Pow(0.75, float64(DefaultSampleAmount)) } diff --git a/state/core_access.go b/state/core_access.go index c4b3844317..ee670e484b 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -413,6 +413,6 @@ func (ca *CoreAccessor) QueryRedelegations( }) } -func (ca *CoreAccessor) IsStopped() bool { +func (ca *CoreAccessor) IsStopped(context.Context) bool { return ca.ctx.Err() != nil } diff --git a/state/core_access_test.go b/state/core_access_test.go index 2bd148edb6..5018cee4f2 100644 --- a/state/core_access_test.go +++ b/state/core_access_test.go @@ -14,19 +14,19 @@ func TestLifecycle(t *testing.T) { err := ca.Start(ctx) require.NoError(t, err) // ensure accessor isn't stopped - require.False(t, ca.IsStopped()) + require.False(t, ca.IsStopped(ctx)) // cancel the top level context (this should not affect the lifecycle of the // accessor as it should manage its own internal context) cancel() // ensure accessor was unaffected by top-level context cancellation - require.False(t, ca.IsStopped()) + require.False(t, ca.IsStopped(ctx)) // stop the accessor stopCtx, stopCancel := context.WithCancel(context.Background()) t.Cleanup(stopCancel) err = ca.Stop(stopCtx) require.NoError(t, err) // ensure accessor is stopped - require.True(t, ca.IsStopped()) + require.True(t, ca.IsStopped(ctx)) // ensure that stopping the accessor again does not return an error err = ca.Stop(stopCtx) require.NoError(t, err) From 0af528ac4c4fd05d941d8c0615873444283a26fd Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 6 Dec 2022 15:26:41 +0100 Subject: [PATCH 0222/1008] feat(share): Periodic GC over EDSStore (#1359) closes https://github.com/celestiaorg/celestia-node/issues/1116 --- nodebuilder/p2p/p2p.go | 3 ++- share/eds/store.go | 52 ++++++++++++++++++++++++++++++++++------- share/eds/store_test.go | 39 +++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 11 deletions(-) diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 5e9ac52fb5..6128c7ff13 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -67,7 +67,8 @@ type Module interface { // BandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID. // The metrics returned include all traffic sent / received for the peer, regardless of protocol. BandwidthForPeer(id peer.ID) metrics.Stats - // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID. + // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given + // protocol.ID. BandwidthForProtocol(proto protocol.ID) metrics.Stats // ResourceState returns the state of the resource manager. diff --git a/share/eds/store.go b/share/eds/store.go index 172d018f5a..7384d62eef 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -5,6 +5,8 @@ import ( "fmt" "io" "os" + "sync/atomic" + "time" "github.com/filecoin-project/dagstore" "github.com/filecoin-project/dagstore/index" @@ -21,6 +23,8 @@ const ( blocksPath = "/blocks/" indexPath = "/index/" transientsPath = "/transients/" + + defaultGCInterval = time.Hour ) // Store maintains (via DAGStore) a top-level index enabling granular and efficient random access to @@ -28,13 +32,18 @@ const ( // Blockstore interface implementation to achieve access. The main use-case is randomized sampling // over the whole chain of EDS block data and getting data by namespace. type Store struct { + cancel context.CancelFunc + dgstr *dagstore.DAGStore mounts *mount.Registry topIdx index.Inverted carIdx index.FullIndexRepo - basepath string + basepath string + gcInterval time.Duration + // lastGCResult is only stored on the store for testing purposes. + lastGCResult atomic.Pointer[dagstore.GCResult] } // NewStore creates a new EDS Store under the given basepath and datastore. @@ -70,25 +79,52 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } return &Store{ - basepath: basepath, - dgstr: dagStore, - topIdx: invertedRepo, - carIdx: fsRepo, - mounts: r, + basepath: basepath, + dgstr: dagStore, + topIdx: invertedRepo, + carIdx: fsRepo, + gcInterval: defaultGCInterval, + mounts: r, }, nil } -// Start starts the underlying DAGStore. func (s *Store) Start(context.Context) error { - ctx := context.Background() + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel + + go s.gc(ctx) return s.dgstr.Start(ctx) } // Stop stops the underlying DAGStore. func (s *Store) Stop(context.Context) error { + defer s.cancel() return s.dgstr.Close() } +// gc periodically removes all inactive or errored shards. +func (s *Store) gc(ctx context.Context) { + ticker := time.NewTicker(s.gcInterval) + // initialize empty gc result to avoid panic on access + s.lastGCResult.Store(&dagstore.GCResult{ + Shards: make(map[shard.Key]error), + }) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + res, err := s.dgstr.GC(ctx) + if err != nil { + log.Errorf("garbage collecting dagstore: %v", err) + return + } + s.lastGCResult.Store(res) + } + + } +} + // Put stores the given data square with DataRoot's hash as a key. // // The square is verified on the Exchange level, and Put only stores the square, trusting it. diff --git a/share/eds/store_test.go b/share/eds/store_test.go index e68fc880c9..d3b6b538ae 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -5,13 +5,13 @@ import ( "fmt" "os" "testing" - - "github.com/stretchr/testify/assert" + "time" "github.com/filecoin-project/dagstore/shard" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" "github.com/ipld/go-car" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/share" @@ -156,6 +156,41 @@ func TestEDSStore_Has(t *testing.T) { assert.True(t, ok) } +// TestEDSStore_GC verifies that unused transient shards are collected by the GC periodically. +func TestEDSStore_GC(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + edsStore.gcInterval = time.Second + require.NoError(t, err) + + // kicks off the gc goroutine + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + shardKey := shard.KeyFromString(dah.String()) + + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + // doesn't exist yet + assert.NotContains(t, edsStore.lastGCResult.Load().Shards, shardKey) + + // wait for gc to run, retry three times + for i := 0; i < 3; i++ { + time.Sleep(edsStore.gcInterval) + if _, ok := edsStore.lastGCResult.Load().Shards[shardKey]; ok { + break + } + } + assert.Contains(t, edsStore.lastGCResult.Load().Shards, shardKey) + + // assert nil in this context means there was no error re-acquiring the shard during GC + assert.Nil(t, edsStore.lastGCResult.Load().Shards[shardKey]) +} + func newStore(t *testing.T) (*Store, error) { t.Helper() From bac5e3813833172e0b256d0ecbd6f48e535bfa20 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 6 Dec 2022 15:53:15 +0100 Subject: [PATCH 0223/1008] docs(adr-11): Block Data Sync Overhaul: Part I - Storage: Various Updates (#1425) --- ...d => adr-011-blocksync-overhaul-part-1.md} | 251 ++++++++++-------- 1 file changed, 142 insertions(+), 109 deletions(-) rename docs/adr/{adr-11-blocksync-overhaul.md => adr-011-blocksync-overhaul-part-1.md} (50%) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-011-blocksync-overhaul-part-1.md similarity index 50% rename from docs/adr/adr-11-blocksync-overhaul.md rename to docs/adr/adr-011-blocksync-overhaul-part-1.md index 98fea28739..720f60c26e 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-011-blocksync-overhaul-part-1.md @@ -4,6 +4,7 @@ - 23.08.22: Initial unfinished draft - 14.09.22: The first finished version +- 02.12.22: Fixing missed gaps ## Authors @@ -17,11 +18,10 @@ - LN - Light Node - FN - Full Node - BN - Bridge Node -- [EDS(Extended Data Square)](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L10) - plain Block -data omitting headers and other block metadata. -- [NMT](https://github.com/celestiaorg/nmt) - Namespaced Merkle Tree -- [DataRoot](https://github.com/celestiaorg/celestia-node/blob/main/service/share/share.go#L35) - alias for -[DAHeader](https://github.com/celestiaorg/celestia-core/blob/v0.34.x-celestia/pkg/da/data_availability_header.go#L29) +- [EDS(Extended Data Square)][eds] - plain Block data omitting headers and other block metadata +- ODS - Original Data Square or the first quadrant of the EDS. Contains real user data and padding +- [NMT][nmt] - Namespaced Merkle Tree +- [DataHash][dh] - Hash commitment over [DAHeader][dah] ## Context @@ -29,7 +29,7 @@ data omitting headers and other block metadata. Current block data synchronization is done over Bitswap, traversing NMT trees of rows and columns of data square quadrants. We know from empirical evidence that it takes more than 200 seconds(~65000 network requests) to download a 4MB block of -256kb shares, which is unacceptable and must be much less than the block time(15/30sec). +256kb shares, which is unacceptable and must be much less than the block time(15-30sec). The DASing, on the other hand, shows acceptable metrics for the block sizes we are aiming for initially. In the case of the same block, a DAS operation takes 50ms * 8(technically 9) blocking requests, which is ~400ms in an ideal scenario @@ -53,37 +53,37 @@ in 2 days to match the following requirements: - Existing Bitswap logic kept as a fallback mechanism for the case of reconstruction from light nodes - Keeping random hash-addressed access to shares for Bitswap to work -### Decision +## Decision This ADR intends to outline design decisions for block data storage. In a nutshell, the decision is to use -___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ +___[CAR format][car]___ and ___[Dagstore][dagstore]___ for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by namespace id) in the happy path. The p2p portion of the document will come in the subsequent Part II document. -#### Key Design Decisions +### Key Design Decisions -- __FNs/BNs store EDSes serialized as [CAR files](https://ipld.io/specs/transport/car).__ CAR format provides an -efficient way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read -sequentially in one read and transferred over the wire. Additionally, [CARv2](https://ipld.io/specs/transport/car/carv2/) -introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read -(if the index is cached in memory). +- __FNs/BNs store EDSes serialized as [CAR files][car].__ CAR format provides an + efficient way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read + sequentially in one read and transferred over the wire. Additionally, [CARv2][carv2] + introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read + (if the index is cached in memory). - __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs -to serve simple hash to data requests. The top-level index maps any Share/NMT Proof hash to any block CARv1 file so -that FNs/BNs can quickly serve DA requests. + to serve simple hash to data requests. The top-level index maps any Share/NMT Proof hash to any block CARv1 file so + that FNs/BNs can quickly serve DA requests. -- __FNs/BNs address EDSes by `DataRoot.Hash`.__ The only alternative is by height; however, it does not allow block -data deduplication in case EDSes are equal and couples the Data layer/pkg with the Header layer/pkg. +- __FNs/BNs address EDSes by `DataHash`.__ The only alternative is by height; however, it does not allow block + data deduplication in case EDSes are equal and couples the Data layer/pkg with the Header layer/pkg. -- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block -files.__ DAGStore provides the top-level indexing and CARv2-based indexing per each CAR file. In essence, it's an -engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. +- __FNs/BNs run a single instance of [`DAGStore`][dagstore] to manage CAR block + files.__ DAGStore provides the top-level indexing and CARv2-based indexing per each CAR file. In essence, it's an + engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2-based -indexing. Usage of CARv1 keeps only one copy of the index, stores/transfers less metadata per EDS, and simplifies -reading EDS from a file. + indexing. Usage of CARv1 keeps only one copy of the index, stores/transfers less metadata per EDS, and simplifies + reading EDS from a file. - __LNs DASing remains untouched__. The networking protocol and storage for LNs remain intact as it fulfills the -requirements. Bitswap usage as the backbone protocol for requesting samples and global Badger KVStore remain unaffected. + requirements. Bitswap usage as the backbone protocol for requesting samples and global Badger KVStore remain unaffected. ### Detailed Design @@ -97,38 +97,37 @@ The central data structure representing Celestia block data is EDS(`rsmt2d.Exten is focused around storing entire EDSes as a whole rather than a set of individual chunks, s.t. storage subsystem can handle storing and streaming/serving blocks of 4MB and more. -#### EDS Serde +#### EDS (De-)Serialization -Storing EDS as a whole requires EDS (de)serialization. For this, the [CAR format](https://ipld.io/specs/transport/car) is -chosen. +Storing EDS as a whole requires EDS (de)serialization. For this, the [CAR format][car] is chosen. -##### `share.WriteEDS` +##### `eds.WriteEDS` To write EDS into a stream/file, `WriteEDS` is introduced. Internally, it -- [Re-imports](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L41) EDS similarly to -[`ipld.ImportShares`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L51-L65) - - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline -exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) and in-memory [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) - - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L56), which saves to the -`Blockstore` only NMT Merkle proofs(no shares) _NOTE: `len(node.Links()) == 2`_ +- [Re-imports](https://github.com/celestiaorg/rsmt2d/blob/80d231f733e9dd8ca166c3d670470ed9a1c165d9/extendeddatasquare.go#L44) EDS similarly to + [`ipld.ImportShares`](https://github.com/celestiaorg/celestia-node/blob/da4f54bca1bfef86f53880ced569d37ffb4b8b84/share/add.go#L48) + - Using [`Blockservice`][blockservice] with [offline + exchange][offexchange] and in-memory [`Blockstore`][blockstore] + - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/da4f54bca1bfef86f53880ced569d37ffb4b8b84/share/add.go#L63), which saves to the + [`Blockstore`][blockstore] only NMT Merkle proofs(no shares) _NOTE: `len(node.Links()) == 2`_ - Actual shares are written further in a particular way explained further -- Creates and [writes](https://github.com/ipld/go-car/blob/master/car.go#L86) header [`CARv1Header`](https://github.com/ipld/go-car/blob/master/car.go#L30) +- Creates and [writes](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L86) header [`CARv1Header`](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L30) - Fills up `Roots` field with `EDS.RowRoots/EDS.ColRoots` roots converted into CIDs - Iterates over shares in quadrant-by-quadrant order via `EDS.GetCell` - - [Writes](https://github.com/ipld/go-car/blob/master/car.go#L118) the shares in row-by-row order -- Iterates over in-memory Blockstore and [writes]((https://github.com/ipld/go-car/blob/master/car.go#L118)) NMT Merkle -proofs stored in it + - [Writes](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L118) the shares in row-by-row order +- Iterates over in-memory Blockstore and [writes](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L118) NMT Merkle + proofs stored in it ___NOTES:___ -- _CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file -and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses -shares and Merkle Proofs in a depth-first manner packing them in a CAR file. However, this is incompatible with the -requirement of being able to truncate the CAR file reading out __only__ the first quadrant out of it without NMT proofs, -so serialization must be different from the utility to support that._ +- _CAR provides [a utility](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/car.go#L47) to serialize any DAG into the file + and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses + shares and Merkle Proofs in a depth-first manner packing them in a CAR file. However, this is incompatible with the + requirement of being able to truncate the CAR file reading out __only__ the first quadrant out of it without NMT proofs, + so serialization must be different from the utility to support that._ - _Alternatively to `WriteEDS`, an `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic -and efficient in some cases, with the cost of more complex implementation._ + and efficient in some cases, with the cost of more complex implementation._ ```go // WriteEDS writes the whole EDS into the given io.Writer as CARv1 file. @@ -136,12 +135,12 @@ and efficient in some cases, with the cost of more complex implementation._ func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error ``` -##### `share.ReadEDS` +##### `eds.ReadEDS` To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it -- Imports EDS with an empty pre-allocated slice. _NOTE: Size can be taken from DataRoot Row/Col len_ -- Wraps given io.Reader with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) +- Imports EDS with an empty pre-allocated slice. _NOTE: Size can be taken from CARHeader. +- Wraps given `io.Reader` with [`BlockReader`](https://github.com/ipld/go-car/blob/dab0fd5bb19dead0da1377270f37be9acf858cf0/v2/block_reader.go#L17) - Reads out blocks one by one and fills up the EDS quadrant via `EDS.SetCell` - In total there should be shares-in-quadrant amount of reads. - Recomputes and validates via `EDS.Repair` @@ -149,24 +148,32 @@ To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it ```go // ReadEDS reads an EDS quadrant(1/4) from an io.Reader CAR file. // It expects strictly first EDS quadrant(top left). -// The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS errors. -func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, error) +// The returned EDS is guaranteed to be full and valid against the DataHash, otherwise ReadEDS errors. +func ReadEDS(context.Context, io.Reader, DataHash) (*rsmt2d.ExtendedDataSquare, error) ``` -#### `share.EDSStore` +##### `eds.ODSReader` -FNs/BNs keep an `EDSStore` to manage every EDS on the disk. The `EDSStore` type is introduced in the `share` pkg. +To read only a quadrant/ODS out of full EDS, `ODSReader` is introduced. + +Its constructor wraps any `io.Reader` containing EDS generated by `WriteEDS` and produces an `io.Reader` which reads +exactly an ODS out of it, similar to `io.LimitReader`. The size of the EDS and ODS can be determined by the amount +of CIDs in the `CARHeader`. + +#### `eds.Store` + +FNs/BNs keep an `eds.Store` to manage every EDS on the disk. The `eds.Store` type is introduced in the `eds` pkg. Each EDS together with its Merkle Proofs serializes into a CARv1 file. All the serialized CARv1 file blobs are mounted on DAGStore via [Local FS Mounts](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#mounts) and registered as [Shards](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#shards). -The introduced `EDSStore` also maintains (via DAGStore) a top-level index enabling granular and efficient random access -to every share and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore` interface +The introduced `eds.Store` also maintains (via DAGStore) a top-level index enabling granular and efficient random access +to every share and/or Merkle proof over every registered CARv1 file. The `eds.Store` provides a custom `Blockstore` interface implementation to achieve access. The main use-case is randomized sampling over the whole chain of EDS block data and getting data by namespace. ```go -type EDSStore struct { +type Store struct { basepath string dgstr dagstore.DAGStore topIdx index.Inverted @@ -175,9 +182,9 @@ type EDSStore struct { ... } -// NewEDSStore constructs EDStore over OS directory to store CARv1 files of EDSes and indices for them. +// NewStore constructs Store over OS directory to store CARv1 files of EDSes and indices for them. // Datastore is used to keep the inverted/top-level index. -func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { +func NewStore(basepath string, ds datastore.Batching) *Store { topIdx := index.NewInverted(datastore) carIdx := index.NewFSRepo(basepath + "/index") mounts := mount.NewRegistry() @@ -187,7 +194,7 @@ func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { panic(err) } - return &EDSStore{ + return &Store{ basepath: basepath, dgst: dagstore.New(dagstore.Config{...}), topIdx: index.NewInverted(datastore), @@ -199,75 +206,90 @@ func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { ___NOTE:___ _EDSStore should have lifecycle methods(Start/Stop)._ -##### `share.EDSStore.Put` +##### `eds.Store.Put` To write an entire EDS `Put` method is introduced. Internally it -- Opens a file under `Store.Path/DataRoot.Hash` path +- Opens a file under `Store.Path/DataHash` path - Serializes the EDS into the file via `share.WriteEDS` -- Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) -- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Wraps it with `DAGStore`'s [FileMount][filemount] +- Converts `DataHash` to the [`shard.Key`][shardkey] - Registers the `Mount` as a `Shard` on the `DAGStore` ___NOTE:___ _Registering on the DAGStore populates the top-level index with shares/proofs accessible from stored EDS, which is out of the scope of the document._ ```go -// Put stores the given data square with DataRoot's hash as a key. +// Put stores the given data square with DataHash as a key. // // The square is verified on the Exchange level and Put only stores the square trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. // Additionally, the file gets indexed s.t. Store.Blockstore can access them. -func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error +func (s *Store) Put(context.Context, DataHash, *rsmt2d.ExtendedDataSquare) error ``` -##### `share.EDSStore.GetCAR` +##### `eds.Store.GetCAR` To read an EDS as a byte stream `GetCAR` method is introduced. Internally it -- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Converts `DataHash` to the [`shard.Key`][shardkey] - Acquires `ShardAccessor` and returns `io.ReadCloser` from it ___NOTES:___ - _`DAGStores`'s `ShardAccessor` has to be extended to return an `io.ReadCloser`. Currently, it only returns a `Blockstore` of the CAR_ -- _The returned `io.Reader` represents actual EDS exchanged over the wire, which should be limited to return the first quadrant_ +- _The returned `io.ReadCloer` represents full EDS exchanged. To get a quadrant an ODSReader should be used instead_ ```go -// GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as a CARv1 file. +// GetCAR takes a DataHash and returns a buffered reader to the respective EDS serialized as a CARv1 file. // -// The Reader strictly reads the first quadrant(1/4) of EDS, omitting all the NMT Merkle proofs. -// Integrity of the store data is not verified. +// The Reader strictly reads our full EDS, and it's integrity is not verified. // // Caller must Close returned reader after reading. -func (s *Store) GetCAR(context.Context, DataRoot) (io.ReadCloser, error) +func (s *Store) GetCAR(context.Context, DataHash) (io.ReadCloser, error) ``` -##### `share.EDSStore.Blockstore` +##### `eds.Store.Blockstore` -`Blockstore` method returns a [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) -interface implementation instance, providing random access over share and NMT Merkle proof in every stored EDS. It is -required for FNs/BNs to serve DAS requests over the Bitswap and for [reading data by namespace](#reading-data-by-namespace). +`Blockstore` method returns a [`Blockstore`][blockstore] interface implementation instance, providing random access over +share and NMT Merkle proof in every stored EDS. It is required for FNs/BNs to serve DAS requests over the Bitswap. -There is a [frozen/un-merged implementation](https://github.com/filecoin-project/dagstore/pull/116) of `Blockstore` -over `DAGStore` and CARv2 indexes. +There is a `Blockstore` over [`DAGStore`][dagstore] and [`CARv2`][carv2] indexes. ___NOTES:___ -- _We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._ +- _We can either use DAGStore's one or implement custom optimized for our needs._ - _The Blockstore does not store whole Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS -blocks._ + blocks._ +- EDIT: We went with custom implementation. ```go // Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS // registered on the Store. NOTE: The Blockstore does not store whole Celestia Blocks but IPFS blocks. // We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can access those. func (s *Store) Blockstore() blockstore.Blockstore +``` + +##### `eds.Store.CARBlockstore` + +`CARBlockstore` method returns a [`Blockstore`][blockstore] interface implementation instance, providing random access +over share and NMT Merkle proof in a specific EDS identified by DataHash. It is required for FNs/BNs to enable [reading +data by namespace](#reading-data-by-namespace). + +___NOTES:___ + +- _The returned Blockstore does not store whole Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS + blocks._ +```go +// CARBlockstore returns an IPFS Blockstore providing access to individual shares/nodes of a specific EDS identified by +// DataHash and registered on the Store. NOTE: The Blockstore does not store whole Celestia Blocks but IPFS blocks. +// We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can access those. +func (s *Store) CARBlockstore(DataHash) (blockstore.Blockstore, error) ``` -##### `share.EDSStore.Get` +##### `eds.Store.Get` To read an entire EDS `Get` method is introduced. Internally it: @@ -277,37 +299,36 @@ To read an entire EDS `Get` method is introduced. Internally it: ___NOTE:___ _It's unnecessary, but an API ergonomics/symmetry nice-to-have._ ```go -// Get reads EDS out of Store by given DataRoot. +// Get reads EDS out of Store by given DataHash. // // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by recomputing it. -func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, error) - +func (s *Store) Get(context.Context, DataHash) (*rsmt2d.ExtendedDataSquare, error) ``` -##### `share.EDSStore.Has` +##### `eds.Store.Has` To check if EDSStore keeps an EDS `Has` method is introduced. Internally it: -- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) -- Checks if [`GetShardInfo`](https://github.com/filecoin-project/dagstore/blob/master/dagstore.go#L483) does not return -[ErrShardUnknown](https://github.com/filecoin-project/dagstore/blob/eac7733212fdd7c80be5078659f7450b3956d2a6/dagstore.go#L55) +- Converts `DataHash` to the [`shard.Key`][shardkey] +- Checks if [`GetShardInfo`](https://github.com/filecoin-project/dagstore/blob/f9e7b7b4594221c8a4840a1e9f3f6e003c1b4c52/dagstore.go#L483) does not return + [ErrShardUnknown](https://github.com/filecoin-project/dagstore/blob/eac7733212fdd7c80be5078659f7450b3956d2a6/dagstore.go#L55) ___NOTE:___ _It's unnecessary, but an API ergonomics/symmetry nice-to-have._ ```go -// Has checks if EDS exists by the given DataRoot. -func (s *Store) Has(context.Context, DataRoot) (bool, error) - +// Has checks if EDS exists by the given DataHash. +func (s *Store) Has(context.Context, DataHash) (bool, error) ``` -##### `share.EDSStore.Remove` +##### `eds.Store.Remove` To remove stored EDS `Remove` method is introduced. Internally it: -- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Converts `DataHash` to the [`shard.Key`][shardkey] - Destroys `Shard` via `DAGStore` - Internally removes its `Mount` as well -- Removes CARv1 file from disk under `Store.Path/DataRoot.Hash` path +- Removes CARv1 file from disk under `Store.Path/DataHash` path +- Drops indecies ___NOTES:___ @@ -315,9 +336,8 @@ ___NOTES:___ - _GC logic on the DAGStore has to be investigated so that Removing is correctly implemented_ ```go -// Remove removes EDS from Store by the given DataRoot and cleans up all the indexing. -func (s *Store) Remove(context.Context, DataRoot) error - +// Remove removes EDS from Store by the given DataHash and cleans up all the indexing. +func (s *Store) Remove(context.Context, DataHash) error ``` #### Reading Data By Namespace @@ -325,8 +345,8 @@ func (s *Store) Remove(context.Context, DataRoot) error Generally stays unchanged with minor edits: - `share/ipld.GetByNamespace` is kept to load data from disk only and not from the network anymore - - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) - - Using [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) provided by `EDSStore` + - Using [`Blockservice`][blockservice] with [offline exchange][offexchange] + - Using [`Blockstore`][blockstore] provided by `eds.Store` - `share/ipld.GetByNamespace` is extended to return NMT Merkle proofs - Similar to `share/ipld.GetProofsForShares` - Ensure Merkle proofs are not duplicated! @@ -336,20 +356,20 @@ encoded shares and NMT Merkle Proofs. #### EDS Deduplication -Addressing EDS by DataRoot allows us to deduplicate equal EDSes. EDS equality is very unlikely to happen in practice, +Addressing EDS by DataHash allows us to deduplicate equal EDSes. EDS equality is very unlikely to happen in practice, beside empty Block case, which always produces the same EDS. #### Empty Block/EDS The empty block is valid and small EDS. It can happen in the early stage of the network. Its body is constant, and to avoid -transferring it over the wire, the `EDSStore` should be pre-initialized with an empty EDS value. +transferring it over the wire, the `eds.Store` should be pre-initialized with an empty EDS value. #### EDSStore Directory Path The EDSStore on construction expects a directory to store CAR files and indices. The path should be gotten based on `node.Store.Path`. -### Alternative Approaches +## Alternative Approaches - Extended blocks as a set of share blobs and Merkle proofs in global Store (_current approach with KVStore_) - Extended block as a single blob only(computing Merkle proofs) @@ -357,16 +377,29 @@ on `node.Store.Path`. - Extended block as a set of DAG/CAR blobs - Extended block as a single DAG/CAR blob -### Considerations +## Considerations - ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the -protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares, and for -more giant blocks during sync, these allocations put significant pressure on GC. One way to substantially alleviate this -is to integrate the bytes buffer pool into rmst2d. + protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares, and for + more giant blocks during sync, these allocations put significant pressure on GC. One way to substantially alleviate this + is to integrate the bytes buffer pool into rmst2d. - ___Disk usage increases from the top-level index.___ This is a temporary solution. The index will have to be removed. -LNs know which block they sample and can provide `DataRoot`'s hash together with sample request over Bitswap, removing -the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extension -](https://github.com/ipfs/specs/pull/270) or proposing a custom Bitswap message extension. Subsequently, the Blockstore -implementation provided via `EDSStore` would have to be changed to expect `DataRoot`'s hash to be passed through the -`context.Context`. + LNs know which block they sample and can provide `DataHash`together with sample request over Bitswap, removing + the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extension + ](https://github.com/ipfs/specs/pull/270) or proposing a custom Bitswap message extension. Subsequently, the Blockstore + implementation provided via `eds.Store` would have to be changed to expect DataHash to be passed through the + `context.Context`. + +[dah]: https://github.com/celestiaorg/celestia-app/blob/86c9bf6b981a8b25033357fddc89ef70abf80681/pkg/da/data_availability_header.go#L28 +[dh]: https://github.com/celestiaorg/celestia-core/blob/f76d026f3525d2d4fa309c62df29d42d33d0e9c6/types/block.go#L354 +[eds]: https://github.com/celestiaorg/rsmt2d/blob/76b270f80f0b9ac966c6f6b043e31514574f90f3/extendeddatasquare.go#L10 +[nmt]: https://github.com/celestiaorg/nmt +[car]: https://ipld.io/specs/transport/car +[carv2]: https://ipld.io/specs/transport/car/carv2/ +[dagstore]: https://github.com/filecoin-project/dagstore +[blockstore]: https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33 +[blockservice]: https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46 +[offexchange]: https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16 +[shardkey]: https://github.com/filecoin-project/dagstore/blob/f9e7b7b4594221c8a4840a1e9f3f6e003c1b4c52/shard/key.go#L12 +[filemount]: https://github.com/filecoin-project/dagstore/blob/f9e7b7b4594221c8a4840a1e9f3f6e003c1b4c52/mount/file.go#L10 From fbfd45b6d760d2b8781a1cd5f90e993ffa2df272 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 6 Dec 2022 10:46:15 -0600 Subject: [PATCH 0224/1008] chore: bump celestia-app v0.10.0 (#1389) Closes https://github.com/celestiaorg/celestia-node/issues/1384 --- core/testing.go | 5 ++++- go.mod | 2 +- go.sum | 4 ++-- nodebuilder/tests/swamp/swamp.go | 6 ++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/testing.go b/core/testing.go index 6253cf1447..c8a564093e 100644 --- a/core/testing.go +++ b/core/testing.go @@ -92,7 +92,10 @@ func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) { _, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) require.NoError(t, err) - t.Cleanup(cleanupCoreNode) + t.Cleanup(func() { + err := cleanupCoreNode() + require.NoError(t, err) + }) endpoint, err := GetEndpoint(tmNode.Config()) require.NoError(t, err) diff --git a/go.mod b/go.mod index 3da051da14..bc2ac6cc98 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.10.0-rc1 + github.com/celestiaorg/celestia-app v0.10.0 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.11.0 github.com/celestiaorg/rsmt2d v0.7.0 diff --git a/go.sum b/go.sum index 032c08b6bf..f11e960227 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.10.0-rc1 h1:Uj7wapqGCTBJ49Ub6luoUGwVtFxbEs0n5eDpGD1j/S0= -github.com/celestiaorg/celestia-app v0.10.0-rc1/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= +github.com/celestiaorg/celestia-app v0.10.0 h1:3v9Gz9R89PkSSnXnaavXLDcUaK0u36bnRg9ESgcYc+E= +github.com/celestiaorg/celestia-app v0.10.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 0aefb2376d..7d14ed94e2 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -103,8 +103,10 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { swp.t.Cleanup(func() { swp.stopAllNodes(ctx, swp.BridgeNodes, swp.FullNodes, swp.LightNodes) - cleanupCoreNode() - cleanupGRPCServer() + err = cleanupCoreNode() + require.NoError(t, err) + err = cleanupGRPCServer() + require.NoError(t, err) }) return swp From b27e710823d25891dbf16b1a8d16aac715525c7d Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 7 Dec 2022 12:28:01 +0100 Subject: [PATCH 0225/1008] feat(share): eds.Blockstore (#1395) Closes https://github.com/celestiaorg/celestia-node/issues/1115 --- share/eds/accessor_cache.go | 108 +++++++++++++++++++++++++++++++ share/eds/blockstore.go | 119 +++++++++++++++++++++++++++++++++++ share/eds/blockstore_test.go | 66 +++++++++++++++++++ share/eds/eds.go | 8 +-- share/eds/eds_test.go | 4 +- share/eds/store.go | 81 +++++++++++++++++++----- share/eds/store_test.go | 25 ++++++++ 7 files changed, 389 insertions(+), 22 deletions(-) create mode 100644 share/eds/accessor_cache.go create mode 100644 share/eds/blockstore.go create mode 100644 share/eds/blockstore_test.go diff --git a/share/eds/accessor_cache.go b/share/eds/accessor_cache.go new file mode 100644 index 0000000000..047ce49104 --- /dev/null +++ b/share/eds/accessor_cache.go @@ -0,0 +1,108 @@ +package eds + +import ( + "errors" + "fmt" + "reflect" + "sync" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/shard" + lru "github.com/hashicorp/golang-lru" +) + +var ( + defaultCacheSize = 128 + errCacheMiss = errors.New("accessor not found in blockstore cache") +) + +// accessorWithBlockstore is the value that we store in the blockstore cache +type accessorWithBlockstore struct { + sa *dagstore.ShardAccessor + // blockstore is stored separately because each access to the blockstore over the shard accessor + // reopens the underlying CAR. + bs dagstore.ReadBlockstore +} + +type blockstoreCache struct { + // stripedLocks prevents simultaneous RW access to the blockstore cache for a shard. Instead + // of using only one lock or one lock per key, we stripe the shard keys across 256 locks. 256 is + // chosen because it 0-255 is the range of values we get looking at the last byte of the key. + stripedLocks [256]sync.Mutex + // caches the blockstore for a given shard for shard read affinity i.e. + // further reads will likely be from the same shard. Maps (shard key -> blockstore). + cache *lru.Cache +} + +func newBlockstoreCache(cacheSize int) (*blockstoreCache, error) { + // instantiate the blockstore cache + bslru, err := lru.NewWithEvict(cacheSize, func(_ interface{}, val interface{}) { + // ensure we close the blockstore for a shard when it's evicted so dagstore can gc it. + abs, ok := val.(*accessorWithBlockstore) + if !ok { + panic(fmt.Sprintf( + "casting value from cache to accessorWithBlockstore: %s", + reflect.TypeOf(val), + )) + } + + if err := abs.sa.Close(); err != nil { + log.Errorf("couldn't close accessor after cache eviction: %s", err) + } + }) + if err != nil { + return nil, fmt.Errorf("failed to instantiate blockstore cache: %w", err) + } + return &blockstoreCache{cache: bslru}, nil +} + +// Get retrieves the blockstore for a given shard key from the cache. If the blockstore is not in +// the cache, it returns an errCacheMiss +func (bc *blockstoreCache) Get(shardContainingCid shard.Key) (*accessorWithBlockstore, error) { + lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)] + lk.Lock() + defer lk.Unlock() + + // We've already ensured that the given shard has the cid/multihash we are looking for. + val, ok := bc.cache.Get(shardContainingCid) + if !ok { + return nil, errCacheMiss + } + + accessor, ok := val.(*accessorWithBlockstore) + if !ok { + panic(fmt.Sprintf( + "casting value from cache to accessorWithBlockstore: %s", + reflect.TypeOf(val), + )) + } + return accessor, nil +} + +// Add adds a blockstore for a given shard key to the cache. +func (bc *blockstoreCache) Add( + shardContainingCid shard.Key, + accessor *dagstore.ShardAccessor, +) (*accessorWithBlockstore, error) { + blockStore, err := accessor.Blockstore() + if err != nil { + return nil, fmt.Errorf("failed to get blockstore from accessor: %w", err) + } + + lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)] + lk.Lock() + defer lk.Unlock() + + newAccessor := &accessorWithBlockstore{ + bs: blockStore, + sa: accessor, + } + bc.cache.Add(shardContainingCid, newAccessor) + return newAccessor, nil +} + +// shardKeyToStriped returns the index of the lock to use for a given shard key. We use the last +// byte of the shard key as the pseudo-random index. +func shardKeyToStriped(sk shard.Key) byte { + return sk.String()[len(sk.String())-1] +} diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go new file mode 100644 index 0000000000..a377fd532a --- /dev/null +++ b/share/eds/blockstore.go @@ -0,0 +1,119 @@ +package eds + +import ( + "context" + "errors" + "fmt" + + "github.com/filecoin-project/dagstore" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + bstore "github.com/ipfs/go-ipfs-blockstore" + ipld "github.com/ipfs/go-ipld-format" +) + +var _ bstore.Blockstore = (*blockstore)(nil) + +var ( + errUnsupportedOperation = errors.New("unsupported operation") + errShardNotFound = errors.New("the provided cid does not map to any shard") +) + +// blockstore implements the store.Blockstore interface on an EDSStore. +// The lru cache approach is heavily inspired by the existing implementation upstream. +// We simplified the design to not support multiple shards per key, call GetSize directly on the +// underlying RO blockstore, and do not throw errors on Put/PutMany. Also, we do not abstract away +// the blockstore operations. +// +// The intuition here is that each CAR file is its own blockstore, so we need this top level +// implementation to allow for the blockstore operations to be routed to the underlying stores. +type blockstore struct { + store *Store + cache *blockstoreCache +} + +func newBlockstore(store *Store, cache *blockstoreCache) *blockstore { + return &blockstore{ + store: store, + cache: cache, + } +} + +func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) + if err != nil { + return false, fmt.Errorf("failed to find shards containing multihash: %w", err) + } + return len(keys) > 0, nil +} + +func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) + if err != nil { + log.Debugf("failed to get blockstore for cid %s: %s", cid, err) + // nmt's GetNode expects an ipld.ErrNotFound when a cid is not found. + return nil, ipld.ErrNotFound{Cid: cid} + } + return blockstr.Get(ctx, cid) +} + +func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) + if err != nil { + return 0, err + } + return blockstr.GetSize(ctx, cid) +} + +// DeleteBlock is a noop on the EDS blockstore that returns an errUnsupportedOperation when called. +func (bs *blockstore) DeleteBlock(context.Context, cid.Cid) error { + return errUnsupportedOperation +} + +// Put is a noop on the EDS blockstore, but it does not return an error because it is called by +// bitswap. For clarification, an implementation of Put does not make sense in this context because +// it is unclear which CAR file the block should be written to. +// +// TODO: throw errUnsupportedOperation after issue #1440 +func (bs *blockstore) Put(context.Context, blocks.Block) error { + return nil +} + +// PutMany is a noop on the EDS blockstore, but it does not return an error because it is called by +// bitswap. For clarification, an implementation of PutMany does not make sense in this context +// because it is unclear which CAR file the blocks should be written to. +// +// TODO: throw errUnsupportedOperation after issue #1440 +func (bs *blockstore) PutMany(context.Context, []blocks.Block) error { + return nil +} + +// AllKeysChan is a noop on the EDS blockstore because the keys are not stored in a single CAR file. +func (bs *blockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) { + return nil, errUnsupportedOperation +} + +// HashOnRead is a noop on the EDS blockstore but an error cannot be returned due to the method +// signature from the blockstore interface. +func (bs *blockstore) HashOnRead(bool) { + log.Warnf("HashOnRead is a noop on the EDS blockstore") +} + +// getReadOnlyBlockstore finds the underlying blockstore of the shard that contains the given CID. +func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (dagstore.ReadBlockstore, error) { + keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) + if err != nil { + return nil, fmt.Errorf("failed to find shards containing multihash: %w", err) + } + if len(keys) == 0 { + return nil, errShardNotFound + } + + // a share can exist in multiple EDSes, so just take the first one. + shardKey := keys[0] + accessor, err := bs.store.getAccessor(ctx, shardKey) + if err != nil { + return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err) + } + return accessor.bs, nil +} diff --git a/share/eds/blockstore_test.go b/share/eds/blockstore_test.go new file mode 100644 index 0000000000..52024e192c --- /dev/null +++ b/share/eds/blockstore_test.go @@ -0,0 +1,66 @@ +package eds + +import ( + "context" + "io" + "testing" + + "github.com/filecoin-project/dagstore" + "github.com/ipld/go-car" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestBlockstore_Operations tests Has, Get, and GetSize on the top level eds.Store blockstore. +// It verifies that these operations are valid and successful on all blocks stored in a CAR file. +func TestBlockstore_Operations(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + r, err := edsStore.GetCAR(ctx, dah) + require.NoError(t, err) + carReader, err := car.NewCarReader(r) + require.NoError(t, err) + + topLevelBS := edsStore.Blockstore() + carBS, err := edsStore.CARBlockstore(ctx, dah.Hash()) + require.NoError(t, err) + + blockstores := []dagstore.ReadBlockstore{topLevelBS, carBS} + + for { + next, err := carReader.Next() + if err != nil { + require.ErrorIs(t, err, io.EOF) + break + } + blockCid := next.Cid() + + for _, bs := range blockstores { + // test GetSize + has, err := bs.Has(ctx, blockCid) + require.NoError(t, err, "blockstore.Has could not find root CID") + require.True(t, has) + + // test GetSize + block, err := bs.Get(ctx, blockCid) + assert.NoError(t, err, "blockstore.Get could not get a leaf CID") + assert.Equal(t, block.Cid(), blockCid) + assert.Equal(t, block.RawData(), next.RawData()) + + // test GetSize + size, err := bs.GetSize(ctx, blockCid) + assert.NotZerof(t, size, "blocksize.GetSize reported a root block from blockstore was empty") + assert.NoError(t, err) + } + } +} diff --git a/share/eds/eds.go b/share/eds/eds.go index 5824ae114d..8fd336dfd6 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -12,7 +12,7 @@ import ( "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - blockstore "github.com/ipfs/go-ipfs-blockstore" + bstore "github.com/ipfs/go-ipfs-blockstore" format "github.com/ipfs/go-ipld-format" "github.com/ipld/go-car" "github.com/ipld/go-car/util" @@ -35,7 +35,7 @@ type writingSession struct { eds *rsmt2d.ExtendedDataSquare // store is an in-memory blockstore, used to cache the inner nodes (proofs) while we walk the nmt // tree. - store blockstore.Blockstore + store bstore.Blockstore w io.Writer } @@ -63,7 +63,7 @@ func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) return fmt.Errorf("share: writing shares: %w", err) } - // 4. Iterates over in-memory Blockstore and writes proofs to the CAR + // 4. Iterates over in-memory blockstore and writes proofs to the CAR err = writer.writeProofs(ctx) if err != nil { return fmt.Errorf("share: writing proofs: %w", err) @@ -74,7 +74,7 @@ func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) // initializeWriter reimports the EDS into an in-memory blockstore in order to cache the proofs. func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) (*writingSession, error) { // we use an in-memory blockstore and an offline exchange - store := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) + store := bstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) bs := blockservice.New(store, nil) // shares are extracted from the eds so that we can reimport them to traverse shares := share.ExtractEDS(eds) diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 0e4db027cd..fe144ea491 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -10,7 +10,7 @@ import ( "testing" ds "github.com/ipfs/go-datastore" - blockstore "github.com/ipfs/go-ipfs-blockstore" + bstore "github.com/ipfs/go-ipfs-blockstore" carv1 "github.com/ipld/go-car" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -86,7 +86,7 @@ func TestWriteEDSIncludesRoots(t *testing.T) { f := openWrittenEDS(t) defer f.Close() - bs := blockstore.NewBlockstore(ds.NewMapDatastore()) + bs := bstore.NewBlockstore(ds.NewMapDatastore()) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) loaded, err := carv1.LoadCar(ctx, bs, f) diff --git a/share/eds/store.go b/share/eds/store.go index 7384d62eef..d5470f7617 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/dagstore/mount" "github.com/filecoin-project/dagstore/shard" "github.com/ipfs/go-datastore" + bstore "github.com/ipfs/go-ipfs-blockstore" "github.com/celestiaorg/celestia-node/share" @@ -29,7 +30,7 @@ const ( // Store maintains (via DAGStore) a top-level index enabling granular and efficient random access to // every share and/or Merkle proof over every registered CARv1 file. The EDSStore provides a custom -// Blockstore interface implementation to achieve access. The main use-case is randomized sampling +// blockstore interface implementation to achieve access. The main use-case is randomized sampling // over the whole chain of EDS block data and getting data by namespace. type Store struct { cancel context.CancelFunc @@ -37,6 +38,9 @@ type Store struct { dgstr *dagstore.DAGStore mounts *mount.Registry + cache *blockstoreCache + bs bstore.Blockstore + topIdx index.Inverted carIdx index.FullIndexRepo @@ -78,14 +82,22 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to create DAGStore: %w", err) } - return &Store{ - basepath: basepath, - dgstr: dagStore, - topIdx: invertedRepo, - carIdx: fsRepo, + cache, err := newBlockstoreCache(defaultCacheSize) + if err != nil { + return nil, fmt.Errorf("failed to create blockstore cache: %w", err) + } + + store := &Store{ + basepath: basepath, + dgstr: dagStore, + topIdx: invertedRepo, + carIdx: fsRepo, gcInterval: defaultGCInterval, - mounts: r, - }, nil + mounts: r, + cache: cache, + } + store.bs = newBlockstore(store, cache) + return store, nil } func (s *Store) Start(context.Context) error { @@ -129,7 +141,7 @@ func (s *Store) gc(ctx context.Context) { // // The square is verified on the Exchange level, and Put only stores the square, trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. -// Additionally, the file gets indexed s.t. store.Blockstore can access them. +// Additionally, the file gets indexed s.t. store.blockstore can access them. func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.ExtendedDataSquare) error { key := root.String() f, err := os.OpenFile(s.basepath+blocksPath+key, os.O_CREATE|os.O_WRONLY, 0600) @@ -170,21 +182,58 @@ func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.Extende // Caller must Close returned reader after reading. func (s *Store) GetCAR(ctx context.Context, root share.Root) (io.ReadCloser, error) { key := root.String() + accessor, err := s.getAccessor(ctx, shard.KeyFromString(key)) + if err != nil { + return nil, fmt.Errorf("failed to get accessor: %w", err) + } + return accessor.sa, nil +} + +// Blockstore returns an IPFS blockstore providing access to individual shares/nodes of all EDS +// registered on the Store. NOTE: The blockstore does not store whole Celestia Blocks but IPFS +// blocks. We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can +// access those. +func (s *Store) Blockstore() bstore.Blockstore { + return s.bs +} + +// CARBlockstore returns the IPFS blockstore that provides access to the IPLD blocks stored in an +// individual CAR file. +func (s *Store) CARBlockstore(ctx context.Context, dataHash []byte) (dagstore.ReadBlockstore, error) { + key := shard.KeyFromString(fmt.Sprintf("%X", dataHash)) + accessor, err := s.getAccessor(ctx, key) + if err != nil { + return nil, err + } + + return accessor.bs, nil +} +func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*accessorWithBlockstore, error) { + // try to fetch from cache + accessor, err := s.cache.Get(key) + if err != nil && err != errCacheMiss { + log.Errorw("unexpected error while reading key from bs cache %s: %s", key, err) + } + if accessor != nil { + return accessor, nil + } + + // wasn't found in cache, so acquire it and add to cache ch := make(chan dagstore.ShardResult, 1) - err := s.dgstr.AcquireShard(ctx, shard.KeyFromString(key), ch, dagstore.AcquireOpts{}) + err = s.dgstr.AcquireShard(ctx, key, ch, dagstore.AcquireOpts{}) if err != nil { - return nil, fmt.Errorf("failed to initiate shard acquisition: %w", err) + return nil, fmt.Errorf("failed to initialize shard acquisition: %w", err) } select { + case res := <-ch: + if res.Error != nil { + return nil, fmt.Errorf("failed to acquire shard: %w", res.Error) + } + return s.cache.Add(key, res.Accessor) case <-ctx.Done(): return nil, ctx.Err() - case result := <-ch: - if result.Error != nil { - return nil, fmt.Errorf("failed to acquire shard: %w", result.Error) - } - return result.Accessor, nil } } diff --git a/share/eds/store_test.go b/share/eds/store_test.go index d3b6b538ae..9a2818e9de 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -191,6 +191,31 @@ func TestEDSStore_GC(t *testing.T) { assert.Nil(t, edsStore.lastGCResult.Load().Shards[shardKey]) } +func Test_BlockstoreCache(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah, eds) + require.NoError(t, err) + + // key isnt in cache yet, so get returns errCacheMiss + shardKey := shard.KeyFromString(dah.String()) + _, err = edsStore.cache.Get(shardKey) + assert.ErrorIs(t, err, errCacheMiss) + + // now get it, so that the key is in the cache + _, err = edsStore.CARBlockstore(ctx, dah.Hash()) + assert.NoError(t, err) + _, err = edsStore.cache.Get(shardKey) + assert.NoError(t, err, errCacheMiss) +} + func newStore(t *testing.T) (*Store, error) { t.Helper() From 7cc511d74bead176e39312798e61a8c83ac2fb7f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 7 Dec 2022 13:01:18 +0100 Subject: [PATCH 0226/1008] refactor(cmd/cel-key): Unify flag names `--node.network` -> `--p2p.network` (#1443) Users will have to specify `--p2p.network` instead of `--node.network` in order to direct the cel-key utility to the correct repository -- default network is still `arabica` alias. --- cmd/cel-key/node_types.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 0a36b04cd9..9c091ada4e 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -14,8 +14,8 @@ import ( ) var ( - nodeDirKey = "node.type" - nodeNetworkKey = "node.network" + nodeDirKey = "node.type" + networkKey = "p2p.network" ) func DirectoryFlags() *flag.FlagSet { @@ -28,7 +28,7 @@ func DirectoryFlags() *flag.FlagSet { "Sets key utility to use the node type's directory (e.g. "+ "~/.celestia-light-"+strings.ToLower(defaultNetwork)+" if --node.type light is passed).") flags.String( - nodeNetworkKey, + networkKey, defaultNetwork, "Sets key utility to use the node network's directory (e.g. "+ "~/.celestia-light-mynetwork if --node.network MyNetwork is passed).") @@ -42,7 +42,7 @@ func ParseDirectoryFlags(cmd *cobra.Command) error { return errors.New("no node type provided") } - network := cmd.Flag(nodeNetworkKey).Value.String() + network := cmd.Flag(networkKey).Value.String() switch nodeType { case "bridge", "full", "light": keyPath := fmt.Sprintf("~/.celestia-%s-%s/keys", nodeType, strings.ToLower(network)) From 5e0cc316886dd956db6387bf4d98f5ad78855095 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 7 Dec 2022 13:57:53 +0100 Subject: [PATCH 0227/1008] refactor(share/eds): Store methods take `DataHash` instead of `share.Root` (#1439) Resolves #1428 Technically breaking API but it's not really used so it doesn't matter :) --- share/eds/blockstore_test.go | 4 ++-- share/eds/eds.go | 14 ++++++++------ share/eds/eds_test.go | 8 ++++---- share/eds/ods_test.go | 10 +++++----- share/eds/store.go | 33 +++++++++++++++++---------------- share/eds/store_test.go | 24 ++++++++++++------------ share/share.go | 16 ++++++++++++++++ 7 files changed, 64 insertions(+), 45 deletions(-) diff --git a/share/eds/blockstore_test.go b/share/eds/blockstore_test.go index 52024e192c..42f7ceab61 100644 --- a/share/eds/blockstore_test.go +++ b/share/eds/blockstore_test.go @@ -23,10 +23,10 @@ func TestBlockstore_Operations(t *testing.T) { require.NoError(t, err) eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) - r, err := edsStore.GetCAR(ctx, dah) + r, err := edsStore.GetCAR(ctx, dah.Hash()) require.NoError(t, err) carReader, err := car.NewCarReader(r) require.NoError(t, err) diff --git a/share/eds/eds.go b/share/eds/eds.go index 8fd336dfd6..236d5f6528 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -21,10 +21,11 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" ) var ErrEmptySquare = errors.New("share: importing empty data") @@ -230,13 +231,14 @@ func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) { // Only the first quadrant will be read, which represents the original data. // The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS // errors. -func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.ExtendedDataSquare, error) { +func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) { carReader, err := car.NewCarReader(r) if err != nil { return nil, fmt.Errorf("share: reading car file: %w", err) } - odsWidth := len(root.RowsRoots) / 2 + // car header includes both row and col roots in header + odsWidth := len(carReader.Header.Roots) / 4 odsSquareSize := odsWidth * odsWidth shares := make([][]byte, odsSquareSize) // the first quadrant is stored directly after the header, @@ -262,11 +264,11 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.Root) (*rsmt2d.Extende } newDah := da.NewDataAvailabilityHeader(eds) - if !bytes.Equal(newDah.Hash(), root.Hash()) { + if !bytes.Equal(newDah.Hash(), root) { return nil, fmt.Errorf( "share: content integrity mismatch: imported root %s doesn't match expected root %s", newDah.Hash(), - root.Hash(), + root, ) } return eds, nil diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index fe144ea491..5eac847ac2 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -148,7 +148,7 @@ func TestReadWriteRoundtrip(t *testing.T) { f := openWrittenEDS(t) defer f.Close() - loaded, err := ReadEDS(context.Background(), f, dah) + loaded, err := ReadEDS(context.Background(), f, dah.Hash()) require.NoError(t, err, "error reading EDS from file") require.Equal(t, eds.RowRoots(), loaded.RowRoots()) require.Equal(t, eds.ColRoots(), loaded.ColRoots()) @@ -162,7 +162,7 @@ func TestReadEDS(t *testing.T) { err = json.Unmarshal([]byte(exampleRoot), &dah) require.NoError(t, err, "error unmarshaling example root") - loaded, err := ReadEDS(context.Background(), f, dah) + loaded, err := ReadEDS(context.Background(), f, dah.Hash()) require.NoError(t, err, "error reading EDS from file") require.Equal(t, dah.RowsRoots, loaded.RowRoots()) require.Equal(t, dah.ColumnRoots, loaded.ColRoots()) @@ -174,7 +174,7 @@ func TestReadEDSContentIntegrityMismatch(t *testing.T) { f := openWrittenEDS(t) defer f.Close() - _, err := ReadEDS(context.Background(), f, dah) + _, err := ReadEDS(context.Background(), f, dah.Hash()) require.ErrorContains(t, err, "share: content integrity mismatch: imported root") } @@ -202,7 +202,7 @@ func BenchmarkReadWriteEDS(b *testing.B) { f := new(bytes.Buffer) _ = WriteEDS(ctx, eds, f) b.StartTimer() - _, err := ReadEDS(ctx, f, dah) + _, err := ReadEDS(ctx, f, dah.Hash()) require.NoError(b, err) } }) diff --git a/share/eds/ods_test.go b/share/eds/ods_test.go index 8509fc04b2..75fad3022e 100644 --- a/share/eds/ods_test.go +++ b/share/eds/ods_test.go @@ -26,11 +26,11 @@ func TestODSReader(t *testing.T) { // generate random eds data and put it into the store eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) // get CAR reader from store - r, err := edsStore.GetCAR(ctx, dah) + r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) // create ODSReader wrapper based on car reader to limit reads to ODS only @@ -75,11 +75,11 @@ func TestODSReaderReconstruction(t *testing.T) { // generate random eds data and put it into the store eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) // get CAR reader from store - r, err := edsStore.GetCAR(ctx, dah) + r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) // create ODSReader wrapper based on car reader to limit reads to ODS only @@ -87,7 +87,7 @@ func TestODSReaderReconstruction(t *testing.T) { assert.NoError(t, err) // reconstruct EDS from ODSReader - loaded, err := ReadEDS(ctx, odsR, dah) + loaded, err := ReadEDS(ctx, odsR, dah.Hash()) assert.NoError(t, err) require.Equal(t, eds.RowRoots(), loaded.RowRoots()) require.Equal(t, eds.ColRoots(), loaded.ColRoots()) diff --git a/share/eds/store.go b/share/eds/store.go index d5470f7617..88179d1603 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -15,9 +15,9 @@ import ( "github.com/ipfs/go-datastore" bstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" ) const ( @@ -88,13 +88,13 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } store := &Store{ - basepath: basepath, - dgstr: dagStore, - topIdx: invertedRepo, - carIdx: fsRepo, + basepath: basepath, + dgstr: dagStore, + topIdx: invertedRepo, + carIdx: fsRepo, gcInterval: defaultGCInterval, - mounts: r, - cache: cache, + mounts: r, + cache: cache, } store.bs = newBlockstore(store, cache) return store, nil @@ -141,8 +141,8 @@ func (s *Store) gc(ctx context.Context) { // // The square is verified on the Exchange level, and Put only stores the square, trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. -// Additionally, the file gets indexed s.t. store.blockstore can access them. -func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.ExtendedDataSquare) error { +// Additionally, the file gets indexed s.t. store.Blockstore can access them. +func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) error { key := root.String() f, err := os.OpenFile(s.basepath+blocksPath+key, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { @@ -180,7 +180,7 @@ func (s *Store) Put(ctx context.Context, root share.Root, square *rsmt2d.Extende // NMT Merkle proofs. Integrity of the store data is not verified. // // Caller must Close returned reader after reading. -func (s *Store) GetCAR(ctx context.Context, root share.Root) (io.ReadCloser, error) { +func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) { key := root.String() accessor, err := s.getAccessor(ctx, shard.KeyFromString(key)) if err != nil { @@ -237,8 +237,9 @@ func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*accessorWithBl } } -// Remove removes EDS from Store by the given share.Root and cleans up all the indexing. -func (s *Store) Remove(ctx context.Context, root share.Root) error { +// Remove removes EDS from Store by the given share.Root hash and cleans up all +// the indexing. +func (s *Store) Remove(ctx context.Context, root share.DataHash) error { key := root.String() ch := make(chan dagstore.ShardResult, 1) @@ -275,7 +276,7 @@ func (s *Store) Remove(ctx context.Context, root share.Root) error { // // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by // recomputing it. -func (s *Store) Get(ctx context.Context, root share.Root) (*rsmt2d.ExtendedDataSquare, error) { +func (s *Store) Get(ctx context.Context, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) { f, err := s.GetCAR(ctx, root) if err != nil { return nil, fmt.Errorf("failed to get CAR file: %w", err) @@ -287,8 +288,8 @@ func (s *Store) Get(ctx context.Context, root share.Root) (*rsmt2d.ExtendedDataS return eds, nil } -// Has checks if EDS exists by the given share.Root. -func (s *Store) Has(ctx context.Context, root share.Root) (bool, error) { +// Has checks if EDS exists by the given share.Root hash. +func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { key := root.String() info, err := s.dgstr.GetShardInfo(shard.KeyFromString(key)) if err == dagstore.ErrShardUnknown { diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 9a2818e9de..709c4bf3c2 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -33,11 +33,11 @@ func TestEDSStore_PutRegistersShard(t *testing.T) { eds, dah := randomEDS(t) // shard hasn't been registered yet - has, err := edsStore.Has(ctx, dah) + has, err := edsStore.Has(ctx, dah.Hash()) assert.False(t, has) assert.Error(t, err, "shard not found") - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) assert.NoError(t, err) _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) @@ -58,7 +58,7 @@ func TestEDSStore_PutIndexesEDS(t *testing.T) { stat, _ := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) assert.False(t, stat.Exists) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) assert.NoError(t, err) stat, err = edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) @@ -78,10 +78,10 @@ func TestEDSStore_GetCAR(t *testing.T) { require.NoError(t, err) eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) - r, err := edsStore.GetCAR(ctx, dah) + r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) carReader, err := car.NewCarReader(r) @@ -109,14 +109,14 @@ func TestEDSStore_Remove(t *testing.T) { eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) // assert that file now exists _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) assert.NoError(t, err) - err = edsStore.Remove(ctx, dah) + err = edsStore.Remove(ctx, dah.Hash()) assert.NoError(t, err) // shard should no longer be registered on the dagstore @@ -144,14 +144,14 @@ func TestEDSStore_Has(t *testing.T) { eds, dah := randomEDS(t) - ok, err := edsStore.Has(ctx, dah) + ok, err := edsStore.Has(ctx, dah.Hash()) assert.Error(t, err, "shard not found") assert.False(t, ok) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) assert.NoError(t, err) - ok, err = edsStore.Has(ctx, dah) + ok, err = edsStore.Has(ctx, dah.Hash()) assert.NoError(t, err) assert.True(t, ok) } @@ -172,7 +172,7 @@ func TestEDSStore_GC(t *testing.T) { eds, dah := randomEDS(t) shardKey := shard.KeyFromString(dah.String()) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) // doesn't exist yet @@ -201,7 +201,7 @@ func Test_BlockstoreCache(t *testing.T) { require.NoError(t, err) eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah, eds) + err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) // key isnt in cache yet, so get returns errCacheMiss diff --git a/share/share.go b/share/share.go index 8923a65dd9..8fbf427a95 100644 --- a/share/share.go +++ b/share/share.go @@ -1,6 +1,8 @@ package share import ( + "fmt" + "go.opentelemetry.io/otel" "github.com/celestiaorg/celestia-app/pkg/appconsts" @@ -39,3 +41,17 @@ func ID(s Share) namespace.ID { func Data(s Share) []byte { return s[NamespaceSize:] } + +// DataHash is a representation of the Root hash. +type DataHash []byte + +func (dh DataHash) Validate() error { + if len(dh) != 32 { + return fmt.Errorf("invalid hash size, expected 32, got %d", len(dh)) + } + return nil +} + +func (dh DataHash) String() string { + return fmt.Sprintf("%X", []byte(dh)) +} From 3210ca2f1b3b93971ba82eb04e57c8a4c13bf501 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 7 Dec 2022 14:18:16 +0100 Subject: [PATCH 0228/1008] refactor(nodebuilder/p2p): Extend `p2p.Module` method signatures to contain `context.Context` as first param (#1415) It is not possible to wrap methods with auth if context.Context is not provided as the first parameter due to quirks in https://github.com/filecoin-project/go-jsonrpc * updates `P2PModule` definition and api implementation * updates public api adr with new signatures --- docs/adr/adr-009-public-api.md | 34 +++---- nodebuilder/p2p/mocks/api.go | 136 ++++++++++++------------- nodebuilder/p2p/p2p.go | 175 ++++++++++++++++---------------- nodebuilder/p2p/p2p_test.go | 47 +++++---- nodebuilder/rpc/constructors.go | 3 +- 5 files changed, 199 insertions(+), 196 deletions(-) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 56ae47a14e..5d81cc47da 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -141,60 +141,60 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) ```go type P2PModule interface { // Info returns address information about the host. - Info() peer.AddrInfo + Info(context.Context) peer.AddrInfo // Peers returns all peer IDs used across all inner stores. - Peers() []peer.ID + Peers(context.Context) []peer.ID // PeerInfo returns a small slice of information Peerstore has on the // given peer. - PeerInfo(id peer.ID) peer.AddrInfo + PeerInfo(context.Context, peer.ID) peer.AddrInfo // Connect ensures there is a connection between this host and the peer with // given peer. Connect(ctx context.Context, pi peer.AddrInfo) error // ClosePeer closes the connection to a given peer. - ClosePeer(id peer.ID) error + ClosePeer(ctx context.Context, id peer.ID) error // Connectedness returns a state signaling connection capabilities. - Connectedness(id peer.ID) network.Connectedness + Connectedness(ctx context.Context, id peer.ID) network.Connectedness // NATStatus returns the current NAT status. - NATStatus() network.Reachability + NATStatus(context.Context) network.Reachability // BlockPeer adds a peer to the set of blocked peers. - BlockPeer(p peer.ID) error + BlockPeer(ctx context.Context, p peer.ID) error // UnblockPeer removes a peer from the set of blocked peers. - UnblockPeer(p peer.ID) error + UnblockPeer(ctx context.Context, p peer.ID) error // ListBlockedPeers returns a list of blocked peers. - ListBlockedPeers() []peer.ID + ListBlockedPeers(context.Context) []peer.ID // MutualAdd adds a peer to the list of peers who have a bidirectional // peering agreement that they are protected from being trimmed, dropped // or negatively scored. - MutualAdd(id peer.ID, tag string) + MutualAdd(ctx context.Context, id peer.ID, tag string) // MutualAdd removes a peer from the list of peers who have a bidirectional // peering agreement that they are protected from being trimmed, dropped // or negatively scored, returning a bool representing whether the given // peer is protected or not. - MutualRm(id peer.ID, tag string) bool + MutualRm(ctx context.Context, id peer.ID, tag string) bool // IsMutual returns whether the given peer is a mutual peer. - IsMutual(id peer.ID, tag string) bool + IsMutual(ctx context.Context, id peer.ID, tag string) bool // BandwidthStats returns a Stats struct with bandwidth metrics for all // data sent/received by the local peer, regardless of protocol or remote // peer IDs. - BandwidthStats() Stats + BandwidthStats(context.Context) Stats // BandwidthForPeer returns a Stats struct with bandwidth metrics associated // with the given peer.ID. The metrics returned include all traffic sent / // received for the peer, regardless of protocol. - BandwidthForPeer(id peer.ID) Stats + BandwidthForPeer(ctx context.Context, id peer.ID) Stats // BandwidthForProtocol returns a Stats struct with bandwidth metrics // associated with the given protocol.ID. - BandwidthForProtocol(proto protocol.ID) Stats + BandwidthForProtocol(ctx context.Context, proto protocol.ID) Stats // ResourceState returns the state of the resource manager. - ResourceState() rcmgr.ResourceManagerStat + ResourceState(context.Context) rcmgr.ResourceManagerStat // PubSubPeers returns the peer IDs of the peers joined on // the given topic. - PubSubPeers(topic string) ([]peer.ID, error) + PubSubPeers(ctx context.Context, topic string) ([]peer.ID, error) } ``` diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go index 85310c5e27..bf5f7a0a5d 100644 --- a/nodebuilder/p2p/mocks/api.go +++ b/nodebuilder/p2p/mocks/api.go @@ -40,73 +40,73 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // BandwidthForPeer mocks base method. -func (m *MockModule) BandwidthForPeer(arg0 peer.ID) metrics.Stats { +func (m *MockModule) BandwidthForPeer(arg0 context.Context, arg1 peer.ID) metrics.Stats { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BandwidthForPeer", arg0) + ret := m.ctrl.Call(m, "BandwidthForPeer", arg0, arg1) ret0, _ := ret[0].(metrics.Stats) return ret0 } // BandwidthForPeer indicates an expected call of BandwidthForPeer. -func (mr *MockModuleMockRecorder) BandwidthForPeer(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) BandwidthForPeer(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForPeer", reflect.TypeOf((*MockModule)(nil).BandwidthForPeer), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForPeer", reflect.TypeOf((*MockModule)(nil).BandwidthForPeer), arg0, arg1) } // BandwidthForProtocol mocks base method. -func (m *MockModule) BandwidthForProtocol(arg0 protocol.ID) metrics.Stats { +func (m *MockModule) BandwidthForProtocol(arg0 context.Context, arg1 protocol.ID) metrics.Stats { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BandwidthForProtocol", arg0) + ret := m.ctrl.Call(m, "BandwidthForProtocol", arg0, arg1) ret0, _ := ret[0].(metrics.Stats) return ret0 } // BandwidthForProtocol indicates an expected call of BandwidthForProtocol. -func (mr *MockModuleMockRecorder) BandwidthForProtocol(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) BandwidthForProtocol(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForProtocol", reflect.TypeOf((*MockModule)(nil).BandwidthForProtocol), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthForProtocol", reflect.TypeOf((*MockModule)(nil).BandwidthForProtocol), arg0, arg1) } // BandwidthStats mocks base method. -func (m *MockModule) BandwidthStats() metrics.Stats { +func (m *MockModule) BandwidthStats(arg0 context.Context) metrics.Stats { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BandwidthStats") + ret := m.ctrl.Call(m, "BandwidthStats", arg0) ret0, _ := ret[0].(metrics.Stats) return ret0 } // BandwidthStats indicates an expected call of BandwidthStats. -func (mr *MockModuleMockRecorder) BandwidthStats() *gomock.Call { +func (mr *MockModuleMockRecorder) BandwidthStats(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthStats", reflect.TypeOf((*MockModule)(nil).BandwidthStats)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthStats", reflect.TypeOf((*MockModule)(nil).BandwidthStats), arg0) } // BlockPeer mocks base method. -func (m *MockModule) BlockPeer(arg0 peer.ID) error { +func (m *MockModule) BlockPeer(arg0 context.Context, arg1 peer.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BlockPeer", arg0) + ret := m.ctrl.Call(m, "BlockPeer", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // BlockPeer indicates an expected call of BlockPeer. -func (mr *MockModuleMockRecorder) BlockPeer(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) BlockPeer(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockPeer", reflect.TypeOf((*MockModule)(nil).BlockPeer), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockPeer", reflect.TypeOf((*MockModule)(nil).BlockPeer), arg0, arg1) } // ClosePeer mocks base method. -func (m *MockModule) ClosePeer(arg0 peer.ID) error { +func (m *MockModule) ClosePeer(arg0 context.Context, arg1 peer.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ClosePeer", arg0) + ret := m.ctrl.Call(m, "ClosePeer", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // ClosePeer indicates an expected call of ClosePeer. -func (mr *MockModuleMockRecorder) ClosePeer(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) ClosePeer(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClosePeer", reflect.TypeOf((*MockModule)(nil).ClosePeer), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClosePeer", reflect.TypeOf((*MockModule)(nil).ClosePeer), arg0, arg1) } // Connect mocks base method. @@ -124,169 +124,169 @@ func (mr *MockModuleMockRecorder) Connect(arg0, arg1 interface{}) *gomock.Call { } // Connectedness mocks base method. -func (m *MockModule) Connectedness(arg0 peer.ID) network.Connectedness { +func (m *MockModule) Connectedness(arg0 context.Context, arg1 peer.ID) network.Connectedness { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Connectedness", arg0) + ret := m.ctrl.Call(m, "Connectedness", arg0, arg1) ret0, _ := ret[0].(network.Connectedness) return ret0 } // Connectedness indicates an expected call of Connectedness. -func (mr *MockModuleMockRecorder) Connectedness(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Connectedness(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connectedness", reflect.TypeOf((*MockModule)(nil).Connectedness), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connectedness", reflect.TypeOf((*MockModule)(nil).Connectedness), arg0, arg1) } // Info mocks base method. -func (m *MockModule) Info() peer.AddrInfo { +func (m *MockModule) Info(arg0 context.Context) peer.AddrInfo { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Info") + ret := m.ctrl.Call(m, "Info", arg0) ret0, _ := ret[0].(peer.AddrInfo) return ret0 } // Info indicates an expected call of Info. -func (mr *MockModuleMockRecorder) Info() *gomock.Call { +func (mr *MockModuleMockRecorder) Info(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockModule)(nil).Info)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockModule)(nil).Info), arg0) } // IsProtected mocks base method. -func (m *MockModule) IsProtected(arg0 peer.ID, arg1 string) bool { +func (m *MockModule) IsProtected(arg0 context.Context, arg1 peer.ID, arg2 string) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsProtected", arg0, arg1) + ret := m.ctrl.Call(m, "IsProtected", arg0, arg1, arg2) ret0, _ := ret[0].(bool) return ret0 } // IsProtected indicates an expected call of IsProtected. -func (mr *MockModuleMockRecorder) IsProtected(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) IsProtected(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsProtected", reflect.TypeOf((*MockModule)(nil).IsProtected), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsProtected", reflect.TypeOf((*MockModule)(nil).IsProtected), arg0, arg1, arg2) } // ListBlockedPeers mocks base method. -func (m *MockModule) ListBlockedPeers() []peer.ID { +func (m *MockModule) ListBlockedPeers(arg0 context.Context) []peer.ID { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListBlockedPeers") + ret := m.ctrl.Call(m, "ListBlockedPeers", arg0) ret0, _ := ret[0].([]peer.ID) return ret0 } // ListBlockedPeers indicates an expected call of ListBlockedPeers. -func (mr *MockModuleMockRecorder) ListBlockedPeers() *gomock.Call { +func (mr *MockModuleMockRecorder) ListBlockedPeers(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBlockedPeers", reflect.TypeOf((*MockModule)(nil).ListBlockedPeers)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBlockedPeers", reflect.TypeOf((*MockModule)(nil).ListBlockedPeers), arg0) } // NATStatus mocks base method. -func (m *MockModule) NATStatus() (network.Reachability, error) { +func (m *MockModule) NATStatus(arg0 context.Context) (network.Reachability, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NATStatus") + ret := m.ctrl.Call(m, "NATStatus", arg0) ret0, _ := ret[0].(network.Reachability) ret1, _ := ret[1].(error) return ret0, ret1 } // NATStatus indicates an expected call of NATStatus. -func (mr *MockModuleMockRecorder) NATStatus() *gomock.Call { +func (mr *MockModuleMockRecorder) NATStatus(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NATStatus", reflect.TypeOf((*MockModule)(nil).NATStatus)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NATStatus", reflect.TypeOf((*MockModule)(nil).NATStatus), arg0) } // PeerInfo mocks base method. -func (m *MockModule) PeerInfo(arg0 peer.ID) peer.AddrInfo { +func (m *MockModule) PeerInfo(arg0 context.Context, arg1 peer.ID) peer.AddrInfo { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PeerInfo", arg0) + ret := m.ctrl.Call(m, "PeerInfo", arg0, arg1) ret0, _ := ret[0].(peer.AddrInfo) return ret0 } // PeerInfo indicates an expected call of PeerInfo. -func (mr *MockModuleMockRecorder) PeerInfo(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) PeerInfo(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerInfo", reflect.TypeOf((*MockModule)(nil).PeerInfo), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerInfo", reflect.TypeOf((*MockModule)(nil).PeerInfo), arg0, arg1) } // Peers mocks base method. -func (m *MockModule) Peers() []peer.ID { +func (m *MockModule) Peers(arg0 context.Context) []peer.ID { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Peers") + ret := m.ctrl.Call(m, "Peers", arg0) ret0, _ := ret[0].([]peer.ID) return ret0 } // Peers indicates an expected call of Peers. -func (mr *MockModuleMockRecorder) Peers() *gomock.Call { +func (mr *MockModuleMockRecorder) Peers(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockModule)(nil).Peers)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peers", reflect.TypeOf((*MockModule)(nil).Peers), arg0) } // Protect mocks base method. -func (m *MockModule) Protect(arg0 peer.ID, arg1 string) { +func (m *MockModule) Protect(arg0 context.Context, arg1 peer.ID, arg2 string) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Protect", arg0, arg1) + m.ctrl.Call(m, "Protect", arg0, arg1, arg2) } // Protect indicates an expected call of Protect. -func (mr *MockModuleMockRecorder) Protect(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Protect(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protect", reflect.TypeOf((*MockModule)(nil).Protect), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Protect", reflect.TypeOf((*MockModule)(nil).Protect), arg0, arg1, arg2) } // PubSubPeers mocks base method. -func (m *MockModule) PubSubPeers(arg0 string) []peer.ID { +func (m *MockModule) PubSubPeers(arg0 context.Context, arg1 string) []peer.ID { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PubSubPeers", arg0) + ret := m.ctrl.Call(m, "PubSubPeers", arg0, arg1) ret0, _ := ret[0].([]peer.ID) return ret0 } // PubSubPeers indicates an expected call of PubSubPeers. -func (mr *MockModuleMockRecorder) PubSubPeers(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) PubSubPeers(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubSubPeers", reflect.TypeOf((*MockModule)(nil).PubSubPeers), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubSubPeers", reflect.TypeOf((*MockModule)(nil).PubSubPeers), arg0, arg1) } // ResourceState mocks base method. -func (m *MockModule) ResourceState() (rcmgr.ResourceManagerStat, error) { +func (m *MockModule) ResourceState(arg0 context.Context) (rcmgr.ResourceManagerStat, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResourceState") + ret := m.ctrl.Call(m, "ResourceState", arg0) ret0, _ := ret[0].(rcmgr.ResourceManagerStat) ret1, _ := ret[1].(error) return ret0, ret1 } // ResourceState indicates an expected call of ResourceState. -func (mr *MockModuleMockRecorder) ResourceState() *gomock.Call { +func (mr *MockModuleMockRecorder) ResourceState(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceState", reflect.TypeOf((*MockModule)(nil).ResourceState)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceState", reflect.TypeOf((*MockModule)(nil).ResourceState), arg0) } // UnblockPeer mocks base method. -func (m *MockModule) UnblockPeer(arg0 peer.ID) error { +func (m *MockModule) UnblockPeer(arg0 context.Context, arg1 peer.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UnblockPeer", arg0) + ret := m.ctrl.Call(m, "UnblockPeer", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // UnblockPeer indicates an expected call of UnblockPeer. -func (mr *MockModuleMockRecorder) UnblockPeer(arg0 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) UnblockPeer(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnblockPeer", reflect.TypeOf((*MockModule)(nil).UnblockPeer), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnblockPeer", reflect.TypeOf((*MockModule)(nil).UnblockPeer), arg0, arg1) } // Unprotect mocks base method. -func (m *MockModule) Unprotect(arg0 peer.ID, arg1 string) bool { +func (m *MockModule) Unprotect(arg0 context.Context, arg1 peer.ID, arg2 string) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Unprotect", arg0, arg1) + ret := m.ctrl.Call(m, "Unprotect", arg0, arg1, arg2) ret0, _ := ret[0].(bool) return ret0 } // Unprotect indicates an expected call of Unprotect. -func (mr *MockModuleMockRecorder) Unprotect(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Unprotect(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unprotect", reflect.TypeOf((*MockModule)(nil).Unprotect), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unprotect", reflect.TypeOf((*MockModule)(nil).Unprotect), arg0, arg1, arg2) } diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 6128c7ff13..5421f12da1 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -25,58 +25,57 @@ var _ Module = (*API)(nil) //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // Info returns address information about the host. - Info() peer.AddrInfo + Info(context.Context) peer.AddrInfo // Peers returns all peer IDs used across all inner stores. - Peers() []peer.ID + Peers(context.Context) []peer.ID // PeerInfo returns a small slice of information Peerstore has on the // given peer. - PeerInfo(id peer.ID) peer.AddrInfo + PeerInfo(ctx context.Context, id peer.ID) peer.AddrInfo // Connect ensures there is a connection between this host and the peer with // given peer. Connect(ctx context.Context, pi peer.AddrInfo) error // ClosePeer closes the connection to a given peer. - ClosePeer(id peer.ID) error + ClosePeer(ctx context.Context, id peer.ID) error // Connectedness returns a state signaling connection capabilities. - Connectedness(id peer.ID) network.Connectedness + Connectedness(ctx context.Context, id peer.ID) network.Connectedness // NATStatus returns the current NAT status. - NATStatus() (network.Reachability, error) + NATStatus(context.Context) (network.Reachability, error) // BlockPeer adds a peer to the set of blocked peers. - BlockPeer(p peer.ID) error + BlockPeer(ctx context.Context, p peer.ID) error // UnblockPeer removes a peer from the set of blocked peers. - UnblockPeer(p peer.ID) error + UnblockPeer(ctx context.Context, p peer.ID) error // ListBlockedPeers returns a list of blocked peers. - ListBlockedPeers() []peer.ID + ListBlockedPeers(context.Context) []peer.ID // Protect adds a peer to the list of peers who have a bidirectional // peering agreement that they are protected from being trimmed, dropped // or negatively scored. - Protect(id peer.ID, tag string) + Protect(ctx context.Context, id peer.ID, tag string) // Unprotect removes a peer from the list of peers who have a bidirectional // peering agreement that they are protected from being trimmed, dropped // or negatively scored, returning a bool representing whether the given // peer is protected or not. - Unprotect(id peer.ID, tag string) bool + Unprotect(ctx context.Context, id peer.ID, tag string) bool // IsProtected returns whether the given peer is protected. - IsProtected(id peer.ID, tag string) bool + IsProtected(ctx context.Context, id peer.ID, tag string) bool // BandwidthStats returns a Stats struct with bandwidth metrics for all // data sent/received by the local peer, regardless of protocol or remote // peer IDs. - BandwidthStats() metrics.Stats + BandwidthStats(context.Context) metrics.Stats // BandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID. // The metrics returned include all traffic sent / received for the peer, regardless of protocol. - BandwidthForPeer(id peer.ID) metrics.Stats - // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given - // protocol.ID. - BandwidthForProtocol(proto protocol.ID) metrics.Stats + BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats + // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID. + BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats // ResourceState returns the state of the resource manager. - ResourceState() (rcmgr.ResourceManagerStat, error) + ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) // PubSubPeers returns the peer IDs of the peers joined on // the given topic. - PubSubPeers(topic string) []peer.ID + PubSubPeers(ctx context.Context, topic string) []peer.ID } // module contains all components necessary to access information and @@ -105,15 +104,15 @@ func newModule( } } -func (m *module) Info() peer.AddrInfo { +func (m *module) Info(context.Context) peer.AddrInfo { return *libhost.InfoFromHost(m.host) } -func (m *module) Peers() []peer.ID { +func (m *module) Peers(context.Context) []peer.ID { return m.host.Peerstore().Peers() } -func (m *module) PeerInfo(id peer.ID) peer.AddrInfo { +func (m *module) PeerInfo(_ context.Context, id peer.ID) peer.AddrInfo { return m.host.Peerstore().PeerInfo(id) } @@ -121,15 +120,15 @@ func (m *module) Connect(ctx context.Context, pi peer.AddrInfo) error { return m.host.Connect(ctx, pi) } -func (m *module) ClosePeer(id peer.ID) error { +func (m *module) ClosePeer(_ context.Context, id peer.ID) error { return m.host.Network().ClosePeer(id) } -func (m *module) Connectedness(id peer.ID) network.Connectedness { +func (m *module) Connectedness(_ context.Context, id peer.ID) network.Connectedness { return m.host.Network().Connectedness(id) } -func (m *module) NATStatus() (network.Reachability, error) { +func (m *module) NATStatus(context.Context) (network.Reachability, error) { basic, ok := m.host.(*basichost.BasicHost) if !ok { return 0, fmt.Errorf("unexpected implementation of host.Host, expected %s, got %T", @@ -138,43 +137,43 @@ func (m *module) NATStatus() (network.Reachability, error) { return basic.GetAutoNat().Status(), nil } -func (m *module) BlockPeer(p peer.ID) error { +func (m *module) BlockPeer(_ context.Context, p peer.ID) error { return m.connGater.BlockPeer(p) } -func (m *module) UnblockPeer(p peer.ID) error { +func (m *module) UnblockPeer(_ context.Context, p peer.ID) error { return m.connGater.UnblockPeer(p) } -func (m *module) ListBlockedPeers() []peer.ID { +func (m *module) ListBlockedPeers(context.Context) []peer.ID { return m.connGater.ListBlockedPeers() } -func (m *module) Protect(id peer.ID, tag string) { +func (m *module) Protect(_ context.Context, id peer.ID, tag string) { m.host.ConnManager().Protect(id, tag) } -func (m *module) Unprotect(id peer.ID, tag string) bool { +func (m *module) Unprotect(_ context.Context, id peer.ID, tag string) bool { return m.host.ConnManager().Unprotect(id, tag) } -func (m *module) IsProtected(id peer.ID, tag string) bool { +func (m *module) IsProtected(_ context.Context, id peer.ID, tag string) bool { return m.host.ConnManager().IsProtected(id, tag) } -func (m *module) BandwidthStats() metrics.Stats { +func (m *module) BandwidthStats(context.Context) metrics.Stats { return m.bw.GetBandwidthTotals() } -func (m *module) BandwidthForPeer(id peer.ID) metrics.Stats { +func (m *module) BandwidthForPeer(_ context.Context, id peer.ID) metrics.Stats { return m.bw.GetBandwidthForPeer(id) } -func (m *module) BandwidthForProtocol(proto protocol.ID) metrics.Stats { +func (m *module) BandwidthForProtocol(_ context.Context, proto protocol.ID) metrics.Stats { return m.bw.GetBandwidthForProtocol(proto) } -func (m *module) ResourceState() (rcmgr.ResourceManagerStat, error) { +func (m *module) ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) { rms, ok := m.rm.(rcmgr.ResourceManagerState) if !ok { return rcmgr.ResourceManagerStat{}, fmt.Errorf("network.ResourceManager does not implement " + @@ -183,7 +182,7 @@ func (m *module) ResourceState() (rcmgr.ResourceManagerStat, error) { return rms.Stat(), nil } -func (m *module) PubSubPeers(topic string) []peer.ID { +func (m *module) PubSubPeers(_ context.Context, topic string) []peer.ID { return m.ps.ListPeers(topic) } @@ -193,95 +192,95 @@ func (m *module) PubSubPeers(topic string) []peer.ID { //nolint:dupl type API struct { Internal struct { - Info func() peer.AddrInfo `perm:"admin"` - Peers func() []peer.ID `perm:"admin"` - PeerInfo func(id peer.ID) peer.AddrInfo `perm:"admin"` - Connect func(ctx context.Context, pi peer.AddrInfo) error `perm:"admin"` - ClosePeer func(id peer.ID) error `perm:"admin"` - Connectedness func(id peer.ID) network.Connectedness `perm:"admin"` - NATStatus func() (network.Reachability, error) `perm:"admin"` - BlockPeer func(p peer.ID) error `perm:"admin"` - UnblockPeer func(p peer.ID) error `perm:"admin"` - ListBlockedPeers func() []peer.ID `perm:"admin"` - Protect func(id peer.ID, tag string) `perm:"admin"` - Unprotect func(id peer.ID, tag string) bool `perm:"admin"` - IsProtected func(id peer.ID, tag string) bool `perm:"admin"` - BandwidthStats func() metrics.Stats `perm:"admin"` - BandwidthForPeer func(id peer.ID) metrics.Stats `perm:"admin"` - BandwidthForProtocol func(proto protocol.ID) metrics.Stats `perm:"admin"` - ResourceState func() (rcmgr.ResourceManagerStat, error) `perm:"admin"` - PubSubPeers func(topic string) []peer.ID `perm:"admin"` + Info func(context.Context) peer.AddrInfo `perm:"admin"` + Peers func(context.Context) []peer.ID `perm:"admin"` + PeerInfo func(ctx context.Context, id peer.ID) peer.AddrInfo `perm:"admin"` + Connect func(ctx context.Context, pi peer.AddrInfo) error `perm:"admin"` + ClosePeer func(ctx context.Context, id peer.ID) error `perm:"admin"` + Connectedness func(ctx context.Context, id peer.ID) network.Connectedness `perm:"admin"` + NATStatus func(context.Context) (network.Reachability, error) `perm:"admin"` + BlockPeer func(ctx context.Context, p peer.ID) error `perm:"admin"` + UnblockPeer func(ctx context.Context, p peer.ID) error `perm:"admin"` + ListBlockedPeers func(context.Context) []peer.ID `perm:"admin"` + Protect func(ctx context.Context, id peer.ID, tag string) `perm:"admin"` + Unprotect func(ctx context.Context, id peer.ID, tag string) bool `perm:"admin"` + IsProtected func(ctx context.Context, id peer.ID, tag string) bool `perm:"admin"` + BandwidthStats func(context.Context) metrics.Stats `perm:"admin"` + BandwidthForPeer func(ctx context.Context, id peer.ID) metrics.Stats `perm:"admin"` + BandwidthForProtocol func(ctx context.Context, proto protocol.ID) metrics.Stats `perm:"admin"` + ResourceState func(context.Context) (rcmgr.ResourceManagerStat, error) `perm:"admin"` + PubSubPeers func(ctx context.Context, topic string) []peer.ID `perm:"admin"` } } -func (api *API) Info() peer.AddrInfo { - return api.Internal.Info() +func (api *API) Info(ctx context.Context) peer.AddrInfo { + return api.Internal.Info(ctx) } -func (api *API) Peers() []peer.ID { - return api.Internal.Peers() +func (api *API) Peers(ctx context.Context) []peer.ID { + return api.Internal.Peers(ctx) } -func (api *API) PeerInfo(id peer.ID) peer.AddrInfo { - return api.Internal.PeerInfo(id) +func (api *API) PeerInfo(ctx context.Context, id peer.ID) peer.AddrInfo { + return api.Internal.PeerInfo(ctx, id) } func (api *API) Connect(ctx context.Context, pi peer.AddrInfo) error { return api.Internal.Connect(ctx, pi) } -func (api *API) ClosePeer(id peer.ID) error { - return api.Internal.ClosePeer(id) +func (api *API) ClosePeer(ctx context.Context, id peer.ID) error { + return api.Internal.ClosePeer(ctx, id) } -func (api *API) Connectedness(id peer.ID) network.Connectedness { - return api.Internal.Connectedness(id) +func (api *API) Connectedness(ctx context.Context, id peer.ID) network.Connectedness { + return api.Internal.Connectedness(ctx, id) } -func (api *API) NATStatus() (network.Reachability, error) { - return api.Internal.NATStatus() +func (api *API) NATStatus(ctx context.Context) (network.Reachability, error) { + return api.Internal.NATStatus(ctx) } -func (api *API) BlockPeer(p peer.ID) error { - return api.Internal.BlockPeer(p) +func (api *API) BlockPeer(ctx context.Context, p peer.ID) error { + return api.Internal.BlockPeer(ctx, p) } -func (api *API) UnblockPeer(p peer.ID) error { - return api.Internal.UnblockPeer(p) +func (api *API) UnblockPeer(ctx context.Context, p peer.ID) error { + return api.Internal.UnblockPeer(ctx, p) } -func (api *API) ListBlockedPeers() []peer.ID { - return api.Internal.ListBlockedPeers() +func (api *API) ListBlockedPeers(ctx context.Context) []peer.ID { + return api.Internal.ListBlockedPeers(ctx) } -func (api *API) Protect(id peer.ID, tag string) { - api.Internal.Protect(id, tag) +func (api *API) Protect(ctx context.Context, id peer.ID, tag string) { + api.Internal.Protect(ctx, id, tag) } -func (api *API) Unprotect(id peer.ID, tag string) bool { - return api.Internal.Unprotect(id, tag) +func (api *API) Unprotect(ctx context.Context, id peer.ID, tag string) bool { + return api.Internal.Unprotect(ctx, id, tag) } -func (api *API) IsProtected(id peer.ID, tag string) bool { - return api.Internal.IsProtected(id, tag) +func (api *API) IsProtected(ctx context.Context, id peer.ID, tag string) bool { + return api.Internal.IsProtected(ctx, id, tag) } -func (api *API) BandwidthStats() metrics.Stats { - return api.Internal.BandwidthStats() +func (api *API) BandwidthStats(ctx context.Context) metrics.Stats { + return api.Internal.BandwidthStats(ctx) } -func (api *API) BandwidthForPeer(id peer.ID) metrics.Stats { - return api.Internal.BandwidthForPeer(id) +func (api *API) BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats { + return api.Internal.BandwidthForPeer(ctx, id) } -func (api *API) BandwidthForProtocol(proto protocol.ID) metrics.Stats { - return api.Internal.BandwidthForProtocol(proto) +func (api *API) BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats { + return api.Internal.BandwidthForProtocol(ctx, proto) } -func (api *API) ResourceState() (rcmgr.ResourceManagerStat, error) { - return api.Internal.ResourceState() +func (api *API) ResourceState(ctx context.Context) (rcmgr.ResourceManagerStat, error) { + return api.Internal.ResourceState(ctx) } -func (api *API) PubSubPeers(topic string) []peer.ID { - return api.Internal.PubSubPeers(topic) +func (api *API) PubSubPeers(ctx context.Context, topic string) []peer.ID { + return api.Internal.PubSubPeers(ctx, topic) } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index da77ff4562..a9b5990177 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -29,14 +29,16 @@ func TestP2PModule_Host(t *testing.T) { mgr := newModule(host, nil, nil, nil, nil) + ctx := context.Background() + // test all methods on `manager.host` - assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), mgr.Peers()) - assert.Equal(t, libhost.InfoFromHost(peer).ID, mgr.PeerInfo(peer.ID()).ID) + assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), mgr.Peers(ctx)) + assert.Equal(t, libhost.InfoFromHost(peer).ID, mgr.PeerInfo(ctx, peer.ID()).ID) - assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(peer.ID())) + assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(ctx, peer.ID())) // now disconnect using manager and check for connectedness match again - assert.NoError(t, mgr.ClosePeer(peer.ID())) - assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(peer.ID())) + assert.NoError(t, mgr.ClosePeer(ctx, peer.ID())) + assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(ctx, peer.ID())) } // TestP2PModule_ConnManager tests P2P Module methods on @@ -59,10 +61,10 @@ func TestP2PModule_ConnManager(t *testing.T) { err = mgr.Connect(ctx, *libhost.InfoFromHost(peer)) require.NoError(t, err) - mgr.Protect(peer.ID(), "test") - assert.True(t, mgr.IsProtected(peer.ID(), "test")) - mgr.Unprotect(peer.ID(), "test") - assert.False(t, mgr.IsProtected(peer.ID(), "test")) + mgr.Protect(ctx, peer.ID(), "test") + assert.True(t, mgr.IsProtected(ctx, peer.ID(), "test")) + mgr.Unprotect(ctx, peer.ID(), "test") + assert.False(t, mgr.IsProtected(ctx, peer.ID(), "test")) } // TestP2PModule_Autonat tests P2P Module methods on @@ -73,7 +75,7 @@ func TestP2PModule_Autonat(t *testing.T) { mgr := newModule(host, nil, nil, nil, nil) - status, err := mgr.NATStatus() + status, err := mgr.NATStatus(context.Background()) assert.NoError(t, err) assert.Equal(t, network.ReachabilityUnknown, status) } @@ -111,11 +113,12 @@ func TestP2PModule_Bandwidth(t *testing.T) { // connect to the peer err = mgr.Connect(ctx, *libhost.InfoFromHost(peer)) require.NoError(t, err) + // check to ensure they're actually connected - require.Equal(t, network.Connected, mgr.Connectedness(peer.ID())) + require.Equal(t, network.Connected, mgr.Connectedness(ctx, peer.ID())) // open stream with host - stream, err := peer.NewStream(ctx, mgr.Info().ID, protoID) + stream, err := peer.NewStream(ctx, mgr.Info(ctx).ID, protoID) require.NoError(t, err) // write to stream to increase bandwidth usage get some substantive @@ -133,12 +136,12 @@ func TestP2PModule_Bandwidth(t *testing.T) { // in the background process time.Sleep(time.Second * 2) - stats := mgr.BandwidthStats() + stats := mgr.BandwidthStats(ctx) assert.NotNil(t, stats) - peerStat := mgr.BandwidthForPeer(peer.ID()) + peerStat := mgr.BandwidthForPeer(ctx, peer.ID()) assert.NotZero(t, peerStat.TotalIn) assert.Greater(t, int(peerStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc - protoStat := mgr.BandwidthForProtocol(protoID) + protoStat := mgr.BandwidthForProtocol(ctx, protoID) assert.NotZero(t, protoStat.TotalIn) assert.Greater(t, int(protoStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc } @@ -182,7 +185,7 @@ func TestP2PModule_Pubsub(t *testing.T) { // anywhere where gossipsub is used in tests) time.Sleep(1 * time.Second) - assert.Equal(t, len(topic.ListPeers()), len(mgr.PubSubPeers(topicStr))) + assert.Equal(t, len(topic.ListPeers()), len(mgr.PubSubPeers(context.Background(), topicStr))) } // TestP2PModule_ConnGater tests P2P Module methods on @@ -193,10 +196,12 @@ func TestP2PModule_ConnGater(t *testing.T) { mgr := newModule(nil, nil, gater, nil, nil) - assert.NoError(t, mgr.BlockPeer("badpeer")) - assert.Len(t, mgr.ListBlockedPeers(), 1) - assert.NoError(t, mgr.UnblockPeer("badpeer")) - assert.Len(t, mgr.ListBlockedPeers(), 0) + ctx := context.Background() + + assert.NoError(t, mgr.BlockPeer(ctx, "badpeer")) + assert.Len(t, mgr.ListBlockedPeers(ctx), 1) + assert.NoError(t, mgr.UnblockPeer(ctx, "badpeer")) + assert.Len(t, mgr.ListBlockedPeers(ctx), 0) } // TestP2PModule_ResourceManager tests P2P Module methods on @@ -207,7 +212,7 @@ func TestP2PModule_ResourceManager(t *testing.T) { mgr := newModule(nil, nil, nil, nil, rm) - state, err := mgr.ResourceState() + state, err := mgr.ResourceState(context.Background()) require.NoError(t, err) assert.NotNil(t, state) diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index 6d6c337226..40408f35ae 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -25,8 +25,7 @@ func RegisterEndpoints( serv.RegisterAuthedService("header", headerMod, &header.API{}) serv.RegisterAuthedService("state", stateMod, &state.API{}) serv.RegisterAuthedService("share", shareMod, &share.API{}) - // @TODO(renaynay): add context to all p2p methods so we can activate auth - serv.RegisterService("p2p", p2pMod) + serv.RegisterAuthedService("p2p", p2pMod, &p2p.API{}) } func Server(cfg *Config) *rpc.Server { From d1144b6f8e351e949b7f671a06672c496898dbeb Mon Sep 17 00:00:00 2001 From: Hoyt Ren Date: Wed, 7 Dec 2022 22:33:59 +0800 Subject: [PATCH 0229/1008] Extract common parsing logic into single function. (#1394) Closes https://github.com/celestiaorg/celestia-node/issues/1331 --- cmd/celestia/bridge.go | 44 +-------------------------- cmd/celestia/full.go | 50 +------------------------------ cmd/celestia/light.go | 51 +------------------------------ cmd/celestia/util.go | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 142 deletions(-) create mode 100644 cmd/celestia/util.go diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index a4ddd99268..8aeb632da8 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -43,48 +43,6 @@ var bridgeCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Bridge node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - var ( - ctx = cmd.Context() - err error - ) - - ctx = cmdnode.WithNodeType(ctx, node.Bridge) - - parsedNetwork, err := p2p.ParseNetwork(cmd) - if err != nil { - return err - } - ctx = cmdnode.WithNetwork(ctx, parsedNetwork) - - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) - if err != nil { - return err - } - - cfg := cmdnode.NodeConfig(ctx) - - err = p2p.ParseFlags(cmd, &cfg.P2P) - if err != nil { - return err - } - - err = core.ParseFlags(cmd, &cfg.Core) - if err != nil { - return err - } - - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) - if err != nil { - return err - } - - rpc.ParseFlags(cmd, &cfg.RPC) - gateway.ParseFlags(cmd, &cfg.Gateway) - state.ParseFlags(cmd, &cfg.State) - - // set config - ctx = cmdnode.WithNodeConfig(ctx, &cfg) - cmd.SetContext(ctx) - return nil + return persistentPreRunEnv(cmd, node.Bridge, args) }, } diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 254545218d..0cd7fd1900 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -1,4 +1,3 @@ -//nolint:dupl package main import ( @@ -51,53 +50,6 @@ var fullCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Full node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - var ( - ctx = cmd.Context() - err error - ) - - ctx = cmdnode.WithNodeType(ctx, node.Full) - - parsedNetwork, err := p2p.ParseNetwork(cmd) - if err != nil { - return err - } - ctx = cmdnode.WithNetwork(ctx, parsedNetwork) - - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) - if err != nil { - return err - } - - cfg := cmdnode.NodeConfig(ctx) - - err = p2p.ParseFlags(cmd, &cfg.P2P) - if err != nil { - return err - } - - err = core.ParseFlags(cmd, &cfg.Core) - if err != nil { - return err - } - - err = header.ParseFlags(cmd, &cfg.Header) - if err != nil { - return err - } - - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) - if err != nil { - return err - } - - rpc.ParseFlags(cmd, &cfg.RPC) - gateway.ParseFlags(cmd, &cfg.Gateway) - state.ParseFlags(cmd, &cfg.State) - - // set config - ctx = cmdnode.WithNodeConfig(ctx, &cfg) - cmd.SetContext(ctx) - return nil + return persistentPreRunEnv(cmd, node.Full, args) }, } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index d035f0aea2..a56d4b30dd 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -1,4 +1,3 @@ -//nolint:dupl package main import ( @@ -51,54 +50,6 @@ var lightCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Light node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - var ( - ctx = cmd.Context() - err error - ) - - ctx = cmdnode.WithNodeType(ctx, node.Light) - - parsedNetwork, err := p2p.ParseNetwork(cmd) - if err != nil { - return err - } - ctx = cmdnode.WithNetwork(ctx, parsedNetwork) - - // loads existing config into the environment - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) - if err != nil { - return err - } - - cfg := cmdnode.NodeConfig(ctx) - - err = p2p.ParseFlags(cmd, &cfg.P2P) - if err != nil { - return err - } - - err = core.ParseFlags(cmd, &cfg.Core) - if err != nil { - return err - } - - err = header.ParseFlags(cmd, &cfg.Header) - if err != nil { - return err - } - - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) - if err != nil { - return err - } - - rpc.ParseFlags(cmd, &cfg.RPC) - gateway.ParseFlags(cmd, &cfg.Gateway) - state.ParseFlags(cmd, &cfg.State) - - // set config - ctx = cmdnode.WithNodeConfig(ctx, &cfg) - cmd.SetContext(ctx) - return nil + return persistentPreRunEnv(cmd, node.Light, args) }, } diff --git a/cmd/celestia/util.go b/cmd/celestia/util.go new file mode 100644 index 0000000000..9c7025fae9 --- /dev/null +++ b/cmd/celestia/util.go @@ -0,0 +1,68 @@ +package main + +import ( + "github.com/spf13/cobra" + + cmdnode "github.com/celestiaorg/celestia-node/cmd" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/state" +) + +func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, args []string) error { + var ( + ctx = cmd.Context() + err error + ) + + ctx = cmdnode.WithNodeType(ctx, nodeType) + + parsedNetwork, err := p2p.ParseNetwork(cmd) + if err != nil { + return err + } + ctx = cmdnode.WithNetwork(ctx, parsedNetwork) + + // loads existing config into the environment + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) + if err != nil { + return err + } + + cfg := cmdnode.NodeConfig(ctx) + + err = p2p.ParseFlags(cmd, &cfg.P2P) + if err != nil { + return err + } + + err = core.ParseFlags(cmd, &cfg.Core) + if err != nil { + return err + } + + if nodeType != node.Bridge { + err = header.ParseFlags(cmd, &cfg.Header) + if err != nil { + return err + } + } + + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) + if err != nil { + return err + } + + rpc.ParseFlags(cmd, &cfg.RPC) + gateway.ParseFlags(cmd, &cfg.Gateway) + state.ParseFlags(cmd, &cfg.State) + + // set config + ctx = cmdnode.WithNodeConfig(ctx, &cfg) + cmd.SetContext(ctx) + return nil +} From b220fa3504a7fc2b82f32eba9378b2446dd6a60d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 7 Dec 2022 18:01:31 +0200 Subject: [PATCH 0230/1008] feat(header/p2p): implement gcing for peerTracker (#1298) Resolves https://github.com/celestiaorg/celestia-node/issues/1411, https://github.com/celestiaorg/celestia-node/issues/1317 Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> Co-authored-by: Ryan --- header/p2p/exchange.go | 17 +++- header/p2p/exchange_test.go | 4 +- header/p2p/options.go | 80 ++++++++++++++++-- header/p2p/peer_stats.go | 4 + header/p2p/peer_tracker.go | 132 +++++++++++++++++++++++++----- header/p2p/peer_tracker_test.go | 33 ++++++++ nodebuilder/header/module.go | 3 + nodebuilder/header/module_test.go | 3 + 8 files changed, 240 insertions(+), 36 deletions(-) create mode 100644 header/p2p/peer_tracker_test.go diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index e816a81531..73e8c87e98 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -53,23 +53,34 @@ func NewExchange( if err != nil { return nil, err } + return &Exchange{ host: host, protocolID: protocolID(protocolSuffix), trustedPeers: peers, - peerTracker: newPeerTracker(host), - Params: params, + peerTracker: newPeerTracker( + host, + params.MaxAwaitingTime, + params.DefaultScore, + params.MaxPeerTrackerSize, + ), + Params: params, }, nil } func (ex *Exchange) Start(context.Context) error { ex.ctx, ex.cancel = context.WithCancel(context.Background()) - go ex.peerTracker.track(ex.ctx) + + go ex.peerTracker.gc() + go ex.peerTracker.track() return nil } func (ex *Exchange) Stop(context.Context) error { + // cancel the session if it exists ex.cancel() + // stop the peerTracker + ex.peerTracker.stop() return nil } diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 7d3bb81bfa..1aa8887907 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -93,7 +93,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { servers[index], err = NewExchangeServer(hosts[index], store, protocolSuffix) require.NoError(t, err) servers[index].Start(context.Background()) //nolint:errcheck - exchange.peerTracker.connectedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()} + exchange.peerTracker.trackedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()} } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) @@ -291,7 +291,7 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private") require.NoError(t, err) - ex.peerTracker.connectedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} + ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} require.NoError(t, ex.Start(context.Background())) t.Cleanup(func() { diff --git a/header/p2p/options.go b/header/p2p/options.go index 27f089aab5..0ccf7491fa 100644 --- a/header/p2p/options.go +++ b/header/p2p/options.go @@ -34,17 +34,15 @@ func DefaultServerParameters() *ServerParameters { } } -const errSuffix = "value should be positive and non-zero" - func (p *ServerParameters) Validate() error { if p.WriteDeadline == 0 { - return fmt.Errorf("invalid write time duration: %s", errSuffix) + return fmt.Errorf("invalid write time duration: %v", p.WriteDeadline) } if p.ReadDeadline == 0 { - return fmt.Errorf("invalid read time duration: %s", errSuffix) + return fmt.Errorf("invalid read time duration: %v", p.ReadDeadline) } if p.MaxRequestSize == 0 { - return fmt.Errorf("invalid max request size: %s", errSuffix) + return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) } return nil } @@ -92,6 +90,14 @@ type ClientParameters struct { MaxRequestSize uint64 // MaxHeadersPerRequest defines the max amount of headers that can be requested per 1 request. MaxHeadersPerRequest uint64 + // MaxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, + // otherwise it will be removed on the next GC cycle. + MaxAwaitingTime time.Duration + // DefaultScore specifies the score for newly connected peers. + DefaultScore float32 + + // MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker. + MaxPeerTrackerSize int } // DefaultClientParameters returns the default params to configure the store. @@ -100,18 +106,41 @@ func DefaultClientParameters() *ClientParameters { MinResponses: 2, MaxRequestSize: 512, MaxHeadersPerRequest: 64, + MaxAwaitingTime: time.Hour, + DefaultScore: 1, + MaxPeerTrackerSize: 100, } } +const ( + greaterThenZero = "should be greater than 0" + providedSuffix = "Provided value" +) + func (p *ClientParameters) Validate() error { if p.MinResponses <= 0 { - return fmt.Errorf("invalid minimum amount of responses: %s", errSuffix) + return fmt.Errorf("invalid MinResponses: %s. %s: %v", + greaterThenZero, providedSuffix, p.MinResponses) } if p.MaxRequestSize == 0 { - return fmt.Errorf("invalid max request size: %s", errSuffix) + return fmt.Errorf("invalid MaxRequestSize: %s. %s: %v", greaterThenZero, providedSuffix, p.MaxRequestSize) + } + if p.MaxHeadersPerRequest == 0 { + return fmt.Errorf("invalid MaxHeadersPerRequest: %s. %s: %v", greaterThenZero, providedSuffix, p.MaxHeadersPerRequest) + } + if p.MaxHeadersPerRequest > p.MaxRequestSize { + return fmt.Errorf("MaxHeadersPerRequest should not be more than MaxRequestSize."+ + "MaxHeadersPerRequest: %v, MaxRequestSize: %v", p.MaxHeadersPerRequest, p.MaxRequestSize) + } + if p.MaxAwaitingTime == 0 { + return fmt.Errorf("invalid MaxAwaitingTime for peerTracker: "+ + "%s. %s: %v", greaterThenZero, providedSuffix, p.MaxAwaitingTime) + } + if p.DefaultScore <= 0 { + return fmt.Errorf("invalid DefaultScore: %s. %s: %f", greaterThenZero, providedSuffix, p.DefaultScore) } - if p.MaxHeadersPerRequest == 0 || p.MaxHeadersPerRequest > p.MaxRequestSize { - return fmt.Errorf("invalid max headers per request: %s", errSuffix) + if p.MaxPeerTrackerSize <= 0 { + return fmt.Errorf("invalid MaxTrackerSize: %s. %s: %d", greaterThenZero, providedSuffix, p.MaxPeerTrackerSize) } return nil } @@ -138,3 +167,36 @@ func WithMaxHeadersPerRequest[T ClientParameters](amount uint64) Option[T] { } } + +// WithMaxAwaitingTime is a functional option that configures the +// `MaxAwaitingTime` parameter. +func WithMaxAwaitingTime[T ClientParameters](duration time.Duration) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ClientParameters: + t.MaxAwaitingTime = duration + } + } +} + +// WithDefaultScore is a functional option that configures the +// `DefaultScore` parameter. +func WithDefaultScore[T ClientParameters](score float32) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ClientParameters: + t.DefaultScore = score + } + } +} + +// WithMaxTrackerSize is a functional option that configures the +// `MaxTrackerSize` parameter. +func WithMaxTrackerSize[T ClientParameters](size int) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ClientParameters: + t.MaxPeerTrackerSize = size + } + } +} diff --git a/header/p2p/peer_stats.go b/header/p2p/peer_stats.go index 37f905e3ed..91c5b939e2 100644 --- a/header/p2p/peer_stats.go +++ b/header/p2p/peer_stats.go @@ -4,6 +4,7 @@ import ( "container/heap" "context" "sync" + "time" "github.com/libp2p/go-libp2p-core/peer" ) @@ -14,6 +15,9 @@ type peerStat struct { peerID peer.ID // score is the average speed per single request peerScore float32 + // pruneDeadline specifies when disconnected peer will be removed if + // it does not return online. + pruneDeadline time.Time } // updateStats recalculates peer.score by averaging the last score diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go index 9451b6f929..3d5aa9c4f9 100644 --- a/header/p2p/peer_tracker.go +++ b/header/p2p/peer_tracker.go @@ -3,6 +3,7 @@ package p2p import ( "context" "sync" + "time" "github.com/libp2p/go-libp2p-core/event" "github.com/libp2p/go-libp2p-core/host" @@ -10,25 +11,61 @@ import ( "github.com/libp2p/go-libp2p-core/peer" ) +// gcCycle defines the duration after which the peerTracker starts removing peers. +var gcCycle = time.Minute * 30 + type peerTracker struct { - sync.RWMutex - connectedPeers map[peer.ID]*peerStat + host host.Host + + peerLk sync.RWMutex + // trackedPeers contains active peers that we can request to. // we cache the peer once they disconnect, // so we can guarantee that peerQueue will only contain active peers + trackedPeers map[peer.ID]*peerStat + // disconnectedPeers contains disconnected peers. In case if peer does not return + // online until pruneDeadline, it will be removed and its score will be lost. disconnectedPeers map[peer.ID]*peerStat - host host.Host + // maxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, + // otherwise it will be removed on the next GC cycle. + maxAwaitingTime time.Duration + // defaultScore specifies the score for newly connected peers. + defaultScore float32 + // maxPeerTrackerSize specifies the max amount of peers that can be added to the peerTracker. + maxPeerTrackerSize int + + ctx context.Context + cancel context.CancelFunc + // done is used to gracefully stop the peerTracker. + // It allows to wait until track() and gc() will be stopped. + done chan struct{} } -func newPeerTracker(h host.Host) *peerTracker { +func newPeerTracker( + h host.Host, + maxAwaitingTime time.Duration, + defaultScore float32, + maxPeerTrackerSize int, +) *peerTracker { + ctx, cancel := context.WithCancel(context.Background()) return &peerTracker{ - disconnectedPeers: make(map[peer.ID]*peerStat), - connectedPeers: make(map[peer.ID]*peerStat), - host: h, + host: h, + disconnectedPeers: make(map[peer.ID]*peerStat), + trackedPeers: make(map[peer.ID]*peerStat), + maxAwaitingTime: maxAwaitingTime, + defaultScore: defaultScore, + maxPeerTrackerSize: maxPeerTrackerSize, + ctx: ctx, + cancel: cancel, + done: make(chan struct{}, 2), } } -func (p *peerTracker) track(ctx context.Context) { +func (p *peerTracker) track() { + defer func() { + p.done <- struct{}{} + }() + // store peers that have been already connected for _, peer := range p.host.Peerstore().Peers() { p.connected(peer) @@ -39,9 +76,10 @@ func (p *peerTracker) track(ctx context.Context) { log.Errorw("subscribing to EvtPeerConnectednessChanged", "err", err) return } + for { select { - case <-ctx.Done(): + case <-p.ctx.Done(): err = subs.Close() if err != nil { log.Errorw("closing subscription", "err", err) @@ -63,42 +101,92 @@ func (p *peerTracker) connected(pID peer.ID) { if p.host.ID() == pID { return } + for _, c := range p.host.Network().ConnsToPeer(pID) { // check if connection is short-termed and skip this peer if c.Stat().Transient { return } } - p.Lock() - defer p.Unlock() - // additional check in p.connectedPeers should be done, + + p.peerLk.Lock() + defer p.peerLk.Unlock() + // skip adding the peer to avoid overfilling of the peerTracker with unused peers if: + // peerTracker reaches the maxTrackerSize and there are more connected peers + // than disconnected peers. + if len(p.trackedPeers)+len(p.disconnectedPeers) > p.maxPeerTrackerSize && + len(p.trackedPeers) > len(p.disconnectedPeers) { + return + } + + // additional check in p.trackedPeers should be done, // because libp2p does not emit multiple Connected events per 1 peer stats, ok := p.disconnectedPeers[pID] if !ok { - stats = &peerStat{peerID: pID} + stats = &peerStat{peerID: pID, peerScore: p.defaultScore} } else { delete(p.disconnectedPeers, pID) } - p.connectedPeers[pID] = stats + p.trackedPeers[pID] = stats } func (p *peerTracker) disconnected(pID peer.ID) { - p.Lock() - defer p.Unlock() - stats, ok := p.connectedPeers[pID] + p.peerLk.Lock() + defer p.peerLk.Unlock() + stats, ok := p.trackedPeers[pID] if !ok { return } + stats.pruneDeadline = time.Now().Add(p.maxAwaitingTime) p.disconnectedPeers[pID] = stats - delete(p.connectedPeers, pID) + delete(p.trackedPeers, pID) } func (p *peerTracker) peers() []*peerStat { - p.RLock() - defer p.RUnlock() - peers := make([]*peerStat, 0, len(p.connectedPeers)) - for _, stat := range p.connectedPeers { + p.peerLk.RLock() + defer p.peerLk.RUnlock() + peers := make([]*peerStat, 0, len(p.trackedPeers)) + for _, stat := range p.trackedPeers { peers = append(peers, stat) } return peers } + +// gc goes through connected and disconnected peers once every gcPeriod +// and removes: +// * disconnected peers which have been disconnected for more than maxAwaitingTime; +// * connected peers whose scores are less than or equal than defaultScore; +func (p *peerTracker) gc() { + ticker := time.NewTicker(gcCycle) + for { + select { + case <-p.ctx.Done(): + p.done <- struct{}{} + return + case <-ticker.C: + p.peerLk.Lock() + now := time.Now() + for id, peer := range p.disconnectedPeers { + if peer.pruneDeadline.Before(now) { + delete(p.disconnectedPeers, id) + } + } + + for id, peer := range p.trackedPeers { + if peer.peerScore <= p.defaultScore { + delete(p.trackedPeers, id) + } + } + p.peerLk.Unlock() + } + } +} + +// stop waits until all background routines will be finished. +func (p *peerTracker) stop() { + p.cancel() + + for i := 0; i < cap(p.done); i++ { + <-p.done + } +} diff --git a/header/p2p/peer_tracker_test.go b/header/p2p/peer_tracker_test.go new file mode 100644 index 0000000000..788ddbcf40 --- /dev/null +++ b/header/p2p/peer_tracker_test.go @@ -0,0 +1,33 @@ +package p2p + +import ( + "testing" + "time" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPeerTracker_GC(t *testing.T) { + h := createMocknet(t, 1) + gcCycle = time.Millisecond * 200 + p := newPeerTracker(h[0], time.Millisecond*1, 1, 5) + pid1 := peer.ID("peer1") + pid2 := peer.ID("peer2") + pid3 := peer.ID("peer3") + pid4 := peer.ID("peer4") + p.trackedPeers[pid1] = &peerStat{peerID: pid1, peerScore: 0.5} + p.trackedPeers[pid2] = &peerStat{peerID: pid2, peerScore: 10} + p.disconnectedPeers[pid3] = &peerStat{peerID: pid3, pruneDeadline: time.Now()} + p.disconnectedPeers[pid4] = &peerStat{peerID: pid4, pruneDeadline: time.Now().Add(time.Minute * 10)} + assert.True(t, len(p.trackedPeers) > 0) + assert.True(t, len(p.disconnectedPeers) > 0) + + go p.track() + go p.gc() + time.Sleep(time.Second * 1) + p.stop() + require.Nil(t, p.trackedPeers[pid1]) + require.Nil(t, p.disconnectedPeers[pid3]) +} diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 6f1399793c..ae596d6139 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -112,6 +112,9 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithMinResponses(cfg.Client.MinResponses), p2p.WithMaxRequestSize[p2p.ClientParameters](cfg.Client.MaxRequestSize), p2p.WithMaxHeadersPerRequest(cfg.Client.MaxHeadersPerRequest), + p2p.WithMaxAwaitingTime(cfg.Client.MaxAwaitingTime), + p2p.WithDefaultScore(cfg.Client.DefaultScore), + p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), } }, ), diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 3e834b4717..68b471f1d6 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -71,6 +71,9 @@ func TestConstructModule_ExchangeParams(t *testing.T) { require.Equal(t, exchange.Params.MinResponses, cfg.Client.MinResponses) require.Equal(t, exchange.Params.MaxRequestSize, cfg.Client.MaxRequestSize) require.Equal(t, exchange.Params.MaxHeadersPerRequest, cfg.Client.MaxHeadersPerRequest) + require.Equal(t, exchange.Params.MaxAwaitingTime, cfg.Client.MaxAwaitingTime) + require.Equal(t, exchange.Params.DefaultScore, cfg.Client.DefaultScore) + require.Equal(t, exchange.Params.MaxPeerTrackerSize, cfg.Client.MaxPeerTrackerSize) require.Equal(t, exchangeServer.Params.WriteDeadline, cfg.Server.WriteDeadline) require.Equal(t, exchangeServer.Params.ReadDeadline, cfg.Server.ReadDeadline) From b5eeaa3db0066adafcbdafb7e646dfa940d1456e Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 9 Dec 2022 15:03:52 +0200 Subject: [PATCH 0231/1008] feat(share/discovery): add Peers method (#1451) ## Overview Resolves #1449 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords Co-authored-by: Ryan --- share/availability/discovery/discovery.go | 6 ++++ share/availability/discovery/set.go | 34 +++++++++++++++++++---- share/availability/discovery/set_test.go | 32 ++++++++++++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 9733d2d927..ff7713ba81 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -169,3 +169,9 @@ func (d *Discovery) Advertise(ctx context.Context) { } } } + +// Peers provides a list of discovered peers in the "full" topic. +// If Discovery hasn't found any peers, it blocks until at least one peer is found. +func (d *Discovery) Peers(ctx context.Context) ([]peer.ID, error) { + return d.set.Peers(ctx) +} diff --git a/share/availability/discovery/set.go b/share/availability/discovery/set.go index 9560375586..d47487cb00 100644 --- a/share/availability/discovery/set.go +++ b/share/availability/discovery/set.go @@ -1,6 +1,7 @@ package discovery import ( + "context" "errors" "sync" @@ -13,7 +14,8 @@ type limitedSet struct { lk sync.RWMutex ps map[peer.ID]struct{} - limit uint + limit uint + waitPeer chan peer.ID } // newLimitedSet constructs a set with the maximum peers amount. @@ -21,6 +23,7 @@ func newLimitedSet(limit uint) *limitedSet { ps := new(limitedSet) ps.ps = make(map[peer.ID]struct{}) ps.limit = limit + ps.waitPeer = make(chan peer.ID) return ps } @@ -47,6 +50,13 @@ func (ps *limitedSet) TryAdd(p peer.ID) error { } if len(ps.ps) < int(ps.limit) { ps.ps[p] = struct{}{} + + // peer will be pushed to the channel only when somebody is reading from it. + // this is done to handle case when Peers() was called on empty set. + select { + case ps.waitPeer <- p: + default: + } return nil } @@ -61,12 +71,24 @@ func (ps *limitedSet) Remove(id peer.ID) { } } -func (ps *limitedSet) Peers() []peer.ID { +// Peers returns all discovered peers from the set. +func (ps *limitedSet) Peers(ctx context.Context) ([]peer.ID, error) { ps.lk.Lock() - out := make([]peer.ID, 0, len(ps.ps)) - for p := range ps.ps { - out = append(out, p) + if len(ps.ps) > 0 { + out := make([]peer.ID, 0, len(ps.ps)) + for p := range ps.ps { + out = append(out, p) + } + ps.lk.Unlock() + return out, nil } ps.lk.Unlock() - return out + + // block until a new peer will be discovered + select { + case <-ctx.Done(): + return nil, ctx.Err() + case p := <-ps.waitPeer: + return []peer.ID{p}, nil + } } diff --git a/share/availability/discovery/set_test.go b/share/availability/discovery/set_test.go index 60e5cdd958..b4c6a8fb49 100644 --- a/share/availability/discovery/set_test.go +++ b/share/availability/discovery/set_test.go @@ -1,7 +1,9 @@ package discovery import ( + "context" "testing" + "time" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" @@ -50,7 +52,35 @@ func TestSet_Peers(t *testing.T) { set := newLimitedSet(2) require.NoError(t, set.TryAdd(h1.ID())) require.NoError(t, set.TryAdd(h2.ID())) - require.True(t, len(set.Peers()) == 2) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) + t.Cleanup(cancel) + + peers, err := set.Peers(ctx) + require.NoError(t, err) + require.True(t, len(peers) == 2) +} + +// TestSet_WaitPeers ensures that `Peers` will be unblocked once +// a new peer was discovered. +func TestSet_WaitPeers(t *testing.T) { + m := mocknet.New() + h1, err := m.GenPeer() + require.NoError(t, err) + + set := newLimitedSet(2) + go func() { + time.Sleep(time.Millisecond * 500) + set.TryAdd(h1.ID()) //nolint:errcheck + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + t.Cleanup(cancel) + + // call `Peers` on empty set will block until a new peer will be discovered + peers, err := set.Peers(ctx) + require.NoError(t, err) + require.True(t, len(peers) == 1) } func TestSet_Size(t *testing.T) { From 93b98e953523365a0263f4d76e238dc112475ebf Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 9 Dec 2022 15:47:16 +0200 Subject: [PATCH 0232/1008] improvement(share/discovery): send peer to multiple readers (#1460) ## Overview Improvement for https://github.com/celestiaorg/celestia-node/pull/1451 Notify multiple readers instead of one. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- share/availability/discovery/set.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/share/availability/discovery/set.go b/share/availability/discovery/set.go index d47487cb00..2dc765a4e9 100644 --- a/share/availability/discovery/set.go +++ b/share/availability/discovery/set.go @@ -48,19 +48,22 @@ func (ps *limitedSet) TryAdd(p peer.ID) error { if _, ok := ps.ps[p]; ok { return errors.New("share: discovery: peer already added") } - if len(ps.ps) < int(ps.limit) { - ps.ps[p] = struct{}{} + if len(ps.ps) >= int(ps.limit) { + return errors.New("share: discovery: peers limit reached") + } + ps.ps[p] = struct{}{} +LOOP: + for { // peer will be pushed to the channel only when somebody is reading from it. // this is done to handle case when Peers() was called on empty set. select { case ps.waitPeer <- p: default: + break LOOP } - return nil } - - return errors.New("share: discovery: peers limit reached") + return nil } func (ps *limitedSet) Remove(id peer.ID) { From ac4961adac2e6a897add1f47f066996583d09791 Mon Sep 17 00:00:00 2001 From: Douglas Chimento Date: Fri, 9 Dec 2022 16:13:21 +0200 Subject: [PATCH 0233/1008] core: Deprecate/remove StartTestClient in favour of StartTestCoreWithApp (#1400) Fixes #1365 ## Overview Replace StartTestClient with StartTestCoreWithApp within tests --- core/client_test.go | 8 ++++---- core/fetcher_test.go | 4 ++-- core/testing.go | 35 ++++++++++++++------------------- nodebuilder/node_bridge_test.go | 2 +- 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/core/client_test.go b/core/client_test.go index 69bc67a527..f74ff6fb26 100644 --- a/core/client_test.go +++ b/core/client_test.go @@ -10,20 +10,20 @@ import ( ) func TestRemoteClient_Status(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestClient(ctx, t) + _, client := StartTestCoreWithApp(t) status, err := client.Status(ctx) require.NoError(t, err) require.NotNil(t, status) } func TestRemoteClient_StartBlockSubscription_And_GetBlock(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestClient(ctx, t) + _, client := StartTestCoreWithApp(t) eventChan, err := client.Subscribe(ctx, newBlockSubscriber, newBlockEventQuery) require.NoError(t, err) diff --git a/core/fetcher_test.go b/core/fetcher_test.go index 49d02fd4a9..95fd4204f4 100644 --- a/core/fetcher_test.go +++ b/core/fetcher_test.go @@ -16,7 +16,7 @@ func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestClient(ctx, t) + _, client := StartTestCoreWithApp(t) fetcher := NewBlockFetcher(client) // generate some blocks @@ -44,7 +44,7 @@ func TestBlockFetcherHeaderValues(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestClient(ctx, t) + _, client := StartTestCoreWithApp(t) fetcher := NewBlockFetcher(client) // generate some blocks diff --git a/core/testing.go b/core/testing.go index c8a564093e..9fadee45fb 100644 --- a/core/testing.go +++ b/core/testing.go @@ -52,26 +52,16 @@ func CreateKVStore(retainBlocks int64) *kvstore.Application { return app } -// StartTestClient returns a started remote Core node process, as well its -// mock Core Client. -func StartTestClient(ctx context.Context, t *testing.T) (tmservice.Service, Client) { - nd, _, cfg := StartTestKVApp(ctx, t) - endpoint, err := GetEndpoint(cfg) - require.NoError(t, err) - ip, port, err := net.SplitHostPort(endpoint) - require.NoError(t, err) - - client, err := NewRemote(ip, port) - require.NoError(t, err) - - err = client.Start() - require.NoError(t, err) - t.Cleanup(func() { - err := client.Stop() - require.NoError(t, err) - }) - - return nd, client +func getFreePort() (port int, err error) { + var a *net.TCPAddr + if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { + var l *net.TCPListener + if l, err = net.ListenTCP("tcp", a); err == nil { + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil + } + } + return } func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) { @@ -89,6 +79,11 @@ func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) { accounts..., ) require.NoError(t, err) + // get a random port for running test in parallel + freePort, err := getFreePort() + require.NoError(t, err) + tmNode.Config().RPC.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", freePort) + tmNode.Config().P2P.ListenAddress = "tcp://0.0.0.0:0" _, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) require.NoError(t, err) diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index 56f1bfad56..1524cd9e44 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -19,7 +19,7 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - _, client := core.StartTestClient(ctx, t) + _, client := core.StartTestCoreWithApp(t) node, err := New(node.Bridge, p2p.Private, repo, coremodule.WithClient(client)) require.NoError(t, err) require.NotNil(t, node) From 1a129a8321afc5f448567da7fee4cd284af83552 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 9 Dec 2022 17:22:09 +0200 Subject: [PATCH 0234/1008] feat(share/availability): extend SharesAvailable method with peer param (#1452) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1450 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- nodebuilder/share/constructors.go | 2 +- nodebuilder/share/mocks/api.go | 11 +++++------ nodebuilder/share/share.go | 15 ++++++++++++--- share/availability.go | 6 ++++-- share/availability/cache/availability.go | 4 +++- share/availability/cache/availability_test.go | 4 +++- share/availability/full/availability.go | 3 ++- share/availability/light/availability.go | 4 +++- 8 files changed, 33 insertions(+), 16 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 45277ab0e1..f53ecf24ed 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -50,5 +50,5 @@ func newModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Ava return serv.Stop(ctx) }, }) - return serv + return &module{serv} } diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index e2c791036d..d7dca8601e 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - da "github.com/celestiaorg/celestia-app/pkg/da" namespace "github.com/celestiaorg/nmt/namespace" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -83,17 +82,17 @@ func (mr *MockModuleMockRecorder) GetSharesByNamespace(arg0, arg1, arg2 interfac } // ProbabilityOfAvailability mocks base method. -func (m *MockModule) ProbabilityOfAvailability() float64 { +func (m *MockModule) ProbabilityOfAvailability(arg0 context.Context) float64 { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ProbabilityOfAvailability") + ret := m.ctrl.Call(m, "ProbabilityOfAvailability", arg0) ret0, _ := ret[0].(float64) return ret0 } // ProbabilityOfAvailability indicates an expected call of ProbabilityOfAvailability. -func (mr *MockModuleMockRecorder) ProbabilityOfAvailability() *gomock.Call { +func (mr *MockModuleMockRecorder) ProbabilityOfAvailability(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProbabilityOfAvailability", reflect.TypeOf((*MockModule)(nil).ProbabilityOfAvailability)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProbabilityOfAvailability", reflect.TypeOf((*MockModule)(nil).ProbabilityOfAvailability), arg0) } // SharesAvailable mocks base method. diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index eaf049aedc..26e8463455 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -3,8 +3,10 @@ package share import ( "context" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/service" ) var _ Module = (*API)(nil) @@ -26,8 +28,7 @@ var _ Module = (*API)(nil) // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on - // the Network. + // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. SharesAvailable(context.Context, *share.Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. @@ -85,3 +86,11 @@ func (api *API) GetSharesByNamespace( ) ([]share.Share, error) { return api.Internal.GetSharesByNamespace(ctx, root, namespace) } + +type module struct { + *service.ShareService +} + +func (m *module) SharesAvailable(ctx context.Context, root *share.Root) error { + return m.ShareService.SharesAvailable(ctx, root) +} diff --git a/share/availability.go b/share/availability.go index 712bf97456..4987129164 100644 --- a/share/availability.go +++ b/share/availability.go @@ -5,6 +5,8 @@ import ( "errors" "time" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/celestiaorg/celestia-app/pkg/da" ) @@ -23,8 +25,8 @@ type Root = da.DataAvailabilityHeader // Availability defines interface for validation of Shares' availability. type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on - // the Network. - SharesAvailable(context.Context, *Root) error + // the Network by requesting the EDS from the provided peers. + SharesAvailable(context.Context, *Root, ...peer.ID) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. // TODO(@Wondertan): Merge with SharesAvailable method, eventually diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index e9ee88f867..50c930a6ff 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -9,8 +9,10 @@ import ( "github.com/ipfs/go-datastore/autobatch" "github.com/ipfs/go-datastore/namespace" logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p-core/peer" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" ) @@ -52,7 +54,7 @@ func NewShareAvailability(avail share.Availability, ds datastore.Batching) *Shar } // SharesAvailable will store, upon success, the hash of the given Root to disk. -func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { +func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root, _ ...peer.ID) error { // short-circuit if the given root is minimum DAH of an empty data square if isMinRoot(root) { return nil diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index 6b3e96229f..e4302307ef 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -10,10 +10,12 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" mdutils "github.com/ipfs/go-merkledag/test" + "github.com/libp2p/go-libp2p-core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/service" @@ -123,7 +125,7 @@ type dummyAvailability struct { // SharesAvailable should only be called once, if called more than once, return // error. -func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root) error { +func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root, _ ...peer.ID) error { if root == &invalidHeader { return fmt.Errorf("invalid header") } diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 68ebb56491..15bdd71c64 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -7,6 +7,7 @@ import ( "github.com/ipfs/go-blockservice" ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p-core/peer" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" @@ -49,7 +50,7 @@ func (fa *ShareAvailability) Stop(context.Context) error { // SharesAvailable reconstructs the data committed to the given Root by requesting // enough Shares from the network. -func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { +func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root, _ ...peer.ID) error { ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) defer cancel() // we assume the caller of this method has already performed basic validation on the diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 71247417c4..0630ea0f6e 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -5,6 +5,8 @@ import ( "errors" "math" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/ipfs/go-blockservice" @@ -56,7 +58,7 @@ func (la *ShareAvailability) Stop(context.Context) error { // SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given // Root. This way SharesAvailable subjectively verifies that Shares are available. -func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { +func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root, _ ...peer.ID) error { log.Debugw("Validate availability", "root", dah.Hash()) // We assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. From 52048ce466d58c1688ba48de0a2656b27306634d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 9 Dec 2022 18:13:01 +0200 Subject: [PATCH 0235/1008] feat(share/blocksync): implementation of ShrEx/Sub (#1436) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1432 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords Co-authored-by: Ryan Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/eds/pubsub.go | 88 +++++++++++++++++++++++++++++++++++++++ share/eds/pubsub_test.go | 40 ++++++++++++++++++ share/eds/subscription.go | 42 +++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 share/eds/pubsub.go create mode 100644 share/eds/pubsub_test.go create mode 100644 share/eds/subscription.go diff --git a/share/eds/pubsub.go b/share/eds/pubsub.go new file mode 100644 index 0000000000..66d6aec7f3 --- /dev/null +++ b/share/eds/pubsub.go @@ -0,0 +1,88 @@ +package eds + +import ( + "context" + "fmt" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/celestiaorg/celestia-node/share" +) + +// pubSubTopic hardcodes the name of the EDS floodsub topic with the provided suffix. +func pubSubTopic(suffix string) string { + return "eds-sub/v0.0.1/" + suffix +} + +// Validator is an injectable func and governs EDS notification or DataHash validity. +// It receives the notification and sender peer and expects the validation result. +// Validator is allowed to be blocking for an indefinite time or until the context is canceled. +type Validator func(context.Context, peer.ID, share.DataHash) pubsub.ValidationResult + +// PubSub manages receiving and propagating the EDS from/to the network +// over "eds-sub" subscription. +type PubSub struct { + pubSub *pubsub.PubSub + topic *pubsub.Topic + + pubSubTopic string +} + +// NewPubSub creates a libp2p.PubSub wrapper. +func NewPubSub(ctx context.Context, h host.Host, suffix string) (*PubSub, error) { + pubsub, err := pubsub.NewFloodSub(ctx, h) + if err != nil { + return nil, err + } + return &PubSub{ + pubSub: pubsub, + pubSubTopic: pubSubTopic(suffix), + }, nil +} + +// Start creates an instances of FloodSub and joins specified topic. +func (s *PubSub) Start(context.Context) error { + topic, err := s.pubSub.Join(s.pubSubTopic) + if err != nil { + return err + } + + s.topic = topic + return nil +} + +// Stop completely stops the PubSub: +// * Unregisters all the added Validators +// * Closes the `ShrEx/Sub` topic +func (s *PubSub) Stop(context.Context) error { + err := s.pubSub.UnregisterTopicValidator(s.pubSubTopic) + if err != nil { + return err + } + + return s.topic.Close() +} + +// AddValidator registers given Validator for EDS notifications (DataHash). +// Any amount of Validators can be registered. +func (s *PubSub) AddValidator(validate Validator) error { + return s.pubSub.RegisterTopicValidator(s.pubSubTopic, + func(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { + return validate(ctx, p, msg.Data) + }) +} + +// Subscribe provides a new Subscription for EDS notifications. +func (s *PubSub) Subscribe() (*Subscription, error) { + if s.topic == nil { + return nil, fmt.Errorf("share/eds: topic is not started") + } + return newSubscription(s.topic) +} + +// Broadcast sends the EDS notification (DataHash) to every connected peer. +func (s *PubSub) Broadcast(ctx context.Context, data share.DataHash) error { + return s.topic.Publish(ctx, data) +} diff --git a/share/eds/pubsub_test.go b/share/eds/pubsub_test.go new file mode 100644 index 0000000000..b630f3afca --- /dev/null +++ b/share/eds/pubsub_test.go @@ -0,0 +1,40 @@ +package eds + +import ( + "context" + "testing" + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" +) + +func TestPubSub(t *testing.T) { + h, err := mocknet.FullMeshConnected(2) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + pSub1, err := NewPubSub(ctx, h.Hosts()[0], "test") + require.NoError(t, err) + + pSub2, err := NewPubSub(ctx, h.Hosts()[1], "test") + require.NoError(t, err) + + require.NoError(t, pSub1.Start(ctx)) + require.NoError(t, pSub2.Start(ctx)) + + subs, err := pSub2.Subscribe() + require.NoError(t, err) + + var edsHash share.DataHash = []byte("data") + err = pSub1.topic.Publish(ctx, edsHash, pubsub.WithReadiness(pubsub.MinTopicSize(1))) + require.NoError(t, err) + + data, err := subs.Next(ctx) + require.NoError(t, err) + require.Equal(t, data, edsHash) +} diff --git a/share/eds/subscription.go b/share/eds/subscription.go new file mode 100644 index 0000000000..2ba7e2842b --- /dev/null +++ b/share/eds/subscription.go @@ -0,0 +1,42 @@ +package eds + +import ( + "context" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/celestiaorg/celestia-node/share" +) + +// Subscription is a wrapper over pubsub.Subscription that handles +// receiving an EDS DataHash from other peers. +type Subscription struct { + subscription *pubsub.Subscription +} + +func newSubscription(t *pubsub.Topic) (*Subscription, error) { + subs, err := t.Subscribe() + if err != nil { + return nil, err + } + + return &Subscription{subscription: subs}, nil +} + +// Next blocks the caller until any new EDS DataHash notification arrives. +// Returns only notifications which successfully pass validation. +func (subs *Subscription) Next(ctx context.Context) (share.DataHash, error) { + msg, err := subs.subscription.Next(ctx) + if err != nil { + log.Errorw("listening for the next eds hash", "err", err) + return nil, err + } + + log.Debugw("received message", "topic", msg.Message.GetTopic(), "sender", msg.ReceivedFrom) + return msg.Data, nil +} + +// Cancel stops the subscription. +func (subs *Subscription) Cancel() { + subs.subscription.Cancel() +} From cf7ad847d31dac7b7a5e861fe71c10ca3f6f602c Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 13 Dec 2022 12:13:00 +0100 Subject: [PATCH 0236/1008] feat(nodebuilder/node): Implement `Module` (#1313) Resolves #977 --- Makefile | 2 +- api/rpc/client/client.go | 12 ++--- api/rpc_test.go | 66 +++++++++++++++++++---- docs/adr/adr-009-public-api.md | 13 +++-- nodebuilder/default_services.go | 2 + nodebuilder/module.go | 1 + nodebuilder/node/admin.go | 45 ++++++++++++++++ nodebuilder/node/mocks/api.go | 96 +++++++++++++++++++++++++++++++++ nodebuilder/node/module.go | 14 +++++ nodebuilder/node/node.go | 51 ++++++++++++++++++ nodebuilder/rpc/constructors.go | 3 ++ 11 files changed, 279 insertions(+), 26 deletions(-) create mode 100644 nodebuilder/node/admin.go create mode 100644 nodebuilder/node/mocks/api.go create mode 100644 nodebuilder/node/module.go create mode 100644 nodebuilder/node/node.go diff --git a/Makefile b/Makefile index b3755e68b7..475589c294 100644 --- a/Makefile +++ b/Makefile @@ -151,6 +151,6 @@ pb-gen: ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api openrpc-gen: @echo "--> Generating OpenRPC spec" - @go run ./cmd/docgen fraud header state share das p2p + @go run ./cmd/docgen fraud header state share das p2p node .PHONY: openrpc-gen diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 2e8c74d254..950373a01c 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -8,20 +8,12 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -type API interface { - fraud.Module - header.Module - state.Module - share.Module - das.Module - p2p.Module -} - type Client struct { Fraud fraud.API Header header.API @@ -29,6 +21,7 @@ type Client struct { Share share.API DAS das.API P2P p2p.API + Node node.API closer multiClientCloser } @@ -68,6 +61,7 @@ func NewClient(ctx context.Context, addr string) (*Client, error) { "fraud": &client.Fraud.Internal, "das": &client.DAS.Internal, "p2p": &client.P2P.Internal, + "node": &client.Node.Internal, } for name, module := range modules { closer, err := jsonrpc.NewClient(ctx, addr, name, module, nil) diff --git a/api/rpc_test.go b/api/rpc_test.go index b06fa3a3fc..a21081ae4b 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -15,12 +15,19 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/das" dasMock "github.com/celestiaorg/celestia-node/nodebuilder/das/mocks" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraudMock "github.com/celestiaorg/celestia-node/nodebuilder/fraud/mocks" + "github.com/celestiaorg/celestia-node/nodebuilder/header" headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/node" + nodeMock "github.com/celestiaorg/celestia-node/nodebuilder/node/mocks" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" p2pMock "github.com/celestiaorg/celestia-node/nodebuilder/p2p/mocks" + "github.com/celestiaorg/celestia-node/nodebuilder/share" shareMock "github.com/celestiaorg/celestia-node/nodebuilder/share/mocks" + statemod "github.com/celestiaorg/celestia-node/nodebuilder/state" stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks" "github.com/celestiaorg/celestia-node/state" ) @@ -58,33 +65,67 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { require.Equal(t, expectedBalance, balance) } +// api contains all modules that are made available as the node's +// public API surface (except for the `node` module as the node +// module contains a method `Info` that is also contained in the +// p2p module). +type api interface { + fraud.Module + header.Module + statemod.Module + share.Module + das.Module + p2p.Module +} + func TestModulesImplementFullAPI(t *testing.T) { - api := reflect.TypeOf(new(client.API)).Elem() + api := reflect.TypeOf(new(api)).Elem() + nodeapi := reflect.TypeOf(new(node.Module)).Elem() // TODO @renaynay: explain client := reflect.TypeOf(new(client.Client)).Elem() for i := 0; i < client.NumField(); i++ { module := client.Field(i) - // the "closers" field is not an actual module - if module.Name == "closer" { + switch module.Name { + case "closer": + // the "closers" field is not an actual module continue - } - internal, ok := module.Type.FieldByName("Internal") - require.True(t, ok, "module %s's API does not have an Internal field", module.Name) - for j := 0; j < internal.Type.NumField(); j++ { - impl := internal.Type.Field(j) - method, _ := api.MethodByName(impl.Name) - require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) + case "Node": + // node module contains a duplicate method to the p2p module + // and must be tested separately. + internal, ok := module.Type.FieldByName("Internal") + require.True(t, ok, "module %s's API does not have an Internal field", module.Name) + for j := 0; j < internal.Type.NumField(); j++ { + impl := internal.Type.Field(j) + method, _ := nodeapi.MethodByName(impl.Name) + require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) + } + default: + internal, ok := module.Type.FieldByName("Internal") + require.True(t, ok, "module %s's API does not have an Internal field", module.Name) + for j := 0; j < internal.Type.NumField(); j++ { + impl := internal.Type.Field(j) + method, _ := api.MethodByName(impl.Name) + require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) + } } } } func TestAllReturnValuesAreMarshalable(t *testing.T) { - ra := reflect.TypeOf(new(client.API)).Elem() + ra := reflect.TypeOf(new(api)).Elem() for i := 0; i < ra.NumMethod(); i++ { m := ra.Method(i) for j := 0; j < m.Type.NumOut(); j++ { implementsMarshaler(t, m.Type.Out(j)) } } + // NOTE: see comment above api interface definition. + na := reflect.TypeOf(new(node.Module)).Elem() + for i := 0; i < na.NumMethod(); i++ { + m := na.Method(i) + for j := 0; j < m.Type.NumOut(); j++ { + implementsMarshaler(t, m.Type.Out(j)) + } + } } func implementsMarshaler(t *testing.T, typ reflect.Type) { @@ -145,6 +186,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { headerMock.NewMockModule(ctrl), dasMock.NewMockModule(ctrl), p2pMock.NewMockModule(ctrl), + nodeMock.NewMockModule(ctrl), } // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root @@ -156,6 +198,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { srv.RegisterService("header", mockAPI.Header) srv.RegisterService("das", mockAPI.Das) srv.RegisterService("p2p", mockAPI.P2P) + srv.RegisterService("node", mockAPI.Node) }) nd := nodebuilder.TestNode(t, node.Full, invokeRPC) // start node @@ -175,4 +218,5 @@ type mockAPI struct { Header *headerMock.MockModule Das *dasMock.MockModule P2P *p2pMock.MockModule + Node *nodeMock.MockModule } diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 5d81cc47da..dbad32f353 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -203,13 +203,16 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) ```go type NodeModule interface { - // Type returns the node type. - Type() node.Type - // Version returns information about the current binary build. - Version() string - + // Info returns administrative information about the node. + Info(context.Context) (Info, error) + // LogLevelSet sets the given component log level to the given level. LogLevelSet(ctx context.Context, name, level string) error + + // AuthVerify returns the permissions assigned to the given token. + AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) + // AuthNew signs and returns a new token with the given permissions. + AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) } ``` diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go index 969688cead..03954c726a 100644 --- a/nodebuilder/default_services.go +++ b/nodebuilder/default_services.go @@ -4,6 +4,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -18,4 +19,5 @@ var PackageToAPI = map[string]interface{}{ "header": &header.API{}, "daser": &das.API{}, "p2p": &p2p.API{}, + "node": &node.API{}, } diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 5f1f3e0cbf..ca44ba3478 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -41,6 +41,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store core.ConstructModule(tp, &cfg.Core), das.ConstructModule(tp, &cfg.DASer), fraud.ConstructModule(tp), + node.ConstructModule(tp), ) return fx.Module( diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go new file mode 100644 index 0000000000..d6339b2ef1 --- /dev/null +++ b/nodebuilder/node/admin.go @@ -0,0 +1,45 @@ +package node + +import ( + "context" + "fmt" + + "github.com/filecoin-project/go-jsonrpc/auth" + logging "github.com/ipfs/go-log/v2" +) + +type module struct { + tp Type +} + +func newModule(tp Type) Module { + return &module{ + tp: tp, + } +} + +// Info contains information related to the administrative +// node. +type Info struct { + Type Type `json:"type"` + APIVersion string `json:"api_version"` +} + +func (m *module) Info(context.Context) (Info, error) { + return Info{ + Type: m.tp, + // TODO @renaynay @distractedm1nd: Implement versioning in API and way to extract that into this struct + }, nil +} + +func (m *module) LogLevelSet(_ context.Context, name, level string) error { + return logging.SetLogLevel(name, level) +} + +func (m *module) AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) { + return []auth.Permission{}, fmt.Errorf("not implemented") +} + +func (m *module) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go new file mode 100644 index 0000000000..98df713429 --- /dev/null +++ b/nodebuilder/node/mocks/api.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/node (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + node "github.com/celestiaorg/celestia-node/nodebuilder/node" + auth "github.com/filecoin-project/go-jsonrpc/auth" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// AuthNew mocks base method. +func (m *MockModule) AuthNew(arg0 context.Context, arg1 []auth.Permission) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthNew", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthNew indicates an expected call of AuthNew. +func (mr *MockModuleMockRecorder) AuthNew(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthNew", reflect.TypeOf((*MockModule)(nil).AuthNew), arg0, arg1) +} + +// AuthVerify mocks base method. +func (m *MockModule) AuthVerify(arg0 context.Context, arg1 string) ([]auth.Permission, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthVerify", arg0, arg1) + ret0, _ := ret[0].([]auth.Permission) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthVerify indicates an expected call of AuthVerify. +func (mr *MockModuleMockRecorder) AuthVerify(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthVerify", reflect.TypeOf((*MockModule)(nil).AuthVerify), arg0, arg1) +} + +// Info mocks base method. +func (m *MockModule) Info(arg0 context.Context) (node.Info, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Info", arg0) + ret0, _ := ret[0].(node.Info) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Info indicates an expected call of Info. +func (mr *MockModuleMockRecorder) Info(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockModule)(nil).Info), arg0) +} + +// LogLevelSet mocks base method. +func (m *MockModule) LogLevelSet(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogLevelSet", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// LogLevelSet indicates an expected call of LogLevelSet. +func (mr *MockModuleMockRecorder) LogLevelSet(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogLevelSet", reflect.TypeOf((*MockModule)(nil).LogLevelSet), arg0, arg1, arg2) +} diff --git a/nodebuilder/node/module.go b/nodebuilder/node/module.go new file mode 100644 index 0000000000..0b7aff337f --- /dev/null +++ b/nodebuilder/node/module.go @@ -0,0 +1,14 @@ +package node + +import ( + "go.uber.org/fx" +) + +func ConstructModule(tp Type) fx.Option { + return fx.Module( + "node", + fx.Provide(func() Module { + return newModule(tp) + }), + ) +} diff --git a/nodebuilder/node/node.go b/nodebuilder/node/node.go new file mode 100644 index 0000000000..c33f73ef58 --- /dev/null +++ b/nodebuilder/node/node.go @@ -0,0 +1,51 @@ +package node + +import ( + "context" + + "github.com/filecoin-project/go-jsonrpc/auth" +) + +// Module defines the API related to interacting with the "administrative" +// node. +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + // Info returns administrative information about the node. + Info(context.Context) (Info, error) + + // LogLevelSet sets the given component log level to the given level. + LogLevelSet(ctx context.Context, name, level string) error + + // AuthVerify returns the permissions assigned to the given token. + AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) + // AuthNew signs and returns a new token with the given permissions. + AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) +} + +var _ Module = (*API)(nil) + +type API struct { + Internal struct { + Info func(context.Context) (Info, error) `perm:"admin"` + LogLevelSet func(ctx context.Context, name, level string) error `perm:"admin"` + AuthVerify func(ctx context.Context, token string) ([]auth.Permission, error) `perm:"admin"` + AuthNew func(ctx context.Context, perms []auth.Permission) ([]byte, error) `perm:"admin"` + } +} + +func (api *API) Info(ctx context.Context) (Info, error) { + return api.Internal.Info(ctx) +} + +func (api *API) LogLevelSet(ctx context.Context, name, level string) error { + return api.Internal.LogLevelSet(ctx, name, level) +} + +func (api *API) AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) { + return api.Internal.AuthVerify(ctx, token) +} + +func (api *API) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) { + return api.Internal.AuthNew(ctx, perms) +} diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index 40408f35ae..f52549f487 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -5,6 +5,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -18,6 +19,7 @@ func RegisterEndpoints( headerMod header.Module, daserMod das.Module, p2pMod p2p.Module, + nodeMod node.Module, serv *rpc.Server, ) { serv.RegisterAuthedService("fraud", fraudMod, &fraud.API{}) @@ -26,6 +28,7 @@ func RegisterEndpoints( serv.RegisterAuthedService("state", stateMod, &state.API{}) serv.RegisterAuthedService("share", shareMod, &share.API{}) serv.RegisterAuthedService("p2p", p2pMod, &p2p.API{}) + serv.RegisterAuthedService("node", nodeMod, &node.API{}) } func Server(cfg *Config) *rpc.Server { From 05254509c19c42cf58c2c8c0af8966cd26d33109 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:41:23 +0100 Subject: [PATCH 0237/1008] chore!(nodebuilder/p2p): Upgrade `mamaki` to `mocha` (#1434) Upgrades `mamaki` to `mocha` TODO - [x] genesis hash --- nodebuilder/p2p/bootstrap.go | 2 +- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 67ac4ea1e7..0a3585bca8 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -31,7 +31,7 @@ var bootstrapList = map[Network][]string{ "/dns4/marsellesa.celestia-devops.dev/tcp/2121/p2p/12D3KooWHr2wqFAsMXnPzpFsgxmePgXb8BqpkePebwUgLyZc95bd", "/dns4/parainem.celestia-devops.dev/tcp/2121/p2p/12D3KooWHX8xpwg8qkP7kLKmKGtgZvmsopvgxc6Fwtu665QC7G8q", }, - Mamaki: { + Mocha: { "/dns4/andromeda.celestia-devops.dev/tcp/2121/p2p/12D3KooWKvPXtV1yaQ6e3BRNUHa5Phh8daBwBi3KkGaSSkUPys6D", "/dns4/libra.celestia-devops.dev/tcp/2121/p2p/12D3KooWK5aDotDcLsabBmWDazehQLMsDkRyARm1k7f1zGAXqbt4", "/dns4/norma.celestia-devops.dev/tcp/2121/p2p/12D3KooWHYczJDVNfYVkLcNHPTDKCeiVvRhg8Q9JU3bE3m9eEVyY", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 32d3e6a87e..9cb799b965 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -22,6 +22,6 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Arabica: "04EE55B212745B88F29943D7B9528C415473A211F12EEF6E9333EF32E4DEAF3C", - Mamaki: "41BBFD05779719E826C4D68C4CCBBC84B2B761EB52BC04CFDE0FF8603C9AA3CA", + Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index e184afc15f..7046bf8dcd 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -13,8 +13,8 @@ const ( DefaultNetwork = Arabica // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica-2" - // Mamaki testnet. See: celestiaorg/networks. - Mamaki Network = "mamaki" + // Mocha testnet. See: celestiaorg/networks. + Mocha Network = "mocha" // Private can be used to set up any private network, including local testing setups. Private Network = "private" // BlockTime is a network block time. @@ -42,7 +42,7 @@ func (n Network) Validate() error { // networksList is a strict list of all known long-standing networks. var networksList = map[Network]struct{}{ Arabica: {}, - Mamaki: {}, + Mocha: {}, Private: {}, } @@ -51,7 +51,7 @@ var networksList = map[Network]struct{}{ // their actual value) to the Network. var networkAliases = map[string]Network{ "arabica": Arabica, - "mamaki": Mamaki, + "mocha": Mocha, "private": Private, } From f9d451add8894a687f1cc56f8a2943f344d44df7 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 13 Dec 2022 12:44:37 -0600 Subject: [PATCH 0238/1008] chore: bump celestia-app to v0.11.0 (#1478) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bc2ac6cc98..ef70941413 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.10.0 + github.com/celestiaorg/celestia-app v0.11.0 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.11.0 github.com/celestiaorg/rsmt2d v0.7.0 diff --git a/go.sum b/go.sum index f11e960227..aa061dc93a 100644 --- a/go.sum +++ b/go.sum @@ -216,8 +216,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.10.0 h1:3v9Gz9R89PkSSnXnaavXLDcUaK0u36bnRg9ESgcYc+E= -github.com/celestiaorg/celestia-app v0.10.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= +github.com/celestiaorg/celestia-app v0.11.0 h1:F76mBdLJZ1LLjWEPVEjoFPJA4skaY7Wrg+5o1LVlHGY= +github.com/celestiaorg/celestia-app v0.11.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ= github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= From a6ac32167c331efe14307425b6200fcff01ae195 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 13 Dec 2022 19:46:41 +0100 Subject: [PATCH 0239/1008] refactor(nodebuilder/p2p): Switch default network from `arabica` to `mocha` (#1462) Based on #1434 Switches default network from arabica to mocha --- nodebuilder/p2p/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 7046bf8dcd..1629047898 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -10,7 +10,7 @@ import ( // NOTE: Every time we add a new long-running network, it has to be added here. const ( // DefaultNetwork is the default network of the current build. - DefaultNetwork = Arabica + DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica-2" // Mocha testnet. See: celestiaorg/networks. From 4734aac019e5d786be1148f8cbd0bc84ebc392a9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 14 Dec 2022 16:02:50 +0100 Subject: [PATCH 0240/1008] feat(state)!: Adding fee parameter to `CoreAccessor` operations (#1484) --- api/gateway/state.go | 24 ++++++++--- docs/adr/adr-009-public-api.md | 18 +++++--- nodebuilder/state/mocks/api.go | 59 +++++++++++++------------- nodebuilder/state/state.go | 76 ++++++++++++++++++++++++---------- state/core_access.go | 31 +++++++++----- 5 files changed, 133 insertions(+), 75 deletions(-) diff --git a/api/gateway/state.go b/api/gateway/state.go index 938daab0d7..6bb472f5f9 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -43,12 +43,14 @@ type submitTxRequest struct { type submitPFDRequest struct { NamespaceID string `json:"namespace_id"` Data string `json:"data"` + Fee int64 `json:"fee"` GasLimit uint64 `json:"gas_limit"` } type transferRequest struct { To string `json:"to"` Amount int64 `json:"amount"` + Fee int64 `json:"fee"` GasLimit uint64 `json:"gas_limit"` } @@ -57,6 +59,7 @@ type transferRequest struct { type delegationRequest struct { To string `json:"to"` Amount int64 `json:"amount"` + Fee int64 `json:"fee"` GasLimit uint64 `json:"gas_limit"` } @@ -65,6 +68,7 @@ type redelegationRequest struct { From string `json:"from"` To string `json:"to"` Amount int64 `json:"amount"` + Fee int64 `json:"fee"` GasLimit uint64 `json:"gas_limit"` } @@ -72,6 +76,7 @@ type redelegationRequest struct { type unbondRequest struct { From string `json:"from"` Amount int64 `json:"amount"` + Fee int64 `json:"fee"` GasLimit uint64 `json:"gas_limit"` } @@ -80,6 +85,7 @@ type cancelUnbondRequest struct { From string `json:"from"` Amount int64 `json:"amount"` Height int64 `json:"height"` + Fee int64 `json:"fee"` GasLimit uint64 `json:"gas_limit"` } @@ -177,8 +183,9 @@ func (h *Handler) handleSubmitPFD(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, submitPFDEndpoint, err) return } + fee := types.NewInt(req.Fee) // perform request - txResp, err := h.state.SubmitPayForData(r.Context(), nID, data, req.GasLimit) + txResp, err := h.state.SubmitPayForData(r.Context(), nID, data, fee, req.GasLimit) if err != nil { writeError(w, http.StatusInternalServerError, submitPFDEndpoint, err) return @@ -212,8 +219,9 @@ func (h *Handler) handleTransfer(w http.ResponseWriter, r *http.Request) { addr = valAddr.Bytes() } amount := types.NewInt(req.Amount) + fee := types.NewInt(req.Fee) - txResp, err := h.state.Transfer(r.Context(), addr, amount, req.GasLimit) + txResp, err := h.state.Transfer(r.Context(), addr, amount, fee, req.GasLimit) if err != nil { writeError(w, http.StatusInternalServerError, transferEndpoint, err) return @@ -242,8 +250,9 @@ func (h *Handler) handleDelegation(w http.ResponseWriter, r *http.Request) { return } amount := types.NewInt(req.Amount) + fee := types.NewInt(req.Fee) - txResp, err := h.state.Delegate(r.Context(), addr, amount, req.GasLimit) + txResp, err := h.state.Delegate(r.Context(), addr, amount, fee, req.GasLimit) if err != nil { writeError(w, http.StatusInternalServerError, delegationEndpoint, err) return @@ -272,8 +281,9 @@ func (h *Handler) handleUndelegation(w http.ResponseWriter, r *http.Request) { return } amount := types.NewInt(req.Amount) + fee := types.NewInt(req.Fee) - txResp, err := h.state.Undelegate(r.Context(), addr, amount, req.GasLimit) + txResp, err := h.state.Undelegate(r.Context(), addr, amount, fee, req.GasLimit) if err != nil { writeError(w, http.StatusInternalServerError, undelegationEndpoint, err) return @@ -303,7 +313,8 @@ func (h *Handler) handleCancelUnbonding(w http.ResponseWriter, r *http.Request) } amount := types.NewInt(req.Amount) height := types.NewInt(req.Height) - txResp, err := h.state.CancelUnbondingDelegation(r.Context(), addr, amount, height, req.GasLimit) + fee := types.NewInt(req.Fee) + txResp, err := h.state.CancelUnbondingDelegation(r.Context(), addr, amount, height, fee, req.GasLimit) if err != nil { writeError(w, http.StatusInternalServerError, cancelUnbondingEndpoint, err) return @@ -337,8 +348,9 @@ func (h *Handler) handleRedelegation(w http.ResponseWriter, r *http.Request) { return } amount := types.NewInt(req.Amount) + fee := types.NewInt(req.Fee) - txResp, err := h.state.BeginRedelegate(r.Context(), srcAddr, dstAddr, amount, req.GasLimit) + txResp, err := h.state.BeginRedelegate(r.Context(), srcAddr, dstAddr, amount, fee, req.GasLimit) if err != nil { writeError(w, http.StatusInternalServerError, beginRedelegationEndpoint, err) return diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index dbad32f353..acfb6094b9 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -246,15 +246,17 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) SubmitPayForData( ctx context.Context, nID namespace.ID, - data []byte, + data []byte, + fee types.Int, gasLimit uint64, ) (*state.TxResponse, error) // Transfer sends the given amount of coins from default wallet of the node // to the given account address. Transfer( ctx context.Context, - to types.Address, - amount types.Int, + to types.Address, + amount types.Int, + fee types.Int, gasLimit uint64, ) (*state.TxResponse, error) @@ -275,7 +277,8 @@ yet. Delegate( ctx context.Context, delAddr state.ValAddress, - amount state.Int, + amount state.Int, + fee types.Int, gasLim uint64, ) (*state.TxResponse, error) // BeginRedelegate sends a user's delegated tokens to a new validator for redelegation. @@ -284,6 +287,7 @@ yet. srcValAddr, dstValAddr state.ValAddress, amount state.Int, + fee types.Int, gasLim uint64, ) (*state.TxResponse, error) // Undelegate undelegates a user's delegated tokens, unbonding them from the @@ -292,6 +296,7 @@ yet. ctx context.Context, delAddr state.ValAddress, amount state.Int, + fee types.Int, gasLim uint64, ) (*state.TxResponse, error) @@ -300,8 +305,9 @@ yet. CancelUnbondingDelegation( ctx context.Context, valAddr state.ValAddress, - amount, - height state.Int, + amount types.Int, + height types.Int, + fee types.Int, gasLim uint64, ) (*state.TxResponse, error) diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index ab97996a00..f3017e4a2b 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,12 +9,11 @@ import ( reflect "reflect" math "cosmossdk.io/math" + namespace "github.com/celestiaorg/nmt/namespace" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" - - namespace "github.com/celestiaorg/nmt/namespace" ) // MockModule is a mock of Module interface. @@ -86,62 +85,62 @@ func (mr *MockModuleMockRecorder) BalanceForAddress(arg0, arg1 interface{}) *gom } // BeginRedelegate mocks base method. -func (m *MockModule) BeginRedelegate(arg0 context.Context, arg1, arg2 types.ValAddress, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { +func (m *MockModule) BeginRedelegate(arg0 context.Context, arg1, arg2 types.ValAddress, arg3, arg4 math.Int, arg5 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BeginRedelegate", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "BeginRedelegate", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // BeginRedelegate indicates an expected call of BeginRedelegate. -func (mr *MockModuleMockRecorder) BeginRedelegate(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) BeginRedelegate(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginRedelegate", reflect.TypeOf((*MockModule)(nil).BeginRedelegate), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginRedelegate", reflect.TypeOf((*MockModule)(nil).BeginRedelegate), arg0, arg1, arg2, arg3, arg4, arg5) } // CancelUnbondingDelegation mocks base method. -func (m *MockModule) CancelUnbondingDelegation(arg0 context.Context, arg1 types.ValAddress, arg2, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { +func (m *MockModule) CancelUnbondingDelegation(arg0 context.Context, arg1 types.ValAddress, arg2, arg3, arg4 math.Int, arg5 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CancelUnbondingDelegation", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "CancelUnbondingDelegation", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // CancelUnbondingDelegation indicates an expected call of CancelUnbondingDelegation. -func (mr *MockModuleMockRecorder) CancelUnbondingDelegation(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) CancelUnbondingDelegation(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelUnbondingDelegation", reflect.TypeOf((*MockModule)(nil).CancelUnbondingDelegation), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelUnbondingDelegation", reflect.TypeOf((*MockModule)(nil).CancelUnbondingDelegation), arg0, arg1, arg2, arg3, arg4, arg5) } // Delegate mocks base method. -func (m *MockModule) Delegate(arg0 context.Context, arg1 types.ValAddress, arg2 math.Int, arg3 uint64) (*types.TxResponse, error) { +func (m *MockModule) Delegate(arg0 context.Context, arg1 types.ValAddress, arg2, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delegate", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Delegate", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // Delegate indicates an expected call of Delegate. -func (mr *MockModuleMockRecorder) Delegate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Delegate(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delegate", reflect.TypeOf((*MockModule)(nil).Delegate), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delegate", reflect.TypeOf((*MockModule)(nil).Delegate), arg0, arg1, arg2, arg3, arg4) } // IsStopped mocks base method. -func (m *MockModule) IsStopped() bool { +func (m *MockModule) IsStopped(arg0 context.Context) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsStopped") + ret := m.ctrl.Call(m, "IsStopped", arg0) ret0, _ := ret[0].(bool) return ret0 } // IsStopped indicates an expected call of IsStopped. -func (mr *MockModuleMockRecorder) IsStopped() *gomock.Call { +func (mr *MockModuleMockRecorder) IsStopped(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsStopped", reflect.TypeOf((*MockModule)(nil).IsStopped)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsStopped", reflect.TypeOf((*MockModule)(nil).IsStopped), arg0) } // QueryDelegation mocks base method. @@ -190,18 +189,18 @@ func (mr *MockModuleMockRecorder) QueryUnbonding(arg0, arg1 interface{}) *gomock } // SubmitPayForData mocks base method. -func (m *MockModule) SubmitPayForData(arg0 context.Context, arg1 namespace.ID, arg2 []byte, arg3 uint64) (*types.TxResponse, error) { +func (m *MockModule) SubmitPayForData(arg0 context.Context, arg1 namespace.ID, arg2 []byte, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubmitPayForData", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "SubmitPayForData", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // SubmitPayForData indicates an expected call of SubmitPayForData. -func (mr *MockModuleMockRecorder) SubmitPayForData(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) SubmitPayForData(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForData", reflect.TypeOf((*MockModule)(nil).SubmitPayForData), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForData", reflect.TypeOf((*MockModule)(nil).SubmitPayForData), arg0, arg1, arg2, arg3, arg4) } // SubmitTx mocks base method. @@ -220,31 +219,31 @@ func (mr *MockModuleMockRecorder) SubmitTx(arg0, arg1 interface{}) *gomock.Call } // Transfer mocks base method. -func (m *MockModule) Transfer(arg0 context.Context, arg1 types.AccAddress, arg2 math.Int, arg3 uint64) (*types.TxResponse, error) { +func (m *MockModule) Transfer(arg0 context.Context, arg1 types.AccAddress, arg2, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Transfer", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Transfer", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // Transfer indicates an expected call of Transfer. -func (mr *MockModuleMockRecorder) Transfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Transfer(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transfer", reflect.TypeOf((*MockModule)(nil).Transfer), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transfer", reflect.TypeOf((*MockModule)(nil).Transfer), arg0, arg1, arg2, arg3, arg4) } // Undelegate mocks base method. -func (m *MockModule) Undelegate(arg0 context.Context, arg1 types.ValAddress, arg2 math.Int, arg3 uint64) (*types.TxResponse, error) { +func (m *MockModule) Undelegate(arg0 context.Context, arg1 types.ValAddress, arg2, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Undelegate", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Undelegate", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // Undelegate indicates an expected call of Undelegate. -func (mr *MockModuleMockRecorder) Undelegate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Undelegate(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Undelegate", reflect.TypeOf((*MockModule)(nil).Undelegate), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Undelegate", reflect.TypeOf((*MockModule)(nil).Undelegate), arg0, arg1, arg2, arg3, arg4) } diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 6058f5ecf4..b79e59015d 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -3,7 +3,6 @@ package state import ( "context" - "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/celestiaorg/celestia-node/state" @@ -37,20 +36,27 @@ type Module interface { // Transfer sends the given amount of coins from default wallet of the node to the given account // address. - Transfer(ctx context.Context, to state.AccAddress, amount math.Int, gasLimit uint64) (*state.TxResponse, error) + Transfer(ctx context.Context, to state.AccAddress, amount, fee state.Int, gasLimit uint64) (*state.TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in // a block. SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) // SubmitPayForData builds, signs and submits a PayForData transaction. - SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*state.TxResponse, error) + SubmitPayForData( + ctx context.Context, + nID namespace.ID, + data []byte, + fee state.Int, + gasLim uint64, + ) (*state.TxResponse, error) // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. CancelUnbondingDelegation( ctx context.Context, valAddr state.ValAddress, amount, - height state.Int, + height, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) // BeginRedelegate sends a user's delegated tokens to a new validator for redelegation. @@ -58,13 +64,24 @@ type Module interface { ctx context.Context, srcValAddr, dstValAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) // Undelegate undelegates a user's delegated tokens, unbonding them from the current validator. - Undelegate(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) (*state.TxResponse, error) + Undelegate( + ctx context.Context, + delAddr state.ValAddress, + amount, fee state.Int, + gasLim uint64, + ) (*state.TxResponse, error) // Delegate sends a user's liquid tokens to a validator for delegation. - Delegate(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) (*state.TxResponse, error) + Delegate( + ctx context.Context, + delAddr state.ValAddress, + amount, fee state.Int, + gasLim uint64, + ) (*state.TxResponse, error) // QueryDelegation retrieves the delegation information between a delegator and a validator. QueryDelegation(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) @@ -89,7 +106,8 @@ type API struct { Transfer func( ctx context.Context, to state.AccAddress, - amount math.Int, + amount, + fee state.Int, gasLimit uint64, ) (*state.TxResponse, error) `perm:"write"` SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"write"` @@ -97,32 +115,37 @@ type API struct { ctx context.Context, nID namespace.ID, data []byte, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) `perm:"write"` CancelUnbondingDelegation func( ctx context.Context, valAddr state.ValAddress, amount, - height state.Int, + height, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) `perm:"write"` BeginRedelegate func( ctx context.Context, srcValAddr, dstValAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) `perm:"write"` Undelegate func( ctx context.Context, delAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) `perm:"write"` Delegate func( ctx context.Context, delAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) `perm:"write"` QueryDelegation func( @@ -156,10 +179,11 @@ func (api *API) BalanceForAddress(ctx context.Context, addr state.Address) (*sta func (api *API) Transfer( ctx context.Context, to state.AccAddress, - amount math.Int, + amount, + fee state.Int, gasLimit uint64, ) (*state.TxResponse, error) { - return api.Internal.Transfer(ctx, to, amount, gasLimit) + return api.Internal.Transfer(ctx, to, amount, fee, gasLimit) } func (api *API) SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) { @@ -170,45 +194,51 @@ func (api *API) SubmitPayForData( ctx context.Context, nID namespace.ID, data []byte, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) { - return api.Internal.SubmitPayForData(ctx, nID, data, gasLim) + return api.Internal.SubmitPayForData(ctx, nID, data, fee, gasLim) } func (api *API) CancelUnbondingDelegation( ctx context.Context, valAddr state.ValAddress, - amount, height state.Int, + amount, + height, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) { - return api.Internal.CancelUnbondingDelegation(ctx, valAddr, amount, height, gasLim) + return api.Internal.CancelUnbondingDelegation(ctx, valAddr, amount, height, fee, gasLim) } func (api *API) BeginRedelegate( ctx context.Context, srcValAddr, dstValAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) { - return api.Internal.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, gasLim) + return api.Internal.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, fee, gasLim) } func (api *API) Undelegate( ctx context.Context, delAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) { - return api.Internal.Undelegate(ctx, delAddr, amount, gasLim) + return api.Internal.Undelegate(ctx, delAddr, amount, fee, gasLim) } func (api *API) Delegate( ctx context.Context, delAddr state.ValAddress, - amount state.Int, + amount, + fee state.Int, gasLim uint64, ) (*state.TxResponse, error) { - return api.Internal.Delegate(ctx, delAddr, amount, gasLim) + return api.Internal.Delegate(ctx, delAddr, amount, fee, gasLim) } func (api *API) QueryDelegation(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) { diff --git a/state/core_access.go b/state/core_access.go index ee670e484b..cd2164095c 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -149,9 +149,10 @@ func (ca *CoreAccessor) SubmitPayForData( ctx context.Context, nID namespace.ID, data []byte, + fee Int, gasLim uint64, ) (*TxResponse, error) { - response, err := payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim) + response, err := payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim, withFee(fee)) // metrics should only be counted on a successful PFD tx if err == nil && response.Code == 0 { ca.lastPayForData = time.Now().UnixMilli() @@ -256,7 +257,8 @@ func (ca *CoreAccessor) SubmitTxWithBroadcastMode( func (ca *CoreAccessor) Transfer( ctx context.Context, addr AccAddress, - amount Int, + amount, + fee Int, gasLim uint64, ) (*TxResponse, error) { if amount.IsNil() || amount.Int64() <= 0 { @@ -269,7 +271,7 @@ func (ca *CoreAccessor) Transfer( } coins := sdktypes.NewCoins(sdktypes.NewCoin(app.BondDenom, amount)) msg := banktypes.NewMsgSend(from, addr, coins) - signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim), withFee(fee)) if err != nil { return nil, err } @@ -280,7 +282,8 @@ func (ca *CoreAccessor) CancelUnbondingDelegation( ctx context.Context, valAddr ValAddress, amount, - height Int, + height, + fee Int, gasLim uint64, ) (*TxResponse, error) { if amount.IsNil() || amount.Int64() <= 0 { @@ -293,7 +296,7 @@ func (ca *CoreAccessor) CancelUnbondingDelegation( } coins := sdktypes.NewCoin(app.BondDenom, amount) msg := stakingtypes.NewMsgCancelUnbondingDelegation(from, valAddr, height.Int64(), coins) - signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim), withFee(fee)) if err != nil { return nil, err } @@ -304,7 +307,8 @@ func (ca *CoreAccessor) BeginRedelegate( ctx context.Context, srcValAddr, dstValAddr ValAddress, - amount Int, + amount, + fee Int, gasLim uint64, ) (*TxResponse, error) { if amount.IsNil() || amount.Int64() <= 0 { @@ -317,7 +321,7 @@ func (ca *CoreAccessor) BeginRedelegate( } coins := sdktypes.NewCoin(app.BondDenom, amount) msg := stakingtypes.NewMsgBeginRedelegate(from, srcValAddr, dstValAddr, coins) - signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim), withFee(fee)) if err != nil { return nil, err } @@ -327,7 +331,8 @@ func (ca *CoreAccessor) BeginRedelegate( func (ca *CoreAccessor) Undelegate( ctx context.Context, delAddr ValAddress, - amount Int, + amount, + fee Int, gasLim uint64, ) (*TxResponse, error) { if amount.IsNil() || amount.Int64() <= 0 { @@ -340,7 +345,7 @@ func (ca *CoreAccessor) Undelegate( } coins := sdktypes.NewCoin(app.BondDenom, amount) msg := stakingtypes.NewMsgUndelegate(from, delAddr, coins) - signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim), withFee(fee)) if err != nil { return nil, err } @@ -351,6 +356,7 @@ func (ca *CoreAccessor) Delegate( ctx context.Context, delAddr ValAddress, amount Int, + fee Int, gasLim uint64, ) (*TxResponse, error) { if amount.IsNil() || amount.Int64() <= 0 { @@ -363,7 +369,7 @@ func (ca *CoreAccessor) Delegate( } coins := sdktypes.NewCoin(app.BondDenom, amount) msg := stakingtypes.NewMsgDelegate(from, delAddr, coins) - signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim), withFee(fee)) if err != nil { return nil, err } @@ -416,3 +422,8 @@ func (ca *CoreAccessor) QueryRedelegations( func (ca *CoreAccessor) IsStopped(context.Context) bool { return ca.ctx.Err() != nil } + +func withFee(fee Int) apptypes.TxBuilderOption { + gasFee := sdktypes.NewCoins(sdktypes.NewCoin(app.BondDenom, fee)) + return apptypes.SetFeeAmount(gasFee) +} From 4c5d247de0d8f529fc5463d780959c326e256c8a Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 14 Dec 2022 10:16:01 -0600 Subject: [PATCH 0241/1008] fix(state): Verify the state inclusion proof without splitting keys (#1483) ## Overview There has to be better way of fixing this, but I'm unsure of how we want to handle it, so submitting this for now. The bug is that, when verifying state inclusion proofs, tendermint will perform [some processing](https://github.com/celestiaorg/celestia-core/blob/a0ccbd068927b638705f63acdf6303d3091da985/crypto/merkle/proof_key_path.go#L85-L110) on the keypath that you give it that the sdk simply doesn't have to do. The iavl doesn't do this, as we just pass it the key it wants, not the path. It just sticks the key that you give in the [request right back](https://github.com/cosmos/cosmos-sdk/blob/b46d83850270b82496e4cd61421d9ebc8bac87f2/store/iavl/store.go#L324-L326), and because the processing in tendermint sometimes results in a different key, the proof verification [fails](https://github.com/celestiaorg/celestia-core/blob/a0ccbd068927b638705f63acdf6303d3091da985/crypto/merkle/proof_op.go#L52). Specifically, the string encoding of the bytes we pass it sometimes have the string `/` and `%` in them from the address, the processing tendermint does thinks its supposed to do something when it actually isn't, and then the correct key returned by the sdk no longer matches. This is why we're only seeing some addresses break! [bad processing point 1](https://github.com/celestiaorg/celestia-core/blob/a0ccbd068927b638705f63acdf6303d3091da985/crypto/merkle/proof_key_path.go#L91) [bad processing point 2](https://github.com/celestiaorg/celestia-core/blob/a0ccbd068927b638705f63acdf6303d3091da985/crypto/merkle/proof_key_path.go#L102) (idek why this is there) My quick and dirty solution was to copy paste fork ~~the file from tendermint~~ a few methods that verify proofs, then change the api so we don't have to split the string :laughing: . We should fix it properly soon. This might be a bug with tendermint, but I was admittedly too tired to look into it or write up an issue. We might just need to pass a different formatted string(?) as a bonus, I added the foundation for an integration test for the core accessor, that should make it waaaaaaaayyyy easier to round trip test things! :slightly_smiling_face: :christmas_tree: pls feel to edit this PR and add whatever else is needed to get this across the finish line closes #1461 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- state/core_access.go | 20 ++++-- state/integration_test.go | 132 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 state/integration_test.go diff --git a/go.mod b/go.mod index ef70941413..2962e3c617 100644 --- a/go.mod +++ b/go.mod @@ -327,5 +327,5 @@ replace ( github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch ) diff --git a/go.sum b/go.sum index aa061dc93a..28c2c74b77 100644 --- a/go.sum +++ b/go.sum @@ -218,8 +218,8 @@ github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOC github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/celestiaorg/celestia-app v0.11.0 h1:F76mBdLJZ1LLjWEPVEjoFPJA4skaY7Wrg+5o1LVlHGY= github.com/celestiaorg/celestia-app v0.11.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= -github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20 h1:BqlcOQqL2UqdDTcdCtrOLXDlmwxIA8DiKiY79oahxkQ= -github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= +github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch h1:b0swFbc0JqivwVz1UwnzFPlXYsVFkoHAmJ3LIfP4/2o= +github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0/go.mod h1:ByQ2rOrZs7s2OnPfeaiTMC8IOlcrT195xIRPgevydCI= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= diff --git a/state/core_access.go b/state/core_access.go index cd2164095c..e820f4eed2 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -7,12 +7,13 @@ import ( "time" "github.com/cosmos/cosmos-sdk/api/tendermint/abci" - "github.com/cosmos/cosmos-sdk/store/rootmulti" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdktypes "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" logging "github.com/ipfs/go-log/v2" + "github.com/tendermint/tendermint/crypto/merkle" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client/http" "google.golang.org/grpc" @@ -44,6 +45,8 @@ type CoreAccessor struct { stakingCli stakingtypes.QueryClient rpcCli rpcclient.ABCIClient + prt *merkle.ProofRuntime + coreConn *grpc.ClientConn coreIP string rpcPort string @@ -63,12 +66,17 @@ func NewCoreAccessor( rpcPort string, grpcPort string, ) *CoreAccessor { + // create verifier + prt := merkle.DefaultProofRuntime() + prt.RegisterOpDecoder(storetypes.ProofOpIAVLCommitment, storetypes.CommitmentOpDecoder) + prt.RegisterOpDecoder(storetypes.ProofOpSimpleMerkleCommitment, storetypes.CommitmentOpDecoder) return &CoreAccessor{ signer: signer, getter: getter, coreIP: coreIP, rpcPort: rpcPort, grpcPort: grpcPort, + prt: prt, } } @@ -97,6 +105,7 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { return err } ca.rpcCli = cli + return nil } @@ -221,9 +230,12 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B return nil, fmt.Errorf("cannot convert %s into sdktypes.Int", string(value)) } // verify balance - path := fmt.Sprintf("/%s/%s", banktypes.StoreKey, string(prefixedAccountKey)) - prt := rootmulti.DefaultProofRuntime() - err = prt.VerifyValue(result.Response.GetProofOps(), head.AppHash, path, value) + err = ca.prt.VerifyValueKeys( + result.Response.GetProofOps(), + head.AppHash, + [][]byte{[]byte(banktypes.StoreKey), + prefixedAccountKey, + }, value) if err != nil { return nil, err } diff --git a/state/integration_test.go b/state/integration_test.go new file mode 100644 index 0000000000..5c0d06b10a --- /dev/null +++ b/state/integration_test.go @@ -0,0 +1,132 @@ +package state + +import ( + "context" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + tmrand "github.com/tendermint/tendermint/libs/rand" + rpcclient "github.com/tendermint/tendermint/rpc/client" + "google.golang.org/grpc" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/testutil/testnode" + blobtypes "github.com/celestiaorg/celestia-app/x/payment/types" + + "github.com/celestiaorg/celestia-node/header" +) + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +type IntegrationTestSuite struct { + suite.Suite + + cleanups []func() error + accounts []string + cctx testnode.Context + + accessor *CoreAccessor +} + +func (s *IntegrationTestSuite) SetupSuite() { + if testing.Short() { + s.T().Skip("skipping test in unit-tests or race-detector mode.") + } + + s.T().Log("setting up integration test suite") + require := s.Require() + + // we create an arbitrary number of funded accounts + for i := 0; i < 25; i++ { + s.accounts = append(s.accounts, tmrand.Str(9)) + } + + tmNode, app, cctx, err := testnode.New( + s.T(), + testnode.DefaultParams(), + testnode.DefaultTendermintConfig(), + false, + s.accounts..., + ) + require.NoError(err) + + cctx, stopNode, err := testnode.StartNode(tmNode, cctx) + require.NoError(err) + s.cleanups = append(s.cleanups, stopNode) + + cctx, cleanupGRPC, err := testnode.StartGRPCServer(app, testnode.DefaultAppConfig(), cctx) + require.NoError(err) + s.cleanups = append(s.cleanups, cleanupGRPC) + + s.cctx = cctx + require.NoError(cctx.WaitForNextBlock()) + + signer := blobtypes.NewKeyringSigner(s.cctx.Keyring, s.accounts[0], cctx.ChainID) + + accessor := NewCoreAccessor(signer, localHeader{s.cctx.Client}, "", "", "") + setClients(accessor, s.cctx.GRPCClient, s.cctx.Client) + s.accessor = accessor +} + +func setClients(ca *CoreAccessor, conn *grpc.ClientConn, abciCli rpcclient.ABCIClient) { + ca.coreConn = conn + // create the query client + queryCli := banktypes.NewQueryClient(ca.coreConn) + ca.queryCli = queryCli + // create the staking query client + stakingCli := stakingtypes.NewQueryClient(ca.coreConn) + ca.stakingCli = stakingCli + + ca.rpcCli = abciCli +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + require := s.Require() + require.NoError(s.accessor.Stop(s.cctx.GoContext())) + for _, c := range s.cleanups { + err := c() + require.NoError(err) + } +} + +func (s *IntegrationTestSuite) getAddress(acc string) sdk.Address { + rec, err := s.cctx.Keyring.Key(acc) + require.NoError(s.T(), err) + + addr, err := rec.GetAddress() + require.NoError(s.T(), err) + + return addr +} + +type localHeader struct { + client rpcclient.Client +} + +func (l localHeader) Head(ctx context.Context) (*header.ExtendedHeader, error) { + latest, err := l.client.Block(ctx, nil) + if err != nil { + return nil, err + } + h := &header.ExtendedHeader{ + RawHeader: latest.Block.Header, + } + return h, nil +} + +func (s *IntegrationTestSuite) TestGetBalance() { + require := s.Require() + expectedBal := sdk.NewCoin(app.BondDenom, sdk.NewInt(int64(99999999999999999))) + for _, acc := range s.accounts { + bal, err := s.accessor.BalanceForAddress(context.Background(), s.getAddress(acc)) + require.NoError(err) + require.Equal(&expectedBal, bal) + } +} From f518fc4e83eb0771b64b8cf74148c751371673d0 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 15 Dec 2022 12:59:35 +0100 Subject: [PATCH 0242/1008] chore(markdownlint): disable 'Multiple headings with the same content' rule (#1480) --- .markdownlint.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 21909e1ab4..b4b33c34d7 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,3 +1,4 @@ "default": true # Default state for all rules "MD013": false # Disable rule for line length "MD033": false # Disable rule banning inline HTML +"MD024": false # Disable "Multiple headings with the same content" rule From 3a58679ed84da966d01173f32780134c7b830594 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 15 Dec 2022 15:29:59 +0100 Subject: [PATCH 0243/1008] feat(nodebuilder/p2p): Enable `InfiniteLimits` for resource manager if bootstrapper (#1496) Resolves #1494 --- nodebuilder/p2p/config.go | 5 ++--- nodebuilder/p2p/module.go | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 991eb0b046..59fc6a6e65 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -20,9 +20,8 @@ type Config struct { // announced/advertised, as undialable from WAN NoAnnounceAddresses []string // TODO(@Wondertan): This should be a build-time parameter. See - // https://github.com/celestiaorg/celestia-node/issues/63 Bootstrapper is flag telling this node is - - // a bootstrapper. + // https://github.com/celestiaorg/celestia-node/issues/63 + // Bootstrapper is flag telling this node is a bootstrapper. Bootstrapper bool // MutualPeers are peers which have a bidirectional peering agreement with the configured node. // Connections with those peers are protected from being trimmed, dropped or negatively scored. diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 524e4d44b7..ecaec7d53c 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -34,7 +34,10 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(ContentRouting), fx.Provide(AddrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), fx.Provide(metrics.NewBandwidthCounter), - fx.Provide(func() (network.ResourceManager, error) { + fx.Provide(func(cfg Config) (network.ResourceManager, error) { + if cfg.Bootstrapper { + return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) + } return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale())) }), fx.Provide(newModule), From a7c591d3a69b7eb32ba009a1b304a2d6594aaa44 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 16 Dec 2022 15:25:10 +0100 Subject: [PATCH 0244/1008] chore(all): lint all the comment with cfmt and add cfmt to lint rules (#1501) --- Makefile | 1 + header/p2p/exchange.go | 4 ++-- header/p2p/helpers.go | 6 +++--- header/p2p/session.go | 7 ++++--- nodebuilder/header/constructors.go | 4 ++-- nodebuilder/node/admin.go | 3 ++- nodebuilder/node/mocks/api.go | 3 ++- nodebuilder/node/type.go | 3 ++- nodebuilder/p2p/p2p.go | 3 ++- nodebuilder/share/mocks/api.go | 3 ++- nodebuilder/share/share.go | 3 ++- nodebuilder/state/mocks/api.go | 3 ++- share/ipld/proof.go | 3 ++- 13 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 475589c294..e18e9f6325 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,7 @@ lint: @echo "--> Running linter" @golangci-lint run @markdownlint --config .markdownlint.yaml '**/*.md' + @cfmt -m=100 ./... .PHONY: lint ## test-unit: Running unit tests diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 73e8c87e98..964d1f26df 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -161,8 +161,8 @@ func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRequest) } -// GetVerifiedRange performs a request for the given range of ExtendedHeaders to the network and ensures -// that returned headers are correct against the passed one. +// GetVerifiedRange performs a request for the given range of ExtendedHeaders to the network and +// ensures that returned headers are correct against the passed one. func (ex *Exchange) GetVerifiedRange( ctx context.Context, from *header.ExtendedHeader, diff --git a/header/p2p/helpers.go b/header/p2p/helpers.go index 93f328eb93..50ee4f4008 100644 --- a/header/p2p/helpers.go +++ b/header/p2p/helpers.go @@ -20,9 +20,9 @@ func protocolID(protocolSuffix string) protocol.ID { return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) } -// sendMessage opens the stream to the given peers and sends ExtendedHeaderRequest to fetch ExtendedHeaders. -// As a result sendMessage returns ExtendedHeaderResponse, the size of fetched data, -// the duration of the request and an error. +// sendMessage opens the stream to the given peers and sends ExtendedHeaderRequest to fetch +// ExtendedHeaders. As a result sendMessage returns ExtendedHeaderResponse, the size of fetched +// data, the duration of the request and an error. func sendMessage( ctx context.Context, host host.Host, diff --git a/header/p2p/session.go b/header/p2p/session.go index 73dc644b14..976b17683d 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -101,7 +101,8 @@ func (s *session) handleOutgoingRequests(ctx context.Context, result chan []*hea } } -// doRequest chooses the best peer to fetch headers and sends a request in range of available maxRetryAttempts. +// doRequest chooses the best peer to fetch headers and sends a request in range of available +// maxRetryAttempts. func (s *session) doRequest( ctx context.Context, stat *peerStat, @@ -129,8 +130,8 @@ func (s *session) doRequest( return } log.Debugw("request headers from peer succeed ", "from", s.host.ID(), "pID", stat.peerID, "amount", req.Amount) - // send headers to the channel, update peer stats and return peer to the queue, so it can be re-used in case - // if there are other requests awaiting + // send headers to the channel, update peer stats and return peer to the queue, so it can be + // re-used in case if there are other requests awaiting headers <- h stat.updateStats(size, duration) s.queue.push(stat) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 6c7ddfd6e6..9c3713b2bc 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -93,8 +93,8 @@ func newInitStore( err = store.Init(ctx, s, ex, trustedHash) if err != nil { // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. - // This is due to requesting step of initialization, which fetches initial Header by trusted hash from - // the network. The step can't be done during unit tests and fixing it would require either + // This is due to requesting step of initialization, which fetches initial Header by trusted hash + // from the network. The step can't be done during unit tests and fixing it would require either // * Having some test/dev/offline mode for Node that mocks out all the networking // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step // * Or removing explicit initialization in favor of automated initialization by Syncer diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index d6339b2ef1..49d2d77341 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -28,7 +28,8 @@ type Info struct { func (m *module) Info(context.Context) (Info, error) { return Info{ Type: m.tp, - // TODO @renaynay @distractedm1nd: Implement versioning in API and way to extract that into this struct + // TODO @renaynay @distractedm1nd: Implement versioning in API and way to extract that into this + // struct }, nil } diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index 98df713429..39d500bc04 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - node "github.com/celestiaorg/celestia-node/nodebuilder/node" auth "github.com/filecoin-project/go-jsonrpc/auth" gomock "github.com/golang/mock/gomock" + + node "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/node/type.go b/nodebuilder/node/type.go index 7af1c56b7a..2f09b26503 100644 --- a/nodebuilder/node/type.go +++ b/nodebuilder/node/type.go @@ -4,7 +4,8 @@ package node // The zero value for Type is invalid. type Type uint8 -// StorePath is an alias used in order to pass the base path of the node store to nodebuilder modules. +// StorePath is an alias used in order to pass the base path of the node store to nodebuilder +// modules. type StorePath string const ( diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 5421f12da1..7728e36c01 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -67,7 +67,8 @@ type Module interface { // BandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID. // The metrics returned include all traffic sent / received for the peer, regardless of protocol. BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats - // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given protocol.ID. + // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given + // protocol.ID. BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats // ResourceState returns the state of the resource manager. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index d7dca8601e..e2f30be425 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" namespace "github.com/celestiaorg/nmt/namespace" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index 26e8463455..9ada1d1a62 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -28,7 +28,8 @@ var _ Module = (*API)(nil) // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on the Network. + // SharesAvailable subjectively validates if Shares committed to the given Root are available on + // the Network. SharesAvailable(context.Context, *share.Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index f3017e4a2b..50996edb23 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,11 +9,12 @@ import ( reflect "reflect" math "cosmossdk.io/math" - namespace "github.com/celestiaorg/nmt/namespace" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" + + namespace "github.com/celestiaorg/nmt/namespace" ) // MockModule is a mock of Module interface. diff --git a/share/ipld/proof.go b/share/ipld/proof.go index 7dae321240..22aba35ebb 100644 --- a/share/ipld/proof.go +++ b/share/ipld/proof.go @@ -12,7 +12,8 @@ type Proof struct { Start, End int } -// proofCollector collects proof nodes' CIDs for the construction of a shares inclusion validation nmt.Proof. +// proofCollector collects proof nodes' CIDs for the construction of a shares inclusion validation +// nmt.Proof. type proofCollector struct { left, right []cid.Cid } From e3062d4d5599a6b7d0754cf1b39d37387bffb101 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 19 Dec 2022 10:26:34 +0100 Subject: [PATCH 0245/1008] fix(node/p2p)!: remove resource manager and it's API (#1500) --- go.mod | 2 +- nodebuilder/p2p/host.go | 7 +++---- nodebuilder/p2p/mocks/api.go | 16 ---------------- nodebuilder/p2p/module.go | 8 -------- nodebuilder/p2p/p2p.go | 21 --------------------- nodebuilder/p2p/p2p_test.go | 27 ++++++--------------------- 6 files changed, 10 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index 2962e3c617..4831fe74f5 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,6 @@ require ( github.com/libp2p/go-libp2p-peerstore v0.7.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 github.com/libp2p/go-libp2p-record v0.1.3 - github.com/libp2p/go-libp2p-resource-manager v0.5.1 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 @@ -211,6 +210,7 @@ require ( github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect github.com/libp2p/go-libp2p-loggables v0.1.0 // indirect + github.com/libp2p/go-libp2p-resource-manager v0.5.1 // indirect github.com/libp2p/go-msgio v0.2.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-netroute v0.2.0 // indirect diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index 3c4f4bda15..c8654b75b4 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -9,7 +9,6 @@ import ( "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/metrics" - "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" "github.com/libp2p/go-libp2p-core/routing" @@ -28,7 +27,7 @@ func RoutedHost(base HostBase, r routing.PeerRouting) host.Host { } // Host returns constructor for Host. -func Host(cfg Config, params hostParams, bw *metrics.BandwidthCounter, rm network.ResourceManager) (HostBase, error) { +func Host(cfg Config, params hostParams) (HostBase, error) { opts := []libp2p.Option{ libp2p.NoListenAddrs, // do not listen automatically libp2p.AddrsFactory(params.AddrF), @@ -39,8 +38,7 @@ func Host(cfg Config, params hostParams, bw *metrics.BandwidthCounter, rm networ libp2p.UserAgent(fmt.Sprintf("celestia-%s", params.Net)), libp2p.NATPortMap(), // enables upnp libp2p.DisableRelay(), - libp2p.BandwidthReporter(bw), - libp2p.ResourceManager(rm), + libp2p.BandwidthReporter(params.Bandwidth), // to clearly define what defaults we rely upon libp2p.DefaultSecurity, libp2p.DefaultTransports, @@ -77,6 +75,7 @@ type hostParams struct { PStore peerstore.Peerstore ConnMngr connmgr.ConnManager ConnGater *conngater.BasicConnectionGater + Bandwidth *metrics.BandwidthCounter Tp node.Type } diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go index bf5f7a0a5d..62f9855ba0 100644 --- a/nodebuilder/p2p/mocks/api.go +++ b/nodebuilder/p2p/mocks/api.go @@ -13,7 +13,6 @@ import ( network "github.com/libp2p/go-libp2p-core/network" peer "github.com/libp2p/go-libp2p-core/peer" protocol "github.com/libp2p/go-libp2p-core/protocol" - rcmgr "github.com/libp2p/go-libp2p-resource-manager" ) // MockModule is a mock of Module interface. @@ -248,21 +247,6 @@ func (mr *MockModuleMockRecorder) PubSubPeers(arg0, arg1 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubSubPeers", reflect.TypeOf((*MockModule)(nil).PubSubPeers), arg0, arg1) } -// ResourceState mocks base method. -func (m *MockModule) ResourceState(arg0 context.Context) (rcmgr.ResourceManagerStat, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResourceState", arg0) - ret0, _ := ret[0].(rcmgr.ResourceManagerStat) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ResourceState indicates an expected call of ResourceState. -func (mr *MockModuleMockRecorder) ResourceState(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceState", reflect.TypeOf((*MockModule)(nil).ResourceState), arg0) -} - // UnblockPeer mocks base method. func (m *MockModule) UnblockPeer(arg0 context.Context, arg1 peer.ID) error { m.ctrl.T.Helper() diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index ecaec7d53c..19f2e4164c 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -3,8 +3,6 @@ package p2p import ( logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/metrics" - "github.com/libp2p/go-libp2p-core/network" - rcmgr "github.com/libp2p/go-libp2p-resource-manager" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -34,12 +32,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(ContentRouting), fx.Provide(AddrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), fx.Provide(metrics.NewBandwidthCounter), - fx.Provide(func(cfg Config) (network.ResourceManager, error) { - if cfg.Bootstrapper { - return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) - } - return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale())) - }), fx.Provide(newModule), fx.Invoke(Listen(cfg.ListenAddresses)), ) diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 7728e36c01..03b6c8c8f6 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -11,7 +11,6 @@ import ( "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" - rcmgr "github.com/libp2p/go-libp2p-resource-manager" basichost "github.com/libp2p/go-libp2p/p2p/host/basic" "github.com/libp2p/go-libp2p/p2p/net/conngater" ) @@ -71,9 +70,6 @@ type Module interface { // protocol.ID. BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats - // ResourceState returns the state of the resource manager. - ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) - // PubSubPeers returns the peer IDs of the peers joined on // the given topic. PubSubPeers(ctx context.Context, topic string) []peer.ID @@ -86,7 +82,6 @@ type module struct { ps *pubsub.PubSub connGater *conngater.BasicConnectionGater bw *metrics.BandwidthCounter - rm network.ResourceManager } func newModule( @@ -94,14 +89,12 @@ func newModule( ps *pubsub.PubSub, cg *conngater.BasicConnectionGater, bw *metrics.BandwidthCounter, - rm network.ResourceManager, ) Module { return &module{ host: host, ps: ps, connGater: cg, bw: bw, - rm: rm, } } @@ -174,15 +167,6 @@ func (m *module) BandwidthForProtocol(_ context.Context, proto protocol.ID) metr return m.bw.GetBandwidthForProtocol(proto) } -func (m *module) ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) { - rms, ok := m.rm.(rcmgr.ResourceManagerState) - if !ok { - return rcmgr.ResourceManagerStat{}, fmt.Errorf("network.ResourceManager does not implement " + - "rcmgr.ResourceManagerState") - } - return rms.Stat(), nil -} - func (m *module) PubSubPeers(_ context.Context, topic string) []peer.ID { return m.ps.ListPeers(topic) } @@ -209,7 +193,6 @@ type API struct { BandwidthStats func(context.Context) metrics.Stats `perm:"admin"` BandwidthForPeer func(ctx context.Context, id peer.ID) metrics.Stats `perm:"admin"` BandwidthForProtocol func(ctx context.Context, proto protocol.ID) metrics.Stats `perm:"admin"` - ResourceState func(context.Context) (rcmgr.ResourceManagerStat, error) `perm:"admin"` PubSubPeers func(ctx context.Context, topic string) []peer.ID `perm:"admin"` } } @@ -278,10 +261,6 @@ func (api *API) BandwidthForProtocol(ctx context.Context, proto protocol.ID) met return api.Internal.BandwidthForProtocol(ctx, proto) } -func (api *API) ResourceState(ctx context.Context) (rcmgr.ResourceManagerStat, error) { - return api.Internal.ResourceState(ctx) -} - func (api *API) PubSubPeers(ctx context.Context, topic string) []peer.ID { return api.Internal.PubSubPeers(ctx, topic) } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index a9b5990177..b5730799de 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -14,7 +14,6 @@ import ( libpeer "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" - rcmgr "github.com/libp2p/go-libp2p-resource-manager" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +26,7 @@ func TestP2PModule_Host(t *testing.T) { require.NoError(t, err) host, peer := net.Hosts()[0], net.Hosts()[1] - mgr := newModule(host, nil, nil, nil, nil) + mgr := newModule(host, nil, nil, nil) ctx := context.Background() @@ -53,7 +52,7 @@ func TestP2PModule_ConnManager(t *testing.T) { peer, err := libp2p.New() require.NoError(t, err) - mgr := newModule(host, nil, nil, nil, nil) + mgr := newModule(host, nil, nil, nil) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -73,7 +72,7 @@ func TestP2PModule_Autonat(t *testing.T) { host, err := libp2p.New(libp2p.EnableNATService()) require.NoError(t, err) - mgr := newModule(host, nil, nil, nil, nil) + mgr := newModule(host, nil, nil, nil) status, err := mgr.NATStatus(context.Background()) assert.NoError(t, err) @@ -105,7 +104,7 @@ func TestP2PModule_Bandwidth(t *testing.T) { require.NoError(t, err) }) - mgr := newModule(host, nil, nil, bw, nil) + mgr := newModule(host, nil, nil, bw) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -160,7 +159,7 @@ func TestP2PModule_Pubsub(t *testing.T) { gs, err := pubsub.NewGossipSub(ctx, host) require.NoError(t, err) - mgr := newModule(host, gs, nil, nil, nil) + mgr := newModule(host, gs, nil, nil) topicStr := "test-topic" @@ -194,7 +193,7 @@ func TestP2PModule_ConnGater(t *testing.T) { gater, err := ConnectionGater(datastore.NewMapDatastore()) require.NoError(t, err) - mgr := newModule(nil, nil, gater, nil, nil) + mgr := newModule(nil, nil, gater, nil) ctx := context.Background() @@ -203,17 +202,3 @@ func TestP2PModule_ConnGater(t *testing.T) { assert.NoError(t, mgr.UnblockPeer(ctx, "badpeer")) assert.Len(t, mgr.ListBlockedPeers(ctx), 0) } - -// TestP2PModule_ResourceManager tests P2P Module methods on -// the ResourceManager. -func TestP2PModule_ResourceManager(t *testing.T) { - rm, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale())) - require.NoError(t, err) - - mgr := newModule(nil, nil, nil, nil, rm) - - state, err := mgr.ResourceState(context.Background()) - require.NoError(t, err) - - assert.NotNil(t, state) -} From d57e493dc7e068d155a35466786e0d9b07e69511 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 19 Dec 2022 12:48:36 +0200 Subject: [PATCH 0246/1008] fix: return a struct instead of a pointer in params creation (#1507) ## Overview Return a struct instead of a pointer to a struct in params creation ## Checklist - [x] New and updated code has appropriate documentation - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates --- header/p2p/exchange.go | 4 ++-- header/p2p/options.go | 8 ++++---- header/p2p/server.go | 4 ++-- header/store/options.go | 4 ++-- header/store/store.go | 4 ++-- nodebuilder/header/config.go | 22 +++++++++++++--------- nodebuilder/header/module.go | 2 +- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 964d1f26df..f5c4a7b5df 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -35,7 +35,7 @@ type Exchange struct { trustedPeers peer.IDSlice peerTracker *peerTracker - Params *ClientParameters + Params ClientParameters } func NewExchange( @@ -46,7 +46,7 @@ func NewExchange( ) (*Exchange, error) { params := DefaultClientParameters() for _, opt := range opts { - opt(params) + opt(¶ms) } err := params.Validate() diff --git a/header/p2p/options.go b/header/p2p/options.go index 0ccf7491fa..3b6d4634a1 100644 --- a/header/p2p/options.go +++ b/header/p2p/options.go @@ -26,8 +26,8 @@ type ServerParameters struct { } // DefaultServerParameters returns the default params to configure the store. -func DefaultServerParameters() *ServerParameters { - return &ServerParameters{ +func DefaultServerParameters() ServerParameters { + return ServerParameters{ WriteDeadline: time.Second * 5, ReadDeadline: time.Minute, MaxRequestSize: 512, @@ -101,8 +101,8 @@ type ClientParameters struct { } // DefaultClientParameters returns the default params to configure the store. -func DefaultClientParameters() *ClientParameters { - return &ClientParameters{ +func DefaultClientParameters() ClientParameters { + return ClientParameters{ MinResponses: 2, MaxRequestSize: 512, MaxHeadersPerRequest: 64, diff --git a/header/p2p/server.go b/header/p2p/server.go index a4b08eabd7..fbd7d8b119 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -26,7 +26,7 @@ type ExchangeServer struct { ctx context.Context cancel context.CancelFunc - Params *ServerParameters + Params ServerParameters } // NewExchangeServer returns a new P2P server that handles inbound @@ -39,7 +39,7 @@ func NewExchangeServer( ) (*ExchangeServer, error) { params := DefaultServerParameters() for _, opt := range opts { - opt(params) + opt(¶ms) } if err := params.Validate(); err != nil { return nil, err diff --git a/header/store/options.go b/header/store/options.go index d9c3246665..0f968f2f4d 100644 --- a/header/store/options.go +++ b/header/store/options.go @@ -22,8 +22,8 @@ type Parameters struct { } // DefaultParameters returns the default params to configure the store. -func DefaultParameters() *Parameters { - return &Parameters{ +func DefaultParameters() Parameters { + return Parameters{ StoreCacheSize: 4096, IndexCacheSize: 16384, WriteBatchSize: 2048, diff --git a/header/store/store.go b/header/store/store.go index 42be50e955..b3859c56b4 100644 --- a/header/store/store.go +++ b/header/store/store.go @@ -51,7 +51,7 @@ type Store struct { // pending keeps headers pending to be written in one batch pending *batch - Params *Parameters + Params Parameters } // NewStore constructs a Store over datastore. @@ -79,7 +79,7 @@ func NewStoreWithHead( func newStore(ds datastore.Batching, opts ...Option) (*Store, error) { params := DefaultParameters() for _, opt := range opts { - opt(params) + opt(¶ms) } if err := params.Validate(); err != nil { diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index e0dd16deaa..a04a39607e 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -24,10 +24,10 @@ type Config struct { // headers at any moment. TrustedPeers []string - Store *store.Parameters + Store store.Parameters - Server *p2p_exchange.ServerParameters - Client *p2p_exchange.ClientParameters `toml:",omitempty"` + Server p2p_exchange.ServerParameters + Client p2p_exchange.ClientParameters `toml:",omitempty"` } func DefaultConfig(tp node.Type) Config { @@ -82,7 +82,7 @@ func (cfg *Config) trustedHash(net p2p.Network) (tmbytes.HexBytes, error) { } // Validate performs basic validation of the config. -func (cfg *Config) Validate() error { +func (cfg *Config) Validate(tp node.Type) error { err := cfg.Store.Validate() if err != nil { return fmt.Errorf("module/header: misconfiguration of store: %w", err) @@ -93,11 +93,15 @@ func (cfg *Config) Validate() error { return fmt.Errorf("module/header: misconfiguration of p2p exchange server: %w", err) } - if cfg.Client != nil { - err = cfg.Client.Validate() - if err != nil { - return fmt.Errorf("module/header: misconfiguration of p2p exchange client: %w", err) - } + // we do not create a client for bridge nodes + if tp == node.Bridge { + return nil + } + + err = cfg.Client.Validate() + if err != nil { + return fmt.Errorf("module/header: misconfiguration of p2p exchange client: %w", err) } + return nil } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index ae596d6139..052817e052 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -21,7 +21,7 @@ var log = logging.Logger("module/header") func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module - cfgErr := cfg.Validate() + cfgErr := cfg.Validate(tp) baseComponents := fx.Options( fx.Supply(*cfg), From d96f79e12ae95415bd908c342cc7fd2228bd33bc Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 20 Dec 2022 08:27:29 +0100 Subject: [PATCH 0247/1008] fix(eds): WriteEDS thread safety for concurrent writingSessions (#1498) --- share/eds/eds.go | 34 +++++++++++++++-------------- share/ipld/namespace_hasher_test.go | 8 +++---- share/ipld/nmt.go | 12 +++++----- share/ipld/test_helpers.go | 2 +- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/share/eds/eds.go b/share/eds/eds.go index 236d5f6528..8b92d5fc74 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -16,6 +16,7 @@ import ( format "github.com/ipfs/go-ipld-format" "github.com/ipld/go-car" "github.com/ipld/go-car/util" + "github.com/minio/sha256-simd" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" @@ -33,11 +34,10 @@ var ErrEmptySquare = errors.New("share: importing empty data") // writingSession contains the components needed to write an EDS to a CARv1 file with our custom // node order. type writingSession struct { - eds *rsmt2d.ExtendedDataSquare - // store is an in-memory blockstore, used to cache the inner nodes (proofs) while we walk the nmt - // tree. - store bstore.Blockstore - w io.Writer + eds *rsmt2d.ExtendedDataSquare + store bstore.Blockstore // caches inner nodes (proofs) while we walk the nmt tree. + hasher *nmt.Hasher + w io.Writer } // WriteEDS writes the entire EDS into the given io.Writer as CARv1 file. @@ -106,9 +106,10 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. } return &writingSession{ - eds: eds, - store: store, - w: w, + eds: eds, + store: store, + hasher: nmt.NewNmtHasher(sha256.New(), ipld.NamespaceSize, ipld.NMTIgnoreMaxNamespace), + w: w, }, nil } @@ -129,7 +130,7 @@ func (w *writingSession) writeHeader() error { func (w *writingSession) writeQuadrants() error { shares := quadrantOrder(w.eds) for _, share := range shares { - cid, err := ipld.CidFromNamespacedSha256(nmt.Sha256Namespace8FlaggedLeaf(share)) + cid, err := ipld.CidFromNamespacedSha256(w.hasher.HashLeaf(share)) if err != nil { return fmt.Errorf("getting cid from share: %w", err) } @@ -151,15 +152,18 @@ func (w *writingSession) writeProofs(ctx context.Context) error { return fmt.Errorf("getting all keys from the blockstore: %w", err) } for proofCid := range proofs { - node, err := w.store.Get(ctx, proofCid) + block, err := w.store.Get(ctx, proofCid) if err != nil { return fmt.Errorf("getting proof from the blockstore: %w", err) } - cid, err := ipld.CidFromNamespacedSha256(nmt.Sha256Namespace8FlaggedInner(node.RawData())) + + node := block.RawData() + left, right := node[:ipld.NmtHashSize], node[ipld.NmtHashSize:] + cid, err := ipld.CidFromNamespacedSha256(w.hasher.HashNode(left, right)) if err != nil { return fmt.Errorf("getting cid: %w", err) } - err = util.LdWrite(w.w, cid.Bytes(), node.RawData()) + err = util.LdWrite(w.w, cid.Bytes(), node) if err != nil { return fmt.Errorf("writing proof to the car: %w", err) } @@ -201,11 +205,10 @@ func getQuadrantCells(eds *rsmt2d.ExtendedDataSquare, i, j uint) [][]byte { // prependNamespace adds the namespace to the passed share if in the first quadrant, // otherwise it adds the ParitySharesNamespace to the beginning. -// TODO(@walldiss): this method will be obselete once the redundant namespace is removed func prependNamespace(quadrant int, share []byte) []byte { switch quadrant { case 0: - return append(share[:appconsts.NamespaceSize], share...) + return append(share[:ipld.NamespaceSize], share...) case 1, 2, 3: return append(appconsts.ParitySharesNamespaceID, share...) default: @@ -250,8 +253,7 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (*rsmt2d.Ext } // the stored first quadrant shares are wrapped with the namespace twice. // we cut it off here, because it is added again while importing to the tree below - // TODO(@walldiss): remove redundant namespace - shares[i] = block.RawData()[appconsts.NamespaceSize:] + shares[i] = block.RawData()[ipld.NamespaceSize:] } eds, err := rsmt2d.ComputeExtendedDataSquare( diff --git a/share/ipld/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go index 6bbabded29..a14c3fe573 100644 --- a/share/ipld/namespace_hasher_test.go +++ b/share/ipld/namespace_hasher_test.go @@ -10,7 +10,7 @@ import ( func TestNamespaceHasherWrite(t *testing.T) { leafSize := appconsts.ShareSize + appconsts.NamespaceSize - innerSize := nmtHashSize * 2 + innerSize := NmtHashSize * 2 tt := []struct { name string expectedSize int @@ -59,7 +59,7 @@ func TestNamespaceHasherWrite(t *testing.T) { func TestNamespaceHasherSum(t *testing.T) { leafSize := appconsts.ShareSize + appconsts.NamespaceSize - innerSize := nmtHashSize * 2 + innerSize := NmtHashSize * 2 tt := []struct { name string expectedSize int @@ -67,12 +67,12 @@ func TestNamespaceHasherSum(t *testing.T) { }{ { "Leaf", - nmtHashSize, + NmtHashSize, leafSize, }, { "Inner", - nmtHashSize, + NmtHashSize, innerSize, }, { diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 096a7f97f3..6124e5b9fe 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -42,11 +42,11 @@ const ( // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize - // nmtHashSize is the size of a digest created by an NMT in bytes. - nmtHashSize = 2*NamespaceSize + sha256.Size + // NmtHashSize is the size of a digest created by an NMT in bytes. + NmtHashSize = 2*NamespaceSize + sha256.Size // innerNodeSize is the size of data in inner nodes. - innerNodeSize = nmtHashSize * 2 + innerNodeSize = NmtHashSize * 2 // leafNodeSize is the size of data in leaf nodes. leafNodeSize = NamespaceSize + appconsts.ShareSize @@ -98,8 +98,8 @@ func (n nmtNode) Links() []*ipld.Link { default: panic(fmt.Sprintf("unexpected size %v", len(n.RawData()))) case innerNodeSize: - leftCid := MustCidFromNamespacedSha256(n.RawData()[:nmtHashSize]) - rightCid := MustCidFromNamespacedSha256(n.RawData()[nmtHashSize:]) + leftCid := MustCidFromNamespacedSha256(n.RawData()[:NmtHashSize]) + rightCid := MustCidFromNamespacedSha256(n.RawData()[NmtHashSize:]) return []*ipld.Link{{Cid: leftCid}, {Cid: rightCid}} case leafNodeSize: @@ -129,7 +129,7 @@ func (n nmtNode) Size() (uint64, error) { // CidFromNamespacedSha256 uses a hash from an nmt tree to create a CID func CidFromNamespacedSha256(namespacedHash []byte) (cid.Cid, error) { - if got, want := len(namespacedHash), nmtHashSize; got != want { + if got, want := len(namespacedHash), NmtHashSize; got != want { return cid.Cid{}, fmt.Errorf("invalid namespaced hash length, got: %v, want: %v", got, want) } buf, err := mh.Encode(namespacedHash, sha256Namespace8Flagged) diff --git a/share/ipld/test_helpers.go b/share/ipld/test_helpers.go index f586aecbd8..2f16d1529d 100644 --- a/share/ipld/test_helpers.go +++ b/share/ipld/test_helpers.go @@ -9,7 +9,7 @@ import ( ) func RandNamespacedCID(t *testing.T) cid.Cid { - raw := make([]byte, nmtHashSize) + raw := make([]byte, NmtHashSize) _, err := mrand.Read(raw) require.NoError(t, err) id, err := CidFromNamespacedSha256(raw) From 8b71eaf0e104516d646937310ba1646390fa86e5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 20 Dec 2022 11:49:24 +0100 Subject: [PATCH 0248/1008] feat(api/rpc): Implement auth middleware on `Server` (#1402) Related to #1187 **This PR is based on #1313** **It implements:** * providing a new JWT secret from the `nodebuilder/node` module to both the node module and the rpc server. * auth middleware for the `rpc.Server` that verifies the given token with the server's secret In a follow-up PR will come the ability to create an rpc client with elevated permissions (this branch restricts client access to read-only API methods). --- api/rpc/permissions/permissions.go | 15 +++++++++ api/rpc/server.go | 51 +++++++++++++++++++----------- go.mod | 1 + go.sum | 2 ++ nodebuilder/node/auth.go | 23 ++++++++++++++ nodebuilder/node/module.go | 4 ++- nodebuilder/rpc/constructors.go | 6 ++-- 7 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 api/rpc/permissions/permissions.go create mode 100644 nodebuilder/node/auth.go diff --git a/api/rpc/permissions/permissions.go b/api/rpc/permissions/permissions.go new file mode 100644 index 0000000000..8a703d2df7 --- /dev/null +++ b/api/rpc/permissions/permissions.go @@ -0,0 +1,15 @@ +package permissions + +import "github.com/filecoin-project/go-jsonrpc/auth" + +var ( + DefaultPerms = []auth.Permission{"public"} + ReadWritePerms = []auth.Permission{"public", "read", "write"} + AllPerms = []auth.Permission{"public", "read", "write", "admin"} +) + +// JWTPayload is a utility struct for marshaling/unmarshalling +// permissions into for token signing/verifying. +type JWTPayload struct { + Allow []auth.Permission +} diff --git a/api/rpc/server.go b/api/rpc/server.go index 684894162e..7f8a90e5de 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -2,51 +2,66 @@ package rpc import ( "context" + "encoding/json" "net" "net/http" "reflect" "sync/atomic" "time" + "github.com/cristalhq/jwt" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-jsonrpc/auth" logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-node/api/rpc/permissions" ) var log = logging.Logger("rpc") -var ( - AllPerms = []auth.Permission{"public", "read", "write", "admin"} - DefaultPerms = []auth.Permission{"public"} -) - type Server struct { srv *http.Server rpc *jsonrpc.RPCServer listener net.Listener started atomic.Bool + + auth jwt.Signer } -func NewServer(address, port string) *Server { +func NewServer(address, port string, secret jwt.Signer) *Server { rpc := jsonrpc.NewServer() - authHandler := &auth.Handler{ - Verify: func(ctx context.Context, token string) ([]auth.Permission, error) { - // TODO(distractedm1nd/renaynay): implement auth - log.Warn("auth not implemented, token: ", token) - return DefaultPerms, nil - }, - Next: rpc.ServeHTTP, - } - return &Server{ + srv := &Server{ rpc: rpc, srv: &http.Server{ - Addr: address + ":" + port, - Handler: authHandler, + Addr: address + ":" + port, // the amount of time allowed to read request headers. set to the default 2 seconds ReadHeaderTimeout: 2 * time.Second, }, + auth: secret, + } + srv.srv.Handler = &auth.Handler{ + Verify: srv.verifyAuth, + Next: rpc.ServeHTTP, + } + return srv +} + +// verifyAuth is the RPC server's auth middleware. This middleware is only +// reached if a token is provided in the header of the request, otherwise only +// methods with `read` permissions are accessible. +func (s *Server) verifyAuth(_ context.Context, token string) ([]auth.Permission, error) { + tk, err := jwt.ParseAndVerifyString(token, s.auth) + if err != nil { + return nil, err + } + p := new(permissions.JWTPayload) + err = json.Unmarshal(tk.RawClaims(), p) + if err != nil { + return nil, err } + // check permissions + return p.Allow, nil } // RegisterService registers a service onto the RPC server. All methods on the service will then be @@ -58,7 +73,7 @@ func (s *Server) RegisterService(namespace string, service interface{}) { // RegisterAuthedService registers a service onto the RPC server. All methods on the service will // then be exposed over the RPC. func (s *Server) RegisterAuthedService(namespace string, service interface{}, out interface{}) { - auth.PermissionedProxy(AllPerms, DefaultPerms, service, getInternalStruct(out)) + auth.PermissionedProxy(permissions.AllPerms, permissions.DefaultPerms, service, getInternalStruct(out)) s.RegisterService(namespace, out) } diff --git a/go.mod b/go.mod index 4831fe74f5..a13b08ccfa 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/celestiaorg/rsmt2d v0.7.0 github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/cosmos-sdk/api v0.1.0 + github.com/cristalhq/jwt v1.2.0 github.com/dgraph-io/badger/v2 v2.2007.4 github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/dagstore v0.5.6 diff --git a/go.sum b/go.sum index 28c2c74b77..0787e35556 100644 --- a/go.sum +++ b/go.sum @@ -330,6 +330,8 @@ github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6V github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cristalhq/jwt v1.2.0 h1:fHmMkFJvEbS4o04aQP8BmtJg7fqkYvd7r8er3sUdS4Q= +github.com/cristalhq/jwt v1.2.0/go.mod h1:QQFazsDzoqeucUEEV0h16uPTZXBAi2SVA8cQ9JEDuFw= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= diff --git a/nodebuilder/node/auth.go b/nodebuilder/node/auth.go new file mode 100644 index 0000000000..abfe214012 --- /dev/null +++ b/nodebuilder/node/auth.go @@ -0,0 +1,23 @@ +package node + +import ( + "crypto/rand" + "io" + + "github.com/cristalhq/jwt" +) + +// secret returns the node's JWT secret if it exists, or generates +// and saves a new one if it does not. +// +// TODO @renaynay: +// 1. implement checking for existing key +// 2. implement saving the generated key to disk +// (if the secret needs to be generated) +func secret() (jwt.Signer, error) { + sk, err := io.ReadAll(io.LimitReader(rand.Reader, 32)) + if err != nil { + return nil, err + } + return jwt.NewHS256(sk) +} diff --git a/nodebuilder/node/module.go b/nodebuilder/node/module.go index 0b7aff337f..e7ae88182e 100644 --- a/nodebuilder/node/module.go +++ b/nodebuilder/node/module.go @@ -1,14 +1,16 @@ package node import ( + "github.com/cristalhq/jwt" "go.uber.org/fx" ) func ConstructModule(tp Type) fx.Option { return fx.Module( "node", - fx.Provide(func() Module { + fx.Provide(func(secret jwt.Signer) Module { return newModule(tp) }), + fx.Provide(secret), ) } diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index f52549f487..97518b64f3 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -1,6 +1,8 @@ package rpc import ( + "github.com/cristalhq/jwt" + "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -31,6 +33,6 @@ func RegisterEndpoints( serv.RegisterAuthedService("node", nodeMod, &node.API{}) } -func Server(cfg *Config) *rpc.Server { - return rpc.NewServer(cfg.Address, cfg.Port) +func Server(cfg *Config, auth jwt.Signer) *rpc.Server { + return rpc.NewServer(cfg.Address, cfg.Port, auth) } From 0c2669574fcbc8478cf9572ae5d36c98901916e8 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 20 Dec 2022 14:52:48 +0100 Subject: [PATCH 0249/1008] feat(share): Ensure empty CAR exists in eds.Store (#1409) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Closes https://github.com/celestiaorg/celestia-node/issues/1113 --- nodebuilder/share/constructors.go | 16 +++++++++++ nodebuilder/share/module.go | 15 +++++++---- nodebuilder/share/share_test.go | 44 +++++++++++++++++++++++++++++++ share/empty.go | 33 ++++++++++++++++++----- 4 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 nodebuilder/share/share_test.go diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index f53ecf24ed..87522d4ba5 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -2,7 +2,9 @@ package share import ( "context" + "errors" + "github.com/filecoin-project/dagstore" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p-core/host" @@ -10,9 +12,11 @@ import ( routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" "go.uber.org/fx" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/service" ) @@ -52,3 +56,15 @@ func newModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Ava }) return &module{serv} } + +// ensureEmptyCARExists adds an empty EDS to the provided EDS store. +func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { + emptyEDS := share.EmptyExtendedDataSquare() + emptyDAH := da.NewDataAvailabilityHeader(emptyEDS) + + err := store.Put(ctx, emptyDAH.Hash(), emptyEDS) + if errors.Is(err, dagstore.ErrShardExists) { + return nil + } + return err +} diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index bfa6961732..58a692095a 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -21,9 +21,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Supply(*cfg), fx.Error(cfgErr), fx.Options(options...), - fx.Invoke(share.EnsureEmptySquareExists), fx.Provide(discovery(*cfg)), fx.Provide(newModule), + fx.Invoke(share.EnsureEmptySquareExists), ) switch tp { @@ -52,11 +52,16 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { return eds.NewStore(string(path), ds) }, - fx.OnStart(func(ctx context.Context, eds *eds.Store) error { - return eds.Start(ctx) + fx.OnStart(func(ctx context.Context, store *eds.Store) error { + err := store.Start(ctx) + if err != nil { + return err + } + + return ensureEmptyCARExists(ctx, store) }), - fx.OnStop(func(ctx context.Context, eds *eds.Store) error { - return eds.Stop(ctx) + fx.OnStop(func(ctx context.Context, store *eds.Store) error { + return store.Stop(ctx) }), )), fx.Provide(fx.Annotate( diff --git a/nodebuilder/share/share_test.go b/nodebuilder/share/share_test.go new file mode 100644 index 0000000000..4fd6eb9365 --- /dev/null +++ b/nodebuilder/share/share_test.go @@ -0,0 +1,44 @@ +package share + +import ( + "context" + "testing" + + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" +) + +func Test_EmptyCARExists(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds := share.EmptyExtendedDataSquare() + dah := da.NewDataAvailabilityHeader(eds) + + // add empty EDS to store + err = ensureEmptyCARExists(ctx, edsStore) + assert.NoError(t, err) + + // assert that the empty car exists + has, err := edsStore.Has(ctx, dah.Hash()) + assert.True(t, has) + assert.NoError(t, err) + + // assert that the empty car is, in fact, empty + emptyEds, err := edsStore.Get(ctx, dah.Hash()) + assert.Equal(t, eds.Flattened(), emptyEds.Flattened()) + assert.NoError(t, err) +} diff --git a/share/empty.go b/share/empty.go index 08c3f3f278..c6707b3225 100644 --- a/share/empty.go +++ b/share/empty.go @@ -3,24 +3,33 @@ package share import ( "bytes" "context" + "fmt" + "math" "github.com/ipfs/go-blockservice" "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" ) // EnsureEmptySquareExists checks if the given DAG contains an empty block data square. // If it does not, it stores an empty block. This optimization exists to prevent // redundant storing of empty block data so that it is only stored once and returned // upon request for a block with an empty data square. Ref: header/constructors.go#L56 -func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockService) error { - shares := make([][]byte, appconsts.MinShareCount) - for i := 0; i < appconsts.MinShareCount; i++ { - shares[i] = tailPaddingShare - } +func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockService) (*rsmt2d.ExtendedDataSquare, error) { + return AddShares(ctx, emptyDataSquare(), bServ) +} - _, err := AddShares(ctx, shares, bServ) - return err +// EmptyExtendedDataSquare returns the EDS of the empty block data square. +func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { + shares := emptyDataSquare() + squareSize := uint64(math.Sqrt(float64(appconsts.MinSquareSize))) + eds, err := da.ExtendShares(squareSize, shares) + if err != nil { + panic(fmt.Errorf("failed to create empty EDS: %w", err)) + } + return eds } // tail is filler for all tail padded shares @@ -29,3 +38,13 @@ var tailPaddingShare = append( append(make([]byte, 0, appconsts.ShareSize), appconsts.TailPaddingNamespaceID...), bytes.Repeat([]byte{0}, appconsts.ShareSize-appconsts.NamespaceSize)..., ) + +// emptyDataSquare returns the minimum size data square filled with tail padding. +func emptyDataSquare() [][]byte { + shares := make([][]byte, appconsts.MinShareCount) + for i := 0; i < appconsts.MinShareCount; i++ { + shares[i] = tailPaddingShare + } + + return shares +} From 1846a08bed6c3758a243ddde19e4f53dbe656b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Tue, 20 Dec 2022 17:03:48 +0100 Subject: [PATCH 0250/1008] fix: correct spacing of celestia banner (#1505) Fix https://github.com/celestiaorg/celestia-node/issues/1504 --- cmd/celestia/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index ada2f3d221..81baf23b34 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -45,7 +45,7 @@ func run() error { var rootCmd = &cobra.Command{ Use: "celestia [ bridge || full || light ] [subcommand]", Short: ` - ____ __ __ _ + ____ __ __ _ / ____/__ / /__ _____/ /_(_)___ _ / / / _ \/ / _ \/ ___/ __/ / __ / / /___/ __/ / __(__ ) /_/ / /_/ / From 05c7527668e2eb7d73788b755a5fdef10e6d6436 Mon Sep 17 00:00:00 2001 From: Hoyt Ren Date: Wed, 21 Dec 2022 03:52:34 +0800 Subject: [PATCH 0251/1008] github/CI: Fix over kill of '.gitignore'. (#1446) Closes https://github.com/celestiaorg/celestia-node/issues/1442 --- .gitignore | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f96f7a7abc..24c3851f21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build/ +/build/ *.bak *.iml *.log @@ -10,14 +10,13 @@ build/ *.coverprofile *.test *.orig -*/vendor vendor .DS_Store .bak .idea/ .vscode/ -celestia -cel-shed -cel-key +/celestia +/cel-shed +/cel-key coverage.txt go.work From ff95cf2b9c82c1e9ac0cd95fe3a3d35908039209 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 21 Dec 2022 08:32:37 +0100 Subject: [PATCH 0252/1008] improvement(cel-key): Network alias check for `--p2p.network` flag (#1482) Allows users to pass the network alias instead of the full network name to the `cel-key` utility as is done with the celestia binary. We allow networks other than the "valid" networks to be specified with the cel-key utility since it can be used for testing, but it will print a warning. --- cmd/cel-key/node_types.go | 9 +++++++-- nodebuilder/p2p/bootstrap.go | 4 +++- nodebuilder/p2p/flags.go | 10 +++------- nodebuilder/p2p/genesis.go | 4 +++- nodebuilder/p2p/network.go | 10 +++++++--- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 9c091ada4e..2ba3a91e8c 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -5,11 +5,10 @@ import ( "fmt" "strings" + sdkflags "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - sdkflags "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -43,9 +42,15 @@ func ParseDirectoryFlags(cmd *cobra.Command) error { } network := cmd.Flag(networkKey).Value.String() + if net, err := p2p.Network(network).Validate(); err == nil { + network = string(net) + } else { + fmt.Println("WARNING: unknown network specified: ", network) + } switch nodeType { case "bridge", "full", "light": keyPath := fmt.Sprintf("~/.celestia-%s-%s/keys", nodeType, strings.ToLower(network)) + fmt.Println("using directory: ", keyPath) if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, keyPath); err != nil { return err } diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 0a3585bca8..add6baed0b 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -17,7 +17,9 @@ func BootstrappersFor(net Network) (Bootstrappers, error) { // bootstrappersFor reports multiaddresses of bootstrap peers for a given network. func bootstrappersFor(net Network) ([]string, error) { - if err := net.Validate(); err != nil { + var err error + net, err = net.Validate() + if err != nil { return nil, err } diff --git a/nodebuilder/p2p/flags.go b/nodebuilder/p2p/flags.go index f7a3d9c463..0faf15e3ca 100644 --- a/nodebuilder/p2p/flags.go +++ b/nodebuilder/p2p/flags.go @@ -77,14 +77,10 @@ func ParseNetwork(cmd *cobra.Command) (Network, error) { } return envNetwork, err } - // check if user provided an alias - parsedNetwork, ok := networkAliases[parsed] - if ok { - return parsedNetwork, nil - } // check if user provided the actual network value - if err := Network(parsed).Validate(); err == nil { - return Network(parsed), nil + // or an alias + if net, err := Network(parsed).Validate(); err == nil { + return net, nil } return "", fmt.Errorf("invalid network specified: %s", parsed) } diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 9cb799b965..fd85d66da0 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -7,7 +7,9 @@ import ( // GenesisFor reports a hash of a genesis block for a given network. // Genesis is strictly defined and can't be modified. func GenesisFor(net Network) (string, error) { - if err := net.Validate(); err != nil { + var err error + net, err = net.Validate() + if err != nil { return "", err } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 1629047898..59d4530715 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -32,11 +32,15 @@ type Bootstrappers []peer.AddrInfo var ErrInvalidNetwork = errors.New("params: invalid network") // Validate the network. -func (n Network) Validate() error { +func (n Network) Validate() (Network, error) { + // return actual network if alias was provided + if net, ok := networkAliases[string(n)]; ok { + return net, nil + } if _, ok := networksList[n]; !ok { - return ErrInvalidNetwork + return "", ErrInvalidNetwork } - return nil + return n, nil } // networksList is a strict list of all known long-standing networks. From 2a02f2fa9d554bf4cc8a065e0e5314961b9bb9c4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 21 Dec 2022 08:58:12 +0100 Subject: [PATCH 0253/1008] feat(shrex/eds): `Client`/`Server` (#1431) Co-authored-by: Viacheslav Closes https://github.com/celestiaorg/celestia-node/issues/1416 Closes https://github.com/celestiaorg/celestia-node/issues/1417 Closes https://github.com/celestiaorg/celestia-node/issues/1418 --- nodebuilder/share/module.go | 20 + share/p2p/shrexeds/client.go | 147 +++++ share/p2p/shrexeds/exchange_test.go | 115 ++++ share/p2p/shrexeds/options.go | 74 +++ .../shrexeds/pb/extended_data_square.pb.go | 508 ++++++++++++++++++ .../shrexeds/pb/extended_data_square.proto | 16 + share/p2p/shrexeds/server.go | 167 ++++++ 7 files changed, 1047 insertions(+) create mode 100644 share/p2p/shrexeds/client.go create mode 100644 share/p2p/shrexeds/exchange_test.go create mode 100644 share/p2p/shrexeds/options.go create mode 100644 share/p2p/shrexeds/pb/extended_data_square.pb.go create mode 100644 share/p2p/shrexeds/pb/extended_data_square.proto create mode 100644 share/p2p/shrexeds/server.go diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 58a692095a..15785dc9a3 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -4,13 +4,16 @@ import ( "context" "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" ) func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { @@ -48,6 +51,23 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, + fx.Provide(fx.Annotate( + func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { + return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(string(network))) + }, + fx.OnStart(func(ctx context.Context, server *shrexeds.Server) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *shrexeds.Server) error { + return server.Stop(ctx) + }), + )), + // Bridge Nodes need a client as well, for requests over FullAvailability + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { + return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(string(network))) + }, + ), fx.Provide(fx.Annotate( func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { return eds.NewStore(string(path), ds) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go new file mode 100644 index 0000000000..06c04c7ec3 --- /dev/null +++ b/share/p2p/shrexeds/client.go @@ -0,0 +1,147 @@ +package shrexeds + +import ( + "context" + "errors" + "fmt" + "net" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + p2p_pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/rsmt2d" +) + +var errNoMorePeers = errors.New("all peers returned invalid responses") + +// Client is responsible for requesting EDSs for blocksync over the ShrEx/EDS protocol. +type Client struct { + protocolID protocol.ID + host host.Host +} + +// NewClient creates a new ShrEx/EDS client. +func NewClient(host host.Host, opts ...Option) (*Client, error) { + params := DefaultParameters() + for _, opt := range opts { + opt(params) + } + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("shrex-eds: client creation failed: %w", err) + } + + return &Client{ + host: host, + protocolID: protocolID(params.protocolSuffix), + }, nil +} + +// RequestEDS requests the full ODS from one of the given peers and returns the EDS. +// +// The peers are requested in a round-robin manner with retries until one of them gives a valid +// response, blocking until the context is canceled or a valid response is given. +func (c *Client) RequestEDS( + ctx context.Context, + dataHash share.DataHash, + peers peer.IDSlice, +) (*rsmt2d.ExtendedDataSquare, error) { + req := &p2p_pb.EDSRequest{Hash: dataHash} + + // requests are retried for every peer until a valid response is received + excludedPeers := make(map[peer.ID]struct{}) + for { + // if no peers are left, return + if len(peers) == len(excludedPeers) { + return nil, errNoMorePeers + } + + for _, to := range peers { + // skip over excluded peers + if _, ok := excludedPeers[to]; ok { + continue + } + eds, err := c.doRequest(ctx, req, to) + if eds != nil { + return eds, err + } + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return nil, ctx.Err() + } + // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not + // unwrap to a ctx err + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { + return nil, context.DeadlineExceeded + } + if err != nil { + // peer has misbehaved, exclude them from round-robin + excludedPeers[to] = struct{}{} + log.Errorw("client: eds request to peer failed", "peer", to, "hash", dataHash.String()) + } + + // no eds was found, continue + } + } +} + +func (c *Client) doRequest( + ctx context.Context, + req *p2p_pb.EDSRequest, + to peer.ID, +) (*rsmt2d.ExtendedDataSquare, error) { + dataHash := share.DataHash(req.Hash) + log.Debugf("client: requesting eds %s from peer %s", dataHash.String(), to) + stream, err := c.host.NewStream(ctx, to, c.protocolID) + if err != nil { + return nil, fmt.Errorf("failed to open stream: %w", err) + } + if dl, ok := ctx.Deadline(); ok { + if err = stream.SetDeadline(dl); err != nil { + log.Debugw("error setting deadline: %s", err) + } + } + + // request ODS + _, err = serde.Write(stream, req) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, fmt.Errorf("failed to write request to stream: %w", err) + } + err = stream.CloseWrite() + if err != nil { + stream.Reset() //nolint:errcheck + return nil, fmt.Errorf("failed to close write on stream: %w", err) + } + + // read and parse status from peer + resp := new(p2p_pb.EDSResponse) + _, err = serde.Read(stream, resp) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, fmt.Errorf("failed to read status from stream: %w", err) + } + + switch resp.Status { + case p2p_pb.Status_OK: + // use header and ODS bytes to construct EDS and verify it against dataHash + eds, err := eds.ReadEDS(ctx, stream, dataHash) + if err != nil { + return nil, fmt.Errorf("failed to read eds from ods bytes: %w", err) + } + return eds, nil + case p2p_pb.Status_NOT_FOUND, p2p_pb.Status_REFUSED: + log.Debugf("client: peer %s couldn't serve eds %s with status %s", to.String(), dataHash.String(), resp.GetStatus()) + // no eds was returned, but the request was valid and should be retried + return nil, nil + case p2p_pb.Status_INVALID: + fallthrough + default: + return nil, fmt.Errorf("request status %s returned for root %s", resp.GetStatus(), dataHash.String()) + } +} diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go new file mode 100644 index 0000000000..95edebd543 --- /dev/null +++ b/share/p2p/shrexeds/exchange_test.go @@ -0,0 +1,115 @@ +package shrexeds + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + libhost "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" +) + +func TestExchange_RequestEDS(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + store, client, server := makeExchange(t) + + err := store.Start(ctx) + require.NoError(t, err) + + err = server.Start(ctx) + require.NoError(t, err) + + // Testcase: EDS is immediately available + t.Run("EDS_Available", func(t *testing.T) { + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + err = store.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), []peer.ID{server.host.ID()}) + assert.NoError(t, err) + assert.Equal(t, eds.Flattened(), requestedEDS.Flattened()) + + }) + + // Testcase: EDS is unavailable initially, but is found after multiple requests + t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { + storageDelay := time.Second + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + go func() { + time.Sleep(storageDelay) + err = store.Put(ctx, dah.Hash(), eds) + // require.NoError(t, err) + }() + + now := time.Now() + requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), []peer.ID{server.host.ID()}) + finished := time.Now() + + assert.Greater(t, finished.Sub(now), storageDelay) + assert.NoError(t, err) + assert.Equal(t, eds.Flattened(), requestedEDS.Flattened()) + }) + + // Testcase: Invalid request excludes peer from round-robin, stopping request + t.Run("EDS_InvalidRequest", func(t *testing.T) { + dataHash := []byte("invalid") + requestedEDS, err := client.RequestEDS(ctx, dataHash, []peer.ID{server.host.ID()}) + assert.ErrorIs(t, err, errNoMorePeers) + assert.Nil(t, requestedEDS) + }) + + // Testcase: Valid request, which server cannot serve, waits forever + t.Run("EDS_ValidTimeout", func(t *testing.T) { + timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + requestedEDS, err := client.RequestEDS(timeoutCtx, dah.Hash(), []peer.ID{server.host.ID()}) + assert.ErrorIs(t, err, timeoutCtx.Err()) + assert.Nil(t, requestedEDS) + }) +} + +func newStore(t *testing.T) *eds.Store { + t.Helper() + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + store, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + return store +} + +func createMocknet(t *testing.T, amount int) []libhost.Host { + t.Helper() + + net, err := mocknet.FullMeshConnected(amount) + require.NoError(t, err) + // get host and peer + return net.Hosts() +} + +func makeExchange(t *testing.T) (*eds.Store, *Client, *Server) { + t.Helper() + store := newStore(t) + hosts := createMocknet(t, 2) + + client, err := NewClient(hosts[0]) + require.NoError(t, err) + server, err := NewServer(hosts[1], store) + require.NoError(t, err) + + return store, client, server +} diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go new file mode 100644 index 0000000000..23c2d54ef4 --- /dev/null +++ b/share/p2p/shrexeds/options.go @@ -0,0 +1,74 @@ +package shrexeds + +import ( + "fmt" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p-core/protocol" +) + +const protocolPrefix = "/shrex/eds/v0.0.1/" + +var log = logging.Logger("shrex-eds") + +// Option is the functional option that is applied to the shrex/eds protocol to configure its +// parameters. +type Option func(*Parameters) + +// Parameters is the set of parameters that must be configured for the shrex/eds protocol. +type Parameters struct { + // ReadDeadline sets the timeout for reading messages from the stream. + ReadDeadline time.Duration + + // WriteDeadline sets the timeout for writing messages to the stream. + WriteDeadline time.Duration + + // ReadCARDeadline defines the deadline for reading a CAR from disk. + ReadCARDeadline time.Duration + + // BufferSize defines the size of the buffer used for writing an ODS over the stream. + BufferSize uint64 + + // protocolSuffix is appended to the protocolID and represents the network the protocol is + // running on. + protocolSuffix string +} + +func DefaultParameters() *Parameters { + return &Parameters{ + ReadDeadline: time.Minute, + WriteDeadline: time.Second * 5, + ReadCARDeadline: time.Minute, + BufferSize: 32 * 1024, + } +} + +const errSuffix = "value should be positive and non-zero" + +func (p *Parameters) Validate() error { + if p.ReadDeadline <= 0 { + return fmt.Errorf("invalid stream read deadline: %s", errSuffix) + } + if p.WriteDeadline <= 0 { + return fmt.Errorf("invalid write deadline: %s", errSuffix) + } + if p.ReadCARDeadline <= 0 { + return fmt.Errorf("invalid read CAR deadline: %s", errSuffix) + } + if p.BufferSize <= 0 { + return fmt.Errorf("invalid buffer size: %s", errSuffix) + } + return nil +} + +// WithProtocolSuffix is a functional option that configures the `protocolSuffix` parameter +func WithProtocolSuffix(protocolSuffix string) Option { + return func(parameters *Parameters) { + parameters.protocolSuffix = protocolSuffix + } +} + +func protocolID(protocolSuffix string) protocol.ID { + return protocol.ID(fmt.Sprintf("%s%s", protocolPrefix, protocolSuffix)) +} diff --git a/share/p2p/shrexeds/pb/extended_data_square.pb.go b/share/p2p/shrexeds/pb/extended_data_square.pb.go new file mode 100644 index 0000000000..9492cb298b --- /dev/null +++ b/share/p2p/shrexeds/pb/extended_data_square.pb.go @@ -0,0 +1,508 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: share/eds/p2p/pb/extended_data_square.proto + +package extended_data_square + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type Status int32 + +const ( + Status_INVALID Status = 0 + Status_OK Status = 100 + Status_NOT_FOUND Status = 200 + Status_REFUSED Status = 201 +) + +var Status_name = map[int32]string{ + 0: "INVALID", + 100: "OK", + 200: "NOT_FOUND", + 201: "REFUSED", +} + +var Status_value = map[string]int32{ + "INVALID": 0, + "OK": 100, + "NOT_FOUND": 200, + "REFUSED": 201, +} + +func (x Status) String() string { + return proto.EnumName(Status_name, int32(x)) +} + +func (Status) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_e8ddcd8d207cc22e, []int{0} +} + +type EDSRequest struct { + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (m *EDSRequest) Reset() { *m = EDSRequest{} } +func (m *EDSRequest) String() string { return proto.CompactTextString(m) } +func (*EDSRequest) ProtoMessage() {} +func (*EDSRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e8ddcd8d207cc22e, []int{0} +} +func (m *EDSRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EDSRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EDSRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EDSRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_EDSRequest.Merge(m, src) +} +func (m *EDSRequest) XXX_Size() int { + return m.Size() +} +func (m *EDSRequest) XXX_DiscardUnknown() { + xxx_messageInfo_EDSRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_EDSRequest proto.InternalMessageInfo + +func (m *EDSRequest) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +type EDSResponse struct { + Status Status `protobuf:"varint,1,opt,name=status,proto3,enum=Status" json:"status,omitempty"` +} + +func (m *EDSResponse) Reset() { *m = EDSResponse{} } +func (m *EDSResponse) String() string { return proto.CompactTextString(m) } +func (*EDSResponse) ProtoMessage() {} +func (*EDSResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e8ddcd8d207cc22e, []int{1} +} +func (m *EDSResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EDSResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EDSResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EDSResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_EDSResponse.Merge(m, src) +} +func (m *EDSResponse) XXX_Size() int { + return m.Size() +} +func (m *EDSResponse) XXX_DiscardUnknown() { + xxx_messageInfo_EDSResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_EDSResponse proto.InternalMessageInfo + +func (m *EDSResponse) GetStatus() Status { + if m != nil { + return m.Status + } + return Status_INVALID +} + +func init() { + proto.RegisterEnum("Status", Status_name, Status_value) + proto.RegisterType((*EDSRequest)(nil), "EDSRequest") + proto.RegisterType((*EDSResponse)(nil), "EDSResponse") +} + +func init() { + proto.RegisterFile("share/eds/p2p/pb/extended_data_square.proto", fileDescriptor_e8ddcd8d207cc22e) +} + +var fileDescriptor_e8ddcd8d207cc22e = []byte{ + // 223 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2e, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x4f, 0x4d, 0x29, 0xd6, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, + 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x4f, 0x49, 0x2c, 0x49, 0x8c, 0x2f, 0x2e, 0x2c, 0x4d, 0x2c, + 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x52, 0xe0, 0xe2, 0x72, 0x75, 0x09, 0x0e, 0x4a, + 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x48, 0x2c, 0xce, 0x90, 0x60, 0x54, + 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xb8, 0xc1, 0x2a, 0x8a, 0x0b, 0xf2, 0xf3, + 0x8a, 0x53, 0x85, 0xe4, 0xb9, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, 0x4a, 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, + 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x35, 0x17, 0x1b, 0x44, 0x44, 0x88, 0x9b, + 0x8b, 0xdd, 0xd3, 0x2f, 0xcc, 0xd1, 0xc7, 0xd3, 0x45, 0x80, 0x41, 0x88, 0x8d, 0x8b, 0xc9, 0xdf, + 0x5b, 0x20, 0x45, 0x88, 0x8f, 0x8b, 0xd3, 0xcf, 0x3f, 0x24, 0xde, 0xcd, 0x3f, 0xd4, 0xcf, 0x45, + 0xe0, 0x04, 0xa3, 0x10, 0x0f, 0x17, 0x7b, 0x90, 0xab, 0x5b, 0x68, 0xb0, 0xab, 0x8b, 0xc0, 0x49, + 0x46, 0x27, 0x89, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, + 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xbb, + 0xd7, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x3c, 0x20, 0x59, 0x9d, 0xde, 0x00, 0x00, 0x00, +} + +func (m *EDSRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EDSRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EDSRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintExtendedDataSquare(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EDSResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EDSResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EDSResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Status != 0 { + i = encodeVarintExtendedDataSquare(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintExtendedDataSquare(dAtA []byte, offset int, v uint64) int { + offset -= sovExtendedDataSquare(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *EDSRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovExtendedDataSquare(uint64(l)) + } + return n +} + +func (m *EDSResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Status != 0 { + n += 1 + sovExtendedDataSquare(uint64(m.Status)) + } + return n +} + +func sovExtendedDataSquare(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozExtendedDataSquare(x uint64) (n int) { + return sovExtendedDataSquare(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EDSRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EDSRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EDSRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthExtendedDataSquare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthExtendedDataSquare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipExtendedDataSquare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthExtendedDataSquare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EDSResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EDSResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EDSResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= Status(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipExtendedDataSquare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthExtendedDataSquare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipExtendedDataSquare(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExtendedDataSquare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthExtendedDataSquare + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupExtendedDataSquare + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthExtendedDataSquare + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthExtendedDataSquare = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowExtendedDataSquare = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupExtendedDataSquare = fmt.Errorf("proto: unexpected end of group") +) diff --git a/share/p2p/shrexeds/pb/extended_data_square.proto b/share/p2p/shrexeds/pb/extended_data_square.proto new file mode 100644 index 0000000000..c7f826aea5 --- /dev/null +++ b/share/p2p/shrexeds/pb/extended_data_square.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +message EDSRequest { + bytes hash = 1; // identifies the requested EDS. +} + +enum Status { + INVALID = 0; + OK = 100; // data found + NOT_FOUND = 200; // data not found + REFUSED = 201; // request refused +} + +message EDSResponse { + Status status = 1; +} diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go new file mode 100644 index 0000000000..a261f8f5cf --- /dev/null +++ b/share/p2p/shrexeds/server.go @@ -0,0 +1,167 @@ +package shrexeds + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/protocol" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + p2p_pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" +) + +// Server is responsible for serving ODSs for blocksync over the ShrEx/EDS protocol. +type Server struct { + ctx context.Context + cancel context.CancelFunc + + host host.Host + protocolID protocol.ID + + store *eds.Store + + params *Parameters +} + +// NewServer creates a new ShrEx/EDS server. +func NewServer(host host.Host, store *eds.Store, opts ...Option) (*Server, error) { + params := DefaultParameters() + for _, opt := range opts { + opt(params) + } + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("shrex-eds: server creation failed: %w", err) + } + + return &Server{ + host: host, + store: store, + protocolID: protocolID(params.protocolSuffix), + params: params, + }, nil +} + +func (s *Server) Start(context.Context) error { + s.ctx, s.cancel = context.WithCancel(context.Background()) + s.host.SetStreamHandler(s.protocolID, s.handleStream) + return nil +} + +func (s *Server) Stop(context.Context) error { + defer s.cancel() + s.host.RemoveStreamHandler(s.protocolID) + return nil +} + +func (s *Server) handleStream(stream network.Stream) { + log.Debug("server: handling eds request") + + // read request from stream to get the dataHash for store lookup + req, err := s.readRequest(stream) + if err != nil { + log.Errorw("server: reading request from stream", "err", err) + stream.Reset() //nolint:errcheck + return + } + + // ensure the requested dataHash is a valid root + hash := share.DataHash(req.Hash) + err = hash.Validate() + if err != nil { + stream.Reset() //nolint:errcheck + return + } + + ctx, cancel := context.WithTimeout(s.ctx, s.params.ReadCARDeadline) + defer cancel() + status := p2p_pb.Status_OK + // determine whether the EDS is available in our store + edsReader, err := s.store.GetCAR(ctx, hash) + if err != nil { + status = p2p_pb.Status_NOT_FOUND + } else { + defer edsReader.Close() + } + + // inform the client of our status + err = s.writeStatus(status, stream) + if err != nil { + log.Errorw("server: writing status to stream", "err", err) + stream.Reset() //nolint:errcheck + return + } + // if we cannot serve the EDS, we are already done + if status != p2p_pb.Status_OK { + stream.Close() + return + } + + // start streaming the ODS to the client + err = s.writeODS(edsReader, stream) + if err != nil { + log.Errorw("server: writing ods to stream", "err", err) + stream.Reset() //nolint:errcheck + return + } + + err = stream.Close() + if err != nil { + log.Errorw("server: closing stream", "err", err) + } +} + +func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) { + err := stream.SetReadDeadline(time.Now().Add(s.params.ReadDeadline)) + if err != nil { + log.Debug(err) + } + + req := new(p2p_pb.EDSRequest) + _, err = serde.Read(stream, req) + if err != nil { + return nil, err + } + err = stream.CloseRead() + if err != nil { + log.Error(err) + } + + return req, nil +} + +func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error { + err := stream.SetWriteDeadline(time.Now().Add(s.params.WriteDeadline)) + if err != nil { + log.Debug(err) + } + + resp := &p2p_pb.EDSResponse{Status: status} + _, err = serde.Write(stream, resp) + return err +} + +func (s *Server) writeODS(edsReader io.ReadCloser, stream network.Stream) error { + err := stream.SetWriteDeadline(time.Now().Add(s.params.WriteDeadline)) + if err != nil { + log.Debug(err) + } + + odsReader, err := eds.ODSReader(edsReader) + if err != nil { + return fmt.Errorf("creating ODS reader: %w", err) + } + buf := make([]byte, s.params.BufferSize) + _, err = io.CopyBuffer(stream, odsReader, buf) + if err != nil { + return fmt.Errorf("writing ODS bytes: %w", err) + } + + return nil +} From ce63f62f85c9ff6739c59d25ce0f048547db8b0a Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 21 Dec 2022 12:59:31 +0200 Subject: [PATCH 0254/1008] share/p2p: move eds sub (#1519) ## Overview Self-explanatory ## Checklist - [x] Required CI checks are passing Co-authored-by: Hlib Kanunnikov --- share/{eds => p2p/shrexpush}/pubsub.go | 8 ++++++-- share/{eds => p2p/shrexpush}/pubsub_test.go | 2 +- share/{eds => p2p/shrexpush}/subscription.go | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) rename share/{eds => p2p/shrexpush}/pubsub.go (93%) rename share/{eds => p2p/shrexpush}/pubsub_test.go (98%) rename share/{eds => p2p/shrexpush}/subscription.go (98%) diff --git a/share/eds/pubsub.go b/share/p2p/shrexpush/pubsub.go similarity index 93% rename from share/eds/pubsub.go rename to share/p2p/shrexpush/pubsub.go index 66d6aec7f3..4e8678ff97 100644 --- a/share/eds/pubsub.go +++ b/share/p2p/shrexpush/pubsub.go @@ -1,9 +1,11 @@ -package eds +package shrexpush import ( "context" "fmt" + logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -11,6 +13,8 @@ import ( "github.com/celestiaorg/celestia-node/share" ) +var log = logging.Logger("shrex-push") + // pubSubTopic hardcodes the name of the EDS floodsub topic with the provided suffix. func pubSubTopic(suffix string) string { return "eds-sub/v0.0.1/" + suffix @@ -77,7 +81,7 @@ func (s *PubSub) AddValidator(validate Validator) error { // Subscribe provides a new Subscription for EDS notifications. func (s *PubSub) Subscribe() (*Subscription, error) { if s.topic == nil { - return nil, fmt.Errorf("share/eds: topic is not started") + return nil, fmt.Errorf("shrex-push: topic is not started") } return newSubscription(s.topic) } diff --git a/share/eds/pubsub_test.go b/share/p2p/shrexpush/pubsub_test.go similarity index 98% rename from share/eds/pubsub_test.go rename to share/p2p/shrexpush/pubsub_test.go index b630f3afca..fde86864a9 100644 --- a/share/eds/pubsub_test.go +++ b/share/p2p/shrexpush/pubsub_test.go @@ -1,4 +1,4 @@ -package eds +package shrexpush import ( "context" diff --git a/share/eds/subscription.go b/share/p2p/shrexpush/subscription.go similarity index 98% rename from share/eds/subscription.go rename to share/p2p/shrexpush/subscription.go index 2ba7e2842b..64126a436c 100644 --- a/share/eds/subscription.go +++ b/share/p2p/shrexpush/subscription.go @@ -1,4 +1,4 @@ -package eds +package shrexpush import ( "context" From 568b166304e38b5ad6e45146a15426119f120f45 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 21 Dec 2022 13:00:04 +0200 Subject: [PATCH 0255/1008] chore(header/p2p): improve logging (#1520) ## Overview Change the logging level in the case when headers are not found in the store. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/header/p2p/server.go b/header/p2p/server.go index fbd7d8b119..b8ff06b380 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -2,6 +2,7 @@ package p2p import ( "context" + "errors" "time" "github.com/libp2p/go-libp2p-core/host" @@ -183,6 +184,10 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe defer cancel() headersByRange, err := serv.store.GetRangeByHeight(ctx, from, to) if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + log.Warnw("server: requested headers not found", "from", from, "to", to) + return nil, header.ErrNotFound + } log.Errorw("server: getting headers", "from", from, "to", to, "err", err) return nil, err } From 5030d6d98617a5b0a355549b8db41942fe4c6569 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 21 Dec 2022 15:02:13 +0100 Subject: [PATCH 0256/1008] chore(nodebuilder/p2p): Update limani nodekey for arabica (#1523) Nodekey for limani has changed. --- nodebuilder/p2p/bootstrap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index add6baed0b..50537dc7fa 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -29,7 +29,7 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ Arabica: { - "/dns4/limani.celestia-devops.dev/tcp/2121/p2p/12D3KooWNpRWxpi1APzV6CnwHvdgNRuTUbMNvg4ta2i1fnqYXR7H", + "/dns4/limani.celestia-devops.dev/tcp/2121/p2p/12D3KooWDgG69kXfmSiHjUErN2ahpUC1SXpSfB2urrqMZ6aWC8NS", "/dns4/marsellesa.celestia-devops.dev/tcp/2121/p2p/12D3KooWHr2wqFAsMXnPzpFsgxmePgXb8BqpkePebwUgLyZc95bd", "/dns4/parainem.celestia-devops.dev/tcp/2121/p2p/12D3KooWHX8xpwg8qkP7kLKmKGtgZvmsopvgxc6Fwtu665QC7G8q", }, From 0d8cb939271fd1a5d0b0f06de54fa845e167d17e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 22 Dec 2022 10:07:04 +0100 Subject: [PATCH 0257/1008] feat(api/client): Implement RPC client authentication (#1426) Resolves #1187 * Adds `NewClientWithPerms` which takes a token and adds it to the request header for rpc calls * Adds tests for authenticated methods --- api/rpc/client/client.go | 20 ++- api/rpc/permissions/permissions.go | 15 --- api/rpc/perms/permissions.go | 36 +++++ api/rpc/server.go | 6 +- api/rpc_test.go | 207 ++++++++++++++++++++++++++++- nodebuilder/node/mocks/api.go | 3 +- 6 files changed, 262 insertions(+), 25 deletions(-) delete mode 100644 api/rpc/permissions/permissions.go create mode 100644 api/rpc/perms/permissions.go diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 950373a01c..04fe84c8f0 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -2,9 +2,12 @@ package client import ( "context" + "fmt" + "net/http" "github.com/filecoin-project/go-jsonrpc" + "github.com/celestiaorg/celestia-node/api/rpc/perms" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -48,8 +51,19 @@ func (c *Client) Close() { c.closer.closeAll() } -// NewClient creates a new Client with one connection per namespace. -func NewClient(ctx context.Context, addr string) (*Client, error) { +// NewPublicClient creates a new Client with one connection per namespace. +func NewPublicClient(ctx context.Context, addr string) (*Client, error) { + return newClient(ctx, addr, nil) +} + +// NewClient creates a new Client with one connection per namespace with the +// given token as the authorization token. +func NewClient(ctx context.Context, addr string, token string) (*Client, error) { + authHeader := http.Header{perms.AuthKey: []string{fmt.Sprintf("Bearer %s", token)}} + return newClient(ctx, addr, authHeader) +} + +func newClient(ctx context.Context, addr string, authHeader http.Header) (*Client, error) { var client Client var multiCloser multiClientCloser @@ -64,7 +78,7 @@ func NewClient(ctx context.Context, addr string) (*Client, error) { "node": &client.Node.Internal, } for name, module := range modules { - closer, err := jsonrpc.NewClient(ctx, addr, name, module, nil) + closer, err := jsonrpc.NewClient(ctx, addr, name, module, authHeader) if err != nil { return nil, err } diff --git a/api/rpc/permissions/permissions.go b/api/rpc/permissions/permissions.go deleted file mode 100644 index 8a703d2df7..0000000000 --- a/api/rpc/permissions/permissions.go +++ /dev/null @@ -1,15 +0,0 @@ -package permissions - -import "github.com/filecoin-project/go-jsonrpc/auth" - -var ( - DefaultPerms = []auth.Permission{"public"} - ReadWritePerms = []auth.Permission{"public", "read", "write"} - AllPerms = []auth.Permission{"public", "read", "write", "admin"} -) - -// JWTPayload is a utility struct for marshaling/unmarshalling -// permissions into for token signing/verifying. -type JWTPayload struct { - Allow []auth.Permission -} diff --git a/api/rpc/perms/permissions.go b/api/rpc/perms/permissions.go new file mode 100644 index 0000000000..00cb056ca9 --- /dev/null +++ b/api/rpc/perms/permissions.go @@ -0,0 +1,36 @@ +package perms + +import ( + "encoding/json" + + "github.com/cristalhq/jwt" + "github.com/filecoin-project/go-jsonrpc/auth" +) + +var ( + DefaultPerms = []auth.Permission{"public"} + ReadPerms = []auth.Permission{"public", "read"} + ReadWritePerms = []auth.Permission{"public", "read", "write"} + AllPerms = []auth.Permission{"public", "read", "write", "admin"} +) + +var AuthKey = "Authorization" + +// JWTPayload is a utility struct for marshaling/unmarshalling +// permissions into for token signing/verifying. +type JWTPayload struct { + Allow []auth.Permission +} + +func (j *JWTPayload) MarshalBinary() (data []byte, err error) { + return json.Marshal(j) +} + +// NewTokenWithPerms generates and signs a new JWT token with the given secret +// and given permissions. +func NewTokenWithPerms(secret jwt.Signer, perms []auth.Permission) ([]byte, error) { + p := &JWTPayload{ + Allow: perms, + } + return jwt.NewTokenBuilder(secret).BuildBytes(p) +} diff --git a/api/rpc/server.go b/api/rpc/server.go index 7f8a90e5de..a4c8c21ce7 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -14,7 +14,7 @@ import ( "github.com/filecoin-project/go-jsonrpc/auth" logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/api/rpc/permissions" + "github.com/celestiaorg/celestia-node/api/rpc/perms" ) var log = logging.Logger("rpc") @@ -55,7 +55,7 @@ func (s *Server) verifyAuth(_ context.Context, token string) ([]auth.Permission, if err != nil { return nil, err } - p := new(permissions.JWTPayload) + p := new(perms.JWTPayload) err = json.Unmarshal(tk.RawClaims(), p) if err != nil { return nil, err @@ -73,7 +73,7 @@ func (s *Server) RegisterService(namespace string, service interface{}) { // RegisterAuthedService registers a service onto the RPC server. All methods on the service will // then be exposed over the RPC. func (s *Server) RegisterAuthedService(namespace string, service interface{}, out interface{}) { - auth.PermissionedProxy(permissions.AllPerms, permissions.DefaultPerms, service, getInternalStruct(out)) + auth.PermissionedProxy(perms.AllPerms, perms.DefaultPerms, service, getInternalStruct(out)) s.RegisterService(namespace, out) } diff --git a/api/rpc_test.go b/api/rpc_test.go index a21081ae4b..d5ac56c5e5 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -4,16 +4,22 @@ import ( "context" "encoding/json" "reflect" + "strconv" "testing" "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cristalhq/jwt" "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p-core/network" "github.com/stretchr/testify/require" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/api/rpc/perms" + daspkg "github.com/celestiaorg/celestia-node/das" + headerpkg "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/das" dasMock "github.com/celestiaorg/celestia-node/nodebuilder/das/mocks" @@ -44,7 +50,7 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { ) for i := 0; i < 3; i++ { time.Sleep(time.Second * 1) - rpcClient, err = client.NewClient(ctx, "http://"+url) + rpcClient, err = client.NewPublicClient(ctx, "http://"+url) if err == nil { t.Cleanup(rpcClient.Close) break @@ -58,7 +64,7 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { Denom: "utia", } - server.State.EXPECT().Balance(gomock.Any()).Return(expectedBalance, nil).Times(1) + server.State.EXPECT().Balance(gomock.Any()).Return(expectedBalance, nil) balance, err := rpcClient.State.Balance(ctx) require.NoError(t, err) @@ -110,6 +116,160 @@ func TestModulesImplementFullAPI(t *testing.T) { } } +func TestAuthedRPC(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // generate dummy signer and sign admin perms token with it + signer, err := jwt.NewHS256(make([]byte, 32)) + require.NoError(t, err) + + nd, server := setupNodeWithAuthedRPC(t, signer) + url := nd.RPCServer.ListenAddr() + + // create permissioned tokens + publicToken, err := perms.NewTokenWithPerms(signer, perms.DefaultPerms) + require.NoError(t, err) + readToken, err := perms.NewTokenWithPerms(signer, perms.ReadPerms) + require.NoError(t, err) + rwToken, err := perms.NewTokenWithPerms(signer, perms.ReadWritePerms) + require.NoError(t, err) + adminToken, err := perms.NewTokenWithPerms(signer, perms.AllPerms) + require.NoError(t, err) + + var tests = []struct { + perm int + token string + }{ + {perm: 1, token: string(publicToken)}, // public + {perm: 2, token: string(readToken)}, // read + {perm: 3, token: string(rwToken)}, // RW + {perm: 4, token: string(adminToken)}, // admin + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + // we need to run this a few times to prevent the race where the server is not yet started + var rpcClient *client.Client + require.NoError(t, err) + for i := 0; i < 3; i++ { + time.Sleep(time.Second * 1) + rpcClient, err = client.NewClient(ctx, "http://"+url, tt.token) + if err == nil { + break + } + } + require.NotNil(t, rpcClient) + require.NoError(t, err) + + // 1. Test method with public permissions + server.Header.EXPECT().Head(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) + got, err := rpcClient.Header.Head(ctx) + require.NoError(t, err) + require.NotNil(t, got) + + // 2. Test method with read-level permissions + expected := daspkg.SamplingStats{ + SampledChainHead: 100, + CatchupHead: 100, + NetworkHead: 1000, + Failed: nil, + Workers: nil, + Concurrency: 0, + CatchUpDone: true, + IsRunning: false, + } + if tt.perm > 1 { + server.Das.EXPECT().SamplingStats(gomock.Any()).Return(expected, nil) + stats, err := rpcClient.DAS.SamplingStats(ctx) + require.NoError(t, err) + require.Equal(t, expected, stats) + } else { + _, err := rpcClient.DAS.SamplingStats(ctx) + require.Error(t, err) + require.ErrorContains(t, err, "missing permission") + } + + // 3. Test method with write-level permissions + expectedResp := &state.TxResponse{} + if tt.perm > 2 { + server.State.EXPECT().SubmitTx(gomock.Any(), gomock.Any()).Return(expectedResp, nil) + txResp, err := rpcClient.State.SubmitTx(ctx, []byte{}) + require.NoError(t, err) + require.Equal(t, expectedResp, txResp) + } else { + _, err := rpcClient.State.SubmitTx(ctx, []byte{}) + require.Error(t, err) + require.ErrorContains(t, err, "missing permission") + } + + // 4. Test method with admin-level permissions + expectedReachability := network.Reachability(3) + if tt.perm > 3 { + server.P2P.EXPECT().NATStatus(gomock.Any()).Return(expectedReachability, nil) + natstatus, err := rpcClient.P2P.NATStatus(ctx) + require.NoError(t, err) + require.Equal(t, expectedReachability, natstatus) + } else { + _, err := rpcClient.P2P.NATStatus(ctx) + require.Error(t, err) + require.ErrorContains(t, err, "missing permission") + } + + rpcClient.Close() + }) + } +} + +// TestPublicClient tests that the public rpc client can only +// access public methods. +func TestPublicClient(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // generate dummy signer and sign admin perms token with it + signer, err := jwt.NewHS256(make([]byte, 32)) + require.NoError(t, err) + + nd, server := setupNodeWithAuthedRPC(t, signer) + url := nd.RPCServer.ListenAddr() + + // we need to run this a few times to prevent the race where the server is not yet started + var rpcClient *client.Client + require.NoError(t, err) + for i := 0; i < 3; i++ { + time.Sleep(time.Second * 1) + rpcClient, err = client.NewPublicClient(ctx, "http://"+url) + if err == nil { + t.Cleanup(rpcClient.Close) + break + } + } + require.NotNil(t, rpcClient) + require.NoError(t, err) + + // 1. Test method with public permissions + server.Header.EXPECT().Head(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) + got, err := rpcClient.Header.Head(ctx) + require.NoError(t, err) + require.NotNil(t, got) + + // 2. Test method with read-level permissions + _, err = rpcClient.DAS.SamplingStats(ctx) + require.Error(t, err) + require.ErrorContains(t, err, "missing permission") + + // 3. Test method with write-level permissions + _, err = rpcClient.State.SubmitTx(ctx, []byte{}) + require.Error(t, err) + require.ErrorContains(t, err, "missing permission") + + // 4. Test method with admin-level permissions + _, err = rpcClient.P2P.NATStatus(ctx) + require.Error(t, err) + require.ErrorContains(t, err, "missing permission") +} + func TestAllReturnValuesAreMarshalable(t *testing.T) { ra := reflect.TypeOf(new(api)).Elem() for i := 0; i < ra.NumMethod(); i++ { @@ -211,6 +371,49 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { return nd, mockAPI } +// setupNodeWithAuthedRPC sets up a node and overrides its JWT +// signer with the given signer. +func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, *mockAPI) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ctrl := gomock.NewController(t) + + mockAPI := &mockAPI{ + stateMock.NewMockModule(ctrl), + shareMock.NewMockModule(ctrl), + fraudMock.NewMockModule(ctrl), + headerMock.NewMockModule(ctrl), + dasMock.NewMockModule(ctrl), + p2pMock.NewMockModule(ctrl), + nodeMock.NewMockModule(ctrl), + } + + // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root + // level module. For further information, check the documentation on fx.Invoke. + invokeRPC := fx.Invoke(func(srv *rpc.Server) { + srv.RegisterAuthedService("state", mockAPI.State, &statemod.API{}) + srv.RegisterAuthedService("share", mockAPI.Share, &share.API{}) + srv.RegisterAuthedService("fraud", mockAPI.Fraud, &fraud.API{}) + srv.RegisterAuthedService("header", mockAPI.Header, &header.API{}) + srv.RegisterAuthedService("das", mockAPI.Das, &das.API{}) + srv.RegisterAuthedService("p2p", mockAPI.P2P, &p2p.API{}) + srv.RegisterAuthedService("node", mockAPI.Node, &node.API{}) + }) + // fx.Replace does not work here, but fx.Decorate does + nd := nodebuilder.TestNode(t, node.Full, invokeRPC, fx.Decorate(func() (jwt.Signer, error) { + return auth, nil + })) + // start node + err := nd.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = nd.Stop(ctx) + require.NoError(t, err) + }) + return nd, mockAPI +} + type mockAPI struct { State *stateMock.MockModule Share *shareMock.MockModule diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index 39d500bc04..98df713429 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + node "github.com/celestiaorg/celestia-node/nodebuilder/node" auth "github.com/filecoin-project/go-jsonrpc/auth" gomock "github.com/golang/mock/gomock" - - node "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // MockModule is a mock of Module interface. From 7c05ade6080dace907de976e29734e167db725c5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 22 Dec 2022 15:33:39 +0200 Subject: [PATCH 0258/1008] improvement(header|p2p)!: remove limit status code (#1530) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1529 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/helpers.go | 2 - header/p2p/pb/extended_header_request.pb.go | 54 ++++++++++----------- header/p2p/pb/extended_header_request.proto | 1 - header/p2p/server.go | 2 - 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/header/p2p/helpers.go b/header/p2p/helpers.go index 50ee4f4008..293743ab3e 100644 --- a/header/p2p/helpers.go +++ b/header/p2p/helpers.go @@ -89,8 +89,6 @@ func convertStatusCodeToError(code p2p_pb.StatusCode) error { return nil case p2p_pb.StatusCode_NOT_FOUND: return header.ErrNotFound - case p2p_pb.StatusCode_LIMIT_EXCEEDED: - return header.ErrHeadersLimitExceeded default: return fmt.Errorf("unknown status code %d", code) } diff --git a/header/p2p/pb/extended_header_request.pb.go b/header/p2p/pb/extended_header_request.pb.go index da44b304b8..cdb80d0e10 100644 --- a/header/p2p/pb/extended_header_request.pb.go +++ b/header/p2p/pb/extended_header_request.pb.go @@ -25,24 +25,21 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type StatusCode int32 const ( - StatusCode_INVALID StatusCode = 0 - StatusCode_OK StatusCode = 1 - StatusCode_NOT_FOUND StatusCode = 2 - StatusCode_LIMIT_EXCEEDED StatusCode = 3 + StatusCode_INVALID StatusCode = 0 + StatusCode_OK StatusCode = 1 + StatusCode_NOT_FOUND StatusCode = 2 ) var StatusCode_name = map[int32]string{ 0: "INVALID", 1: "OK", 2: "NOT_FOUND", - 3: "LIMIT_EXCEEDED", } var StatusCode_value = map[string]int32{ - "INVALID": 0, - "OK": 1, - "NOT_FOUND": 2, - "LIMIT_EXCEEDED": 3, + "INVALID": 0, + "OK": 1, + "NOT_FOUND": 2, } func (x StatusCode) String() string { @@ -209,26 +206,25 @@ func init() { } var fileDescriptor_ea2a1467b965216e = []byte{ - // 293 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xc1, 0x4a, 0xc3, 0x40, - 0x10, 0x86, 0xb3, 0x6d, 0x88, 0x38, 0xd6, 0x12, 0x06, 0x2d, 0x39, 0x85, 0xd2, 0x53, 0x51, 0x48, - 0x21, 0x3e, 0x81, 0x6d, 0x22, 0x0d, 0xd6, 0x16, 0xd6, 0x2a, 0xde, 0xe2, 0x86, 0x5d, 0xda, 0x1e, - 0xcc, 0xae, 0xd9, 0x2d, 0xe8, 0x5b, 0xf8, 0x58, 0x1e, 0x7b, 0xf4, 0x28, 0xed, 0x8b, 0x88, 0xdb, - 0xa0, 0xe2, 0x6d, 0xfe, 0xf9, 0x3f, 0xf8, 0x86, 0x81, 0xf3, 0xa5, 0x60, 0x5c, 0x54, 0x03, 0x15, - 0xab, 0x81, 0x2a, 0x06, 0xe2, 0xc5, 0x88, 0x92, 0x0b, 0x9e, 0xef, 0xd7, 0x79, 0x25, 0x9e, 0xd7, - 0x42, 0x9b, 0x48, 0x55, 0xd2, 0x48, 0xf4, 0x54, 0xac, 0x22, 0x55, 0xf4, 0x16, 0x70, 0x9a, 0xd6, - 0xe0, 0xd8, 0x72, 0x74, 0x8f, 0x61, 0x00, 0x9e, 0xac, 0x56, 0x8b, 0x55, 0x19, 0x90, 0x2e, 0xe9, - 0xbb, 0x63, 0x87, 0xd6, 0x19, 0x4f, 0xc0, 0x5d, 0x32, 0xbd, 0x0c, 0x1a, 0x5d, 0xd2, 0x6f, 0x8d, - 0x1d, 0x6a, 0x13, 0x76, 0xc0, 0x63, 0x4f, 0x72, 0x5d, 0x9a, 0xa0, 0xf9, 0xcd, 0xd3, 0x3a, 0x0d, - 0x3d, 0x70, 0x39, 0x33, 0xac, 0xf7, 0x08, 0x9d, 0xff, 0x22, 0xad, 0x64, 0xa9, 0x05, 0x22, 0xb8, - 0x85, 0xe4, 0xaf, 0xd6, 0xd3, 0xa2, 0x76, 0xc6, 0x18, 0x40, 0x1b, 0x66, 0xd6, 0x7a, 0x24, 0xb9, - 0xb0, 0xa6, 0x76, 0x8c, 0xd1, 0xfe, 0xe6, 0xe8, 0xf6, 0xa7, 0xa1, 0x7f, 0xa8, 0xb3, 0x04, 0xe0, - 0xb7, 0xc1, 0x23, 0x38, 0xc8, 0xa6, 0xf7, 0x97, 0x93, 0x2c, 0xf1, 0x1d, 0xf4, 0xa0, 0x31, 0xbb, - 0xf6, 0x09, 0x1e, 0xc3, 0xe1, 0x74, 0x36, 0xcf, 0xaf, 0x66, 0x77, 0xd3, 0xc4, 0x6f, 0x20, 0x42, - 0x7b, 0x92, 0xdd, 0x64, 0xf3, 0x3c, 0x7d, 0x18, 0xa5, 0x69, 0x92, 0x26, 0x7e, 0x73, 0x18, 0xbc, - 0x6f, 0x43, 0xb2, 0xd9, 0x86, 0xe4, 0x73, 0x1b, 0x92, 0xb7, 0x5d, 0xe8, 0x6c, 0x76, 0xa1, 0xf3, - 0xb1, 0x0b, 0x9d, 0xc2, 0xb3, 0x9f, 0xbb, 0xf8, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x41, 0xe0, - 0x70, 0x68, 0x01, 0x00, 0x00, + // 278 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xcd, 0x4a, 0xf3, 0x40, + 0x14, 0x86, 0x67, 0xfa, 0x85, 0xf9, 0xf0, 0x58, 0xa5, 0x0c, 0x5a, 0xb2, 0x1a, 0x4a, 0x57, 0x45, + 0x21, 0x95, 0x78, 0x05, 0xd6, 0x2a, 0x2d, 0x4a, 0x03, 0xe3, 0xcf, 0x36, 0x4e, 0x98, 0xa1, 0xe9, + 0xc2, 0xcc, 0x98, 0x99, 0x80, 0xde, 0x85, 0x97, 0xe5, 0xb2, 0x4b, 0x97, 0x92, 0xdc, 0x88, 0x38, + 0x09, 0x2a, 0xee, 0xce, 0x7b, 0xde, 0x07, 0x9e, 0xc3, 0x81, 0xe3, 0x5c, 0x09, 0xa9, 0xca, 0xa9, + 0x89, 0xcd, 0xd4, 0x64, 0x53, 0xf5, 0xec, 0x54, 0x21, 0x95, 0x4c, 0xdb, 0x75, 0x5a, 0xaa, 0xa7, + 0x4a, 0x59, 0x17, 0x99, 0x52, 0x3b, 0x4d, 0x89, 0x89, 0x4d, 0x64, 0xb2, 0xf1, 0x1a, 0x0e, 0x2f, + 0x3a, 0x70, 0xe1, 0x39, 0xde, 0x62, 0x34, 0x04, 0xa2, 0xcb, 0xcd, 0x7a, 0x53, 0x84, 0x78, 0x84, + 0x27, 0xc1, 0x02, 0xf1, 0x2e, 0xd3, 0x03, 0x08, 0x72, 0x61, 0xf3, 0xb0, 0x37, 0xc2, 0x93, 0xfe, + 0x02, 0x71, 0x9f, 0xe8, 0x10, 0x88, 0x78, 0xd4, 0x55, 0xe1, 0xc2, 0x7f, 0x5f, 0x3c, 0xef, 0xd2, + 0x8c, 0x40, 0x20, 0x85, 0x13, 0xe3, 0x07, 0x18, 0xfe, 0x15, 0x59, 0xa3, 0x0b, 0xab, 0x28, 0x85, + 0x20, 0xd3, 0xf2, 0xc5, 0x7b, 0xfa, 0xdc, 0xcf, 0x34, 0x06, 0xb0, 0x4e, 0xb8, 0xca, 0x9e, 0x6b, + 0xa9, 0xbc, 0x69, 0x3f, 0xa6, 0x51, 0x7b, 0x73, 0x74, 0xf3, 0xdd, 0xf0, 0x5f, 0xd4, 0xd1, 0x09, + 0xc0, 0x4f, 0x43, 0x77, 0xe1, 0xff, 0x72, 0x75, 0x7f, 0x76, 0xbd, 0x9c, 0x0f, 0x10, 0x25, 0xd0, + 0x4b, 0xae, 0x06, 0x98, 0xee, 0xc1, 0xce, 0x2a, 0xb9, 0x4d, 0x2f, 0x93, 0xbb, 0xd5, 0x7c, 0xd0, + 0x9b, 0x85, 0x6f, 0x35, 0xc3, 0xdb, 0x9a, 0xe1, 0x8f, 0x9a, 0xe1, 0xd7, 0x86, 0xa1, 0x6d, 0xc3, + 0xd0, 0x7b, 0xc3, 0x50, 0x46, 0xfc, 0x97, 0x4e, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x3d, 0x8f, + 0x64, 0x4b, 0x54, 0x01, 0x00, 0x00, } func (m *ExtendedHeaderRequest) Marshal() (dAtA []byte, err error) { diff --git a/header/p2p/pb/extended_header_request.proto b/header/p2p/pb/extended_header_request.proto index d508c1cb76..875c9d89dc 100644 --- a/header/p2p/pb/extended_header_request.proto +++ b/header/p2p/pb/extended_header_request.proto @@ -14,7 +14,6 @@ enum StatusCode { INVALID = 0; OK = 1; NOT_FOUND = 2; - LIMIT_EXCEEDED = 3; }; message ExtendedHeaderResponse { diff --git a/header/p2p/server.go b/header/p2p/server.go index b8ff06b380..7596dfc36d 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -108,8 +108,6 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { code = p2p_pb.StatusCode_OK case header.ErrNotFound: code = p2p_pb.StatusCode_NOT_FOUND - case header.ErrHeadersLimitExceeded: - code = p2p_pb.StatusCode_LIMIT_EXCEEDED default: stream.Reset() //nolint:errcheck return From 603834170138824adda8bfe6892382d6aa03ec4e Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 22 Dec 2022 16:51:28 +0200 Subject: [PATCH 0259/1008] feat(header|p2p): add blacklisting to peerTracker (#1532) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1531 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/exchange.go | 3 +++ header/p2p/exchange_test.go | 12 +++++++++--- header/p2p/peer_tracker.go | 28 +++++++++++++++++++++++++++- header/p2p/peer_tracker_test.go | 17 ++++++++++++++++- nodebuilder/header/constructors.go | 5 ++++- nodebuilder/header/module_test.go | 4 ++++ 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index f5c4a7b5df..d64f10d961 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -11,6 +11,7 @@ import ( "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/p2p/net/conngater" tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/celestia-node/header" @@ -42,6 +43,7 @@ func NewExchange( host host.Host, peers peer.IDSlice, protocolSuffix string, + connGater *conngater.BasicConnectionGater, opts ...Option[ClientParameters], ) (*Exchange, error) { params := DefaultClientParameters() @@ -60,6 +62,7 @@ func NewExchange( trustedPeers: peers, peerTracker: newPeerTracker( host, + connGater, params.MaxAwaitingTime, params.DefaultScore, params.MaxPeerTrackerSize, diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 1aa8887907..c809c6c21d 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -6,8 +6,11 @@ import ( "testing" "time" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" libhost "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -82,8 +85,10 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { totalAmount := 80 store := createStore(t, totalAmount) protocolSuffix := "private" + connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + require.NoError(t, err) // create new exchange - exchange, err := NewExchange(hosts[len(hosts)-1], []peer.ID{}, protocolSuffix) + exchange, err := NewExchange(hosts[len(hosts)-1], []peer.ID{}, protocolSuffix, connGater) require.NoError(t, err) exchange.Params.MaxHeadersPerRequest = 10 exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) @@ -288,8 +293,9 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan require.NoError(t, err) err = serverSideEx.Start(context.Background()) require.NoError(t, err) - - ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private") + connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + require.NoError(t, err) + ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private", connGater) require.NoError(t, err) ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} require.NoError(t, ex.Start(context.Background())) diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go index 3d5aa9c4f9..66870f59a0 100644 --- a/header/p2p/peer_tracker.go +++ b/header/p2p/peer_tracker.go @@ -9,13 +9,15 @@ import ( "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/p2p/net/conngater" ) // gcCycle defines the duration after which the peerTracker starts removing peers. var gcCycle = time.Minute * 30 type peerTracker struct { - host host.Host + host host.Host + connGater *conngater.BasicConnectionGater peerLk sync.RWMutex // trackedPeers contains active peers that we can request to. @@ -43,6 +45,7 @@ type peerTracker struct { func newPeerTracker( h host.Host, + connGater *conngater.BasicConnectionGater, maxAwaitingTime time.Duration, defaultScore float32, maxPeerTrackerSize int, @@ -50,6 +53,7 @@ func newPeerTracker( ctx, cancel := context.WithCancel(context.Background()) return &peerTracker{ host: h, + connGater: connGater, disconnectedPeers: make(map[peer.ID]*peerStat), trackedPeers: make(map[peer.ID]*peerStat), maxAwaitingTime: maxAwaitingTime, @@ -190,3 +194,25 @@ func (p *peerTracker) stop() { <-p.done } } + +// blockPeer blocks a peer on the networking level and removes it from the local cache. +func (p *peerTracker) blockPeer(pID peer.ID) { + // add peer to the blacklist, so we can't connect to it in the future. + err := p.connGater.BlockPeer(pID) + if err != nil { + log.Errorw("header/p2p: blocking peer failed", "err", err, "pID", pID) + } + // close connections to peer. + err = p.host.Network().ClosePeer(pID) + if err != nil { + log.Errorw("header/p2p: closing connection with peer failed", "err", err, "pID", pID) + } + + log.Warnw("header/p2p: blocked peer", "pID", pID) + + p.peerLk.Lock() + defer p.peerLk.Unlock() + // remove peer from cache. + delete(p.trackedPeers, pID) + delete(p.disconnectedPeers, pID) +} diff --git a/header/p2p/peer_tracker_test.go b/header/p2p/peer_tracker_test.go index 788ddbcf40..13791b0b16 100644 --- a/header/p2p/peer_tracker_test.go +++ b/header/p2p/peer_tracker_test.go @@ -4,7 +4,10 @@ import ( "testing" "time" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -12,7 +15,9 @@ import ( func TestPeerTracker_GC(t *testing.T) { h := createMocknet(t, 1) gcCycle = time.Millisecond * 200 - p := newPeerTracker(h[0], time.Millisecond*1, 1, 5) + connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + require.NoError(t, err) + p := newPeerTracker(h[0], connGater, time.Millisecond*1, 1, 5) pid1 := peer.ID("peer1") pid2 := peer.ID("peer2") pid3 := peer.ID("peer3") @@ -31,3 +36,13 @@ func TestPeerTracker_GC(t *testing.T) { require.Nil(t, p.trackedPeers[pid1]) require.Nil(t, p.disconnectedPeers[pid3]) } + +func TestPeerTracker_BlockPeer(t *testing.T) { + h := createMocknet(t, 2) + connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + require.NoError(t, err) + p := newPeerTracker(h[0], connGater, time.Millisecond*1, 1, 5) + p.blockPeer(h[1].ID()) + require.Len(t, connGater.ListBlockedPeers(), 1) + require.True(t, connGater.ListBlockedPeers()[0] == h[1].ID()) +} diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 9c3713b2bc..cd616c0a29 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -7,6 +7,7 @@ import ( "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/header" @@ -32,6 +33,7 @@ func newP2PExchange(cfg Config) func( modp2p.Bootstrappers, modp2p.Network, host.Host, + *conngater.BasicConnectionGater, []p2p.Option[p2p.ClientParameters], ) (header.Exchange, error) { return func( @@ -39,6 +41,7 @@ func newP2PExchange(cfg Config) func( bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host, + conngater *conngater.BasicConnectionGater, opts []p2p.Option[p2p.ClientParameters], ) (header.Exchange, error) { peers, err := cfg.trustedPeers(bpeers) @@ -50,7 +53,7 @@ func newP2PExchange(cfg Config) func( ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - exchange, err := p2p.NewExchange(host, ids, string(network), opts...) + exchange, err := p2p.NewExchange(host, ids, string(network), conngater, opts...) if err != nil { return nil, err } diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 68b471f1d6..3dffc4b3ac 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -5,6 +5,7 @@ import ( "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" @@ -60,6 +61,9 @@ func TestConstructModule_ExchangeParams(t *testing.T) { return datastore.NewMapDatastore() }), ConstructModule(node.Light, &cfg), + fx.Provide(func(b datastore.Batching) (*conngater.BasicConnectionGater, error) { + return conngater.NewBasicConnectionGater(b) + }), fx.Invoke( func(e header.Exchange, server *p2p.ExchangeServer) { ex := e.(*p2p.Exchange) From bde004d9fb00ed1934185544312a87a8da2a6c0e Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 22 Dec 2022 16:17:41 +0100 Subject: [PATCH 0260/1008] refactor: exporting nodebuilder/header.InitStore, moving mock to nodebuilder/header (#1515) --- header/mocks/store.go | 100 ++++++++++++++++++++++ header/p2p/exchange_test.go | 129 +++++------------------------ nodebuilder/header/constructors.go | 20 ++--- nodebuilder/testing.go | 4 + 4 files changed, 128 insertions(+), 125 deletions(-) create mode 100644 header/mocks/store.go diff --git a/header/mocks/store.go b/header/mocks/store.go new file mode 100644 index 0000000000..5cbb65dbe9 --- /dev/null +++ b/header/mocks/store.go @@ -0,0 +1,100 @@ +package mocks + +import ( + "bytes" + "context" + "testing" + + tmbytes "github.com/tendermint/tendermint/libs/bytes" + + "github.com/celestiaorg/celestia-node/header" +) + +type MockStore struct { + Headers map[int64]*header.ExtendedHeader + HeadHeight int64 +} + +// NewStore creates a mock store and adds several random +// headers +func NewStore(t *testing.T, numHeaders int) *MockStore { + store := &MockStore{ + Headers: make(map[int64]*header.ExtendedHeader), + HeadHeight: 0, + } + + suite := header.NewTestSuite(t, numHeaders) + + for i := 0; i < numHeaders; i++ { + header := suite.GenExtendedHeader() + store.Headers[header.Height] = header + + if header.Height > store.HeadHeight { + store.HeadHeight = header.Height + } + } + return store +} + +func (m *MockStore) Init(context.Context, *header.ExtendedHeader) error { return nil } +func (m *MockStore) Start(context.Context) error { return nil } +func (m *MockStore) Stop(context.Context) error { return nil } + +func (m *MockStore) Height() uint64 { + return uint64(m.HeadHeight) +} + +func (m *MockStore) Head(context.Context) (*header.ExtendedHeader, error) { + return m.Headers[m.HeadHeight], nil +} + +func (m *MockStore) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { + for _, header := range m.Headers { + if bytes.Equal(header.Hash(), hash) { + return header, nil + } + } + return nil, header.ErrNotFound +} + +func (m *MockStore) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + return m.Headers[int64(height)], nil +} + +func (m *MockStore) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { + headers := make([]*header.ExtendedHeader, to-from) + // As the requested range is [from; to), + // check that (to-1) height in request is less than + // the biggest header height in store. + if to-1 > m.Height() { + return nil, header.ErrNotFound + } + for i := range headers { + headers[i] = m.Headers[int64(from)] + from++ + } + return headers, nil +} + +func (m *MockStore) GetVerifiedRange( + ctx context.Context, + h *header.ExtendedHeader, + to uint64, +) ([]*header.ExtendedHeader, error) { + return m.GetRangeByHeight(ctx, uint64(h.Height)+1, to) +} + +func (m *MockStore) Has(context.Context, tmbytes.HexBytes) (bool, error) { + return false, nil +} + +func (m *MockStore) Append(ctx context.Context, headers ...*header.ExtendedHeader) (int, error) { + for _, header := range headers { + m.Headers[header.Height] = header + // set head + if header.Height > m.HeadHeight { + m.HeadHeight = header.Height + } + } + return len(headers), nil +} diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index c809c6c21d..0da7343dfc 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -1,7 +1,6 @@ package p2p import ( - "bytes" "context" "testing" "time" @@ -15,11 +14,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/go-libp2p-messenger/serde" "github.com/celestiaorg/celestia-node/header" + headerMock "github.com/celestiaorg/celestia-node/header/mocks" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) @@ -32,8 +30,8 @@ func TestExchange_RequestHead(t *testing.T) { header, err := exchg.Head(context.Background()) require.NoError(t, err) - assert.Equal(t, store.headers[store.headHeight].Height, header.Height) - assert.Equal(t, store.headers[store.headHeight].Hash(), header.Hash()) + assert.Equal(t, store.Headers[store.HeadHeight].Height, header.Height) + assert.Equal(t, store.Headers[store.HeadHeight].Hash(), header.Hash()) } func TestExchange_RequestHeader(t *testing.T) { @@ -42,8 +40,8 @@ func TestExchange_RequestHeader(t *testing.T) { // perform expected request header, err := exchg.GetByHeight(context.Background(), 5) require.NoError(t, err) - assert.Equal(t, store.headers[5].Height, header.Height) - assert.Equal(t, store.headers[5].Hash(), header.Hash()) + assert.Equal(t, store.Headers[5].Height, header.Height) + assert.Equal(t, store.Headers[5].Hash(), header.Hash()) } func TestExchange_RequestHeaders(t *testing.T) { @@ -53,8 +51,8 @@ func TestExchange_RequestHeaders(t *testing.T) { gotHeaders, err := exchg.GetRangeByHeight(context.Background(), 1, 5) require.NoError(t, err) for _, got := range gotHeaders { - assert.Equal(t, store.headers[got.Height].Height, got.Height) - assert.Equal(t, store.headers[got.Height].Hash(), got.Hash()) + assert.Equal(t, store.Headers[got.Height].Height, got.Height) + assert.Equal(t, store.Headers[got.Height].Hash(), got.Hash()) } } @@ -62,7 +60,7 @@ func TestExchange_RequestVerifiedHeaders(t *testing.T) { hosts := createMocknet(t, 2) exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) // perform expected request - h := store.headers[1] + h := store.Headers[1] _, err := exchg.GetVerifiedRange(context.Background(), h, 3) require.NoError(t, err) } @@ -70,9 +68,9 @@ func TestExchange_RequestVerifiedHeaders(t *testing.T) { func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { hosts := createMocknet(t, 2) exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - store.headers[2] = store.headers[3] + store.Headers[2] = store.Headers[3] // perform expected request - h := store.headers[1] + h := store.Headers[1] _, err := exchg.GetVerifiedRange(context.Background(), h, 3) require.Error(t, err) } @@ -83,7 +81,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { // create mocknet with 5 peers hosts := createMocknet(t, 5) totalAmount := 80 - store := createStore(t, totalAmount) + store := headerMock.NewStore(t, totalAmount) protocolSuffix := "private" connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) @@ -146,7 +144,7 @@ func TestExchange_RequestByHash(t *testing.T) { // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] // create and start the ExchangeServer - store := createStore(t, 5) + store := headerMock.NewStore(t, 5) serv, err := NewExchangeServer(host, store, "private") require.NoError(t, err) err = serv.Start(ctx) @@ -159,9 +157,9 @@ func TestExchange_RequestByHash(t *testing.T) { stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, privateProtocolID) require.NoError(t, err) // create request for a header at a random height - reqHeight := store.headHeight - 2 + reqHeight := store.HeadHeight - 2 req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: store.headers[reqHeight].Hash()}, + Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: store.Headers[reqHeight].Hash()}, Amount: 1, } // send request @@ -175,8 +173,8 @@ func TestExchange_RequestByHash(t *testing.T) { eh, err := header.UnmarshalExtendedHeader(resp.Body) require.NoError(t, err) - assert.Equal(t, store.headers[reqHeight].Height, eh.Height) - assert.Equal(t, store.headers[reqHeight].Hash(), eh.Hash()) + assert.Equal(t, store.Headers[reqHeight].Height, eh.Height) + assert.Equal(t, store.Headers[reqHeight].Hash(), eh.Hash()) } func Test_bestHead(t *testing.T) { @@ -255,7 +253,7 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.NoError(t, err) // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] - serv, err := NewExchangeServer(host, createStore(t, 0), "private") + serv, err := NewExchangeServer(host, headerMock.NewStore(t, 0), "private") require.NoError(t, err) err = serv.Start(ctx) require.NoError(t, err) @@ -287,8 +285,8 @@ func createMocknet(t *testing.T, amount int) []libhost.Host { } // createP2PExAndServer creates a Exchange with 5 headers already in its store. -func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchange, *mockStore) { - store := createStore(t, 5) +func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchange, *headerMock.MockStore) { + store := headerMock.NewStore(t, 5) serverSideEx, err := NewExchangeServer(tpeer, store, "private") require.NoError(t, err) err = serverSideEx.Start(context.Background()) @@ -306,92 +304,3 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan }) return ex, store } - -type mockStore struct { - headers map[int64]*header.ExtendedHeader - headHeight int64 -} - -// createStore creates a mock store and adds several random -// headers -func createStore(t *testing.T, numHeaders int) *mockStore { - store := &mockStore{ - headers: make(map[int64]*header.ExtendedHeader), - headHeight: 0, - } - - suite := header.NewTestSuite(t, numHeaders) - - for i := 0; i < numHeaders; i++ { - header := suite.GenExtendedHeader() - store.headers[header.Height] = header - - if header.Height > store.headHeight { - store.headHeight = header.Height - } - } - return store -} - -func (m *mockStore) Init(context.Context, *header.ExtendedHeader) error { return nil } -func (m *mockStore) Start(context.Context) error { return nil } -func (m *mockStore) Stop(context.Context) error { return nil } - -func (m *mockStore) Height() uint64 { - return uint64(m.headHeight) -} - -func (m *mockStore) Head(context.Context) (*header.ExtendedHeader, error) { - return m.headers[m.headHeight], nil -} - -func (m *mockStore) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { - for _, header := range m.headers { - if bytes.Equal(header.Hash(), hash) { - return header, nil - } - } - return nil, header.ErrNotFound -} - -func (m *mockStore) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { - return m.headers[int64(height)], nil -} - -func (m *mockStore) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { - headers := make([]*header.ExtendedHeader, to-from) - // As the requested range is [from; to), - // check that (to-1) height in request is less than - // the biggest header height in store. - if to-1 > m.Height() { - return nil, header.ErrNotFound - } - for i := range headers { - headers[i] = m.headers[int64(from)] - from++ - } - return headers, nil -} - -func (m *mockStore) GetVerifiedRange( - ctx context.Context, - h *header.ExtendedHeader, - to uint64, -) ([]*header.ExtendedHeader, error) { - return m.GetRangeByHeight(ctx, uint64(h.Height)+1, to) -} - -func (m *mockStore) Has(context.Context, tmbytes.HexBytes) (bool, error) { - return false, nil -} - -func (m *mockStore) Append(ctx context.Context, headers ...*header.ExtendedHeader) (int, error) { - for _, header := range headers { - m.headers[header.Height] = header - // set head - if header.Height > m.headHeight { - m.headHeight = header.Height - } - } - return len(headers), nil -} diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index cd616c0a29..c1806af0c4 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -70,13 +70,13 @@ func newP2PExchange(cfg Config) func( } // newSyncer constructs new Syncer for headers. -func newSyncer(ex header.Exchange, store initStore, sub header.Subscriber, duration time.Duration) *sync.Syncer { +func newSyncer(ex header.Exchange, store InitStore, sub header.Subscriber, duration time.Duration) *sync.Syncer { return sync.NewSyncer(ex, store, sub, duration) } -// initStore is a type representing initialized header store. +// InitStore is a type representing initialized header store. // NOTE: It is needed to ensure that Store is always initialized before Syncer is started. -type initStore header.Store +type InitStore header.Store // newInitStore constructs an initialized store func newInitStore( @@ -85,7 +85,7 @@ func newInitStore( net modp2p.Network, s header.Store, ex header.Exchange, -) (initStore, error) { +) (InitStore, error) { trustedHash, err := cfg.trustedHash(net) if err != nil { return nil, err @@ -93,17 +93,7 @@ func newInitStore( lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { - err = store.Init(ctx, s, ex, trustedHash) - if err != nil { - // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. - // This is due to requesting step of initialization, which fetches initial Header by trusted hash - // from the network. The step can't be done during unit tests and fixing it would require either - // * Having some test/dev/offline mode for Node that mocks out all the networking - // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step - // * Or removing explicit initialization in favor of automated initialization by Syncer - log.Errorf("initializing header store failed: %s", err) - } - return nil + return store.Init(ctx, s, ex, trustedHash) }, }) diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 0ea3684294..ec3b90af26 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -16,6 +16,9 @@ import ( apptypes "github.com/celestiaorg/celestia-app/x/payment/types" "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header/mocks" + "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -53,6 +56,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti opts = append(opts, state.WithKeyringSigner(TestKeyringSigner(t)), fx.Replace(node.StorePath(storePath)), + fxutil.ReplaceAs(mocks.NewStore(t, 20), new(header.InitStore)), ) nd, err := New(tp, p2p.Private, store, opts...) require.NoError(t, err) From 5c91784a0bfc6d220438a6feef547a46cfb61476 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 22 Dec 2022 17:20:10 +0200 Subject: [PATCH 0261/1008] improvement(header|p2p): log the reason on blocking (#1534) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1533 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/peer_tracker.go | 8 ++++---- header/p2p/peer_tracker_test.go | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go index 66870f59a0..6bbe87cdae 100644 --- a/header/p2p/peer_tracker.go +++ b/header/p2p/peer_tracker.go @@ -196,19 +196,19 @@ func (p *peerTracker) stop() { } // blockPeer blocks a peer on the networking level and removes it from the local cache. -func (p *peerTracker) blockPeer(pID peer.ID) { +func (p *peerTracker) blockPeer(pID peer.ID, reason error) { // add peer to the blacklist, so we can't connect to it in the future. err := p.connGater.BlockPeer(pID) if err != nil { - log.Errorw("header/p2p: blocking peer failed", "err", err, "pID", pID) + log.Errorw("header/p2p: blocking peer failed", "pID", pID, "err", err) } // close connections to peer. err = p.host.Network().ClosePeer(pID) if err != nil { - log.Errorw("header/p2p: closing connection with peer failed", "err", err, "pID", pID) + log.Errorw("header/p2p: closing connection with peer failed", "pID", pID, "err", err) } - log.Warnw("header/p2p: blocked peer", "pID", pID) + log.Warnw("header/p2p: blocked peer", "pID", pID, "reason", reason) p.peerLk.Lock() defer p.peerLk.Unlock() diff --git a/header/p2p/peer_tracker_test.go b/header/p2p/peer_tracker_test.go index 13791b0b16..61bb64f104 100644 --- a/header/p2p/peer_tracker_test.go +++ b/header/p2p/peer_tracker_test.go @@ -1,6 +1,7 @@ package p2p import ( + "errors" "testing" "time" @@ -42,7 +43,7 @@ func TestPeerTracker_BlockPeer(t *testing.T) { connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) p := newPeerTracker(h[0], connGater, time.Millisecond*1, 1, 5) - p.blockPeer(h[1].ID()) + p.blockPeer(h[1].ID(), errors.New("test")) require.Len(t, connGater.ListBlockedPeers(), 1) require.True(t, connGater.ListBlockedPeers()[0] == h[1].ID()) } From 6179f3e91161bb6403860dcf865adac36b366dc3 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 22 Dec 2022 23:04:52 +0100 Subject: [PATCH 0262/1008] feat(eds/store): `store.GetDAH` (#1511) Co-authored-by: Viacheslav Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> Closes https://github.com/celestiaorg/celestia-node/issues/1509 --- docs/adr/adr-011-blocksync-overhaul-part-1.md | 23 ++++- share/eds/blockstore.go | 2 +- share/eds/blockstore_test.go | 4 + share/eds/store.go | 85 +++++++++++++++---- share/eds/store_test.go | 2 +- 5 files changed, 93 insertions(+), 23 deletions(-) diff --git a/docs/adr/adr-011-blocksync-overhaul-part-1.md b/docs/adr/adr-011-blocksync-overhaul-part-1.md index 720f60c26e..2967f696ac 100644 --- a/docs/adr/adr-011-blocksync-overhaul-part-1.md +++ b/docs/adr/adr-011-blocksync-overhaul-part-1.md @@ -273,9 +273,10 @@ func (s *Store) Blockstore() blockstore.Blockstore ##### `eds.Store.CARBlockstore` -`CARBlockstore` method returns a [`Blockstore`][blockstore] interface implementation instance, providing random access -over share and NMT Merkle proof in a specific EDS identified by DataHash. It is required for FNs/BNs to enable [reading -data by namespace](#reading-data-by-namespace). +`CARBlockstore` method returns a read-only [`Blockstore`][blockstore] interface implementation +instance to provide random access over share and NMT Merkle proof in a specific EDS identified by +DataHash, along with its corresponding DAH. It is required for FNs/BNs to enable [reading data by +namespace](#reading-data-by-namespace). ___NOTES:___ @@ -286,7 +287,21 @@ ___NOTES:___ // CARBlockstore returns an IPFS Blockstore providing access to individual shares/nodes of a specific EDS identified by // DataHash and registered on the Store. NOTE: The Blockstore does not store whole Celestia Blocks but IPFS blocks. // We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can access those. -func (s *Store) CARBlockstore(DataHash) (blockstore.Blockstore, error) +func (s *Store) CARBlockstore(context.Context, DataHash) (dagstore.ReadBlockstore, error) +``` + +##### `eds.Store.GetDAH` + +The `GetDAH` method returns the DAH (`share.Root`) of the EDS identified by `DataHash`. Internally it: + +- Acquires a `ShardAccessor` for the corresponding shard +- Reads the CAR Header from the accessor +- Converts the header's root CIDs into a `share.Root` +- Verifies the integrity of the `share.Root` by comparing it with the `DataHash` + +```go +// GetDAH returns the DataAvailabilityHeader for the EDS identified by DataHash. +func (s *Store) GetDAH(context.Context, share.DataHash) (*share.Root, error) ``` ##### `eds.Store.Get` diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index a377fd532a..68992a94aa 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -111,7 +111,7 @@ func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (d // a share can exist in multiple EDSes, so just take the first one. shardKey := keys[0] - accessor, err := bs.store.getAccessor(ctx, shardKey) + accessor, err := bs.store.getCachedAccessor(ctx, shardKey) if err != nil { return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err) } diff --git a/share/eds/blockstore_test.go b/share/eds/blockstore_test.go index 42f7ceab61..72af7c3d40 100644 --- a/share/eds/blockstore_test.go +++ b/share/eds/blockstore_test.go @@ -35,6 +35,10 @@ func TestBlockstore_Operations(t *testing.T) { carBS, err := edsStore.CARBlockstore(ctx, dah.Hash()) require.NoError(t, err) + root, err := edsStore.GetDAH(ctx, dah.Hash()) + require.NoError(t, err) + require.True(t, dah.Equals(root)) + blockstores := []dagstore.ReadBlockstore{topLevelBS, carBS} for { diff --git a/share/eds/store.go b/share/eds/store.go index 88179d1603..84313f3d4b 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -1,6 +1,8 @@ package eds import ( + "bufio" + "bytes" "context" "fmt" "io" @@ -14,10 +16,13 @@ import ( "github.com/filecoin-project/dagstore/shard" "github.com/ipfs/go-datastore" bstore "github.com/ipfs/go-ipfs-blockstore" + carv1 "github.com/ipld/go-car" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" ) const ( @@ -186,7 +191,7 @@ func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, if err != nil { return nil, fmt.Errorf("failed to get accessor: %w", err) } - return accessor.sa, nil + return accessor, nil } // Blockstore returns an IPFS blockstore providing access to individual shares/nodes of all EDS @@ -197,31 +202,59 @@ func (s *Store) Blockstore() bstore.Blockstore { return s.bs } -// CARBlockstore returns the IPFS blockstore that provides access to the IPLD blocks stored in an -// individual CAR file. -func (s *Store) CARBlockstore(ctx context.Context, dataHash []byte) (dagstore.ReadBlockstore, error) { - key := shard.KeyFromString(fmt.Sprintf("%X", dataHash)) +// CARBlockstore returns an IPFS Blockstore providing access to individual shares/nodes of a +// specific EDS identified by DataHash and registered on the Store. NOTE: The Blockstore does not +// store whole Celestia Blocks but IPFS blocks. We represent `shares` and NMT Merkle proofs as IPFS +// blocks and IPLD nodes so Bitswap can access those. +func (s *Store) CARBlockstore( + ctx context.Context, + root share.DataHash, +) (dagstore.ReadBlockstore, error) { + key := shard.KeyFromString(root.String()) + accessor, err := s.getCachedAccessor(ctx, key) + if err != nil { + return nil, fmt.Errorf("eds/store: failed to get accessor: %w", err) + } + return accessor.bs, nil +} + +// GetDAH returns the DataAvailabilityHeader for the EDS identified by DataHash. +func (s *Store) GetDAH(ctx context.Context, root share.DataHash) (*share.Root, error) { + key := shard.KeyFromString(root.String()) accessor, err := s.getAccessor(ctx, key) if err != nil { - return nil, err + return nil, fmt.Errorf("eds/store: failed to get accessor: %w", err) } + defer accessor.Close() - return accessor.bs, nil + carHeader, err := carv1.ReadHeader(bufio.NewReader(accessor)) + if err != nil { + return nil, fmt.Errorf("eds/store: failed to read car header: %w", err) + } + + dah := dahFromCARHeader(carHeader) + if !bytes.Equal(dah.Hash(), root) { + return nil, fmt.Errorf("eds/store: content integrity mismatch from CAR for root %x", root) + } + return dah, nil } -func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*accessorWithBlockstore, error) { - // try to fetch from cache - accessor, err := s.cache.Get(key) - if err != nil && err != errCacheMiss { - log.Errorw("unexpected error while reading key from bs cache %s: %s", key, err) +// dahFromCARHeader returns the DataAvailabilityHeader stored in the CIDs of a CARv1 header. +func dahFromCARHeader(carHeader *carv1.CarHeader) *header.DataAvailabilityHeader { + rootCount := len(carHeader.Roots) + rootBytes := make([][]byte, 0, rootCount) + for _, root := range carHeader.Roots { + rootBytes = append(rootBytes, ipld.NamespacedSha256FromCID(root)) } - if accessor != nil { - return accessor, nil + return &header.DataAvailabilityHeader{ + RowsRoots: rootBytes[:rootCount/2], + ColumnRoots: rootBytes[rootCount/2:], } +} - // wasn't found in cache, so acquire it and add to cache +func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*dagstore.ShardAccessor, error) { ch := make(chan dagstore.ShardResult, 1) - err = s.dgstr.AcquireShard(ctx, key, ch, dagstore.AcquireOpts{}) + err := s.dgstr.AcquireShard(ctx, key, ch, dagstore.AcquireOpts{}) if err != nil { return nil, fmt.Errorf("failed to initialize shard acquisition: %w", err) } @@ -231,12 +264,30 @@ func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*accessorWithBl if res.Error != nil { return nil, fmt.Errorf("failed to acquire shard: %w", res.Error) } - return s.cache.Add(key, res.Accessor) + return res.Accessor, nil case <-ctx.Done(): return nil, ctx.Err() } } +func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessorWithBlockstore, error) { + // try to fetch from cache + accessor, err := s.cache.Get(key) + if err != nil && err != errCacheMiss { + log.Errorf("unexpected error while reading key from bs cache %s: %s", key, err) + } + if accessor != nil { + return accessor, nil + } + + // wasn't found in cache, so acquire it and add to cache + shardAccessor, err := s.getAccessor(ctx, key) + if err != nil { + return nil, err + } + return s.cache.Add(key, shardAccessor) +} + // Remove removes EDS from Store by the given share.Root hash and cleans up all // the indexing. func (s *Store) Remove(ctx context.Context, root share.DataHash) error { diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 709c4bf3c2..1f0dedf4d4 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -210,7 +210,7 @@ func Test_BlockstoreCache(t *testing.T) { assert.ErrorIs(t, err, errCacheMiss) // now get it, so that the key is in the cache - _, err = edsStore.CARBlockstore(ctx, dah.Hash()) + _, err = edsStore.getCachedAccessor(ctx, shardKey) assert.NoError(t, err) _, err = edsStore.cache.Get(shardKey) assert.NoError(t, err, errCacheMiss) From f25b5f12b00c487c3b340f32b2e82699bab4bd35 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 23 Dec 2022 13:10:41 +0200 Subject: [PATCH 0263/1008] feat(header/p2p)!: implement retry mechanism for header/p2p session (#1477) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1406 - [x] implement a retry mechanism in case of failed requests; - [x] punish peer in case of invalid data; - [x] fix issue with peer scoring; ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords Co-authored-by: Ryan --- header/p2p/exchange.go | 18 ++----- header/p2p/exchange_test.go | 66 ++++++++++++++--------- header/p2p/peer_stats.go | 8 ++- header/p2p/session.go | 102 +++++++++++++++++++++++++++++------- 4 files changed, 132 insertions(+), 62 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index d64f10d961..39bcd89db2 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -159,7 +159,7 @@ func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( if amount > ex.Params.MaxRequestSize { return nil, header.ErrHeadersLimitExceeded } - session := newSession(ex.ctx, ex.host, ex.peerTracker.peers(), ex.protocolID) + session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID) defer session.close() return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRequest) } @@ -171,22 +171,10 @@ func (ex *Exchange) GetVerifiedRange( from *header.ExtendedHeader, amount uint64, ) ([]*header.ExtendedHeader, error) { - session := newSession(ex.ctx, ex.host, ex.peerTracker.peers(), ex.protocolID) + session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID, withValidation(from)) defer session.close() - headers, err := session.getRangeByHeight(ctx, uint64(from.Height)+1, amount, ex.Params.MaxHeadersPerRequest) - if err != nil { - return nil, err - } - - for _, h := range headers { - err := from.VerifyAdjacent(h) - if err != nil { - return nil, err - } - from = h - } - return headers, nil + return session.getRangeByHeight(ctx, uint64(from.Height)+1, amount, ex.Params.MaxHeadersPerRequest) } // Get performs a request for the ExtendedHeader by the given hash corresponding diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 0da7343dfc..5e89956855 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -71,8 +71,15 @@ func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { store.Headers[2] = store.Headers[3] // perform expected request h := store.Headers[1] - _, err := exchg.GetVerifiedRange(context.Background(), h, 3) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) + t.Cleanup(cancel) + _, err := exchg.GetVerifiedRange(ctx, h, 3) require.Error(t, err) + + // ensure that peer was added to the blacklist + peers := exchg.peerTracker.connGater.ListBlockedPeers() + require.Len(t, peers, 1) + require.True(t, hosts[1].ID() == peers[0]) } // TestExchange_RequestFullRangeHeaders requests max amount of headers @@ -96,7 +103,9 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { servers[index], err = NewExchangeServer(hosts[index], store, protocolSuffix) require.NoError(t, err) servers[index].Start(context.Background()) //nolint:errcheck + exchange.peerTracker.peerLk.Lock() exchange.peerTracker.trackedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()} + exchange.peerTracker.peerLk.Unlock() } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) @@ -107,30 +116,37 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { require.Len(t, headers, 80) } -// TestExchange_RequestHeadersFails tests that the Exchange instance will return -// header.ErrNotFound if it will not have requested header. -func TestExchange_RequestHeadersFails(t *testing.T) { +// TestExchange_RequestHeadersLimitExceeded tests that the Exchange instance will return +// header.ErrHeadersLimitExceeded if the requested range will be move than MaxRequestSize. +func TestExchange_RequestHeadersLimitExceeded(t *testing.T) { hosts := createMocknet(t, 2) exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) - tt := []struct { - amount uint64 - expectedErr *error - }{ - { - amount: 10, - expectedErr: &header.ErrNotFound, - }, - { - amount: 600, - expectedErr: &header.ErrHeadersLimitExceeded, - }, - } - for _, test := range tt { - // perform expected request - _, err := exchg.GetRangeByHeight(context.Background(), 1, test.amount) - require.Error(t, err) - require.ErrorAs(t, err, test.expectedErr) - } + _, err := exchg.GetRangeByHeight(context.Background(), 1, 600) + require.Error(t, err) + require.ErrorAs(t, err, &header.ErrHeadersLimitExceeded) +} + +// TestExchange_RequestHeadersFromAnotherPeer tests that the Exchange instance will request range +// from another peer with lower score after receiving header.ErrNotFound +func TestExchange_RequestHeadersFromAnotherPeer(t *testing.T) { + hosts := createMocknet(t, 3) + // create client + server(it does not have needed headers) + exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) + // create one more server(with more headers in the store) + serverSideEx, err := NewExchangeServer(hosts[2], headerMock.NewStore(t, 10), "private") + require.NoError(t, err) + require.NoError(t, serverSideEx.Start(context.Background())) + t.Cleanup(func() { + serverSideEx.Stop(context.Background()) //nolint:errcheck + }) + exchg.peerTracker.peerLk.Lock() + exchg.peerTracker.trackedPeers[hosts[2].ID()] = &peerStat{peerID: hosts[2].ID(), peerScore: 20} + exchg.peerTracker.peerLk.Unlock() + _, err = exchg.GetRangeByHeight(context.Background(), 5, 3) + require.NoError(t, err) + // ensure that peerScore for the second peer is changed + newPeerScore := exchg.peerTracker.trackedPeers[hosts[2].ID()].score() + require.NotEqual(t, 20, newPeerScore) } // TestExchange_RequestByHash tests that the Exchange instance can @@ -285,7 +301,7 @@ func createMocknet(t *testing.T, amount int) []libhost.Host { } // createP2PExAndServer creates a Exchange with 5 headers already in its store. -func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchange, *headerMock.MockStore) { +func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (*Exchange, *headerMock.MockStore) { store := headerMock.NewStore(t, 5) serverSideEx, err := NewExchangeServer(tpeer, store, "private") require.NoError(t, err) @@ -295,7 +311,7 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (header.Exchan require.NoError(t, err) ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private", connGater) require.NoError(t, err) - ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID()} + ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID(), peerScore: 100} require.NoError(t, ex.Start(context.Background())) t.Cleanup(func() { diff --git a/header/p2p/peer_stats.go b/header/p2p/peer_stats.go index 91c5b939e2..938b9e014b 100644 --- a/header/p2p/peer_stats.go +++ b/header/p2p/peer_stats.go @@ -29,9 +29,9 @@ type peerStat struct { func (p *peerStat) updateStats(amount uint64, time uint64) { p.Lock() defer p.Unlock() - var averageSpeed float32 + averageSpeed := float32(amount) if time != 0 { - averageSpeed = float32(amount / time) + averageSpeed /= float32(time) } if p.peerScore == 0.0 { p.peerScore = averageSpeed @@ -112,6 +112,10 @@ func newPeerQueue(ctx context.Context, stats []*peerStat) *peerQueue { // in case if there are no peer available in current session, it blocks until // the peer will be pushed in. func (p *peerQueue) waitPop(ctx context.Context) *peerStat { + // TODO(vgonkivs): implement fallback solution for cases when peer queue is empty. + // As we discussed with @Wondertan there could be 2 possible solutions: + // * use libp2p.Discovery to find new peers outside peerTracker to request headers; + // * implement IWANT/IHAVE messaging system and start requesting ranges from the Peerstore; select { case <-ctx.Done(): return &peerStat{} diff --git a/header/p2p/session.go b/header/p2p/session.go index 976b17683d..d799d15940 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -12,30 +12,53 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) +type option func(*session) + +func withValidation(from *header.ExtendedHeader) option { + return func(s *session) { + s.from = from + } +} + // session aims to divide a range of headers // into several smaller requests among different peers. type session struct { - ctx context.Context - cancel context.CancelFunc host host.Host protocolID protocol.ID + queue *peerQueue // peerTracker contains discovered peers with records that describes their activity. - queue *peerQueue + peerTracker *peerTracker - reqCh chan *p2p_pb.ExtendedHeaderRequest - errCh chan error + // `from` is set when additional validation for range is needed. + // Otherwise, it will be nil. + from *header.ExtendedHeader + + ctx context.Context + cancel context.CancelFunc + reqCh chan *p2p_pb.ExtendedHeaderRequest } -func newSession(ctx context.Context, h host.Host, peerTracker []*peerStat, protocolID protocol.ID) *session { +func newSession( + ctx context.Context, + h host.Host, + peerTracker *peerTracker, + protocolID protocol.ID, + options ...option, +) *session { ctx, cancel := context.WithCancel(ctx) - return &session{ - ctx: ctx, - cancel: cancel, - protocolID: protocolID, - host: h, - queue: newPeerQueue(ctx, peerTracker), - errCh: make(chan error), + ses := &session{ + ctx: ctx, + cancel: cancel, + protocolID: protocolID, + host: h, + queue: newPeerQueue(ctx, peerTracker.peers()), + peerTracker: peerTracker, + } + + for _, opt := range options { + opt(ses) } + return ses } // GetRangeByHeight requests headers from different peers. @@ -61,8 +84,6 @@ func (s *session) getRangeByHeight( return nil, errors.New("header/p2p: exchange is closed") case <-ctx.Done(): return nil, ctx.Err() - case err := <-s.errCh: - return nil, err case res := <-result: headers = append(headers, res...) } @@ -114,19 +135,29 @@ func (s *session) doRequest( if err == context.Canceled || err == context.DeadlineExceeded { return } - log.Errorw("requesting headers from peer failed."+ - "Retrying the request from different peer", "failed peer", stat.peerID, "err", err) + log.Errorw("requesting headers from peer failed.", "failed peer", stat.peerID, "err", err) select { case <-ctx.Done(): case <-s.ctx.Done(): + // retry request case s.reqCh <- req: + log.Debug("Retrying the request from different peer") } return } h, err := s.processResponse(r) if err != nil { - s.errCh <- err + switch err { + case header.ErrNotFound: + default: + s.peerTracker.blockPeer(stat.peerID, err) + } + select { + case <-ctx.Done(): + case <-s.ctx.Done(): + case s.reqCh <- req: + } return } log.Debugw("request headers from peer succeed ", "from", s.host.ID(), "pID", stat.peerID, "amount", req.Amount) @@ -147,14 +178,45 @@ func (s *session) processResponse(responses []*p2p_pb.ExtendedHeaderResponse) ([ } header, err := header.UnmarshalExtendedHeader(resp.Body) if err != nil { - return nil, err + return nil, errors.New("unmarshalling error") } headers = append(headers, header) } if len(headers) == 0 { return nil, header.ErrNotFound } - return headers, nil + + err := s.validate(headers) + return headers, err +} + +// validate checks that the received range of headers is valid against the provided header. +func (s *session) validate(headers []*header.ExtendedHeader) error { + // if `from` is empty, then additional validation for the header`s range is not needed. + if s.from == nil { + return nil + } + + // verify that the first header in range is valid against the trusted header. + err := s.from.VerifyNonAdjacent(headers[0]) + if err != nil { + return nil + } + + if len(headers) == 1 { + return nil + } + + trusted := headers[0] + // verify that the whole range is valid. + for i := 1; i < len(headers); i++ { + err = trusted.VerifyAdjacent(headers[i]) + if err != nil { + return err + } + trusted = headers[i] + } + return nil } // prepareRequests converts incoming range into separate ExtendedHeaderRequest. From bcaa4318f5750f3486239dab05c479939efe4ed0 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:40:39 +0100 Subject: [PATCH 0264/1008] feat(cmd/celestia): Add `auth` subcommand to bridge, full and light (#1429) Resolves #1187 This PR adds an `auth` subcommand to `bridge`, `full` and `light` nodes. Intended use: after a node is already initialized and started, a user can use the CLI to generate a signed token with some given perms via the `auth` cmd. Example: ``` celestia bridge auth admin ``` The above command outputs a signed JWT token with admin permissions that a user can use with `client.NewClient()` to access admin-level methods on the node. Reference [this commit](https://github.com/celestiaorg/celestia-node/pull/1429/commits/2c833bca89790ff5503822875751f3330207b87f) for the actual diff. --- cmd/auth.go | 85 ++++++++++++++++++++++++++++++++++ cmd/celestia/bridge.go | 32 ++++++------- cmd/celestia/full.go | 41 +++++++--------- cmd/celestia/light.go | 41 +++++++--------- docs/adr/adr-009-public-api.md | 2 +- nodebuilder/node/auth.go | 30 +++++++++--- nodebuilder/p2p/p2p.go | 10 ++-- nodebuilder/p2p/p2p_test.go | 4 +- 8 files changed, 166 insertions(+), 79 deletions(-) create mode 100644 cmd/auth.go diff --git a/cmd/auth.go b/cmd/auth.go new file mode 100644 index 0000000000..7d5523618e --- /dev/null +++ b/cmd/auth.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "fmt" + "path/filepath" + + "github.com/cristalhq/jwt" + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + + "github.com/celestiaorg/celestia-node/api/rpc/perms" + "github.com/celestiaorg/celestia-node/libs/keystore" + nodemod "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +func AuthCmd(fsets ...*flag.FlagSet) *cobra.Command { + var cmd = &cobra.Command{ + Use: "auth [permission-level (e.g. read || write || admin)]", + Short: "Signs and outputs a hex-encoded JWT token with the given permissions.", + Long: "Signs and outputs a hex-encoded JWT token with the given permissions. NOTE: only use this command when " + + "the node has already been initialized and started.", + RunE: newToken, + } + + for _, set := range fsets { + cmd.Flags().AddFlagSet(set) + } + return cmd +} + +func newToken(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("must specify permissions") + } + + permissions, err := convertToPerms(args[0]) + if err != nil { + return err + } + + expanded, err := homedir.Expand(filepath.Clean(StorePath(cmd.Context()))) + if err != nil { + return err + } + ks, err := keystore.NewFSKeystore(expanded + "/keys") + if err != nil { + return err + } + + key, err := ks.Get(nodemod.SecretName) + if err != nil { + return err + } + signer, err := jwt.NewHS256(key.Body) + if err != nil { + return err + } + + token, err := jwt.NewTokenBuilder(signer).Build(&perms.JWTPayload{ + Allow: permissions, + }) + if err != nil { + return err + } + + fmt.Printf("%s", token.InsecureString()) + return nil +} + +func convertToPerms(perm string) ([]auth.Permission, error) { + perms, ok := stringsToPerms[perm] + if !ok { + return nil, fmt.Errorf("invalid permission specified: %s", perm) + } + return perms, nil +} + +var stringsToPerms = map[string][]auth.Permission{ + "public": perms.DefaultPerms, + "read": perms.ReadPerms, + "write": perms.ReadWritePerms, + "admin": perms.AllPerms, +} diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index 8aeb632da8..9014236541 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -2,6 +2,7 @@ package main import ( "github.com/spf13/cobra" + "github.com/spf13/pflag" cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" @@ -16,25 +17,20 @@ import ( // PersistentPreRun func on parent command. func init() { + flags := []*pflag.FlagSet{ + cmdnode.NodeFlags(), + p2p.Flags(), + core.Flags(), + cmdnode.MiscFlags(), + rpc.Flags(), + gateway.Flags(), + state.Flags(), + } + bridgeCmd.AddCommand( - cmdnode.Init( - cmdnode.NodeFlags(), - p2p.Flags(), - core.Flags(), - cmdnode.MiscFlags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - ), - cmdnode.Start( - cmdnode.NodeFlags(), - p2p.Flags(), - core.Flags(), - cmdnode.MiscFlags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - ), + cmdnode.Init(flags...), + cmdnode.Start(flags...), + cmdnode.AuthCmd(flags...), ) } diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 0cd7fd1900..c5356b3ee5 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -2,6 +2,7 @@ package main import ( "github.com/spf13/cobra" + "github.com/spf13/pflag" cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" @@ -17,31 +18,23 @@ import ( // PersistentPreRun func on parent command. func init() { + flags := []*pflag.FlagSet{ + cmdnode.NodeFlags(), + p2p.Flags(), + header.Flags(), + cmdnode.MiscFlags(), + // NOTE: for now, state-related queries can only be accessed + // over an RPC connection with a celestia-core node. + core.Flags(), + rpc.Flags(), + gateway.Flags(), + state.Flags(), + } + fullCmd.AddCommand( - cmdnode.Init( - cmdnode.NodeFlags(), - p2p.Flags(), - header.Flags(), - cmdnode.MiscFlags(), - // NOTE: for now, state-related queries can only be accessed - // over an RPC connection with a celestia-core node. - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - ), - cmdnode.Start( - cmdnode.NodeFlags(), - p2p.Flags(), - header.Flags(), - cmdnode.MiscFlags(), - // NOTE: for now, state-related queries can only be accessed - // over an RPC connection with a celestia-core node. - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - ), + cmdnode.Init(flags...), + cmdnode.Start(flags...), + cmdnode.AuthCmd(flags...), ) } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index a56d4b30dd..19580638b9 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -2,6 +2,7 @@ package main import ( "github.com/spf13/cobra" + "github.com/spf13/pflag" cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/core" @@ -17,31 +18,23 @@ import ( // PersistentPreRun func on parent command. func init() { + flags := []*pflag.FlagSet{ + cmdnode.NodeFlags(), + p2p.Flags(), + header.Flags(), + cmdnode.MiscFlags(), + // NOTE: for now, state-related queries can only be accessed + // over an RPC connection with a celestia-core node. + core.Flags(), + rpc.Flags(), + gateway.Flags(), + state.Flags(), + } + lightCmd.AddCommand( - cmdnode.Init( - cmdnode.NodeFlags(), - p2p.Flags(), - header.Flags(), - cmdnode.MiscFlags(), - // NOTE: for now, state-related queries can only be accessed - // over an RPC connection with a celestia-core node. - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - ), - cmdnode.Start( - cmdnode.NodeFlags(), - p2p.Flags(), - header.Flags(), - cmdnode.MiscFlags(), - // NOTE: for now, state-related queries can only be accessed - // over an RPC connection with a celestia-core node. - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - ), + cmdnode.Init(flags...), + cmdnode.Start(flags...), + cmdnode.AuthCmd(flags...), ) } diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index acfb6094b9..64e1d9ca54 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -141,7 +141,7 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) ```go type P2PModule interface { // Info returns address information about the host. - Info(context.Context) peer.AddrInfo + Info(context.Context) (peer.AddrInfo, error) // Peers returns all peer IDs used across all inner stores. Peers(context.Context) []peer.ID // PeerInfo returns a small slice of information Peerstore has on the diff --git a/nodebuilder/node/auth.go b/nodebuilder/node/auth.go index abfe214012..9c16af92c3 100644 --- a/nodebuilder/node/auth.go +++ b/nodebuilder/node/auth.go @@ -5,19 +5,37 @@ import ( "io" "github.com/cristalhq/jwt" + + "github.com/celestiaorg/celestia-node/libs/keystore" ) +var SecretName = keystore.KeyName("jwt-secret.jwt") + // secret returns the node's JWT secret if it exists, or generates // and saves a new one if it does not. -// -// TODO @renaynay: -// 1. implement checking for existing key -// 2. implement saving the generated key to disk -// (if the secret needs to be generated) -func secret() (jwt.Signer, error) { +func secret(ks keystore.Keystore) (jwt.Signer, error) { + // if key already exists, use it + if pk, ok := existing(ks); ok { + return jwt.NewHS256(pk) + } + // otherwise, generate and save new priv key sk, err := io.ReadAll(io.LimitReader(rand.Reader, 32)) if err != nil { return nil, err } + // save key + err = ks.Put(SecretName, keystore.PrivKey{Body: sk}) + if err != nil { + return nil, err + } + return jwt.NewHS256(sk) } + +func existing(ks keystore.Keystore) ([]byte, bool) { + sk, err := ks.Get(SecretName) + if err != nil { + return nil, false + } + return sk.Body, true +} diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 03b6c8c8f6..976b77324d 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -24,7 +24,7 @@ var _ Module = (*API)(nil) //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // Info returns address information about the host. - Info(context.Context) peer.AddrInfo + Info(context.Context) (peer.AddrInfo, error) // Peers returns all peer IDs used across all inner stores. Peers(context.Context) []peer.ID // PeerInfo returns a small slice of information Peerstore has on the @@ -98,8 +98,8 @@ func newModule( } } -func (m *module) Info(context.Context) peer.AddrInfo { - return *libhost.InfoFromHost(m.host) +func (m *module) Info(context.Context) (peer.AddrInfo, error) { + return *libhost.InfoFromHost(m.host), nil } func (m *module) Peers(context.Context) []peer.ID { @@ -177,7 +177,7 @@ func (m *module) PubSubPeers(_ context.Context, topic string) []peer.ID { //nolint:dupl type API struct { Internal struct { - Info func(context.Context) peer.AddrInfo `perm:"admin"` + Info func(context.Context) (peer.AddrInfo, error) `perm:"admin"` Peers func(context.Context) []peer.ID `perm:"admin"` PeerInfo func(ctx context.Context, id peer.ID) peer.AddrInfo `perm:"admin"` Connect func(ctx context.Context, pi peer.AddrInfo) error `perm:"admin"` @@ -197,7 +197,7 @@ type API struct { } } -func (api *API) Info(ctx context.Context) peer.AddrInfo { +func (api *API) Info(ctx context.Context) (peer.AddrInfo, error) { return api.Internal.Info(ctx) } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index b5730799de..e5e680f257 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -117,7 +117,9 @@ func TestP2PModule_Bandwidth(t *testing.T) { require.Equal(t, network.Connected, mgr.Connectedness(ctx, peer.ID())) // open stream with host - stream, err := peer.NewStream(ctx, mgr.Info(ctx).ID, protoID) + info, err := mgr.Info(ctx) + require.NoError(t, err) + stream, err := peer.NewStream(ctx, info.ID, protoID) require.NoError(t, err) // write to stream to increase bandwidth usage get some substantive From 0ab5df64adb7a1452043ef7677a448ec1b103dfb Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:21:05 +0100 Subject: [PATCH 0265/1008] refactor(nodebuilder/p2p): Add errors for all methods (#1539) Based on #1429 Adds an error as a return value for all methods as it is needed for elevated permission methods. --- docs/adr/adr-009-public-api.md | 4 +- nodebuilder/p2p/mocks/api.go | 61 +++++++++------- nodebuilder/p2p/p2p.go | 123 +++++++++++++++++---------------- nodebuilder/p2p/p2p_test.go | 61 ++++++++++++---- 4 files changed, 147 insertions(+), 102 deletions(-) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 64e1d9ca54..e13c4dea8e 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -143,10 +143,10 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) // Info returns address information about the host. Info(context.Context) (peer.AddrInfo, error) // Peers returns all peer IDs used across all inner stores. - Peers(context.Context) []peer.ID + Peers(context.Context) ([]peer.ID, error) // PeerInfo returns a small slice of information Peerstore has on the // given peer. - PeerInfo(context.Context, peer.ID) peer.AddrInfo + PeerInfo(context.Context, peer.ID) (peer.AddrInfo, error) // Connect ensures there is a connection between this host and the peer with // given peer. diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go index 62f9855ba0..e7e4fbf88b 100644 --- a/nodebuilder/p2p/mocks/api.go +++ b/nodebuilder/p2p/mocks/api.go @@ -39,11 +39,12 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // BandwidthForPeer mocks base method. -func (m *MockModule) BandwidthForPeer(arg0 context.Context, arg1 peer.ID) metrics.Stats { +func (m *MockModule) BandwidthForPeer(arg0 context.Context, arg1 peer.ID) (metrics.Stats, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BandwidthForPeer", arg0, arg1) ret0, _ := ret[0].(metrics.Stats) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // BandwidthForPeer indicates an expected call of BandwidthForPeer. @@ -53,11 +54,12 @@ func (mr *MockModuleMockRecorder) BandwidthForPeer(arg0, arg1 interface{}) *gomo } // BandwidthForProtocol mocks base method. -func (m *MockModule) BandwidthForProtocol(arg0 context.Context, arg1 protocol.ID) metrics.Stats { +func (m *MockModule) BandwidthForProtocol(arg0 context.Context, arg1 protocol.ID) (metrics.Stats, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BandwidthForProtocol", arg0, arg1) ret0, _ := ret[0].(metrics.Stats) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // BandwidthForProtocol indicates an expected call of BandwidthForProtocol. @@ -67,11 +69,12 @@ func (mr *MockModuleMockRecorder) BandwidthForProtocol(arg0, arg1 interface{}) * } // BandwidthStats mocks base method. -func (m *MockModule) BandwidthStats(arg0 context.Context) metrics.Stats { +func (m *MockModule) BandwidthStats(arg0 context.Context) (metrics.Stats, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BandwidthStats", arg0) ret0, _ := ret[0].(metrics.Stats) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // BandwidthStats indicates an expected call of BandwidthStats. @@ -123,11 +126,12 @@ func (mr *MockModuleMockRecorder) Connect(arg0, arg1 interface{}) *gomock.Call { } // Connectedness mocks base method. -func (m *MockModule) Connectedness(arg0 context.Context, arg1 peer.ID) network.Connectedness { +func (m *MockModule) Connectedness(arg0 context.Context, arg1 peer.ID) (network.Connectedness, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Connectedness", arg0, arg1) ret0, _ := ret[0].(network.Connectedness) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Connectedness indicates an expected call of Connectedness. @@ -137,11 +141,12 @@ func (mr *MockModuleMockRecorder) Connectedness(arg0, arg1 interface{}) *gomock. } // Info mocks base method. -func (m *MockModule) Info(arg0 context.Context) peer.AddrInfo { +func (m *MockModule) Info(arg0 context.Context) (peer.AddrInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Info", arg0) ret0, _ := ret[0].(peer.AddrInfo) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Info indicates an expected call of Info. @@ -151,11 +156,12 @@ func (mr *MockModuleMockRecorder) Info(arg0 interface{}) *gomock.Call { } // IsProtected mocks base method. -func (m *MockModule) IsProtected(arg0 context.Context, arg1 peer.ID, arg2 string) bool { +func (m *MockModule) IsProtected(arg0 context.Context, arg1 peer.ID, arg2 string) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsProtected", arg0, arg1, arg2) ret0, _ := ret[0].(bool) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // IsProtected indicates an expected call of IsProtected. @@ -165,11 +171,12 @@ func (mr *MockModuleMockRecorder) IsProtected(arg0, arg1, arg2 interface{}) *gom } // ListBlockedPeers mocks base method. -func (m *MockModule) ListBlockedPeers(arg0 context.Context) []peer.ID { +func (m *MockModule) ListBlockedPeers(arg0 context.Context) ([]peer.ID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListBlockedPeers", arg0) ret0, _ := ret[0].([]peer.ID) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // ListBlockedPeers indicates an expected call of ListBlockedPeers. @@ -194,11 +201,12 @@ func (mr *MockModuleMockRecorder) NATStatus(arg0 interface{}) *gomock.Call { } // PeerInfo mocks base method. -func (m *MockModule) PeerInfo(arg0 context.Context, arg1 peer.ID) peer.AddrInfo { +func (m *MockModule) PeerInfo(arg0 context.Context, arg1 peer.ID) (peer.AddrInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PeerInfo", arg0, arg1) ret0, _ := ret[0].(peer.AddrInfo) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // PeerInfo indicates an expected call of PeerInfo. @@ -208,11 +216,12 @@ func (mr *MockModuleMockRecorder) PeerInfo(arg0, arg1 interface{}) *gomock.Call } // Peers mocks base method. -func (m *MockModule) Peers(arg0 context.Context) []peer.ID { +func (m *MockModule) Peers(arg0 context.Context) ([]peer.ID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Peers", arg0) ret0, _ := ret[0].([]peer.ID) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Peers indicates an expected call of Peers. @@ -222,9 +231,11 @@ func (mr *MockModuleMockRecorder) Peers(arg0 interface{}) *gomock.Call { } // Protect mocks base method. -func (m *MockModule) Protect(arg0 context.Context, arg1 peer.ID, arg2 string) { +func (m *MockModule) Protect(arg0 context.Context, arg1 peer.ID, arg2 string) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "Protect", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Protect", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 } // Protect indicates an expected call of Protect. @@ -234,11 +245,12 @@ func (mr *MockModuleMockRecorder) Protect(arg0, arg1, arg2 interface{}) *gomock. } // PubSubPeers mocks base method. -func (m *MockModule) PubSubPeers(arg0 context.Context, arg1 string) []peer.ID { +func (m *MockModule) PubSubPeers(arg0 context.Context, arg1 string) ([]peer.ID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PubSubPeers", arg0, arg1) ret0, _ := ret[0].([]peer.ID) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // PubSubPeers indicates an expected call of PubSubPeers. @@ -262,11 +274,12 @@ func (mr *MockModuleMockRecorder) UnblockPeer(arg0, arg1 interface{}) *gomock.Ca } // Unprotect mocks base method. -func (m *MockModule) Unprotect(arg0 context.Context, arg1 peer.ID, arg2 string) bool { +func (m *MockModule) Unprotect(arg0 context.Context, arg1 peer.ID, arg2 string) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Unprotect", arg0, arg1, arg2) ret0, _ := ret[0].(bool) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Unprotect indicates an expected call of Unprotect. diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 976b77324d..e511e5a594 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -26,10 +26,10 @@ type Module interface { // Info returns address information about the host. Info(context.Context) (peer.AddrInfo, error) // Peers returns all peer IDs used across all inner stores. - Peers(context.Context) []peer.ID + Peers(context.Context) ([]peer.ID, error) // PeerInfo returns a small slice of information Peerstore has on the // given peer. - PeerInfo(ctx context.Context, id peer.ID) peer.AddrInfo + PeerInfo(ctx context.Context, id peer.ID) (peer.AddrInfo, error) // Connect ensures there is a connection between this host and the peer with // given peer. @@ -37,7 +37,7 @@ type Module interface { // ClosePeer closes the connection to a given peer. ClosePeer(ctx context.Context, id peer.ID) error // Connectedness returns a state signaling connection capabilities. - Connectedness(ctx context.Context, id peer.ID) network.Connectedness + Connectedness(ctx context.Context, id peer.ID) (network.Connectedness, error) // NATStatus returns the current NAT status. NATStatus(context.Context) (network.Reachability, error) @@ -46,33 +46,33 @@ type Module interface { // UnblockPeer removes a peer from the set of blocked peers. UnblockPeer(ctx context.Context, p peer.ID) error // ListBlockedPeers returns a list of blocked peers. - ListBlockedPeers(context.Context) []peer.ID + ListBlockedPeers(context.Context) ([]peer.ID, error) // Protect adds a peer to the list of peers who have a bidirectional // peering agreement that they are protected from being trimmed, dropped // or negatively scored. - Protect(ctx context.Context, id peer.ID, tag string) + Protect(ctx context.Context, id peer.ID, tag string) error // Unprotect removes a peer from the list of peers who have a bidirectional // peering agreement that they are protected from being trimmed, dropped // or negatively scored, returning a bool representing whether the given // peer is protected or not. - Unprotect(ctx context.Context, id peer.ID, tag string) bool + Unprotect(ctx context.Context, id peer.ID, tag string) (bool, error) // IsProtected returns whether the given peer is protected. - IsProtected(ctx context.Context, id peer.ID, tag string) bool + IsProtected(ctx context.Context, id peer.ID, tag string) (bool, error) // BandwidthStats returns a Stats struct with bandwidth metrics for all // data sent/received by the local peer, regardless of protocol or remote // peer IDs. - BandwidthStats(context.Context) metrics.Stats + BandwidthStats(context.Context) (metrics.Stats, error) // BandwidthForPeer returns a Stats struct with bandwidth metrics associated with the given peer.ID. // The metrics returned include all traffic sent / received for the peer, regardless of protocol. - BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats + BandwidthForPeer(ctx context.Context, id peer.ID) (metrics.Stats, error) // BandwidthForProtocol returns a Stats struct with bandwidth metrics associated with the given // protocol.ID. - BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats + BandwidthForProtocol(ctx context.Context, proto protocol.ID) (metrics.Stats, error) // PubSubPeers returns the peer IDs of the peers joined on // the given topic. - PubSubPeers(ctx context.Context, topic string) []peer.ID + PubSubPeers(ctx context.Context, topic string) ([]peer.ID, error) } // module contains all components necessary to access information and @@ -102,12 +102,12 @@ func (m *module) Info(context.Context) (peer.AddrInfo, error) { return *libhost.InfoFromHost(m.host), nil } -func (m *module) Peers(context.Context) []peer.ID { - return m.host.Peerstore().Peers() +func (m *module) Peers(context.Context) ([]peer.ID, error) { + return m.host.Peerstore().Peers(), nil } -func (m *module) PeerInfo(_ context.Context, id peer.ID) peer.AddrInfo { - return m.host.Peerstore().PeerInfo(id) +func (m *module) PeerInfo(_ context.Context, id peer.ID) (peer.AddrInfo, error) { + return m.host.Peerstore().PeerInfo(id), nil } func (m *module) Connect(ctx context.Context, pi peer.AddrInfo) error { @@ -118,8 +118,8 @@ func (m *module) ClosePeer(_ context.Context, id peer.ID) error { return m.host.Network().ClosePeer(id) } -func (m *module) Connectedness(_ context.Context, id peer.ID) network.Connectedness { - return m.host.Network().Connectedness(id) +func (m *module) Connectedness(_ context.Context, id peer.ID) (network.Connectedness, error) { + return m.host.Network().Connectedness(id), nil } func (m *module) NATStatus(context.Context) (network.Reachability, error) { @@ -139,36 +139,37 @@ func (m *module) UnblockPeer(_ context.Context, p peer.ID) error { return m.connGater.UnblockPeer(p) } -func (m *module) ListBlockedPeers(context.Context) []peer.ID { - return m.connGater.ListBlockedPeers() +func (m *module) ListBlockedPeers(context.Context) ([]peer.ID, error) { + return m.connGater.ListBlockedPeers(), nil } -func (m *module) Protect(_ context.Context, id peer.ID, tag string) { +func (m *module) Protect(_ context.Context, id peer.ID, tag string) error { m.host.ConnManager().Protect(id, tag) + return nil } -func (m *module) Unprotect(_ context.Context, id peer.ID, tag string) bool { - return m.host.ConnManager().Unprotect(id, tag) +func (m *module) Unprotect(_ context.Context, id peer.ID, tag string) (bool, error) { + return m.host.ConnManager().Unprotect(id, tag), nil } -func (m *module) IsProtected(_ context.Context, id peer.ID, tag string) bool { - return m.host.ConnManager().IsProtected(id, tag) +func (m *module) IsProtected(_ context.Context, id peer.ID, tag string) (bool, error) { + return m.host.ConnManager().IsProtected(id, tag), nil } -func (m *module) BandwidthStats(context.Context) metrics.Stats { - return m.bw.GetBandwidthTotals() +func (m *module) BandwidthStats(context.Context) (metrics.Stats, error) { + return m.bw.GetBandwidthTotals(), nil } -func (m *module) BandwidthForPeer(_ context.Context, id peer.ID) metrics.Stats { - return m.bw.GetBandwidthForPeer(id) +func (m *module) BandwidthForPeer(_ context.Context, id peer.ID) (metrics.Stats, error) { + return m.bw.GetBandwidthForPeer(id), nil } -func (m *module) BandwidthForProtocol(_ context.Context, proto protocol.ID) metrics.Stats { - return m.bw.GetBandwidthForProtocol(proto) +func (m *module) BandwidthForProtocol(_ context.Context, proto protocol.ID) (metrics.Stats, error) { + return m.bw.GetBandwidthForProtocol(proto), nil } -func (m *module) PubSubPeers(_ context.Context, topic string) []peer.ID { - return m.ps.ListPeers(topic) +func (m *module) PubSubPeers(_ context.Context, topic string) ([]peer.ID, error) { + return m.ps.ListPeers(topic), nil } // API is a wrapper around Module for the RPC. @@ -177,23 +178,23 @@ func (m *module) PubSubPeers(_ context.Context, topic string) []peer.ID { //nolint:dupl type API struct { Internal struct { - Info func(context.Context) (peer.AddrInfo, error) `perm:"admin"` - Peers func(context.Context) []peer.ID `perm:"admin"` - PeerInfo func(ctx context.Context, id peer.ID) peer.AddrInfo `perm:"admin"` - Connect func(ctx context.Context, pi peer.AddrInfo) error `perm:"admin"` - ClosePeer func(ctx context.Context, id peer.ID) error `perm:"admin"` - Connectedness func(ctx context.Context, id peer.ID) network.Connectedness `perm:"admin"` - NATStatus func(context.Context) (network.Reachability, error) `perm:"admin"` - BlockPeer func(ctx context.Context, p peer.ID) error `perm:"admin"` - UnblockPeer func(ctx context.Context, p peer.ID) error `perm:"admin"` - ListBlockedPeers func(context.Context) []peer.ID `perm:"admin"` - Protect func(ctx context.Context, id peer.ID, tag string) `perm:"admin"` - Unprotect func(ctx context.Context, id peer.ID, tag string) bool `perm:"admin"` - IsProtected func(ctx context.Context, id peer.ID, tag string) bool `perm:"admin"` - BandwidthStats func(context.Context) metrics.Stats `perm:"admin"` - BandwidthForPeer func(ctx context.Context, id peer.ID) metrics.Stats `perm:"admin"` - BandwidthForProtocol func(ctx context.Context, proto protocol.ID) metrics.Stats `perm:"admin"` - PubSubPeers func(ctx context.Context, topic string) []peer.ID `perm:"admin"` + Info func(context.Context) (peer.AddrInfo, error) `perm:"admin"` + Peers func(context.Context) ([]peer.ID, error) `perm:"admin"` + PeerInfo func(ctx context.Context, id peer.ID) (peer.AddrInfo, error) `perm:"admin"` + Connect func(ctx context.Context, pi peer.AddrInfo) error `perm:"admin"` + ClosePeer func(ctx context.Context, id peer.ID) error `perm:"admin"` + Connectedness func(ctx context.Context, id peer.ID) (network.Connectedness, error) `perm:"admin"` + NATStatus func(context.Context) (network.Reachability, error) `perm:"admin"` + BlockPeer func(ctx context.Context, p peer.ID) error `perm:"admin"` + UnblockPeer func(ctx context.Context, p peer.ID) error `perm:"admin"` + ListBlockedPeers func(context.Context) ([]peer.ID, error) `perm:"admin"` + Protect func(ctx context.Context, id peer.ID, tag string) error `perm:"admin"` + Unprotect func(ctx context.Context, id peer.ID, tag string) (bool, error) `perm:"admin"` + IsProtected func(ctx context.Context, id peer.ID, tag string) (bool, error) `perm:"admin"` + BandwidthStats func(context.Context) (metrics.Stats, error) `perm:"admin"` + BandwidthForPeer func(ctx context.Context, id peer.ID) (metrics.Stats, error) `perm:"admin"` + BandwidthForProtocol func(ctx context.Context, proto protocol.ID) (metrics.Stats, error) `perm:"admin"` + PubSubPeers func(ctx context.Context, topic string) ([]peer.ID, error) `perm:"admin"` } } @@ -201,11 +202,11 @@ func (api *API) Info(ctx context.Context) (peer.AddrInfo, error) { return api.Internal.Info(ctx) } -func (api *API) Peers(ctx context.Context) []peer.ID { +func (api *API) Peers(ctx context.Context) ([]peer.ID, error) { return api.Internal.Peers(ctx) } -func (api *API) PeerInfo(ctx context.Context, id peer.ID) peer.AddrInfo { +func (api *API) PeerInfo(ctx context.Context, id peer.ID) (peer.AddrInfo, error) { return api.Internal.PeerInfo(ctx, id) } @@ -217,7 +218,7 @@ func (api *API) ClosePeer(ctx context.Context, id peer.ID) error { return api.Internal.ClosePeer(ctx, id) } -func (api *API) Connectedness(ctx context.Context, id peer.ID) network.Connectedness { +func (api *API) Connectedness(ctx context.Context, id peer.ID) (network.Connectedness, error) { return api.Internal.Connectedness(ctx, id) } @@ -233,34 +234,34 @@ func (api *API) UnblockPeer(ctx context.Context, p peer.ID) error { return api.Internal.UnblockPeer(ctx, p) } -func (api *API) ListBlockedPeers(ctx context.Context) []peer.ID { +func (api *API) ListBlockedPeers(ctx context.Context) ([]peer.ID, error) { return api.Internal.ListBlockedPeers(ctx) } -func (api *API) Protect(ctx context.Context, id peer.ID, tag string) { - api.Internal.Protect(ctx, id, tag) +func (api *API) Protect(ctx context.Context, id peer.ID, tag string) error { + return api.Internal.Protect(ctx, id, tag) } -func (api *API) Unprotect(ctx context.Context, id peer.ID, tag string) bool { +func (api *API) Unprotect(ctx context.Context, id peer.ID, tag string) (bool, error) { return api.Internal.Unprotect(ctx, id, tag) } -func (api *API) IsProtected(ctx context.Context, id peer.ID, tag string) bool { +func (api *API) IsProtected(ctx context.Context, id peer.ID, tag string) (bool, error) { return api.Internal.IsProtected(ctx, id, tag) } -func (api *API) BandwidthStats(ctx context.Context) metrics.Stats { +func (api *API) BandwidthStats(ctx context.Context) (metrics.Stats, error) { return api.Internal.BandwidthStats(ctx) } -func (api *API) BandwidthForPeer(ctx context.Context, id peer.ID) metrics.Stats { +func (api *API) BandwidthForPeer(ctx context.Context, id peer.ID) (metrics.Stats, error) { return api.Internal.BandwidthForPeer(ctx, id) } -func (api *API) BandwidthForProtocol(ctx context.Context, proto protocol.ID) metrics.Stats { +func (api *API) BandwidthForProtocol(ctx context.Context, proto protocol.ID) (metrics.Stats, error) { return api.Internal.BandwidthForProtocol(ctx, proto) } -func (api *API) PubSubPeers(ctx context.Context, topic string) []peer.ID { +func (api *API) PubSubPeers(ctx context.Context, topic string) ([]peer.ID, error) { return api.Internal.PubSubPeers(ctx, topic) } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index e5e680f257..78660c091a 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -31,13 +31,22 @@ func TestP2PModule_Host(t *testing.T) { ctx := context.Background() // test all methods on `manager.host` - assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), mgr.Peers(ctx)) - assert.Equal(t, libhost.InfoFromHost(peer).ID, mgr.PeerInfo(ctx, peer.ID()).ID) + peers, err := mgr.Peers(ctx) + require.NoError(t, err) + assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), peers) + + peerInfo, err := mgr.PeerInfo(ctx, peer.ID()) + require.NoError(t, err) + assert.Equal(t, libhost.InfoFromHost(peer).ID, peerInfo.ID) - assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(ctx, peer.ID())) + connectedness, err := mgr.Connectedness(ctx, peer.ID()) + require.NoError(t, err) + assert.Equal(t, host.Network().Connectedness(peer.ID()), connectedness) // now disconnect using manager and check for connectedness match again assert.NoError(t, mgr.ClosePeer(ctx, peer.ID())) - assert.Equal(t, host.Network().Connectedness(peer.ID()), mgr.Connectedness(ctx, peer.ID())) + connectedness, err = mgr.Connectedness(ctx, peer.ID()) + require.NoError(t, err) + assert.Equal(t, host.Network().Connectedness(peer.ID()), connectedness) } // TestP2PModule_ConnManager tests P2P Module methods on @@ -60,10 +69,18 @@ func TestP2PModule_ConnManager(t *testing.T) { err = mgr.Connect(ctx, *libhost.InfoFromHost(peer)) require.NoError(t, err) - mgr.Protect(ctx, peer.ID(), "test") - assert.True(t, mgr.IsProtected(ctx, peer.ID(), "test")) - mgr.Unprotect(ctx, peer.ID(), "test") - assert.False(t, mgr.IsProtected(ctx, peer.ID(), "test")) + err = mgr.Protect(ctx, peer.ID(), "test") + require.NoError(t, err) + protected, err := mgr.IsProtected(ctx, peer.ID(), "test") + require.NoError(t, err) + assert.True(t, protected) + + ok, err := mgr.Unprotect(ctx, peer.ID(), "test") + require.False(t, ok) + require.NoError(t, err) + protected, err = mgr.IsProtected(ctx, peer.ID(), "test") + require.NoError(t, err) + assert.False(t, protected) } // TestP2PModule_Autonat tests P2P Module methods on @@ -114,7 +131,9 @@ func TestP2PModule_Bandwidth(t *testing.T) { require.NoError(t, err) // check to ensure they're actually connected - require.Equal(t, network.Connected, mgr.Connectedness(ctx, peer.ID())) + connectedness, err := mgr.Connectedness(ctx, peer.ID()) + require.NoError(t, err) + require.Equal(t, network.Connected, connectedness) // open stream with host info, err := mgr.Info(ctx) @@ -137,12 +156,17 @@ func TestP2PModule_Bandwidth(t *testing.T) { // in the background process time.Sleep(time.Second * 2) - stats := mgr.BandwidthStats(ctx) + stats, err := mgr.BandwidthStats(ctx) + require.NoError(t, err) assert.NotNil(t, stats) - peerStat := mgr.BandwidthForPeer(ctx, peer.ID()) + + peerStat, err := mgr.BandwidthForPeer(ctx, peer.ID()) + require.NoError(t, err) assert.NotZero(t, peerStat.TotalIn) assert.Greater(t, int(peerStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc - protoStat := mgr.BandwidthForProtocol(ctx, protoID) + + protoStat, err := mgr.BandwidthForProtocol(ctx, protoID) + require.NoError(t, err) assert.NotZero(t, protoStat.TotalIn) assert.Greater(t, int(protoStat.TotalIn), bufSize) // should be slightly more than buf size due negotiations, etc } @@ -186,7 +210,9 @@ func TestP2PModule_Pubsub(t *testing.T) { // anywhere where gossipsub is used in tests) time.Sleep(1 * time.Second) - assert.Equal(t, len(topic.ListPeers()), len(mgr.PubSubPeers(context.Background(), topicStr))) + psPeers, err := mgr.PubSubPeers(context.Background(), topicStr) + require.NoError(t, err) + assert.Equal(t, len(topic.ListPeers()), len(psPeers)) } // TestP2PModule_ConnGater tests P2P Module methods on @@ -200,7 +226,12 @@ func TestP2PModule_ConnGater(t *testing.T) { ctx := context.Background() assert.NoError(t, mgr.BlockPeer(ctx, "badpeer")) - assert.Len(t, mgr.ListBlockedPeers(ctx), 1) + blocked, err := mgr.ListBlockedPeers(ctx) + require.NoError(t, err) + assert.Len(t, blocked, 1) + assert.NoError(t, mgr.UnblockPeer(ctx, "badpeer")) - assert.Len(t, mgr.ListBlockedPeers(ctx), 0) + blocked, err = mgr.ListBlockedPeers(ctx) + require.NoError(t, err) + assert.Len(t, blocked, 0) } From 7e4f27a85eee8cb675c328e2b4456b4ca3593bb5 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 23 Dec 2022 22:04:52 +0100 Subject: [PATCH 0266/1008] refactor(eds/store): store_test.go cleanup (#1538) --- share/eds/store_test.go | 186 +++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 98 deletions(-) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 1f0dedf4d4..a613ff1572 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -20,8 +20,7 @@ import ( "github.com/celestiaorg/rsmt2d" ) -// TestEDSStore_PutRegistersShard tests if Put registers the shard on the underlying DAGStore -func TestEDSStore_PutRegistersShard(t *testing.T) { +func TestEDSStore(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -30,130 +29,121 @@ func TestEDSStore_PutRegistersShard(t *testing.T) { err = edsStore.Start(ctx) require.NoError(t, err) - eds, dah := randomEDS(t) + // PutRegistersShard tests if Put registers the shard on the underlying DAGStore + t.Run("PutRegistersShard", func(t *testing.T) { + eds, dah := randomEDS(t) - // shard hasn't been registered yet - has, err := edsStore.Has(ctx, dah.Hash()) - assert.False(t, has) - assert.Error(t, err, "shard not found") + // shard hasn't been registered yet + has, err := edsStore.Has(ctx, dah.Hash()) + assert.False(t, has) + assert.Error(t, err, "shard not found") - err = edsStore.Put(ctx, dah.Hash(), eds) - assert.NoError(t, err) + err = edsStore.Put(ctx, dah.Hash(), eds) + assert.NoError(t, err) - _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) - assert.NoError(t, err) -} + _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) + assert.NoError(t, err) + }) -// TestEDSStore_PutIndexesEDS ensures that Putting an EDS indexes it into the car index -func TestEDSStore_PutIndexesEDS(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + // PutIndexesEDS ensures that Putting an EDS indexes it into the car index + t.Run("PutIndexesEDS", func(t *testing.T) { + eds, dah := randomEDS(t) - edsStore, err := newStore(t) - require.NoError(t, err) - err = edsStore.Start(ctx) - require.NoError(t, err) + stat, _ := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) + assert.False(t, stat.Exists) - eds, dah := randomEDS(t) - stat, _ := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) - assert.False(t, stat.Exists) + err = edsStore.Put(ctx, dah.Hash(), eds) + assert.NoError(t, err) - err = edsStore.Put(ctx, dah.Hash(), eds) - assert.NoError(t, err) + stat, err = edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) + assert.True(t, stat.Exists) + assert.NoError(t, err) + }) - stat, err = edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) - assert.True(t, stat.Exists) - assert.NoError(t, err) -} + // GetCAR ensures that the reader returned from GetCAR is capable of reading the CAR header and + // ODS. + t.Run("GetCAR", func(t *testing.T) { + eds, dah := randomEDS(t) -// TestEDSStore_GetCAR ensures that the reader returned from GetCAR is capable of reading the CAR -// header and ODS. -func TestEDSStore_GetCAR(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - edsStore, err := newStore(t) - require.NoError(t, err) - err = edsStore.Start(ctx) - require.NoError(t, err) - - eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), eds) - require.NoError(t, err) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) - r, err := edsStore.GetCAR(ctx, dah.Hash()) - assert.NoError(t, err) - carReader, err := car.NewCarReader(r) + r, err := edsStore.GetCAR(ctx, dah.Hash()) + assert.NoError(t, err) + carReader, err := car.NewCarReader(r) - fmt.Println(car.HeaderSize(carReader.Header)) - assert.NoError(t, err) + fmt.Println(car.HeaderSize(carReader.Header)) + assert.NoError(t, err) - for i := 0; i < 4; i++ { - for j := 0; j < 4; j++ { - original := eds.GetCell(uint(i), uint(j)) - block, err := carReader.Next() - assert.NoError(t, err) - assert.Equal(t, original, block.RawData()[share.NamespaceSize:]) + for i := 0; i < 4; i++ { + for j := 0; j < 4; j++ { + original := eds.GetCell(uint(i), uint(j)) + block, err := carReader.Next() + assert.NoError(t, err) + assert.Equal(t, original, block.RawData()[share.NamespaceSize:]) + } } - } -} + }) -func TestEDSStore_Remove(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + t.Run("Remove", func(t *testing.T) { + eds, dah := randomEDS(t) - edsStore, err := newStore(t) - require.NoError(t, err) - err = edsStore.Start(ctx) - require.NoError(t, err) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) - eds, dah := randomEDS(t) + // assert that file now exists + _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) + assert.NoError(t, err) - err = edsStore.Put(ctx, dah.Hash(), eds) - require.NoError(t, err) + err = edsStore.Remove(ctx, dah.Hash()) + assert.NoError(t, err) - // assert that file now exists - _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) - assert.NoError(t, err) + // shard should no longer be registered on the dagstore + _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) + assert.Error(t, err, "shard not found") - err = edsStore.Remove(ctx, dah.Hash()) - assert.NoError(t, err) + // shard should have been dropped from the index, which also removes the file under /index/ + indexStat, err := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) + assert.NoError(t, err) + assert.False(t, indexStat.Exists) - // shard should no longer be registered on the dagstore - _, err = edsStore.dgstr.GetShardInfo(shard.KeyFromString(dah.String())) - assert.Error(t, err, "shard not found") + // file no longer exists + _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) + assert.ErrorContains(t, err, "no such file or directory") + }) - // shard should have been dropped from the index, which also removes the file under /index/ - indexStat, err := edsStore.carIdx.StatFullIndex(shard.KeyFromString(dah.String())) - assert.NoError(t, err) - assert.False(t, indexStat.Exists) + t.Run("Has", func(t *testing.T) { + eds, dah := randomEDS(t) - // file no longer exists - _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) - assert.ErrorContains(t, err, "no such file or directory") -} + ok, err := edsStore.Has(ctx, dah.Hash()) + assert.Error(t, err, "shard not found") + assert.False(t, ok) -func TestEDSStore_Has(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + err = edsStore.Put(ctx, dah.Hash(), eds) + assert.NoError(t, err) - edsStore, err := newStore(t) - require.NoError(t, err) - err = edsStore.Start(ctx) - require.NoError(t, err) + ok, err = edsStore.Has(ctx, dah.Hash()) + assert.NoError(t, err) + assert.True(t, ok) + }) - eds, dah := randomEDS(t) + t.Run("BlockstoreCache", func(t *testing.T) { + eds, dah := randomEDS(t) - ok, err := edsStore.Has(ctx, dah.Hash()) - assert.Error(t, err, "shard not found") - assert.False(t, ok) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) - err = edsStore.Put(ctx, dah.Hash(), eds) - assert.NoError(t, err) + // key isnt in cache yet, so get returns errCacheMiss + shardKey := shard.KeyFromString(dah.String()) + _, err = edsStore.cache.Get(shardKey) + assert.ErrorIs(t, err, errCacheMiss) - ok, err = edsStore.Has(ctx, dah.Hash()) - assert.NoError(t, err) - assert.True(t, ok) + // now get it, so that the key is in the cache + _, err = edsStore.CARBlockstore(ctx, dah.Hash()) + assert.NoError(t, err) + _, err = edsStore.cache.Get(shardKey) + assert.NoError(t, err, errCacheMiss) + }) } // TestEDSStore_GC verifies that unused transient shards are collected by the GC periodically. From 6eec517d55cf47ed228944663711d7ccd3908052 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 2 Jan 2023 18:29:07 +0100 Subject: [PATCH 0267/1008] chore(modheader): don't ignore Syncer error on start (#1553) --- nodebuilder/header/module.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 052817e052..101222bbea 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -12,7 +12,7 @@ import ( "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/header/sync" - fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -63,19 +63,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(fx.Annotate( newSyncer, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, syncer *sync.Syncer) error { - syncerStartFunc := func(ctx context.Context) error { - err := syncer.Start(ctx) - switch err { - default: - return err - case header.ErrNoHead: - log.Warnw("Syncer running on uninitialized Store - headers won't be synced") - case nil: - } - return nil - } - return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, - syncerStartFunc, syncer.Stop) + return modfraud.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, + syncer.Start, syncer.Stop) }), fx.OnStop(func(ctx context.Context, syncer *sync.Syncer) error { return syncer.Stop(ctx) From 13740bd6a01a4bd99936184749648369ff4c856f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 2 Jan 2023 18:35:04 +0100 Subject: [PATCH 0268/1008] fix(header/p2p): preconnect trusted peers on header-ex start (#1552) We need to connect to trusted peers and kick off various protocols to start talking to them. In the past, a node was always connected to trusted peers to get headers from them. As from recent improvements to header-ex, we now only rely on trusted peers for initialization. Hence, if a node is already initialized, it won't contact go to connect to trusted peers, and this commit fixes this. This was found during fixing no trusted peers issue in swamp tests in #1556 --- header/p2p/exchange.go | 6 ++++++ header/p2p/peer_tracker.go | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 39bcd89db2..89fec0536d 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -76,6 +76,12 @@ func (ex *Exchange) Start(context.Context) error { go ex.peerTracker.gc() go ex.peerTracker.track() + for _, p := range ex.trustedPeers { + // Try to pre-connect to trusted peers. + // We don't really care if we succeed at this point + // and just need any peers in the peerTracker asap + go ex.host.Connect(ex.ctx, peer.AddrInfo{ID: p}) //nolint:errcheck + } return nil } diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go index 6bbe87cdae..ebecf0c51a 100644 --- a/header/p2p/peer_tracker.go +++ b/header/p2p/peer_tracker.go @@ -71,8 +71,8 @@ func (p *peerTracker) track() { }() // store peers that have been already connected - for _, peer := range p.host.Peerstore().Peers() { - p.connected(peer) + for _, c := range p.host.Network().Conns() { + p.connected(c.RemotePeer()) } subs, err := p.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) From a5715d55d537ec717f849f5e5f70dd3873aa4ab2 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 2 Jan 2023 18:46:44 +0100 Subject: [PATCH 0269/1008] fix(ipld): Make namespaceHasher fully implement hash.Hash and update go-multihash (#1546) In preparation for the full libp2p update * New version of go-multihash has stricter rules on passed hash.Hash implementation and check size * Previously, we returned 32 as what underlying sha256 returns, but in fact Sum results in 48, thus the change * Removes old ugly 'if' and supporting tests that came as a solution for go-multihash omitting error check after Write * Makes namespaceHasher fully compliant with hash.Hash and it can now be reused anywhere and ideally moved to NMT lib LI or documentation updates --- go.mod | 2 +- go.sum | 4 +-- nodebuilder/node/mocks/api.go | 3 +- share/ipld/namespace_hasher.go | 56 +++++++++++++++++++---------- share/ipld/namespace_hasher_test.go | 17 +++------ 5 files changed, 47 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index a13b08ccfa..1dc9a6898b 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.7.0 - github.com/multiformats/go-multihash v0.2.0 + github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/spf13/cobra v1.6.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 0787e35556..3b840eaf35 100644 --- a/go.sum +++ b/go.sum @@ -1435,8 +1435,8 @@ github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= -github.com/multiformats/go-multihash v0.2.0 h1:oytJb9ZA1OUW0r0f9ea18GiaPOo4SXyc7p2movyUuo4= -github.com/multiformats/go-multihash v0.2.0/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 h1:qLF997Rz0X1WvdcZ2r5CUkLZ2rvdiXwG1JRSrJZEAuE= +github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6/go.mod h1:kaHxr8TfO1cxIR/tYxgZ7e59HraJq8arEQQR8E/YNvI= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index 98df713429..39d500bc04 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - node "github.com/celestiaorg/celestia-node/nodebuilder/node" auth "github.com/filecoin-project/go-jsonrpc/auth" gomock "github.com/golang/mock/gomock" + + node "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // MockModule is a mock of Module interface. diff --git a/share/ipld/namespace_hasher.go b/share/ipld/namespace_hasher.go index b849b703e2..bf7eb8ddf8 100644 --- a/share/ipld/namespace_hasher.go +++ b/share/ipld/namespace_hasher.go @@ -19,16 +19,29 @@ func init() { } // namespaceHasher implements hash.Hash over NMT Hasher. -// TODO: Move to NMT repo? +// TODO: Move to NMT repo! +// +// As NMT already defines Hasher that implements hash.Hash by embedding, +// it should define full and *correct* implementation of hash.Hash, +// which is the namespaceHasher. This hash.Hash, is not IPLD specific and +// can be used elsewhere. type namespaceHasher struct { - *nmt.Hasher - tp byte - data []byte + hasher *nmt.Hasher + + tp byte // keeps type of NMT node to be hashed + data []byte // written data of the NMT node } // defaultHasher constructs the namespaceHasher with default configuration func defaultHasher() *namespaceHasher { - return &namespaceHasher{Hasher: nmt.NewNmtHasher(sha256.New(), NamespaceSize, true)} + nh := &namespaceHasher{hasher: nmt.NewNmtHasher(sha256.New(), NamespaceSize, true)} + nh.Reset() + return nh +} + +// Size returns the number of bytes Sum will return. +func (n *namespaceHasher) Size() int { + return n.hasher.Size() + int(n.hasher.NamespaceLen)*2 } // Write writes the namespaced data to be hashed. @@ -37,14 +50,14 @@ func defaultHasher() *namespaceHasher { // Only one write is allowed. func (n *namespaceHasher) Write(data []byte) (int, error) { if n.data != nil { - return 0, fmt.Errorf("ipld: only one write to hasher is allowed") + panic("namespaceHasher: only single Write is allowed") } ln := len(data) switch ln { default: log.Warnf("unexpected data size: %d", ln) - return 0, fmt.Errorf("ipld: wrong sized data written to the hasher, len: %v", ln) + return 0, fmt.Errorf("wrong sized data written to the hasher, len: %v", ln) case innerNodeSize: n.tp = nmt.NodePrefix case leafNodeSize: @@ -58,18 +71,25 @@ func (n *namespaceHasher) Write(data []byte) (int, error) { // Sum computes the hash. // Does not append the given suffix and violating the interface. func (n *namespaceHasher) Sum([]byte) []byte { - // if n.data is empty, it hit the default case in Write. - // this will be seen by multihash.encodeHash, where it will be caught and an error will be returned. - if len(n.data) == 0 { - return nil + switch n.tp { + case nmt.LeafPrefix: + return n.hasher.HashLeaf(n.data) + case nmt.NodePrefix: + flagLen := int(n.hasher.NamespaceLen * 2) + sha256Len := n.hasher.Size() + return n.hasher.HashNode(n.data[:flagLen+sha256Len], n.data[flagLen+sha256Len:]) + default: + panic("namespaceHasher: node type wasn't set") } +} - isLeafData := n.tp == nmt.LeafPrefix - if isLeafData { - return n.Hasher.HashLeaf(n.data) - } +// Reset resets the Hash to its initial state. +func (n *namespaceHasher) Reset() { + n.tp, n.data = 255, nil // reset with an invalid node type, as zero value is a valid Leaf + n.hasher.Reset() +} - flagLen := int(n.NamespaceLen * 2) - sha256Len := n.Hasher.Size() - return n.Hasher.HashNode(n.data[:flagLen+sha256Len], n.data[flagLen+sha256Len:]) +// BlockSize returns the hash's underlying block size. +func (n *namespaceHasher) BlockSize() int { + return n.hasher.BlockSize() } diff --git a/share/ipld/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go index a14c3fe573..4bc0c9f9c0 100644 --- a/share/ipld/namespace_hasher_test.go +++ b/share/ipld/namespace_hasher_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/appconsts" ) @@ -44,9 +45,9 @@ func TestNamespaceHasherWrite(t *testing.T) { assert.NoError(t, err) assert.Equal(t, leafSize, n) - n, err = h.Write(make([]byte, leafSize)) - assert.Error(t, err) - assert.Equal(t, 0, n) + require.Panics(t, func() { + _, _ = h.Write(make([]byte, leafSize)) + }) }) t.Run("ErrorIncorrectSize", func(t *testing.T) { @@ -75,16 +76,6 @@ func TestNamespaceHasherSum(t *testing.T) { NmtHashSize, innerSize, }, - { - "ShortGarbage", - 0, - 13, - }, - { - "LongGarbage", - 0, - 500, - }, } for _, ts := range tt { From 59bb7a825682a3bef37d124d03996fb6596e4d7a Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 3 Jan 2023 08:57:53 +0300 Subject: [PATCH 0270/1008] fix(share): order proof nodes by depth before returning (#1545) Fix the order of inclusion proof nodes returned by `GetSharesByNamespace` to be possible incorrect. --- share/ipld/get.go | 7 +++---- share/ipld/proof.go | 29 ++++++++++++++++++----------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/share/ipld/get.go b/share/ipld/get.go index eb477a2cfc..b5981137c3 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -292,8 +292,7 @@ func GetLeavesByNamespace( id: lnk.Cid, // sharePos represents potential share position in share slice sharePos: j.sharePos*2 + i, - // position represents the index in a flattened binary tree, - // so we can return a slice of leaves in order + // depth represents the number of edges present in path from the root node of a tree to that node depth: j.depth + 1, // we pass the context to job so that spans are tracked in a tree // structure @@ -306,7 +305,7 @@ func GetLeavesByNamespace( // proof is on the right side, if the nID is less than min namespace of jobNid if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) { if collectProofs { - proofs.addRight(lnk.Cid) + proofs.addRight(lnk.Cid, newJob.depth) } continue } @@ -314,7 +313,7 @@ func GetLeavesByNamespace( // proof is on the left side, if the nID is bigger than max namespace of jobNid if !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { if collectProofs { - proofs.addLeft(lnk.Cid) + proofs.addLeft(lnk.Cid, newJob.depth) } continue } diff --git a/share/ipld/proof.go b/share/ipld/proof.go index 22aba35ebb..407335ed94 100644 --- a/share/ipld/proof.go +++ b/share/ipld/proof.go @@ -20,32 +20,39 @@ type proofCollector struct { func newProofCollector(maxShares int) *proofCollector { // maximum possible amount of required proofs from each side is equal to tree height. - height := int(math.Log2(float64(maxShares))) + height := int(math.Log2(float64(maxShares))) + 1 return &proofCollector{ - left: make([]cid.Cid, 0, height), - right: make([]cid.Cid, 0, height), + left: make([]cid.Cid, height), + right: make([]cid.Cid, height), } } -func (c *proofCollector) addLeft(node cid.Cid) { - c.left = append(c.left, node) +func (c *proofCollector) addLeft(cid cid.Cid, depth int) { + c.left[depth] = cid } -func (c *proofCollector) addRight(node cid.Cid) { - c.right = append(c.right, node) +func (c *proofCollector) addRight(cid cid.Cid, depth int) { + c.right[depth] = cid } // Nodes returns nodes collected by proofCollector in the order that nmt.Proof validator will use // to traverse the tree. func (c *proofCollector) Nodes() []cid.Cid { - nodes := make([]cid.Cid, 0, len(c.left)+len(c.left)) + cids := make([]cid.Cid, 0, len(c.left)+len(c.right)) // left side will be traversed in bottom-up order - nodes = append(nodes, c.left...) + for _, cid := range c.left { + if cid.Defined() { + cids = append(cids, cid) + } + } // right side of the tree will be traversed from top to bottom, // so sort in reversed order for i := len(c.right) - 1; i >= 0; i-- { - nodes = append(nodes, c.right[i]) + cid := c.right[i] + if cid.Defined() { + cids = append(cids, cid) + } } - return nodes + return cids } From 8f13efab8aa314d52708dea8873eea70027745cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 09:34:52 +0000 Subject: [PATCH 0271/1008] chore(deps): bump mheap/github-action-required-labels from 2 to 3 (#1559) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 8c92eed914..df6c86eaf1 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -12,7 +12,7 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@v2 + - uses: mheap/github-action-required-labels@v3 with: mode: minimum count: 1 From bada0429d18b55edbd1d8b98a4d0fb23586a0ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Thu, 5 Jan 2023 11:39:35 +0100 Subject: [PATCH 0272/1008] fix: use udp/quic instead of tcp/quic in NoAnnounceAddress (#1560) Resolves https://github.com/celestiaorg/celestia-node/issues/1457 --- nodebuilder/p2p/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 59fc6a6e65..e623b5444e 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -47,7 +47,7 @@ func DefaultConfig() Config { AnnounceAddresses: []string{}, NoAnnounceAddresses: []string{ "/ip4/0.0.0.0/udp/2121/quic", - "/ip4/127.0.0.1/tcp/2121/quic", + "/ip4/127.0.0.1/udp/2121/quic", "/ip6/::/udp/2121/quic", "/ip4/0.0.0.0/tcp/2121", "/ip4/127.0.0.1/tcp/2121", From cbc49b97506ebe202dcf61c285a630dc4a6827da Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 5 Jan 2023 15:00:18 +0100 Subject: [PATCH 0273/1008] tests(swamp): correct fraud syncing test (#1555) Previously it succeeded, but the FP was actually transmitted through PubSub and not fraud syncing. It also makes the test discovery independent. --- nodebuilder/tests/fraud_test.go | 37 +++++++++++++++------------------ 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index db5ba91c57..4f058c0f74 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -97,14 +97,7 @@ Steps: func TestFraudProofSyncing(t *testing.T) { sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*300)) - const defaultTimeInterval = time.Second * 5 - cfg := nodebuilder.DefaultConfig(node.Bridge) - cfg.P2P.Bootstrapper = true - cfg.P2P.RoutingTableRefreshPeriod = defaultTimeInterval - cfg.Share.DiscoveryInterval = defaultTimeInterval - cfg.Share.AdvertiseInterval = defaultTimeInterval - store := nodebuilder.MockStore(t, cfg) bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(header.FraudMaker(t, 10))) @@ -122,32 +115,36 @@ func TestFraudProofSyncing(t *testing.T) { full := sw.NewNodeWithStore(node.Full, nodebuilder.MockStore(t, fullCfg)) lightCfg := nodebuilder.DefaultConfig(node.Light) - lightCfg.P2P.RoutingTableRefreshPeriod = defaultTimeInterval - lightCfg.Share.DiscoveryInterval = defaultTimeInterval lightCfg.Header.TrustedPeers = append(lightCfg.Header.TrustedPeers, addrs[0].String()) ln := sw.NewNodeWithStore(node.Light, nodebuilder.MockStore(t, lightCfg)) - require.NoError(t, full.Start(ctx)) - require.NoError(t, ln.Start(ctx)) + subsFN, err := full.FraudServ.Subscribe(ctx, fraud.BadEncoding) require.NoError(t, err) + select { + case <-subsFN: + case <-ctx.Done(): + t.Fatal("full node didn't get FP in time") + } + + // start LN to enforce syncing logic, not the PubSub's broadcasting + err = ln.Start(ctx) + require.NoError(t, err) + // internal subscription for the fraud proof is done in order to ensure that light node // receives the BEFP. subsLN, err := ln.FraudServ.Subscribe(ctx, fraud.BadEncoding) require.NoError(t, err) - // ensure that the full and light node are connected to preempt flakiness + // ensure that the full and light node are connected to speed up test + // alternatively, they would discover each other err = ln.Host.Connect(ctx, *host.InfoFromHost(full.Host)) require.NoError(t, err) - // wait for BEFP to come through both subscriptions - for i := 0; i < 2; i++ { - select { - case <-subsFN: - case <-subsLN: - case <-ctx.Done(): - t.Fatal(ctx.Err()) - } + select { + case <-subsLN: + case <-ctx.Done(): + t.Fatal("light node didn't get FP in time") } } From 054d494710ed6ac9a31fa564271c71259a76f821 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 5 Jan 2023 16:36:44 +0100 Subject: [PATCH 0274/1008] fix(header): don't stop Subscriber in Syncer and remove Stop from interface (#1554) We need to fix this earlier to fix fraud tests. Closes #1386 --- header/interface.go | 2 -- header/sync/sync.go | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/header/interface.go b/header/interface.go index b6a439f4f1..897e5aec93 100644 --- a/header/interface.go +++ b/header/interface.go @@ -35,8 +35,6 @@ type Subscriber interface { // before they are sent through Subscriptions. // Multiple validators can be registered. AddValidator(Validator) error - // Stop removes header-sub validator and closes the topic. - Stop(context.Context) error } // Subscription can retrieve the next ExtendedHeader from the diff --git a/header/sync/sync.go b/header/sync/sync.go index 55df34e99c..7d88650f7a 100644 --- a/header/sync/sync.go +++ b/header/sync/sync.go @@ -87,9 +87,9 @@ func (s *Syncer) Start(ctx context.Context) error { } // Stop stops Syncer. -func (s *Syncer) Stop(ctx context.Context) error { +func (s *Syncer) Stop(context.Context) error { s.cancel() - return s.sub.Stop(ctx) + return nil } // WaitSync blocks until ongoing sync is done. From c1e51f81b337c897b63fc55178e2690f6a9b1639 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 5 Jan 2023 16:53:43 +0100 Subject: [PATCH 0275/1008] deps: bump go-libp2p and all supporting pkgs (#1547) Based on #1546 Closes #1029 Also enable quic-v1 --- api/rpc_test.go | 2 +- cmd/cel-shed/p2p.go | 4 +- fraud/helpers.go | 2 +- fraud/requester.go | 2 +- fraud/service.go | 6 +- fraud/service_test.go | 2 +- fraud/sync.go | 6 +- fraud/testing.go | 2 +- go.mod | 105 ++++---- go.sum | 254 +++++++----------- header/core/listener_test.go | 2 +- header/p2p/exchange.go | 6 +- header/p2p/exchange_test.go | 4 +- header/p2p/helpers.go | 6 +- header/p2p/peer_stats.go | 2 +- header/p2p/peer_stats_test.go | 2 +- header/p2p/peer_tracker.go | 8 +- header/p2p/peer_tracker_test.go | 2 +- header/p2p/server.go | 6 +- header/p2p/session.go | 4 +- header/p2p/subscriber.go | 2 +- header/p2p/subscription_test.go | 2 +- nodebuilder/fraud/constructors.go | 2 +- nodebuilder/header/config.go | 2 +- nodebuilder/header/constructors.go | 6 +- nodebuilder/node.go | 6 +- nodebuilder/node_light_test.go | 2 +- nodebuilder/p2p/addrs.go | 2 +- nodebuilder/p2p/bitswap.go | 4 +- nodebuilder/p2p/bootstrap.go | 2 +- nodebuilder/p2p/config.go | 12 +- nodebuilder/p2p/host.go | 14 +- nodebuilder/p2p/identity.go | 6 +- nodebuilder/p2p/misc.go | 8 +- nodebuilder/p2p/mocks/api.go | 8 +- nodebuilder/p2p/module.go | 2 +- nodebuilder/p2p/network.go | 2 +- nodebuilder/p2p/opts.go | 4 +- nodebuilder/p2p/p2p.go | 10 +- nodebuilder/p2p/p2p_test.go | 10 +- nodebuilder/p2p/pubsub.go | 2 +- nodebuilder/p2p/routing.go | 4 +- nodebuilder/settings.go | 2 +- nodebuilder/share/constructors.go | 4 +- nodebuilder/share/module.go | 2 +- nodebuilder/tests/fraud_test.go | 4 +- nodebuilder/tests/p2p_test.go | 8 +- nodebuilder/tests/reconstruct_test.go | 6 +- nodebuilder/tests/swamp/swamp.go | 5 +- nodebuilder/tests/sync_test.go | 4 +- share/availability.go | 2 +- share/availability/cache/availability.go | 2 +- share/availability/cache/availability_test.go | 2 +- share/availability/discovery/backoff.go | 4 +- share/availability/discovery/backoff_test.go | 2 +- share/availability/discovery/discovery.go | 14 +- share/availability/discovery/set.go | 2 +- share/availability/full/availability.go | 2 +- share/availability/light/availability.go | 2 +- share/availability/test/testing.go | 4 +- share/p2p/shrexeds/client.go | 6 +- share/p2p/shrexeds/exchange_test.go | 4 +- share/p2p/shrexeds/options.go | 2 +- share/p2p/shrexeds/server.go | 6 +- share/p2p/shrexpush/pubsub.go | 4 +- 65 files changed, 285 insertions(+), 347 deletions(-) diff --git a/api/rpc_test.go b/api/rpc_test.go index d5ac56c5e5..218f31d2de 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -11,7 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cristalhq/jwt" "github.com/golang/mock/gomock" - "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p/core/network" "github.com/stretchr/testify/require" "go.uber.org/fx" diff --git a/cmd/cel-shed/p2p.go b/cmd/cel-shed/p2p.go index 0dd2c36e67..a313841fa9 100644 --- a/cmd/cel-shed/p2p.go +++ b/cmd/cel-shed/p2p.go @@ -5,8 +5,8 @@ import ( "encoding/hex" "fmt" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" "github.com/spf13/cobra" ) diff --git a/fraud/helpers.go b/fraud/helpers.go index d4b7fa9624..260232bd95 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -3,8 +3,8 @@ package fraud import ( "context" - "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" ) func join(p *pubsub.PubSub, proofType ProofType, diff --git a/fraud/requester.go b/fraud/requester.go index 78184915c1..06d960a1e4 100644 --- a/fraud/requester.go +++ b/fraud/requester.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/go-libp2p-messenger/serde" diff --git a/fraud/service.go b/fraud/service.go index cea4ec0a60..f9c260f4ab 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -9,10 +9,10 @@ import ( "sync" "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" diff --git a/fraud/service_test.go b/fraud/service_test.go index 184380c2e5..a1f405e3d1 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -9,9 +9,9 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/sync" - "github.com/libp2p/go-libp2p-core/host" pubsub "github.com/libp2p/go-libp2p-pubsub" pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb" + "github.com/libp2p/go-libp2p/core/host" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" diff --git a/fraud/sync.go b/fraud/sync.go index 0dd0eaabd1..cb51810748 100644 --- a/fraud/sync.go +++ b/fraud/sync.go @@ -5,10 +5,10 @@ import ( "time" "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/event" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/event" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" diff --git a/fraud/testing.go b/fraud/testing.go index 9a8b64a0f1..ae7e7cd8f2 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -9,8 +9,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" - "github.com/libp2p/go-libp2p-core/host" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" diff --git a/go.mod b/go.mod index 1dc9a6898b..475180c447 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/ipfs/go-bitswap v0.8.0 + github.com/ipfs/go-bitswap v0.11.0 github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.4.0 github.com/ipfs/go-cid v0.3.2 @@ -34,23 +34,21 @@ require ( github.com/ipfs/go-ipfs-blockstore v1.2.0 github.com/ipfs/go-ipfs-exchange-interface v0.2.0 github.com/ipfs/go-ipfs-exchange-offline v0.3.0 - github.com/ipfs/go-ipfs-routing v0.2.1 + github.com/ipfs/go-ipfs-routing v0.3.0 github.com/ipfs/go-ipld-cbor v0.0.5 github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.7.0 github.com/ipld/go-car v0.5.0 - github.com/libp2p/go-libp2p v0.21.0 - github.com/libp2p/go-libp2p-core v0.19.1 - github.com/libp2p/go-libp2p-kad-dht v0.17.0 - github.com/libp2p/go-libp2p-peerstore v0.7.1 - github.com/libp2p/go-libp2p-pubsub v0.7.0 - github.com/libp2p/go-libp2p-record v0.1.3 - github.com/libp2p/go-libp2p-routing-helpers v0.2.3 + github.com/libp2p/go-libp2p v0.24.1 + github.com/libp2p/go-libp2p-kad-dht v0.20.0 + github.com/libp2p/go-libp2p-pubsub v0.8.2 + github.com/libp2p/go-libp2p-record v0.2.0 + github.com/libp2p/go-libp2p-routing-helpers v0.4.0 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.7.0 + github.com/multiformats/go-multiaddr v0.8.0 github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/spf13/cobra v1.6.0 @@ -66,9 +64,9 @@ require ( go.opentelemetry.io/otel/trace v1.11.1 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.8.0 - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 - golang.org/x/text v0.4.0 + golang.org/x/crypto v0.3.0 + golang.org/x/sync v0.1.0 + golang.org/x/text v0.5.0 google.golang.org/grpc v1.51.0 ) @@ -98,13 +96,12 @@ require ( github.com/celestiaorg/quantum-gravity-bridge v1.2.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/cheekybits/genny v1.0.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/confio/ics23/go v0.7.0 // indirect github.com/containerd/cgroups v1.0.4 // indirect - github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect @@ -119,11 +116,11 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set v1.8.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/docker/go-units v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect @@ -156,6 +153,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect + github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.4.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect @@ -185,10 +183,10 @@ require ( github.com/ipfs/go-ipfs-pq v0.0.2 // indirect github.com/ipfs/go-ipfs-util v0.0.2 // indirect github.com/ipfs/go-ipld-legacy v0.1.0 // indirect - github.com/ipfs/go-ipns v0.1.2 // indirect + github.com/ipfs/go-ipns v0.2.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect - github.com/ipfs/go-peertaskqueue v0.7.0 // indirect + github.com/ipfs/go-peertaskqueue v0.8.0 // indirect github.com/ipfs/go-verifcid v0.0.1 // indirect github.com/ipld/go-car/v2 v2.4.1 // indirect github.com/ipld/go-codec-dagpb v1.3.1 // indirect @@ -199,36 +197,32 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.15.6 // indirect - github.com/klauspost/cpuid/v2 v2.1.1 // indirect + github.com/klauspost/compress v1.15.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.1 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect - github.com/libp2p/go-eventbus v0.2.1 // indirect - github.com/libp2p/go-flow-metrics v0.0.3 // indirect + github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect - github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect - github.com/libp2p/go-libp2p-loggables v0.1.0 // indirect - github.com/libp2p/go-libp2p-resource-manager v0.5.1 // indirect + github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect github.com/libp2p/go-msgio v0.2.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect - github.com/libp2p/go-netroute v0.2.0 // indirect - github.com/libp2p/go-openssl v0.0.7 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect + github.com/libp2p/go-openssl v0.1.0 // indirect github.com/libp2p/go-reuseport v0.2.0 // indirect - github.com/libp2p/go-yamux/v3 v3.1.2 // indirect - github.com/lucas-clemente/quic-go v0.28.0 // indirect + github.com/libp2p/go-yamux/v4 v4.0.0 // indirect + github.com/lucas-clemente/quic-go v0.31.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect - github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect - github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect - github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect + github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect + github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-pointer v0.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -238,15 +232,14 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/multiformats/go-base36 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multicodec v0.5.0 // indirect + github.com/multiformats/go-multicodec v0.7.0 // indirect github.com/multiformats/go-multistream v0.3.3 // indirect - github.com/multiformats/go-varint v0.0.6 // indirect - github.com/nxadm/tail v1.4.8 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/onsi/ginkgo/v2 v2.5.1 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -257,10 +250,10 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect - github.com/prometheus/client_golang v1.12.2 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.35.0 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect @@ -293,21 +286,21 @@ require ( github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect go.etcd.io/bbolt v1.3.6 // indirect - go.opencensus.io v0.23.0 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect - go.uber.org/atomic v1.9.0 // indirect + go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect - go.uber.org/zap v1.21.0 // indirect - golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/net v0.3.0 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/tools v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.81.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -315,7 +308,6 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect @@ -327,6 +319,5 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch ) diff --git a/go.sum b/go.sum index 3b840eaf35..7be8edd0e7 100644 --- a/go.sum +++ b/go.sum @@ -165,7 +165,6 @@ github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7 github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -192,7 +191,6 @@ github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= @@ -226,8 +224,6 @@ github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GY github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= github.com/celestiaorg/go-libp2p-messenger v0.1.0/go.mod h1:XzNksXrH0VxuNRGOnjPL9Ck4UyQlbmMpCYg9YwSBerI= -github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 h1:Tb1lPVAGSJvBjRCM7YpC+VaITzdZjjno4+KEnbPT6tU= -github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2/go.mod h1:y+eARQ8AoaWpe0mlz3/uYKdl+GhFOtIxjG5xeek2kbM= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= @@ -249,9 +245,9 @@ github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -293,14 +289,14 @@ github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= @@ -349,8 +345,9 @@ github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsP github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= @@ -381,8 +378,9 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -637,6 +635,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -712,7 +712,6 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= @@ -783,8 +782,8 @@ github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitfield v1.0.0 h1:y/XHm2GEmD9wKngheWNNCNL0pzrWXZwCdQGv1ikXknQ= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= -github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= -github.com/ipfs/go-bitswap v0.8.0/go.mod h1:/h8sBij8UVEaNWl8ABzpLRA5Y1cttdNUnpeGo2AA/LQ= +github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= +github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= @@ -819,7 +818,6 @@ github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaH github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= -github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= @@ -848,8 +846,9 @@ github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uY github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= -github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= +github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= +github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= @@ -862,8 +861,8 @@ github.com/ipfs/go-ipld-format v0.4.0 h1:yqJSaJftjmjc9jEOFYlpkwOLVKv68OD27jFLlSg github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-legacy v0.1.0 h1:wxkkc4k8cnvIGIjPO0waJCe7SHEyFgl+yQdafdjGrpA= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= -github.com/ipfs/go-ipns v0.1.2 h1:O/s/0ht+4Jl9+VoxoUo0zaHjnZUS+aBQIKTuzdZ/ucI= -github.com/ipfs/go-ipns v0.1.2/go.mod h1:ioQ0j02o6jdIVW+bmi18f4k2gRf0AV3kZ9KeHYHICnQ= +github.com/ipfs/go-ipns v0.2.0 h1:BgmNtQhqOw5XEZ8RAfWEpK4DhqaYiuP6h71MhIp7xXU= +github.com/ipfs/go-ipns v0.2.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= @@ -885,8 +884,9 @@ github.com/ipfs/go-merkledag v0.7.0 h1:PHdWOGwx+J2uRAuP9Mu+bz89ulmf3W2QmbSS/N6O2 github.com/ipfs/go-merkledag v0.7.0/go.mod h1:/1cuN4VbcDn/xbVMAqjPUwejJYr8W9SvizmyYLU/B7k= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= -github.com/ipfs/go-peertaskqueue v0.7.0 h1:VyO6G4sbzX80K58N60cCaHsSsypbUNs1GjO5seGNsQ0= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= +github.com/ipfs/go-peertaskqueue v0.8.0 h1:JyNO144tfu9bx6Hpo119zvbEL9iQ760FHOiJYsUjqaU= +github.com/ipfs/go-peertaskqueue v0.8.0/go.mod h1:cz8hEnnARq4Du5TGqiWKgMr/BOSQ5XOgMOh1K5YYKKM= github.com/ipfs/go-unixfsnode v1.4.0 h1:9BUxHBXrbNi8mWHc6j+5C580WJqtVw9uoeEKn4tMhwA= github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= @@ -896,7 +896,6 @@ github.com/ipld/go-car/v2 v2.4.1/go.mod h1:zjpRf0Jew9gHqSvjsKVyoq9OY9SWoEKdYCQUK github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.3.1 h1:yVNlWRQexCa54ln3MSIiUN++ItH7pdhBFhh0hSgZu1w= github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= -github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= @@ -963,16 +962,14 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= -github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= -github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI= +github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= @@ -1016,24 +1013,20 @@ github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0 github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= -github.com/libp2p/go-conn-security-multistream v0.3.0/go.mod h1:EEP47t4fw/bTelVmEzIDqSe69hO/ip52xBEhZMLWAHM= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= -github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= -github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= -github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= +github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= +github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= -github.com/libp2p/go-libp2p v0.20.3/go.mod h1:I+vndVanE/p/SjFbnA+BEmmfAUEpWxrdXZeyQ1Dus5c= -github.com/libp2p/go-libp2p v0.21.0 h1:s9yYScuIFY33FOOzwTXbc8QqbvsRyKIWFf0FCSJKrfM= -github.com/libp2p/go-libp2p v0.21.0/go.mod h1:zvcA6/C4mr5/XQarRICh+L1SN9dAHHlSWDq4x5VYxg4= -github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= +github.com/libp2p/go-libp2p v0.24.1 h1:+lS4fqj7RF9egcPq9Yo3iqdRTcDMApzoBbQMhxtwOVw= +github.com/libp2p/go-libp2p v0.24.1/go.mod h1:5LJqbrqFsUzWrq70JHCYqjATlX4ey8Klpct3OEe8hSI= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= @@ -1047,28 +1040,22 @@ github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vM github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= -github.com/libp2p/go-libp2p-circuit v0.6.0/go.mod h1:kB8hY+zCpMeScyvFrKrGicRdid6vNXbunKE4rXATZ0M= -github.com/libp2p/go-libp2p-connmgr v0.2.4 h1:TMS0vc0TCBomtQJyWr7fYxcVYYhx+q/2gF++G5Jkl/w= -github.com/libp2p/go-libp2p-connmgr v0.2.4/go.mod h1:YV0b/RIm8NGPnnNWM7hG9Q38OeQiQfKhHCCs1++ufn0= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= -github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.3/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.6.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= @@ -1076,23 +1063,15 @@ github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJB github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= -github.com/libp2p/go-libp2p-core v0.10.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= -github.com/libp2p/go-libp2p-core v0.12.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= -github.com/libp2p/go-libp2p-core v0.14.0/go.mod h1:tLasfcVdTXnixsLB0QYaT1syJOhsbrhG7q6pGrHtBg8= -github.com/libp2p/go-libp2p-core v0.16.1/go.mod h1:O3i/7y+LqUb0N+qhzXjBjjpchgptWAVMG1Voegk7b4c= -github.com/libp2p/go-libp2p-core v0.17.0/go.mod h1:h/iAbFij28ASmI+tvXfjoipg1g2N33O4UN6LIb6QfoU= -github.com/libp2p/go-libp2p-core v0.19.1 h1:zaZQQCeCrFMtxFa1wHy6AhsVynyNmZAvwgWqSSPT3WE= -github.com/libp2p/go-libp2p-core v0.19.1/go.mod h1:2uLhmmqDiFY+dw+70KkBLeKvvsJHGWUINRDdeV1ip7k= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.17.0 h1:HWEjqjNVDuf8yuccuswGy1vYGzB0v4Z+yQ4DMDMSIqk= -github.com/libp2p/go-libp2p-kad-dht v0.17.0/go.mod h1:zeE26Xo+PY7sS2AgkBQQcBnJEazMT26KGZLUFttl+rk= -github.com/libp2p/go-libp2p-kbucket v0.4.7 h1:spZAcgxifvFZHBD8tErvppbnNiKA5uokDu3CV7axu70= -github.com/libp2p/go-libp2p-kbucket v0.4.7/go.mod h1:XyVo99AfQH0foSf176k4jY1xUJ2+jUJIZCSDm7r2YKk= -github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= +github.com/libp2p/go-libp2p-kad-dht v0.20.0 h1:1bcMa74JFwExCHZMFEmjtHzxX5DovhJ07EtR6UOTEpc= +github.com/libp2p/go-libp2p-kad-dht v0.20.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= +github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= +github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= @@ -1100,10 +1079,8 @@ github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= -github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5BcKwrrb2VmOYb3M= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= -github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= @@ -1116,22 +1093,16 @@ github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRj github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= -github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= -github.com/libp2p/go-libp2p-peerstore v0.7.1 h1:7FpALlqR+3+oOBXdzm3AVt0vjMYLW1b7jM03E4iEHlw= -github.com/libp2p/go-libp2p-peerstore v0.7.1/go.mod h1:cdUWTHro83vpg6unCpGUr8qJoX3e93Vy8o97u5ppIM0= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= +github.com/libp2p/go-libp2p-pubsub v0.8.2 h1:QLGUmkgKmwEVxVDYGsqc5t9CykOMY2Y21cXQHjR462I= +github.com/libp2p/go-libp2p-pubsub v0.8.2/go.mod h1:e4kT+DYjzPUYGZeWk4I+oxCSYTXizzXii5LDRRhjKSw= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= -github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= -github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= -github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0= -github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= -github.com/libp2p/go-libp2p-resource-manager v0.3.0/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= -github.com/libp2p/go-libp2p-resource-manager v0.5.1 h1:jm0mdqn7yfh7wbUzlj948BYZX0KZ3RW7OqerkGQ5rYY= -github.com/libp2p/go-libp2p-resource-manager v0.5.1/go.mod h1:CggtV6EZb+Y0dGh41q5ezO4udcVKyhcEFpydHD8EMe0= -github.com/libp2p/go-libp2p-routing-helpers v0.2.3 h1:xY61alxJ6PurSi+MXbywZpelvuU4U4p/gPTxjqCqTzY= -github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= +github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= +github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= +github.com/libp2p/go-libp2p-routing-helpers v0.4.0 h1:b7y4aixQ7AwbqYfcOQ6wTw8DQvuRZeTAA0Od3YYN5yc= +github.com/libp2p/go-libp2p-routing-helpers v0.4.0/go.mod h1:dYEAgkVhqho3/YKxfOEGdFMIcWfAFNlZX8iAIihYA2E= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= @@ -1143,7 +1114,6 @@ github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0 github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= -github.com/libp2p/go-libp2p-swarm v0.10.0/go.mod h1:71ceMcV6Rg/0rIQ97rsZWMzto1l9LnNquef+efcRbmA= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -1153,20 +1123,15 @@ github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= -github.com/libp2p/go-libp2p-testing v0.5.0/go.mod h1:QBk8fqIL1XNcno/l3/hhaIEn4aLRijpYOR+zVjjlh+A= -github.com/libp2p/go-libp2p-testing v0.7.0/go.mod h1:OLbdn9DbgdMwv00v+tlp1l3oe2Cl+FAjoWIA2pa0X6E= -github.com/libp2p/go-libp2p-testing v0.9.2/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= -github.com/libp2p/go-libp2p-testing v0.11.0 h1:+R7FRl/U3Y00neyBSM2qgDzqz3HkWH24U9nMlascHL4= +github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= -github.com/libp2p/go-libp2p-tls v0.3.0/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5oFlg+wo2FxJDY= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= -github.com/libp2p/go-libp2p-transport-upgrader v0.7.0/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= @@ -1175,7 +1140,6 @@ github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2Ez github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= -github.com/libp2p/go-libp2p-yamux v0.8.0/go.mod h1:yTkPgN2ib8FHyU1ZcVD7aelzyAqXXwEPbyx+aSKm9h8= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= @@ -1185,8 +1149,6 @@ github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3 github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= -github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= -github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= @@ -1200,38 +1162,35 @@ github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdm github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= -github.com/libp2p/go-netroute v0.2.0 h1:0FpsbsvuSnAhXFnCY0VLFbJOzaK0VnP0r1QT/o4nWRE= -github.com/libp2p/go-netroute v0.2.0/go.mod h1:Vio7LTzZ+6hoT4CMZi5/6CpY3Snzh2vgZhWgxMNwlQI= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo= +github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= -github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= -github.com/libp2p/go-reuseport-transport v0.1.0/go.mod h1:vev0C0uMkzriDY59yFHD9v+ujJvYmDQVLowvAjEOmfw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= -github.com/libp2p/go-stream-muxer-multistream v0.4.0/go.mod h1:nb+dGViZleRP4XcyHuZSVrJCBl55nRBOMmiSL/dyziw= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= -github.com/libp2p/go-tcp-transport v0.5.0/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= @@ -1244,19 +1203,15 @@ github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= -github.com/libp2p/go-yamux/v3 v3.0.1/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= -github.com/libp2p/go-yamux/v3 v3.1.2 h1:lNEy28MBk1HavUAlzKgShp+F6mn/ea1nDYWftZhFW9Q= -github.com/libp2p/go-yamux/v3 v3.1.2/go.mod h1:jeLEQgLXqE2YqX1ilAClIfCMDY+0uXQUKmmb/qp0gT4= +github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= +github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= -github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= -github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= -github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= -github.com/lucas-clemente/quic-go v0.28.0 h1:9eXVRgIkMQQyiyorz/dAaOYIx3TFzXsIFkNFz4cxuJM= -github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= +github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= +github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1271,26 +1226,20 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= -github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ= -github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= -github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= -github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= -github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ= -github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= -github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= -github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= -github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ= -github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= +github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= +github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE= +github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/marten-seemann/webtransport-go v0.4.2 h1:8ZRr9AsPuDiLQwnX2PxGs2t35GPvUaqPJnvk+c2SFSs= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1307,8 +1256,11 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1317,8 +1269,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -1382,8 +1334,9 @@ github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ib github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= @@ -1396,10 +1349,8 @@ github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4 github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= -github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= -github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.7.0 h1:gskHcdaCyPtp9XskVwtvEeQOG465sCohbQIirSyqxrc= -github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= +github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= +github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -1420,16 +1371,13 @@ github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/g github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= -github.com/multiformats/go-multicodec v0.2.0/go.mod h1:/y4YVwkfMyry5kFbMTbLJKErhycTIftytRV+llXdyS4= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.5.0 h1:EgU6cBe/D7WRwQb1KmnBvU7lrcFGMggZVTPtOW9dDHs= -github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= +github.com/multiformats/go-multicodec v0.7.0 h1:rTUjGOwjlhGHbEMbPoSUJowG1spZTVsITRANCjKTUAQ= +github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= -github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= @@ -1446,8 +1394,9 @@ github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcR github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -1484,15 +1433,16 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= +github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1564,15 +1514,16 @@ github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66Id github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1586,8 +1537,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE= -github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1597,14 +1548,13 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= -github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= -github.com/raulk/go-watchdog v1.2.0/go.mod h1:lzSbAl5sh4rtI8tYHU01BWIDzgzqaQLj6RcA1i4mlqI= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1844,8 +1794,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= @@ -1879,8 +1830,9 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= @@ -1888,7 +1840,6 @@ go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1906,8 +1857,8 @@ go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1946,9 +1897,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1966,8 +1916,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= -golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1997,8 +1947,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2067,16 +2017,14 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2112,8 +2060,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2229,18 +2177,18 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2250,8 +2198,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2328,8 +2276,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/header/core/listener_test.go b/header/core/listener_test.go index fd879563c1..5fa22aa1d7 100644 --- a/header/core/listener_test.go +++ b/header/core/listener_test.go @@ -6,8 +6,8 @@ import ( "time" mdutils "github.com/ipfs/go-merkledag/test" - "github.com/libp2p/go-libp2p-core/event" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/event" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 89fec0536d..8a55335374 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -8,9 +8,9 @@ import ( "sort" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/p2p/net/conngater" tmbytes "github.com/tendermint/tendermint/libs/bytes" diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 5e89956855..a8f8ca6454 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -7,8 +7,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" - libhost "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + libhost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" diff --git a/header/p2p/helpers.go b/header/p2p/helpers.go index 293743ab3e..8b497a6e6d 100644 --- a/header/p2p/helpers.go +++ b/header/p2p/helpers.go @@ -6,9 +6,9 @@ import ( "io" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/celestiaorg/go-libp2p-messenger/serde" diff --git a/header/p2p/peer_stats.go b/header/p2p/peer_stats.go index 938b9e014b..89d44085ff 100644 --- a/header/p2p/peer_stats.go +++ b/header/p2p/peer_stats.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" ) // peerStat represents a peer's average statistics. diff --git a/header/p2p/peer_stats_test.go b/header/p2p/peer_stats_test.go index 1ac8b741de..e020bb5d95 100644 --- a/header/p2p/peer_stats_test.go +++ b/header/p2p/peer_stats_test.go @@ -5,7 +5,7 @@ import ( "context" "testing" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" ) diff --git a/header/p2p/peer_tracker.go b/header/p2p/peer_tracker.go index ebecf0c51a..fddd2360b2 100644 --- a/header/p2p/peer_tracker.go +++ b/header/p2p/peer_tracker.go @@ -5,10 +5,10 @@ import ( "sync" "time" - "github.com/libp2p/go-libp2p-core/event" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/event" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/net/conngater" ) diff --git a/header/p2p/peer_tracker_test.go b/header/p2p/peer_tracker_test.go index 61bb64f104..0f3175aae6 100644 --- a/header/p2p/peer_tracker_test.go +++ b/header/p2p/peer_tracker_test.go @@ -7,7 +7,7 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/header/p2p/server.go b/header/p2p/server.go index 7596dfc36d..1fad669918 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -5,9 +5,9 @@ import ( "errors" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/protocol" tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/go-libp2p-messenger/serde" diff --git a/header/p2p/session.go b/header/p2p/session.go index d799d15940..beb45d1c7d 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -5,8 +5,8 @@ import ( "errors" "sort" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" diff --git a/header/p2p/subscriber.go b/header/p2p/subscriber.go index 7ae46178d4..ed33c14d30 100644 --- a/header/p2p/subscriber.go +++ b/header/p2p/subscriber.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" pb "github.com/libp2p/go-libp2p-pubsub/pb" + "github.com/libp2p/go-libp2p/core/peer" "golang.org/x/crypto/blake2b" "github.com/celestiaorg/celestia-node/header" diff --git a/header/p2p/subscription_test.go b/header/p2p/subscription_test.go index b1ff09ccd3..9c51846614 100644 --- a/header/p2p/subscription_test.go +++ b/header/p2p/subscription_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/event" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/event" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index d0afddcbd2..fa1ea2f881 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -2,8 +2,8 @@ package fraud import ( "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/fraud" diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index a04a39607e..893a816726 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "fmt" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" tmbytes "github.com/tendermint/tendermint/libs/bytes" diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index c1806af0c4..5c65941312 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" diff --git a/nodebuilder/node.go b/nodebuilder/node.go index fd9065aaf1..6968379c02 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -9,10 +9,10 @@ import ( "github.com/ipfs/go-blockservice" exchange "github.com/ipfs/go-ipfs-exchange-interface" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/routing" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/routing" "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" diff --git a/nodebuilder/node_light_test.go b/nodebuilder/node_light_test.go index c5e1d8b2ab..a7a70d0622 100644 --- a/nodebuilder/node_light_test.go +++ b/nodebuilder/node_light_test.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "testing" - "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p/core/crypto" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/nodebuilder/p2p/addrs.go b/nodebuilder/p2p/addrs.go index 70a9fa8fde..3d15179c90 100644 --- a/nodebuilder/p2p/addrs.go +++ b/nodebuilder/p2p/addrs.go @@ -3,8 +3,8 @@ package p2p import ( "fmt" - "github.com/libp2p/go-libp2p-core/host" p2pconfig "github.com/libp2p/go-libp2p/config" + "github.com/libp2p/go-libp2p/core/host" ma "github.com/multiformats/go-multiaddr" ) diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index 49cf44891d..a81580e8cd 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -9,9 +9,9 @@ import ( "github.com/ipfs/go-datastore" blockstore "github.com/ipfs/go-ipfs-blockstore" exchange "github.com/ipfs/go-ipfs-exchange-interface" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/protocol" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/protocol" "go.uber.org/fx" ) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 50537dc7fa..2cff9ea899 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -1,7 +1,7 @@ package p2p import ( - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" ) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index e623b5444e..070049a91e 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" ) @@ -39,16 +39,16 @@ type Config struct { func DefaultConfig() Config { return Config{ ListenAddresses: []string{ - "/ip4/0.0.0.0/udp/2121/quic", - "/ip6/::/udp/2121/quic", + "/ip4/0.0.0.0/udp/2121/quic-v1", + "/ip6/::/udp/2121/quic-v1", "/ip4/0.0.0.0/tcp/2121", "/ip6/::/tcp/2121", }, AnnounceAddresses: []string{}, NoAnnounceAddresses: []string{ - "/ip4/0.0.0.0/udp/2121/quic", - "/ip4/127.0.0.1/udp/2121/quic", - "/ip6/::/udp/2121/quic", + "/ip4/0.0.0.0/udp/2121/quic-v1", + "/ip4/127.0.0.1/udp/2121/quic-v1", + "/ip6/::/udp/2121/quic-v1", "/ip4/0.0.0.0/tcp/2121", "/ip4/127.0.0.1/tcp/2121", "/ip6/::/tcp/2121", diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index c8654b75b4..4da2d87b16 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -5,14 +5,14 @@ import ( "fmt" "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p-core/connmgr" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/metrics" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" - "github.com/libp2p/go-libp2p-core/routing" p2pconfig "github.com/libp2p/go-libp2p/config" + "github.com/libp2p/go-libp2p/core/connmgr" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/core/routing" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" diff --git a/nodebuilder/p2p/identity.go b/nodebuilder/p2p/identity.go index 92e18b0591..24f9f6a94a 100644 --- a/nodebuilder/p2p/identity.go +++ b/nodebuilder/p2p/identity.go @@ -4,9 +4,9 @@ import ( "crypto/rand" "errors" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/peerstore" "github.com/celestiaorg/celestia-node/libs/keystore" ) diff --git a/nodebuilder/p2p/misc.go b/nodebuilder/p2p/misc.go index fd8555d630..bd1c169a72 100644 --- a/nodebuilder/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -4,9 +4,9 @@ import ( "time" "github.com/ipfs/go-datastore" - coreconnmgr "github.com/libp2p/go-libp2p-core/connmgr" - "github.com/libp2p/go-libp2p-core/peerstore" - "github.com/libp2p/go-libp2p-peerstore/pstoremem" + connmgri "github.com/libp2p/go-libp2p/core/connmgr" + "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/libp2p/go-libp2p/p2p/net/connmgr" ) @@ -30,7 +30,7 @@ func DefaultConnManagerConfig() ConnManagerConfig { } // ConnectionManager provides a constructor for ConnectionManager. -func ConnectionManager(cfg Config, bpeers Bootstrappers) (coreconnmgr.ConnManager, error) { +func ConnectionManager(cfg Config, bpeers Bootstrappers) (connmgri.ConnManager, error) { fpeers, err := cfg.mutualPeers() if err != nil { return nil, err diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go index e7e4fbf88b..4874c4cf62 100644 --- a/nodebuilder/p2p/mocks/api.go +++ b/nodebuilder/p2p/mocks/api.go @@ -9,10 +9,10 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - metrics "github.com/libp2p/go-libp2p-core/metrics" - network "github.com/libp2p/go-libp2p-core/network" - peer "github.com/libp2p/go-libp2p-core/peer" - protocol "github.com/libp2p/go-libp2p-core/protocol" + metrics "github.com/libp2p/go-libp2p/core/metrics" + network "github.com/libp2p/go-libp2p/core/network" + peer "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 19f2e4164c..990a98cc27 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -2,7 +2,7 @@ package p2p import ( logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/metrics" + "github.com/libp2p/go-libp2p/core/metrics" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 59d4530715..11427f5ae7 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" ) // NOTE: Every time we add a new long-running network, it has to be added here. diff --git a/nodebuilder/p2p/opts.go b/nodebuilder/p2p/opts.go index 8dabe9a954..b96cbef5da 100644 --- a/nodebuilder/p2p/opts.go +++ b/nodebuilder/p2p/opts.go @@ -3,8 +3,8 @@ package p2p import ( "encoding/hex" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/libs/fxutil" diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index e511e5a594..687ef3dfc5 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -5,12 +5,12 @@ import ( "fmt" "reflect" - libhost "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/metrics" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" + libhost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" basichost "github.com/libp2p/go-libp2p/p2p/host/basic" "github.com/libp2p/go-libp2p/p2p/net/conngater" ) diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index 78660c091a..ca6e6709ab 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -8,12 +8,12 @@ import ( "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p" - libhost "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/metrics" - "github.com/libp2p/go-libp2p-core/network" - libpeer "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" + libhost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + libpeer "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 98947331cf..10932fd4d2 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -3,9 +3,9 @@ package p2p import ( "context" - "github.com/libp2p/go-libp2p-core/host" pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" + "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "golang.org/x/crypto/blake2b" ) diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index 12aff41eb7..e2a9aeb01e 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/protocol" - "github.com/libp2p/go-libp2p-core/routing" dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/libp2p/go-libp2p/core/routing" "go.uber.org/fx" ) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index e02be3d3dc..e5af35538d 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/metric/global" controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 87522d4ba5..ca880a84dd 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -7,8 +7,8 @@ import ( "github.com/filecoin-project/dagstore" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/routing" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/routing" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" "go.uber.org/fx" diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 15785dc9a3..06eb5a5c8c 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -4,7 +4,7 @@ import ( "context" "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 4f058c0f74..12ff92c696 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/fraud" diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index c913f5281c..4ca74b04f3 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/event" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/event" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index ea803a9802..dbbed77c2f 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/event" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/event" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 7d14ed94e2..ff2ef54e12 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" @@ -268,7 +268,6 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti p2p.WithHost(s.createPeer(ks)), fx.Replace(node.StorePath(tempDir)), ) - node, err := nodebuilder.New(t, p2p.Private, store, options...) require.NoError(s.t, err) diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index a4d2591e95..d121c44c79 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/share/availability.go b/share/availability.go index 4987129164..3f2091699d 100644 --- a/share/availability.go +++ b/share/availability.go @@ -5,7 +5,7 @@ import ( "errors" "time" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-app/pkg/da" ) diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index 50c930a6ff..80ff4822df 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -9,7 +9,7 @@ import ( "github.com/ipfs/go-datastore/autobatch" "github.com/ipfs/go-datastore/namespace" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-app/pkg/da" diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index e4302307ef..c86b3a522d 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -10,7 +10,7 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" mdutils "github.com/ipfs/go-merkledag/test" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/share/availability/discovery/backoff.go b/share/availability/discovery/backoff.go index 9e5c627aa7..e76449f2c6 100644 --- a/share/availability/discovery/backoff.go +++ b/share/availability/discovery/backoff.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/discovery/backoff" ) diff --git a/share/availability/discovery/backoff_test.go b/share/availability/discovery/backoff_test.go index 735bc9ef6d..95e84fbb8c 100644 --- a/share/availability/discovery/backoff_test.go +++ b/share/availability/discovery/backoff_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/p2p/discovery/backoff" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index ff7713ba81..afb89f00e5 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -5,11 +5,11 @@ import ( "time" logging "github.com/ipfs/go-log/v2" - core "github.com/libp2p/go-libp2p-core/discovery" - "github.com/libp2p/go-libp2p-core/event" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/discovery" + "github.com/libp2p/go-libp2p/core/event" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" ) var log = logging.Logger("share/discovery") @@ -31,7 +31,7 @@ var waitF = func(ttl time.Duration) time.Duration { type Discovery struct { set *limitedSet host host.Host - disc core.Discovery + disc discovery.Discovery connector *backoffConnector // peersLimit is max amount of peers that will be discovered during a discovery session. peersLimit uint @@ -44,7 +44,7 @@ type Discovery struct { // NewDiscovery constructs a new discovery. func NewDiscovery( h host.Host, - d core.Discovery, + d discovery.Discovery, peersLimit uint, discInterval, advertiseInterval time.Duration, diff --git a/share/availability/discovery/set.go b/share/availability/discovery/set.go index 2dc765a4e9..a7b330fadd 100644 --- a/share/availability/discovery/set.go +++ b/share/availability/discovery/set.go @@ -5,7 +5,7 @@ import ( "errors" "sync" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" ) // limitedSet is a thread safe set of peers with given limit. diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 15bdd71c64..8fdd98007c 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -7,7 +7,7 @@ import ( "github.com/ipfs/go-blockservice" ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 0630ea0f6e..e90d46e816 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -5,7 +5,7 @@ import ( "errors" "math" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share/ipld" diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index e9399ddad6..421256428f 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -11,9 +11,9 @@ import ( dssync "github.com/ipfs/go-datastore/sync" blockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/ipfs/go-ipfs-routing/offline" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" record "github.com/libp2p/go-libp2p-record" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 06c04c7ec3..57cdf789a0 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -6,9 +6,9 @@ import ( "fmt" "net" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 95edebd543..21e90297ef 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -7,8 +7,8 @@ import ( "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - libhost "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" + libhost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go index 23c2d54ef4..973de4c530 100644 --- a/share/p2p/shrexeds/options.go +++ b/share/p2p/shrexeds/options.go @@ -5,7 +5,7 @@ import ( "time" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/protocol" ) const protocolPrefix = "/shrex/eds/v0.0.1/" diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index a261f8f5cf..1922cf9e00 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -6,9 +6,9 @@ import ( "io" "time" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" diff --git a/share/p2p/shrexpush/pubsub.go b/share/p2p/shrexpush/pubsub.go index 4e8678ff97..00d50a3e3c 100644 --- a/share/p2p/shrexpush/pubsub.go +++ b/share/p2p/shrexpush/pubsub.go @@ -6,9 +6,9 @@ import ( logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" ) From d9c1ae7e31c3213b4ac900c39ae73d587bbb3961 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 5 Jan 2023 17:38:37 +0100 Subject: [PATCH 0276/1008] test(core|state|swamp): unify and refactor core/app testing utilities (#1551) This PR is one of the cleaning PRs that I am doing. It removes the usage of KVStore. Besides simplifying and unifying tests, it also fixes all the existing and potential "bind already in used" errors from the app and tendermint tandem. I tested this by running all the tests with `-parallel 12`, which runs in parallel per pkg. With this flag, we can speed up the time it takes to run all the tests on Ci and locally. Going further, we can also consider running all the tests in parallel with only one instance of the app running rather than an instance per test. Would love this diff to be reddish, but unfortunately, I had to copy a bunch of code from Cosmos SDK. The rationale is in the comments. Also, see https://github.com/cosmos/cosmos-sdk/issues/14429 --- core/client_test.go | 4 +- core/fetcher_test.go | 4 +- core/testing.go | 163 ++++++++++++++++-------------- core/testing_grpc.go | 114 +++++++++++++++++++++ header/core/exchange_test.go | 2 +- header/header_test.go | 2 +- nodebuilder/node_bridge_test.go | 2 +- nodebuilder/testing.go | 35 +++---- nodebuilder/tests/swamp/config.go | 23 ++--- nodebuilder/tests/swamp/swamp.go | 37 ++----- state/integration_test.go | 40 ++------ 11 files changed, 250 insertions(+), 176 deletions(-) create mode 100644 core/testing_grpc.go diff --git a/core/client_test.go b/core/client_test.go index f74ff6fb26..e197dc58e7 100644 --- a/core/client_test.go +++ b/core/client_test.go @@ -13,7 +13,7 @@ func TestRemoteClient_Status(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestCoreWithApp(t) + client := StartTestNode(t).Client status, err := client.Status(ctx) require.NoError(t, err) require.NotNil(t, status) @@ -23,7 +23,7 @@ func TestRemoteClient_StartBlockSubscription_And_GetBlock(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestCoreWithApp(t) + client := StartTestNode(t).Client eventChan, err := client.Subscribe(ctx, newBlockSubscriber, newBlockEventQuery) require.NoError(t, err) diff --git a/core/fetcher_test.go b/core/fetcher_test.go index 95fd4204f4..ece9660c39 100644 --- a/core/fetcher_test.go +++ b/core/fetcher_test.go @@ -16,7 +16,7 @@ func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestCoreWithApp(t) + client := StartTestNode(t).Client fetcher := NewBlockFetcher(client) // generate some blocks @@ -44,7 +44,7 @@ func TestBlockFetcherHeaderValues(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) t.Cleanup(cancel) - _, client := StartTestCoreWithApp(t) + client := StartTestNode(t).Client fetcher := NewBlockFetcher(client) // generate some blocks diff --git a/core/testing.go b/core/testing.go index 9fadee45fb..7b84621c9d 100644 --- a/core/testing.go +++ b/core/testing.go @@ -1,7 +1,6 @@ package core import ( - "context" "fmt" "math/rand" "net" @@ -10,94 +9,98 @@ import ( "testing" "time" + appconfig "github.com/cosmos/cosmos-sdk/server/config" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/abci/example/kvstore" - "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/config" + tmconfig "github.com/tendermint/tendermint/config" tmrand "github.com/tendermint/tendermint/libs/rand" - tmservice "github.com/tendermint/tendermint/libs/service" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - rpctest "github.com/tendermint/tendermint/rpc/test" tmtypes "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/testutil/testnode" ) -// so that we never hit an issue where we request blocks that are removed -const defaultRetainBlocks int64 = 10000 +// TestConfig encompasses all the configs required to run test Tendermint + Celestia App tandem. +type TestConfig struct { + ConsensusParams *tmproto.ConsensusParams + Tendermint *tmconfig.Config + App *appconfig.Config -// StartTestNode starts a mock Core node background process and returns it. -func StartTestNode(ctx context.Context, t *testing.T, app types.Application, cfg *config.Config) tmservice.Service { - nd := rpctest.StartTendermint(app, rpctest.SuppressStdout, func(options *rpctest.Options) { - options.SpecificConfig = cfg - }) - t.Cleanup(func() { - rpctest.StopTendermint(nd) - }) - return nd -} - -// StartTestKVApp starts Tendermint KVApp. -func StartTestKVApp(ctx context.Context, t *testing.T) (tmservice.Service, types.Application, *config.Config) { - cfg := rpctest.GetConfig(true) - app := CreateKVStore(defaultRetainBlocks) - return StartTestNode(ctx, t, app, cfg), app, cfg -} - -// CreateKVStore creates a simple kv store app and gives the user -// ability to set desired amount of blocks to be retained. -func CreateKVStore(retainBlocks int64) *kvstore.Application { - app := kvstore.NewApplication() - app.RetainBlocks = retainBlocks - return app -} - -func getFreePort() (port int, err error) { - var a *net.TCPAddr - if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { - var l *net.TCPListener - if l, err = net.ListenTCP("tcp", a); err == nil { - defer l.Close() - return l.Addr().(*net.TCPAddr).Port, nil - } - } - return + Accounts []string + SuppressLogs bool } -func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) { - // we create an arbitrary number of funded accounts +// DefaultTestConfig returns the default testing configuration for Tendermint + Celestia App tandem. +// +// It fetches free ports from OS and sets them into configs, s.t. +// user can make use of them(unlike 0 port) and allowing to run +// multiple tests nodes in parallel. +// +// Additionally, it instructs Tendermint + Celestia App tandem to setup 10 funded accounts. +func DefaultTestConfig() *TestConfig { + conCfg := testnode.DefaultParams() + + tnCfg := testnode.DefaultTendermintConfig() + tnCfg.RPC.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) + tnCfg.RPC.GRPCListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) + tnCfg.P2P.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) + + appCfg := testnode.DefaultAppConfig() + appCfg.GRPC.Address = fmt.Sprintf("127.0.0.1:%d", getFreePort()) + appCfg.API.Address = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) + + // instructs creating funded accounts + // 10 usually is enough for testing accounts := make([]string, 10) for i := range accounts { accounts[i] = tmrand.Str(9) } - tmNode, _, cctx, err := testnode.New( + return &TestConfig{ + ConsensusParams: conCfg, + Tendermint: tnCfg, + App: appCfg, + Accounts: accounts, + SuppressLogs: true, + } +} + +// StartTestNode simply starts Tendermint and Celestia App tandem with default testing +// configuration. +func StartTestNode(t *testing.T) testnode.Context { + return StartTestNodeWithConfig(t, DefaultTestConfig()) +} + +// StartTestNodeWithConfig starts Tendermint and Celestia App tandem with custom configuration. +func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { + tmNode, app, cctx, err := testnode.New( t, - testnode.DefaultParams(), - testnode.DefaultTendermintConfig(), - true, - accounts..., + cfg.ConsensusParams, + cfg.Tendermint, + cfg.SuppressLogs, + cfg.Accounts..., ) require.NoError(t, err) - // get a random port for running test in parallel - freePort, err := getFreePort() - require.NoError(t, err) - tmNode.Config().RPC.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", freePort) - tmNode.Config().P2P.ListenAddress = "tcp://0.0.0.0:0" - _, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) + cctx, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) require.NoError(t, err) t.Cleanup(func() { err := cleanupCoreNode() require.NoError(t, err) }) - endpoint, err := GetEndpoint(tmNode.Config()) + cctx, cleanupGRPCServer, err := StartGRPCServer(app, cfg.App, cctx) require.NoError(t, err) + t.Cleanup(func() { + err := cleanupGRPCServer() + require.NoError(t, err) + }) - ip, port, err := net.SplitHostPort(endpoint) + // we want to test over remote http client, + // so we are as close to the real environment as possible + // however, it might be useful to use local tendermint client + // if you need to debug something inside of it + ip, port, err := getEndpoint(cfg.Tendermint) require.NoError(t, err) - client, err := NewRemote(ip, port) require.NoError(t, err) @@ -108,20 +111,8 @@ func StartTestCoreWithApp(t *testing.T) (tmservice.Service, Client) { require.NoError(t, err) }) - return tmNode, client -} - -// GetEndpoint returns the remote node's RPC endpoint. -func GetEndpoint(cfg *config.Config) (string, error) { - url, err := url.Parse(cfg.RPC.ListenAddress) - if err != nil { - return "", err - } - host, _, err := net.SplitHostPort(url.Host) - if err != nil { - return "", err - } - return fmt.Sprintf("%s:%s", host, url.Port()), nil + cctx.WithClient(client) + return cctx } func RandValidator(randPower bool, minPower int64) (*tmtypes.Validator, tmtypes.PrivValidator) { @@ -193,3 +184,27 @@ func signAddVote(privVal tmtypes.PrivValidator, vote *tmtypes.Vote, voteSet *tmt vote.Signature = v.Signature return voteSet.AddVote(vote) } + +func getFreePort() int { + a, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err == nil { + var l *net.TCPListener + if l, err = net.ListenTCP("tcp", a); err == nil { + defer l.Close() + return l.Addr().(*net.TCPAddr).Port + } + } + panic("while getting free port: " + err.Error()) +} + +func getEndpoint(cfg *tmconfig.Config) (string, string, error) { + url, err := url.Parse(cfg.RPC.ListenAddress) + if err != nil { + return "", "", err + } + host, _, err := net.SplitHostPort(url.Host) + if err != nil { + return "", "", err + } + return host, url.Port(), nil +} diff --git a/core/testing_grpc.go b/core/testing_grpc.go new file mode 100644 index 0000000000..2306b61723 --- /dev/null +++ b/core/testing_grpc.go @@ -0,0 +1,114 @@ +package core + +import ( + "net" + "strings" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/server/grpc/gogoreflection" + reflection "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1" + srvtypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/celestiaorg/celestia-app/testutil/testnode" +) + +/* +StartGRPCServer is a copy of https://github.com/celestiaorg/celestia-app/blob/e5a679d11b464d583b616d4d686de9dd44bdab2e/testutil/testnode/rpc_client.go#L46 +// It's copied as internal Cosmos SDK logic take 5 seconds to run: https://github.com/cosmos/cosmos-sdk/blob/6dfa0c98062d5d8b38d85ca1d2807937f47da4a3/server/grpc/server.go#L80 +// FIXME once the fix for https://github.com/cosmos/cosmos-sdk/issues/14429 lands in our fork +*/ +func StartGRPCServer( + app srvtypes.Application, + appCfg *srvconfig.Config, + cctx testnode.Context, +) (testnode.Context, func() error, error) { + emptycleanup := func() error { return nil } + // Add the tx service in the gRPC router. + app.RegisterTxService(cctx.Context) + + // Add the tendermint queries service in the gRPC router. + app.RegisterTendermintService(cctx.Context) + + grpcSrv, err := startGRPCServer(cctx.Context, app, appCfg.GRPC) + if err != nil { + return testnode.Context{}, emptycleanup, err + } + + nodeGRPCAddr := strings.Replace(appCfg.GRPC.Address, "0.0.0.0", "localhost", 1) + conn, err := grpc.Dial(nodeGRPCAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return testnode.Context{}, emptycleanup, err + } + + cctx.Context = cctx.WithGRPCClient(conn) + return cctx, func() error { + grpcSrv.Stop() + return nil + }, nil +} + +func startGRPCServer( + clientCtx client.Context, + app srvtypes.Application, + cfg srvconfig.GRPCConfig, +) (*grpc.Server, error) { + maxSendMsgSize := cfg.MaxSendMsgSize + if maxSendMsgSize == 0 { + maxSendMsgSize = srvconfig.DefaultGRPCMaxSendMsgSize + } + + maxRecvMsgSize := cfg.MaxRecvMsgSize + if maxRecvMsgSize == 0 { + maxRecvMsgSize = srvconfig.DefaultGRPCMaxRecvMsgSize + } + + grpcSrv := grpc.NewServer( + grpc.ForceServerCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()), + grpc.MaxSendMsgSize(maxSendMsgSize), + grpc.MaxRecvMsgSize(maxRecvMsgSize), + ) + + app.RegisterGRPCServer(grpcSrv) + + // Reflection allows consumers to build dynamic clients that can write to any + // Cosmos SDK application without relying on application packages at compile + // time. + err := reflection.Register(grpcSrv, reflection.Config{ + SigningModes: func() map[string]int32 { + modes := make(map[string]int32, len(clientCtx.TxConfig.SignModeHandler().Modes())) + for _, m := range clientCtx.TxConfig.SignModeHandler().Modes() { + modes[m.String()] = (int32)(m) + } + return modes + }(), + ChainID: clientCtx.ChainID, + SdkConfig: sdk.GetConfig(), + InterfaceRegistry: clientCtx.InterfaceRegistry, + }) + if err != nil { + return nil, err + } + + // Reflection allows external clients to see what services and methods + // the gRPC server exposes. + gogoreflection.Register(grpcSrv) + + listener, err := net.Listen("tcp", cfg.Address) + if err != nil { + return nil, err + } + + go func() { + err = grpcSrv.Serve(listener) + if err != nil { + log.Error("serving GRPC: ", err) + } + }() + + return grpcSrv, nil +} diff --git a/header/core/exchange_test.go b/header/core/exchange_test.go index 4c379abb64..c667dc2b2a 100644 --- a/header/core/exchange_test.go +++ b/header/core/exchange_test.go @@ -35,7 +35,7 @@ func Test_hashMatch(t *testing.T) { } func createCoreFetcher(t *testing.T) *core.BlockFetcher { - _, client := core.StartTestCoreWithApp(t) + client := core.StartTestNode(t).Client return core.NewBlockFetcher(client) } diff --git a/header/header_test.go b/header/header_test.go index 11aac1a44f..47b8543121 100644 --- a/header/header_test.go +++ b/header/header_test.go @@ -16,7 +16,7 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - _, client := core.StartTestCoreWithApp(t) + client := core.StartTestNode(t).Client fetcher := core.NewBlockFetcher(client) store := mdutils.Bserv() diff --git a/nodebuilder/node_bridge_test.go b/nodebuilder/node_bridge_test.go index 1524cd9e44..d2b7ebaf4e 100644 --- a/nodebuilder/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -19,7 +19,7 @@ func TestBridge_WithMockedCoreClient(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - _, client := core.StartTestCoreWithApp(t) + client := core.StartTestNode(t).Client node, err := New(node.Bridge, p2p.Private, repo, coremodule.WithClient(client)) require.NoError(t, err) require.NotNil(t, node) diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index ec3b90af26..d8b66e8baf 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -1,10 +1,7 @@ package nodebuilder import ( - "context" - "net" "testing" - "time" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -38,27 +35,27 @@ func TestNode(t *testing.T, tp node.Type, opts ...fx.Option) *Node { } func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Option) *Node { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - t.Cleanup(cancel) - - store := MockStore(t, cfg) - _, _, coreCfg := core.StartTestKVApp(ctx, t) - endpoint, err := core.GetEndpoint(coreCfg) - require.NoError(t, err) - ip, port, err := net.SplitHostPort(endpoint) - require.NoError(t, err) - cfg.Core.IP = ip - cfg.Core.RPCPort = port + // avoids port conflicts cfg.RPC.Port = "0" - - // storePath is used for the eds blockstore - storePath := t.TempDir() opts = append(opts, + // avoid writing keyring on disk state.WithKeyringSigner(TestKeyringSigner(t)), - fx.Replace(node.StorePath(storePath)), + // temp dir for the eds store FIXME: Should be in mem + fx.Replace(node.StorePath(t.TempDir())), + // avoid requesting trustedPeer during initialization fxutil.ReplaceAs(mocks.NewStore(t, 20), new(header.InitStore)), ) - nd, err := New(tp, p2p.Private, store, opts...) + + // in fact, we don't need core.Client in tests, but Bridge requires is a valid one + // or fails otherwise with failed attempt to connect with custom build client + if tp == node.Bridge { + cctx := core.StartTestNode(t) + opts = append(opts, + fxutil.ReplaceAs(cctx.Client, new(core.Client)), + ) + } + + nd, err := New(tp, p2p.Private, MockStore(t, cfg), opts...) require.NoError(t, err) return nd } diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index 985295103f..11cfed0607 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -3,28 +3,21 @@ package swamp import ( "time" - tn "github.com/tendermint/tendermint/config" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-node/core" ) // Components struct represents a set of pre-requisite attributes from the test scenario type Components struct { - CoreCfg *tn.Config - ConsensusParams *tmproto.ConsensusParams - SupressLogs bool + *core.TestConfig } // DefaultComponents creates a celestia-app instance with a block time of around // 100ms func DefaultComponents() *Components { - tnCfg := tn.TestConfig() - tnCfg.Consensus.TimeoutCommit = 100 * time.Millisecond + cfg := core.DefaultTestConfig() + cfg.Tendermint.Consensus.TimeoutCommit = 100 * time.Millisecond return &Components{ - CoreCfg: tnCfg, - ConsensusParams: testnode.DefaultParams(), - SupressLogs: true, + cfg, } } @@ -35,9 +28,9 @@ type Option func(*Components) func WithBlockTime(t time.Duration) Option { return func(c *Components) { // for empty block - c.CoreCfg.Consensus.CreateEmptyBlocksInterval = t + c.Tendermint.Consensus.CreateEmptyBlocksInterval = t // for filled block - c.CoreCfg.Consensus.TimeoutCommit = t - c.CoreCfg.Consensus.SkipTimeoutCommit = false + c.Tendermint.Consensus.TimeoutCommit = t + c.Tendermint.Consensus.SkipTimeoutCommit = false } } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index ff2ef54e12..6bbe0c8d11 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -14,9 +14,10 @@ import ( ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/bytes" - tmrand "github.com/tendermint/tendermint/libs/rand" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-app/testutil/testnode" "github.com/celestiaorg/celestia-node/libs/keystore" @@ -63,50 +64,24 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { option(ic) } - var err error - ctx := context.Background() - - // we create an arbitrary number of funded accounts - accounts := make([]string, 10) - for i := range accounts { - accounts[i] = tmrand.Str(9) - } + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) - // TODO(@Bidon15): CoreClient(limitation) // Now, we are making an assumption that consensus mechanism is already tested out // so, we are not creating bridge nodes with each one containing its own core client // instead we are assigning all created BNs to 1 Core from the swamp - tmNode, app, cctx, err := testnode.New( - t, - ic.ConsensusParams, - ic.CoreCfg, - ic.SupressLogs, - accounts..., - ) - - require.NoError(t, err) - - cctx, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) - require.NoError(t, err) - - cctx, cleanupGRPCServer, err := testnode.StartGRPCServer(app, testnode.DefaultAppConfig(), cctx) - require.NoError(t, err) - + cctx := core.StartTestNodeWithConfig(t, ic.TestConfig) swp := &Swamp{ t: t, Network: mocknet.New(), ClientContext: cctx, comps: ic, - accounts: accounts, + accounts: ic.Accounts, } swp.trustedHash = swp.getTrustedHash(ctx) swp.t.Cleanup(func() { swp.stopAllNodes(ctx, swp.BridgeNodes, swp.FullNodes, swp.LightNodes) - err = cleanupCoreNode() - require.NoError(t, err) - err = cleanupGRPCServer() - require.NoError(t, err) }) return swp diff --git a/state/integration_test.go b/state/integration_test.go index 5c0d06b10a..ffb84b92a3 100644 --- a/state/integration_test.go +++ b/state/integration_test.go @@ -9,10 +9,11 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - tmrand "github.com/tendermint/tendermint/libs/rand" rpcclient "github.com/tendermint/tendermint/rpc/client" "google.golang.org/grpc" + "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/testutil/testnode" blobtypes "github.com/celestiaorg/celestia-app/x/payment/types" @@ -36,42 +37,21 @@ type IntegrationTestSuite struct { func (s *IntegrationTestSuite) SetupSuite() { if testing.Short() { - s.T().Skip("skipping test in unit-tests or race-detector mode.") + s.T().Skip("skipping test in unit-tests") } - s.T().Log("setting up integration test suite") - require := s.Require() - - // we create an arbitrary number of funded accounts - for i := 0; i < 25; i++ { - s.accounts = append(s.accounts, tmrand.Str(9)) - } - tmNode, app, cctx, err := testnode.New( - s.T(), - testnode.DefaultParams(), - testnode.DefaultTendermintConfig(), - false, - s.accounts..., - ) - require.NoError(err) - - cctx, stopNode, err := testnode.StartNode(tmNode, cctx) - require.NoError(err) - s.cleanups = append(s.cleanups, stopNode) - - cctx, cleanupGRPC, err := testnode.StartGRPCServer(app, testnode.DefaultAppConfig(), cctx) - require.NoError(err) - s.cleanups = append(s.cleanups, cleanupGRPC) - - s.cctx = cctx - require.NoError(cctx.WaitForNextBlock()) - - signer := blobtypes.NewKeyringSigner(s.cctx.Keyring, s.accounts[0], cctx.ChainID) + cfg := core.DefaultTestConfig() + s.cctx = core.StartTestNodeWithConfig(s.T(), cfg) + s.accounts = cfg.Accounts + signer := blobtypes.NewKeyringSigner(s.cctx.Keyring, s.accounts[0], s.cctx.ChainID) accessor := NewCoreAccessor(signer, localHeader{s.cctx.Client}, "", "", "") setClients(accessor, s.cctx.GRPCClient, s.cctx.Client) s.accessor = accessor + + // required to ensure the Head request is non-nil + require.NoError(s.T(), s.cctx.WaitForNextBlock()) } func setClients(ca *CoreAccessor, conn *grpc.ClientConn, abciCli rpcclient.ABCIClient) { From 0863eb13c620672cecb344d8ea070f4a755041e7 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 5 Jan 2023 19:47:57 +0300 Subject: [PATCH 0277/1008] feat(share/eds): Create a `blockservice.BlockGetter` adapter for `eds.Store` (#1561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit сreate a `blockservice.BlockGetter` adapter for `eds.Store`, that could be used for share pkg. Resolves https://github.com/celestiaorg/celestia-node/issues/1563 Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/eds/adapters.go | 66 ++++++++++++++++ share/eds/adapters_test.go | 154 +++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 share/eds/adapters.go create mode 100644 share/eds/adapters_test.go diff --git a/share/eds/adapters.go b/share/eds/adapters.go new file mode 100644 index 0000000000..fe498400b3 --- /dev/null +++ b/share/eds/adapters.go @@ -0,0 +1,66 @@ +package eds + +import ( + "context" + "sync" + + "github.com/filecoin-project/dagstore" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" +) + +var _ blockservice.BlockGetter = (*BlockGetter)(nil) + +// NewBlockGetter creates new blockservice.BlockGetter adapter from dagstore.ReadBlockstore +func NewBlockGetter(store dagstore.ReadBlockstore) *BlockGetter { + return &BlockGetter{store: store} +} + +// BlockGetter is an adapter for dagstore.ReadBlockstore to implement blockservice.BlockGetter +// interface. +type BlockGetter struct { + store dagstore.ReadBlockstore +} + +// GetBlock gets the requested block by the given CID. +func (bg *BlockGetter) GetBlock(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + return bg.store.Get(ctx, cid) +} + +// GetBlocks does a batch request for the given cids, returning blocks as +// they are found, in no particular order. +// +// It implements blockservice.BlockGetter interface, that requires: +// It may not be able to find all requested blocks (or the context may +// be canceled). In that case, it will close the channel early. It is up +// to the consumer to detect this situation and keep track which blocks +// it has received and which it hasn't. +func (bg *BlockGetter) GetBlocks(ctx context.Context, cids []cid.Cid) <-chan blocks.Block { + bCh := make(chan blocks.Block) + + go func() { + var wg sync.WaitGroup + wg.Add(len(cids)) + for _, c := range cids { + go func(cid cid.Cid) { + defer wg.Done() + block, err := bg.store.Get(ctx, cid) + if err != nil { + log.Debugw("getblocks: error getting block by cid", "cid", cid, "error", err) + return + } + + select { + case bCh <- block: + case <-ctx.Done(): + return + } + }(c) + } + wg.Wait() + close(bCh) + }() + + return bCh +} diff --git a/share/eds/adapters_test.go b/share/eds/adapters_test.go new file mode 100644 index 0000000000..4e98f4141e --- /dev/null +++ b/share/eds/adapters_test.go @@ -0,0 +1,154 @@ +package eds + +import ( + "context" + "errors" + "math/rand" + "sort" + "testing" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share/ipld" +) + +func TestBlockGetter_GetBlocks(t *testing.T) { + t.Run("happy path", func(t *testing.T) { + cids := randCIDs(32) + // sort cids in asc order + sort.Slice(cids, func(i, j int) bool { + return cids[i].String() < cids[j].String() + }) + + bg := &BlockGetter{store: rbsMock{}} + blocksCh := bg.GetBlocks(context.Background(), cids) + + // collect blocks from channel + blocks := make([]blocks.Block, 0, len(cids)) + for block := range blocksCh { + blocks = append(blocks, block) + } + + // sort blocks in cid asc order + sort.Slice(blocks, func(i, j int) bool { + return blocks[i].Cid().String() < blocks[j].Cid().String() + }) + + // validate results + require.Equal(t, len(cids), len(blocks)) + for i, block := range blocks { + require.Equal(t, cids[i].String(), block.Cid().String()) + } + }) + t.Run("retrieval error", func(t *testing.T) { + cids := randCIDs(32) + + // split cids into failed and succeeded + failedLen := rand.Intn(len(cids)-1) + 1 + failed := make(map[cid.Cid]struct{}, failedLen) + succeeded := make([]cid.Cid, 0, len(cids)-failedLen) + for i, cid := range cids { + if i < failedLen { + failed[cid] = struct{}{} + continue + } + succeeded = append(succeeded, cid) + } + + // sort succeeded cids in asc order + sort.Slice(succeeded, func(i, j int) bool { + return succeeded[i].String() < succeeded[j].String() + }) + + bg := &BlockGetter{store: rbsMock{failed: failed}} + blocksCh := bg.GetBlocks(context.Background(), cids) + + // collect blocks from channel + blocks := make([]blocks.Block, 0, len(cids)) + for block := range blocksCh { + blocks = append(blocks, block) + } + + // sort blocks in cid asc order + sort.Slice(blocks, func(i, j int) bool { + return blocks[i].Cid().String() < blocks[j].Cid().String() + }) + + // validate results + require.Equal(t, len(succeeded), len(blocks)) + for i, block := range blocks { + require.Equal(t, succeeded[i].String(), block.Cid().String()) + } + }) + t.Run("retrieval timeout", func(t *testing.T) { + cids := randCIDs(128) + + bg := &BlockGetter{ + store: rbsMock{}, + } + + // cancel the context before any blocks are collected + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + blocksCh := bg.GetBlocks(ctx, cids) + + // pretend nobody is reading from blocksCh after context is canceled + time.Sleep(50 * time.Millisecond) + + // blocksCh should be closed indicating GetBlocks exited + select { + case _, ok := <-blocksCh: + require.False(t, ok) + default: + t.Error("channel is not closed on canceled context") + } + }) +} + +// rbsMock is a dagstore.ReadBlockstore mock +type rbsMock struct { + failed map[cid.Cid]struct{} +} + +func (r rbsMock) Has(ctx context.Context, cid cid.Cid) (bool, error) { + panic("implement me") +} + +func (r rbsMock) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + // return error for failed items + if _, ok := r.failed[cid]; ok { + return nil, errors.New("not found") + } + + return blocks.NewBlockWithCid(nil, cid) +} + +func (r rbsMock) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + panic("implement me") +} + +func (r rbsMock) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + panic("implement me") +} + +func (r rbsMock) HashOnRead(enabled bool) { + panic("implement me") +} + +func randCID() cid.Cid { + hash := make([]byte, ipld.NmtHashSize) + rand.Read(hash) + return ipld.MustCidFromNamespacedSha256(hash) +} + +func randCIDs(n int) []cid.Cid { + cids := make([]cid.Cid, n) + for i := range cids { + cids[i] = randCID() + } + return cids +} From 92904dc5c4f2235fddc9de2768252967ca8422b8 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 5 Jan 2023 19:03:29 +0200 Subject: [PATCH 0278/1008] fix(header/p2p): fix crash in server (#1569) ## Overview Resolves #1564 + small fixes in the peerTracker ## Checklist - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/exchange.go | 4 ++-- header/p2p/exchange_test.go | 6 ++++-- header/p2p/server.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 8a55335374..e98917a59d 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -74,14 +74,14 @@ func NewExchange( func (ex *Exchange) Start(context.Context) error { ex.ctx, ex.cancel = context.WithCancel(context.Background()) - go ex.peerTracker.gc() - go ex.peerTracker.track() for _, p := range ex.trustedPeers { // Try to pre-connect to trusted peers. // We don't really care if we succeed at this point // and just need any peers in the peerTracker asap go ex.host.Connect(ex.ctx, peer.AddrInfo{ID: p}) //nolint:errcheck } + go ex.peerTracker.gc() + go ex.peerTracker.track() return nil } diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index a8f8ca6454..3adca5a78d 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -311,9 +311,11 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (*Exchange, *h require.NoError(t, err) ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private", connGater) require.NoError(t, err) - ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID(), peerScore: 100} require.NoError(t, ex.Start(context.Background())) - + time.Sleep(time.Millisecond * 100) // give peerTracker time to add a trusted peer + ex.peerTracker.peerLk.Lock() + ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID(), peerScore: 100.0} + ex.peerTracker.peerLk.Unlock() t.Cleanup(func() { serverSideEx.Stop(context.Background()) //nolint:errcheck ex.Stop(context.Background()) //nolint:errcheck diff --git a/header/p2p/server.go b/header/p2p/server.go index 1fad669918..f59f8b0a58 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -135,7 +135,7 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { } _, err = serde.Write(stream, &p2p_pb.ExtendedHeaderResponse{Body: bin, StatusCode: code}) if err != nil { - log.Errorw("server: writing header to stream", "height", h.Height, "err", err) + log.Errorw("server: writing header to stream", "err", err) stream.Reset() //nolint:errcheck return } From 439cdad915d546c07a60df62f6a2527487a87792 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 6 Jan 2023 10:38:53 +0100 Subject: [PATCH 0279/1008] Revert "fix(node/p2p)!: remove resource manager and it's API (#1500)" (#1570) --- nodebuilder/p2p/host.go | 23 +++++++++++++---------- nodebuilder/p2p/mocks/api.go | 16 ++++++++++++++++ nodebuilder/p2p/module.go | 18 +++++++++++++++++- nodebuilder/p2p/p2p.go | 21 +++++++++++++++++++++ nodebuilder/p2p/p2p_test.go | 27 +++++++++++++++++++++------ 5 files changed, 88 insertions(+), 17 deletions(-) diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index 4da2d87b16..c8bf52153a 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -10,6 +10,7 @@ import ( "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/core/routing" @@ -27,7 +28,7 @@ func RoutedHost(base HostBase, r routing.PeerRouting) host.Host { } // Host returns constructor for Host. -func Host(cfg Config, params hostParams) (HostBase, error) { +func Host(params hostParams) (HostBase, error) { opts := []libp2p.Option{ libp2p.NoListenAddrs, // do not listen automatically libp2p.AddrsFactory(params.AddrF), @@ -39,6 +40,7 @@ func Host(cfg Config, params hostParams) (HostBase, error) { libp2p.NATPortMap(), // enables upnp libp2p.DisableRelay(), libp2p.BandwidthReporter(params.Bandwidth), + libp2p.ResourceManager(params.ResourceManager), // to clearly define what defaults we rely upon libp2p.DefaultSecurity, libp2p.DefaultTransports, @@ -67,15 +69,16 @@ type HostBase host.Host type hostParams struct { fx.In - Net Network - Lc fx.Lifecycle - ID peer.ID - Key crypto.PrivKey - AddrF p2pconfig.AddrsFactory - PStore peerstore.Peerstore - ConnMngr connmgr.ConnManager - ConnGater *conngater.BasicConnectionGater - Bandwidth *metrics.BandwidthCounter + Net Network + Lc fx.Lifecycle + ID peer.ID + Key crypto.PrivKey + AddrF p2pconfig.AddrsFactory + PStore peerstore.Peerstore + ConnMngr connmgr.ConnManager + ConnGater *conngater.BasicConnectionGater + Bandwidth *metrics.BandwidthCounter + ResourceManager network.ResourceManager Tp node.Type } diff --git a/nodebuilder/p2p/mocks/api.go b/nodebuilder/p2p/mocks/api.go index 4874c4cf62..aa5083199f 100644 --- a/nodebuilder/p2p/mocks/api.go +++ b/nodebuilder/p2p/mocks/api.go @@ -13,6 +13,7 @@ import ( network "github.com/libp2p/go-libp2p/core/network" peer "github.com/libp2p/go-libp2p/core/peer" protocol "github.com/libp2p/go-libp2p/core/protocol" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" ) // MockModule is a mock of Module interface. @@ -259,6 +260,21 @@ func (mr *MockModuleMockRecorder) PubSubPeers(arg0, arg1 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PubSubPeers", reflect.TypeOf((*MockModule)(nil).PubSubPeers), arg0, arg1) } +// ResourceState mocks base method. +func (m *MockModule) ResourceState(arg0 context.Context) (rcmgr.ResourceManagerStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ResourceState", arg0) + ret0, _ := ret[0].(rcmgr.ResourceManagerStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResourceState indicates an expected call of ResourceState. +func (mr *MockModuleMockRecorder) ResourceState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceState", reflect.TypeOf((*MockModule)(nil).ResourceState), arg0) +} + // UnblockPeer mocks base method. func (m *MockModule) UnblockPeer(arg0 context.Context, arg1 peer.ID) error { m.ctrl.T.Helper() diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 990a98cc27..270e760f21 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -2,7 +2,10 @@ package p2p import ( logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -37,10 +40,23 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { ) switch tp { - case node.Light, node.Full, node.Bridge: + case node.Full, node.Bridge: return fx.Module( "p2p", baseComponents, + fx.Provide(func() (network.ResourceManager, error) { + return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) + }), + ) + case node.Light: + return fx.Module( + "p2p", + baseComponents, + fx.Provide(func() (network.ResourceManager, error) { + limits := rcmgr.DefaultLimits + libp2p.SetDefaultServiceLimits(&limits) + return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(limits.AutoScale())) + }), ) default: panic("invalid node type") diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index 687ef3dfc5..d146bb63dd 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -12,6 +12,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" basichost "github.com/libp2p/go-libp2p/p2p/host/basic" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/libp2p/go-libp2p/p2p/net/conngater" ) @@ -70,6 +71,9 @@ type Module interface { // protocol.ID. BandwidthForProtocol(ctx context.Context, proto protocol.ID) (metrics.Stats, error) + // ResourceState returns the state of the resource manager. + ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) + // PubSubPeers returns the peer IDs of the peers joined on // the given topic. PubSubPeers(ctx context.Context, topic string) ([]peer.ID, error) @@ -82,6 +86,7 @@ type module struct { ps *pubsub.PubSub connGater *conngater.BasicConnectionGater bw *metrics.BandwidthCounter + rm network.ResourceManager } func newModule( @@ -89,12 +94,14 @@ func newModule( ps *pubsub.PubSub, cg *conngater.BasicConnectionGater, bw *metrics.BandwidthCounter, + rm network.ResourceManager, ) Module { return &module{ host: host, ps: ps, connGater: cg, bw: bw, + rm: rm, } } @@ -168,6 +175,15 @@ func (m *module) BandwidthForProtocol(_ context.Context, proto protocol.ID) (met return m.bw.GetBandwidthForProtocol(proto), nil } +func (m *module) ResourceState(context.Context) (rcmgr.ResourceManagerStat, error) { + rms, ok := m.rm.(rcmgr.ResourceManagerState) + if !ok { + return rcmgr.ResourceManagerStat{}, fmt.Errorf("network.resourceManager does not implement " + + "rcmgr.ResourceManagerState") + } + return rms.Stat(), nil +} + func (m *module) PubSubPeers(_ context.Context, topic string) ([]peer.ID, error) { return m.ps.ListPeers(topic), nil } @@ -194,6 +210,7 @@ type API struct { BandwidthStats func(context.Context) (metrics.Stats, error) `perm:"admin"` BandwidthForPeer func(ctx context.Context, id peer.ID) (metrics.Stats, error) `perm:"admin"` BandwidthForProtocol func(ctx context.Context, proto protocol.ID) (metrics.Stats, error) `perm:"admin"` + ResourceState func(context.Context) (rcmgr.ResourceManagerStat, error) `perm:"admin"` PubSubPeers func(ctx context.Context, topic string) ([]peer.ID, error) `perm:"admin"` } } @@ -262,6 +279,10 @@ func (api *API) BandwidthForProtocol(ctx context.Context, proto protocol.ID) (me return api.Internal.BandwidthForProtocol(ctx, proto) } +func (api *API) ResourceState(ctx context.Context) (rcmgr.ResourceManagerStat, error) { + return api.Internal.ResourceState(ctx) +} + func (api *API) PubSubPeers(ctx context.Context, topic string) ([]peer.ID, error) { return api.Internal.PubSubPeers(ctx, topic) } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index ca6e6709ab..1bde19e78f 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -14,6 +14,7 @@ import ( "github.com/libp2p/go-libp2p/core/network" libpeer "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,7 +27,7 @@ func TestP2PModule_Host(t *testing.T) { require.NoError(t, err) host, peer := net.Hosts()[0], net.Hosts()[1] - mgr := newModule(host, nil, nil, nil) + mgr := newModule(host, nil, nil, nil, nil) ctx := context.Background() @@ -61,7 +62,7 @@ func TestP2PModule_ConnManager(t *testing.T) { peer, err := libp2p.New() require.NoError(t, err) - mgr := newModule(host, nil, nil, nil) + mgr := newModule(host, nil, nil, nil, nil) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -89,7 +90,7 @@ func TestP2PModule_Autonat(t *testing.T) { host, err := libp2p.New(libp2p.EnableNATService()) require.NoError(t, err) - mgr := newModule(host, nil, nil, nil) + mgr := newModule(host, nil, nil, nil, nil) status, err := mgr.NATStatus(context.Background()) assert.NoError(t, err) @@ -121,7 +122,7 @@ func TestP2PModule_Bandwidth(t *testing.T) { require.NoError(t, err) }) - mgr := newModule(host, nil, nil, bw) + mgr := newModule(host, nil, nil, bw, nil) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -185,7 +186,7 @@ func TestP2PModule_Pubsub(t *testing.T) { gs, err := pubsub.NewGossipSub(ctx, host) require.NoError(t, err) - mgr := newModule(host, gs, nil, nil) + mgr := newModule(host, gs, nil, nil, nil) topicStr := "test-topic" @@ -221,7 +222,7 @@ func TestP2PModule_ConnGater(t *testing.T) { gater, err := ConnectionGater(datastore.NewMapDatastore()) require.NoError(t, err) - mgr := newModule(nil, nil, gater, nil) + mgr := newModule(nil, nil, gater, nil, nil) ctx := context.Background() @@ -235,3 +236,17 @@ func TestP2PModule_ConnGater(t *testing.T) { require.NoError(t, err) assert.Len(t, blocked, 0) } + +// TestP2PModule_ResourceManager tests P2P Module methods on +// the resourceManager. +func TestP2PModule_ResourceManager(t *testing.T) { + rm, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale())) + require.NoError(t, err) + + mgr := newModule(nil, nil, nil, nil, rm) + + state, err := mgr.ResourceState(context.Background()) + require.NoError(t, err) + + assert.NotNil(t, state) +} From aeb876ad3125df64a1b45e74876d5b4dedf56278 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 6 Jan 2023 13:03:03 +0200 Subject: [PATCH 0280/1008] feat(header|syncer)!: add params to syncer (#1544) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1543 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords Co-authored-by: Ryan --- header/sync/options.go | 70 ++++++++++++++++++++++++++++++ header/sync/sync.go | 32 +++++++------- header/sync/sync_head.go | 8 ++-- header/sync/sync_test.go | 46 ++++++++++++-------- header/verify.go | 14 +----- nodebuilder/header/config.go | 10 ++++- nodebuilder/header/constructors.go | 5 +-- nodebuilder/header/module.go | 7 +++ nodebuilder/header/module_test.go | 39 +++++++++++++++++ 9 files changed, 175 insertions(+), 56 deletions(-) create mode 100644 header/sync/options.go diff --git a/header/sync/options.go b/header/sync/options.go new file mode 100644 index 0000000000..b56d13142b --- /dev/null +++ b/header/sync/options.go @@ -0,0 +1,70 @@ +package sync + +import ( + "fmt" + "time" +) + +type Options func(*Parameters) + +// Parameters is the set of parameters that must be configured for the syncer. +type Parameters struct { + // TrustingPeriod is period through which we can trust a header's validators set. + // + // Should be significantly less than the unbonding period (e.g. unbonding + // period = 3 weeks, trusting period = 2 weeks). + // + // More specifically, trusting period + time needed to check headers + time + // needed to report and punish misbehavior should be less than the unbonding + // period. + TrustingPeriod time.Duration + // MaxRequestSizeNumber of headers that can be requested at once. + MaxRequestSize uint64 + // blockTime provides a reference point for the Syncer to determine + // whether its subjective head is outdated. + // Keeping it private, we don't want users to independently configure it. + // default value is set to 0 so syncer will constantly request networking head. + blockTime time.Duration +} + +// DefaultParameters returns the default params to configure the syncer. +func DefaultParameters() Parameters { + return Parameters{ + TrustingPeriod: 168 * time.Hour, + MaxRequestSize: 512, + } +} + +func (p *Parameters) Validate() error { + if p.TrustingPeriod == 0 { + return fmt.Errorf("invalid trusting period duration: %v", p.TrustingPeriod) + } + if p.MaxRequestSize == 0 { + return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) + } + return nil +} + +// WithBlockTime is a functional option that configures the +// `blockTime` parameter. +func WithBlockTime(duration time.Duration) Options { + return func(p *Parameters) { + p.blockTime = duration + } +} + +// WithTrustingPeriod is a functional option that configures the +// `TrustingPeriod` parameter. +func WithTrustingPeriod(duration time.Duration) Options { + return func(p *Parameters) { + p.TrustingPeriod = duration + } +} + +// WithMaxRequestSize is a functional option that configures the +// `MaxRequestSize` parameter. +func WithMaxRequestSize(amount uint64) Options { + return func(p *Parameters) { + p.MaxRequestSize = amount + } +} diff --git a/header/sync/sync.go b/header/sync/sync.go index 7d88650f7a..c2b90d6f02 100644 --- a/header/sync/sync.go +++ b/header/sync/sync.go @@ -37,10 +37,6 @@ type Syncer struct { exchange header.Exchange store header.Store - // blockTime provides a reference point for the Syncer to determine - // whether its subjective head is outdated - blockTime time.Duration - // stateLk protects state which represents the current or latest sync stateLk sync.RWMutex state State @@ -54,17 +50,27 @@ type Syncer struct { // controls lifecycle for syncLoop ctx context.Context cancel context.CancelFunc + + Params *Parameters } // NewSyncer creates a new instance of Syncer. -func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscriber, blockTime time.Duration) *Syncer { +func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscriber, opts ...Options) (*Syncer, error) { + params := DefaultParameters() + for _, opt := range opts { + opt(¶ms) + } + if err := params.Validate(); err != nil { + return nil, err + } + return &Syncer{ sub: sub, exchange: exchange, store: store, - blockTime: blockTime, triggerSync: make(chan struct{}, 1), // should be buffered - } + Params: ¶ms, + }, nil } // Start starts the syncing routine. @@ -239,20 +245,12 @@ func (s *Syncer) processHeaders(ctx context.Context, from, to uint64) (int, erro return s.store.Append(ctx, headers...) } -// TODO(@Wondertan): Number of headers that can be requested at once. Either make this configurable -// or, -// -// find a proper rationale for constant. -// -// TODO(@Wondertan): Make configurable -var requestSize uint64 = 512 - // findHeaders gets headers from either remote peers or from local cache of headers received by // PubSub - [from:to] func (s *Syncer) findHeaders(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { amount := to - from + 1 // + 1 to include 'to' height as well - if amount > requestSize { - to, amount = from+requestSize, requestSize + if amount > s.Params.MaxRequestSize { + to, amount = from+s.Params.MaxRequestSize, s.Params.MaxRequestSize } out := make([]*header.ExtendedHeader, 0, amount) diff --git a/header/sync/sync_head.go b/header/sync/sync_head.go index ee64cc8d81..1ec33b17f7 100644 --- a/header/sync/sync_head.go +++ b/header/sync/sync_head.go @@ -33,7 +33,7 @@ func (s *Syncer) subjectiveHead(ctx context.Context) (*header.ExtendedHeader, er return nil, err } // check if our subjective header is not expired and use it - if !netHead.IsExpired() { + if !netHead.IsExpired(s.Params.TrustingPeriod) { return netHead, nil } log.Infow("subjective header expired", "height", netHead.Height) @@ -49,9 +49,9 @@ func (s *Syncer) subjectiveHead(ctx context.Context) (*header.ExtendedHeader, er default: log.Infow("subjective initialization finished", "height", netHead.Height) return netHead, nil - case netHead.IsExpired(): + case netHead.IsExpired(s.Params.TrustingPeriod): log.Warnw("subjective initialization with an expired header", "height", netHead.Height) - case !netHead.IsRecent(s.blockTime): + case !netHead.IsRecent(s.Params.blockTime): log.Warnw("subjective initialization with an old header", "height", netHead.Height) } log.Warn("trusted peer is out of sync") @@ -68,7 +68,7 @@ func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error return nil, err } // if subjective header is recent enough (relative to the network's block time) - just use it - if sbjHead.IsRecent(s.blockTime) { + if sbjHead.IsRecent(s.Params.blockTime) { return sbjHead, nil } // otherwise, request head from a trusted peer, as we assume it is fully synced diff --git a/header/sync/sync_test.go b/header/sync/sync_test.go index 4324925f56..174312b193 100644 --- a/header/sync/sync_test.go +++ b/header/sync/sync_test.go @@ -15,13 +15,7 @@ import ( "github.com/celestiaorg/celestia-node/header/store" ) -var blockTime = 30 * time.Second - func TestSyncSimpleRequestingHead(t *testing.T) { - // this way we force local head of Syncer to expire, so it requests a new one from trusted peer - header.TrustingPeriod = time.Microsecond - requestSize = 13 // just some random number - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -36,7 +30,15 @@ func TestSyncSimpleRequestingHead(t *testing.T) { require.NoError(t, err) localStore := store.NewTestStore(ctx, t, head) - syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, blockTime) + syncer, err := NewSyncer( + local.NewExchange(remoteStore), + localStore, + &header.DummySubscriber{}, + WithBlockTime(time.Second*30), + WithTrustingPeriod(time.Microsecond), + WithMaxRequestSize(13), + ) + require.NoError(t, err) err = syncer.Start(ctx) require.NoError(t, err) @@ -60,9 +62,6 @@ func TestSyncSimpleRequestingHead(t *testing.T) { } func TestSyncCatchUp(t *testing.T) { - // just set a big enough value, so we trust local header and don't request anything - header.TrustingPeriod = time.Minute - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -71,9 +70,15 @@ func TestSyncCatchUp(t *testing.T) { remoteStore := store.NewTestStore(ctx, t, head) localStore := store.NewTestStore(ctx, t, head) - syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, blockTime) + syncer, err := NewSyncer( + local.NewExchange(remoteStore), + localStore, + &header.DummySubscriber{}, + WithTrustingPeriod(time.Minute), + ) + require.NoError(t, err) // 1. Initial sync - err := syncer.Start(ctx) + err = syncer.Start(ctx) require.NoError(t, err) // 2. chain grows and syncer misses that @@ -105,9 +110,6 @@ func TestSyncCatchUp(t *testing.T) { } func TestSyncPendingRangesWithMisses(t *testing.T) { - // just set a big enough value, so we trust local header and don't request anything - header.TrustingPeriod = time.Minute - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -116,8 +118,14 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { remoteStore := store.NewTestStore(ctx, t, head) localStore := store.NewTestStore(ctx, t, head) - syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, blockTime) - err := syncer.Start(ctx) + syncer, err := NewSyncer( + local.NewExchange(remoteStore), + localStore, + &header.DummySubscriber{}, + WithTrustingPeriod(time.Minute), + ) + require.NoError(t, err) + err = syncer.Start(ctx) require.NoError(t, err) // miss 1 (helps to test that Syncer properly requests missed Headers from Exchange) @@ -161,7 +169,6 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { // Test that only one objective header is requested at a time func TestSyncer_OnlyOneRecentRequest(t *testing.T) { - blockTime := time.Nanosecond // so that we always request recent ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -169,7 +176,8 @@ func TestSyncer_OnlyOneRecentRequest(t *testing.T) { store := store.NewTestStore(ctx, t, suite.Head()) newHead := suite.GenExtendedHeader() exchange := &exchangeCountingHead{header: newHead} - syncer := NewSyncer(exchange, store, &header.DummySubscriber{}, blockTime) + syncer, err := NewSyncer(exchange, store, &header.DummySubscriber{}, WithBlockTime(time.Nanosecond)) + require.NoError(t, err) res := make(chan *header.ExtendedHeader) for i := 0; i < 10; i++ { diff --git a/header/verify.go b/header/verify.go index d035308c14..a753dd11d3 100644 --- a/header/verify.go +++ b/header/verify.go @@ -11,19 +11,9 @@ import ( // TODO(@Wondertan): We should request TrustingPeriod from the network's state params or // listen for network params changes to always have a topical value. -// TrustingPeriod is period through which we can trust a header's validators set. -// -// Should be significantly less than the unbonding period (e.g. unbonding -// period = 3 weeks, trusting period = 2 weeks). -// -// More specifically, trusting period + time needed to check headers + time -// needed to report and punish misbehavior should be less than the unbonding -// period. -var TrustingPeriod = 168 * time.Hour - // IsExpired checks if header is expired against trusting period. -func (eh *ExtendedHeader) IsExpired() bool { - expirationTime := eh.Time.Add(TrustingPeriod) +func (eh *ExtendedHeader) IsExpired(period time.Duration) bool { + expirationTime := eh.Time.Add(period) return !expirationTime.After(time.Now()) } diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 893a816726..1a695e881e 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -10,6 +10,7 @@ import ( p2p_exchange "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -24,7 +25,8 @@ type Config struct { // headers at any moment. TrustedPeers []string - Store store.Parameters + Store store.Parameters + Syncer sync.Parameters Server p2p_exchange.ServerParameters Client p2p_exchange.ClientParameters `toml:",omitempty"` @@ -35,6 +37,7 @@ func DefaultConfig(tp node.Type) Config { TrustedHash: "", TrustedPeers: make([]string, 0), Store: store.DefaultParameters(), + Syncer: sync.DefaultParameters(), Server: p2p_exchange.DefaultServerParameters(), } @@ -88,6 +91,11 @@ func (cfg *Config) Validate(tp node.Type) error { return fmt.Errorf("module/header: misconfiguration of store: %w", err) } + err = cfg.Syncer.Validate() + if err != nil { + return fmt.Errorf("module/header: misconfiguration of syncer: %w", err) + } + err = cfg.Server.Validate() if err != nil { return fmt.Errorf("module/header: misconfiguration of p2p exchange server: %w", err) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 5c65941312..35e0ac8303 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -2,7 +2,6 @@ package header import ( "context" - "time" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" @@ -70,8 +69,8 @@ func newP2PExchange(cfg Config) func( } // newSyncer constructs new Syncer for headers. -func newSyncer(ex header.Exchange, store InitStore, sub header.Subscriber, duration time.Duration) *sync.Syncer { - return sync.NewSyncer(ex, store, sub, duration) +func newSyncer(ex header.Exchange, store InitStore, sub header.Subscriber, opts []sync.Options) (*sync.Syncer, error) { + return sync.NewSyncer(ex, store, sub, opts...) } // InitStore is a type representing initialized header store. diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 101222bbea..c51accb612 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -60,6 +60,13 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(func(subscriber *p2p.Subscriber) header.Subscriber { return subscriber }), + fx.Provide(func(cfg Config) []sync.Options { + return []sync.Options{ + sync.WithBlockTime(modp2p.BlockTime), + sync.WithTrustingPeriod(cfg.Syncer.TrustingPeriod), + sync.WithMaxRequestSize(cfg.Syncer.MaxRequestSize), + } + }), fx.Provide(fx.Annotate( newSyncer, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, syncer *sync.Syncer) error { diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 3dffc4b3ac..0a0f074f25 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -1,18 +1,23 @@ package header import ( + "context" "testing" + "time" "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p" + pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/p2p" "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -43,6 +48,40 @@ func TestConstructModule_StoreParams(t *testing.T) { require.Equal(t, headerStore.Params.WriteBatchSize, cfg.Store.WriteBatchSize) } +// TestConstructModule_SyncerParams ensures that all passed via functional options +// params are set in syncer correctly. +func TestConstructModule_SyncerParams(t *testing.T) { + cfg := DefaultConfig(node.Light) + cfg.Syncer.TrustingPeriod = time.Hour + cfg.Syncer.MaxRequestSize = 1 + var syncer *sync.Syncer + app := fxtest.New(t, + fx.Supply(modp2p.Private), + fx.Supply(modp2p.Bootstrappers{}), + fx.Provide(context.Background), + fx.Provide(libp2p.New), + fx.Provide(func(b datastore.Batching) (*conngater.BasicConnectionGater, error) { + return conngater.NewBasicConnectionGater(b) + }), + fx.Provide(func() *pubsub.PubSub { + return nil + }), + fx.Provide(func() datastore.Batching { + return datastore.NewMapDatastore() + }), + fx.Provide(func() fraud.Service { + return nil + }), + ConstructModule(node.Light, &cfg), + fx.Invoke(func(s *sync.Syncer) { + syncer = s + }), + ) + require.Equal(t, cfg.Syncer.TrustingPeriod, syncer.Params.TrustingPeriod) + require.Equal(t, cfg.Syncer.MaxRequestSize, syncer.Params.MaxRequestSize) + require.NoError(t, app.Err()) +} + // TestConstructModule_ExchangeParams ensures that all passed via functional options // params are set in store correctly. func TestConstructModule_ExchangeParams(t *testing.T) { From bbe34a23e8accaf67845cb3a7742a63d1b0660a8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 6 Jan 2023 15:44:50 +0100 Subject: [PATCH 0281/1008] fix(cel-key): `keyring-dir` flag should override `node.type` if provided. (#1571) This PR fixes `cel-key` such that if the `--keyring-dir` flag is provided, it will override the `node.type` store determination. So if a user passes `--keyring-dir`, the utility will look to the given path instead of the default paths calculated via `node.type` flag. --- cmd/cel-key/node_types.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 2ba3a91e8c..9f7d0b70d9 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -36,6 +36,11 @@ func DirectoryFlags() *flag.FlagSet { } func ParseDirectoryFlags(cmd *cobra.Command) error { + // if keyring-dir is explicitly set, use it + if cmd.Flags().Changed(sdkflags.FlagKeyringDir) { + return nil + } + nodeType := cmd.Flag(nodeDirKey).Value.String() if nodeType == "" { return errors.New("no node type provided") From 753b52a1f3f0c37b2f9ed6c77e3cd3f393e56124 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 9 Jan 2023 12:14:13 +0100 Subject: [PATCH 0282/1008] feat(swamp): preinit all swamp nodes with genesis header (#1556) HeaderEx recently has started to sync from any peers, and the only place where trusted peers are required now is initialization. This change does initialization for nodes during instantiation in swap completely avoiding the requirement for trusted peers in the node's config. As a side effect, we won't cover initialization anymore in swamp tests, so we should write some single test to specifically this. --- nodebuilder/tests/swamp/swamp.go | 34 ++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 6bbe0c8d11..005805ae11 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -8,6 +8,7 @@ import ( "testing" "time" + mdutils "github.com/ipfs/go-merkledag/test" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" @@ -16,10 +17,11 @@ import ( "github.com/tendermint/tendermint/libs/bytes" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header" + headercore "github.com/celestiaorg/celestia-node/header/core" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" @@ -46,11 +48,12 @@ type Swamp struct { BridgeNodes []*nodebuilder.Node FullNodes []*nodebuilder.Node LightNodes []*nodebuilder.Node - trustedHash string comps *Components ClientContext testnode.Context accounts []string + + genesis *header.ExtendedHeader } // NewSwamp creates a new instance of Swamp. @@ -78,12 +81,12 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { comps: ic, accounts: ic.Accounts, } - swp.trustedHash = swp.getTrustedHash(ctx) swp.t.Cleanup(func() { swp.stopAllNodes(ctx, swp.BridgeNodes, swp.FullNodes, swp.LightNodes) }) + swp.setupGenesis(ctx) return swp } @@ -156,10 +159,20 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { return host } -// getTrustedHash is needed for celestia nodes to get the trustedhash -// from CoreClient. This is required to initialize and start correctly. -func (s *Swamp) getTrustedHash(ctx context.Context) string { - return s.WaitTillHeight(ctx, 1).String() +// setupGenesis sets up genesis Header. +// This is required to initialize and start correctly. +func (s *Swamp) setupGenesis(ctx context.Context) { + s.WaitTillHeight(ctx, 1) + + ex := headercore.NewExchange( + core.NewBlockFetcher(s.ClientContext.Client), + mdutils.Bserv(), + header.MakeExtendedHeader, + ) + + h, err := ex.GetByHeight(ctx, 1) + require.NoError(s.t, err) + s.genesis = h } // NewBridgeNode creates a new instance of a BridgeNode providing a default config @@ -234,7 +247,6 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti // like from the test case, we need to check them and not use // default that are set here cfg, _ := store.Config() - cfg.Header.TrustedHash = s.trustedHash cfg.RPC.Port = "0" // tempDir is used for the eds.Store @@ -242,10 +254,12 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti options = append(options, p2p.WithHost(s.createPeer(ks)), fx.Replace(node.StorePath(tempDir)), + fx.Invoke(func(ctx context.Context, store header.Store) error { + return store.Init(ctx, s.genesis) + }), ) node, err := nodebuilder.New(t, p2p.Private, store, options...) require.NoError(s.t, err) - return node } From 016454ea63f79233118bc3a54ea1217150d4ba39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:04:59 +0200 Subject: [PATCH 0283/1008] chore(deps): bump golang.org/x/crypto from 0.3.0 to 0.5.0 (#1577) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 475180c447..9bdaa37545 100644 --- a/go.mod +++ b/go.mod @@ -64,9 +64,9 @@ require ( go.opentelemetry.io/otel/trace v1.11.1 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.8.0 - golang.org/x/crypto v0.3.0 + golang.org/x/crypto v0.5.0 golang.org/x/sync v0.1.0 - golang.org/x/text v0.5.0 + golang.org/x/text v0.6.0 google.golang.org/grpc v1.51.0 ) @@ -296,10 +296,10 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.3.0 // indirect + golang.org/x/net v0.5.0 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/term v0.3.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/term v0.4.0 // indirect golang.org/x/tools v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.81.0 // indirect diff --git a/go.sum b/go.sum index 7be8edd0e7..641d9d33ab 100644 --- a/go.sum +++ b/go.sum @@ -1897,8 +1897,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2023,8 +2023,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2182,13 +2182,13 @@ golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2198,8 +2198,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From da272197bc67f12de8ba742808d7777d33e6ce65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 13:17:40 +0000 Subject: [PATCH 0284/1008] chore(deps): bump github.com/ipfs/go-blockservice from 0.4.0 to 0.5.0 (#1574) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9bdaa37545..05d4013c12 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/ipfs/go-bitswap v0.11.0 github.com/ipfs/go-block-format v0.0.3 - github.com/ipfs/go-blockservice v0.4.0 + github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 diff --git a/go.sum b/go.sum index 641d9d33ab..4c30b422ce 100644 --- a/go.sum +++ b/go.sum @@ -788,8 +788,8 @@ github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJ github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= -github.com/ipfs/go-blockservice v0.4.0 h1:7MUijAW5SqdsqEW/EhnNFRJXVF8mGU5aGhZ3CQaCWbY= -github.com/ipfs/go-blockservice v0.4.0/go.mod h1:kRjO3wlGW9mS1aKuiCeGhx9K1DagQ10ACpVO59qgAx4= +github.com/ipfs/go-blockservice v0.5.0 h1:B2mwhhhVQl2ntW2EIpaWPwSCxSuqr5fFA93Ms4bYLEY= +github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= From 6adccf1fd1d805f3aea30af796964a36ba333f61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 13:25:28 +0000 Subject: [PATCH 0285/1008] chore(deps): bump golang.org/x/text from 0.5.0 to 0.6.0 (#1572) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From aa40a543a05af8337b793216079f67ba40b157b4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 9 Jan 2023 18:15:57 +0100 Subject: [PATCH 0286/1008] feat!: `share.Getter` and `getters.IPLDGetter` (#1518) Co-authored-by: Vlad Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Closes https://github.com/celestiaorg/celestia-node/issues/1488 --- api/gateway/share.go | 2 +- api/rpc_test.go | 4 + das/daser_test.go | 7 +- docs/adr/adr-009-public-api.md | 8 +- nodebuilder/das/mocks/api.go | 17 ++- nodebuilder/fraud/mocks/api.go | 3 +- nodebuilder/header/mocks/api.go | 11 +- nodebuilder/share/constructors.go | 15 +- nodebuilder/share/mocks/api.go | 37 ++--- nodebuilder/share/module.go | 4 + nodebuilder/share/share.go | 31 ++-- nodebuilder/state/mocks/api.go | 3 +- share/availability/cache/availability_test.go | 28 ++-- share/availability/cache/testing.go | 22 +-- share/availability/full/availability.go | 14 +- share/availability/full/availability_test.go | 5 +- share/availability/full/testing.go | 17 ++- share/availability/light/availability.go | 27 ++-- share/availability/light/availability_test.go | 70 ++++----- share/availability/light/testing.go | 28 ++-- share/availability/test/testing.go | 4 +- share/getter.go | 97 ++++++++++++ share/getters/ipld.go | 141 ++++++++++++++++++ share/ipld/corrupted_data_test.go | 4 +- share/service/service.go | 140 ----------------- 25 files changed, 415 insertions(+), 324 deletions(-) create mode 100644 share/getter.go create mode 100644 share/getters/ipld.go delete mode 100644 share/service/service.go diff --git a/api/gateway/share.go b/api/gateway/share.go index 303a0c3546..7161ae121e 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -108,7 +108,7 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID } // perform request shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, nID) - return shares, header.Height, err + return shares.Flatten(), header.Height, err } func dataFromShares(shares []share.Share) ([][]byte, error) { diff --git a/api/rpc_test.go b/api/rpc_test.go index 218f31d2de..c2e4f761f8 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -289,6 +289,10 @@ func TestAllReturnValuesAreMarshalable(t *testing.T) { } func implementsMarshaler(t *testing.T, typ reflect.Type) { + // TODO(@distractedm1nd): Write marshaller for ExtendedDataSquare + if typ.Name() == "ExtendedDataSquare" { + return + } // the passed type may already implement json.Marshaler and we don't need to go deeper if typ.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()) { return diff --git a/das/daser_test.go b/das/daser_test.go index fac628418b..c829171161 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -23,6 +23,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share/getters" ) var timeout = time.Second * 15 @@ -32,7 +33,7 @@ var timeout = time.Second * 15 func TestDASerLifecycle(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) bServ := mdutils.Bserv() - avail := light.TestAvailability(bServ) + avail := light.TestAvailability(getters.NewIPLDGetter(bServ)) // 15 headers from the past and 15 future headers mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) @@ -72,7 +73,7 @@ func TestDASerLifecycle(t *testing.T) { func TestDASer_Restart(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) bServ := mdutils.Bserv() - avail := light.TestAvailability(bServ) + avail := light.TestAvailability(getters.NewIPLDGetter(bServ)) // 15 headers from the past and 15 future headers mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) @@ -147,7 +148,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { ps, err := pubsub.NewGossipSub(ctx, net.Hosts()[0], pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) - avail := full.TestAvailability(bServ) + avail := full.TestAvailability(getters.NewIPLDGetter(bServ)) // 15 headers from the past and 15 future headers mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index e13c4dea8e..7eb8dd8076 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -120,13 +120,15 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) // GetShare returns the Share from the given data Root at the given row/col // coordinates. GetShare(ctx context.Context, root *Root, row, col int) (Share, error) - // GetSharesByNamespace returns all shares of the given nID from the given data - // Root. + // GetEDS gets the full EDS identified by the given root. + GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) + // GetSharesByNamespace gets all shares from an EDS within the given namespace. + // Shares are returned in a row-by-row order if the namespace spans multiple rows. GetSharesByNamespace( ctx context.Context, root *Root, nID namespace.ID, - ) ([]Share, error) + ) (share.NamespacedShares, error) // SharesAvailable subjectively validates if Shares committed to the given data // Root are available on the network. SharesAvailable(ctx context.Context, root *Root) error diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go index 04a123115a..c4046e90e8 100644 --- a/nodebuilder/das/mocks/api.go +++ b/nodebuilder/das/mocks/api.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - das "github.com/celestiaorg/celestia-node/das" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -50,3 +49,17 @@ func (mr *MockModuleMockRecorder) SamplingStats(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SamplingStats", reflect.TypeOf((*MockModule)(nil).SamplingStats), arg0) } + +// WaitCatchUp mocks base method. +func (m *MockModule) WaitCatchUp(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitCatchUp", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitCatchUp indicates an expected call of WaitCatchUp. +func (mr *MockModuleMockRecorder) WaitCatchUp(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitCatchUp", reflect.TypeOf((*MockModule)(nil).WaitCatchUp), arg0) +} diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index cc94a4e794..6b31c2b851 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - fraud "github.com/celestiaorg/celestia-node/fraud" fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 7ddae3f113..c6c9ee0a88 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -67,15 +66,15 @@ func (mr *MockModuleMockRecorder) Head(arg0 interface{}) *gomock.Call { } // IsSyncing mocks base method. -func (m *MockModule) IsSyncing() bool { +func (m *MockModule) IsSyncing(arg0 context.Context) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsSyncing") + ret := m.ctrl.Call(m, "IsSyncing", arg0) ret0, _ := ret[0].(bool) return ret0 } // IsSyncing indicates an expected call of IsSyncing. -func (mr *MockModuleMockRecorder) IsSyncing() *gomock.Call { +func (mr *MockModuleMockRecorder) IsSyncing(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSyncing", reflect.TypeOf((*MockModule)(nil).IsSyncing)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSyncing", reflect.TypeOf((*MockModule)(nil).IsSyncing), arg0) } diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index ca880a84dd..0a5f5061aa 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/filecoin-project/dagstore" - "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" @@ -17,7 +16,6 @@ import ( "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/service" ) func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { @@ -44,17 +42,8 @@ func cacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batch return ca } -func newModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Availability) Module { - serv := service.NewShareService(bServ, avail) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return serv.Start(ctx) - }, - OnStop: func(ctx context.Context) error { - return serv.Stop(ctx) - }, - }) - return &module{serv} +func newModule(getter share.Getter, avail share.Availability) Module { + return &module{getter, avail} } // ensureEmptyCARExists adds an empty EDS to the provided EDS store. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index e2f30be425..586c6dab4b 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - da "github.com/celestiaorg/celestia-app/pkg/da" + share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" + rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -37,41 +38,41 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { return m.recorder } -// GetShare mocks base method. -func (m *MockModule) GetShare(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2, arg3 int) ([]byte, error) { +// GetEDS mocks base method. +func (m *MockModule) GetEDS(arg0 context.Context, arg1 *da.DataAvailabilityHeader) (*rsmt2d.ExtendedDataSquare, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetShare", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].([]byte) + ret := m.ctrl.Call(m, "GetEDS", arg0, arg1) + ret0, _ := ret[0].(*rsmt2d.ExtendedDataSquare) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetShare indicates an expected call of GetShare. -func (mr *MockModuleMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +// GetEDS indicates an expected call of GetEDS. +func (mr *MockModuleMockRecorder) GetEDS(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShare", reflect.TypeOf((*MockModule)(nil).GetShare), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEDS", reflect.TypeOf((*MockModule)(nil).GetEDS), arg0, arg1) } -// GetShares mocks base method. -func (m *MockModule) GetShares(arg0 context.Context, arg1 *da.DataAvailabilityHeader) ([][][]byte, error) { +// GetShare mocks base method. +func (m *MockModule) GetShare(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2, arg3 int) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetShares", arg0, arg1) - ret0, _ := ret[0].([][][]byte) + ret := m.ctrl.Call(m, "GetShare", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetShares indicates an expected call of GetShares. -func (mr *MockModuleMockRecorder) GetShares(arg0, arg1 interface{}) *gomock.Call { +// GetShare indicates an expected call of GetShare. +func (mr *MockModuleMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShares", reflect.TypeOf((*MockModule)(nil).GetShares), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShare", reflect.TypeOf((*MockModule)(nil).GetShare), arg0, arg1, arg2, arg3) } // GetSharesByNamespace mocks base method. -func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 namespace.ID) ([][]byte, error) { +func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 namespace.ID) (share.NamespacedShares, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) - ret0, _ := ret[0].([][]byte) + ret0, _ := ret[0].(share.NamespacedShares) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 06eb5a5c8c..24f6d265b4 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -13,7 +13,10 @@ import ( "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" + + "github.com/celestiaorg/celestia-node/libs/fxutil" ) func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { @@ -27,6 +30,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(discovery(*cfg)), fx.Provide(newModule), fx.Invoke(share.EnsureEmptySquareExists), + fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), ) switch tp { diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index 9ada1d1a62..0c703f9a15 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -4,9 +4,9 @@ import ( "context" "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/service" ) var _ Module = (*API)(nil) @@ -34,11 +34,13 @@ type Module interface { // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. ProbabilityOfAvailability(context.Context) float64 + // GetShare gets a Share by coordinates in EDS. GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) - GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) - // GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the - // given namespace.ID. - GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) + // GetEDS gets the full EDS identified by the given root. + GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) + // GetSharesByNamespace gets all shares from an EDS within the given namespace. + // Shares are returned in a row-by-row order if the namespace spans multiple rows. + GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) (share.NamespacedShares, error) } // API is a wrapper around Module for the RPC. @@ -52,15 +54,15 @@ type API struct { dah *share.Root, row, col int, ) (share.Share, error) `perm:"public"` - GetShares func( + GetEDS func( ctx context.Context, root *share.Root, - ) ([][]share.Share, error) `perm:"public"` + ) (*rsmt2d.ExtendedDataSquare, error) `perm:"public"` GetSharesByNamespace func( ctx context.Context, root *share.Root, namespace namespace.ID, - ) ([]share.Share, error) `perm:"public"` + ) (share.NamespacedShares, error) `perm:"public"` } } @@ -76,22 +78,23 @@ func (api *API) GetShare(ctx context.Context, dah *share.Root, row, col int) (sh return api.Internal.GetShare(ctx, dah, row, col) } -func (api *API) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) { - return api.Internal.GetShares(ctx, root) +func (api *API) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + return api.Internal.GetEDS(ctx, root) } func (api *API) GetSharesByNamespace( ctx context.Context, root *share.Root, namespace namespace.ID, -) ([]share.Share, error) { +) (share.NamespacedShares, error) { return api.Internal.GetSharesByNamespace(ctx, root, namespace) } type module struct { - *service.ShareService + share.Getter + share.Availability } -func (m *module) SharesAvailable(ctx context.Context, root *share.Root) error { - return m.ShareService.SharesAvailable(ctx, root) +func (m module) SharesAvailable(ctx context.Context, root *share.Root) error { + return m.Availability.SharesAvailable(ctx, root) } diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 50996edb23..f3017e4a2b 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,12 +9,11 @@ import ( reflect "reflect" math "cosmossdk.io/math" + namespace "github.com/celestiaorg/nmt/namespace" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" - - namespace "github.com/celestiaorg/nmt/namespace" ) // MockModule is a mock of Module interface. diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index c86b3a522d..e578c0db6f 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -18,7 +18,6 @@ import ( "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/service" ) // TestCacheAvailability tests to ensure that the successful result of a @@ -27,31 +26,31 @@ func TestCacheAvailability(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fullLocalServ, dah0 := RandFullLocalServiceWithSquare(t, 16) - lightLocalServ, dah1 := RandLightLocalServiceWithSquare(t, 16) + fullLocalServ, dah0 := FullAvailabilityWithLocalRandSquare(t, 16) + lightLocalServ, dah1 := LightAvailabilityWithLocalRandSquare(t, 16) var tests = []struct { - service *service.ShareService - root *share.Root + avail share.Availability + root *share.Root }{ { - service: fullLocalServ, - root: dah0, + avail: fullLocalServ, + root: dah0, }, { - service: lightLocalServ, - root: dah1, + avail: lightLocalServ, + root: dah1, }, } for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - ca := tt.service.Availability.(*ShareAvailability) + ca := tt.avail.(*ShareAvailability) // ensure the dah isn't yet in the cache exists, err := ca.ds.Has(ctx, rootKey(tt.root)) require.NoError(t, err) assert.False(t, exists) - err = tt.service.SharesAvailable(ctx, tt.root) + err = tt.avail.SharesAvailable(ctx, tt.root) require.NoError(t, err) // ensure the dah was stored properly exists, err = ca.ds.Has(ctx, rootKey(tt.root)) @@ -72,9 +71,8 @@ func TestCacheAvailability_Failed(t *testing.T) { defer cancel() ca := NewShareAvailability(&dummyAvailability{}, sync.MutexWrap(datastore.NewMapDatastore())) - serv := service.NewShareService(mdutils.Bserv(), ca) - err := serv.SharesAvailable(ctx, &invalidHeader) + err := ca.SharesAvailable(ctx, &invalidHeader) require.Error(t, err) // ensure the dah was NOT cached exists, err := ca.ds.Has(ctx, rootKey(&invalidHeader)) @@ -112,10 +110,10 @@ func TestCacheAvailability_MinRoot(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fullLocalServ, _ := RandFullLocalServiceWithSquare(t, 16) + fullLocalAvail, _ := FullAvailabilityWithLocalRandSquare(t, 16) minDAH := da.MinDataAvailabilityHeader() - err := fullLocalServ.SharesAvailable(ctx, &minDAH) + err := fullLocalAvail.SharesAvailable(ctx, &minDAH) assert.NoError(t, err) } diff --git a/share/availability/cache/testing.go b/share/availability/cache/testing.go index 8ef7a1e743..978d51b6b5 100644 --- a/share/availability/cache/testing.go +++ b/share/availability/cache/testing.go @@ -11,29 +11,29 @@ import ( "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/service" + "github.com/celestiaorg/celestia-node/share/getters" ) -// RandLightLocalServiceWithSquare is the same as light.RandServiceWithSquare, except -// the share.Availability is wrapped with cache availability. -func RandLightLocalServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { +// LightAvailabilityWithLocalRandSquare wraps light.GetterWithRandSquare with cache availability +func LightAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availability, *share.Root) { bServ := mdutils.Bserv() store := dssync.MutexWrap(ds.NewMapDatastore()) + getter := getters.NewIPLDGetter(bServ) avail := NewShareAvailability( - light.TestAvailability(bServ), + light.TestAvailability(getter), store, ) - return service.NewShareService(bServ, avail), availability_test.RandFillBS(t, n, bServ) + return avail, availability_test.RandFillBS(t, n, bServ) } -// RandFullLocalServiceWithSquare is the same as full.RandServiceWithSquare, except -// the share.Availability is wrapped with cache availability. -func RandFullLocalServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { +// FullAvailabilityWithLocalRandSquare wraps full.GetterWithRandSquare with cache availability +func FullAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availability, *share.Root) { bServ := mdutils.Bserv() store := dssync.MutexWrap(ds.NewMapDatastore()) + getter := getters.NewIPLDGetter(bServ) avail := NewShareAvailability( - full.TestAvailability(bServ), + full.TestAvailability(getter), store, ) - return service.NewShareService(bServ, avail), availability_test.RandFillBS(t, n, bServ) + return avail, availability_test.RandFillBS(t, n, bServ) } diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 8fdd98007c..f6ef1ff05f 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -4,14 +4,12 @@ import ( "context" "errors" - "github.com/ipfs/go-blockservice" ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" - "github.com/celestiaorg/celestia-node/share/eds" ) var log = logging.Logger("share/full") @@ -20,17 +18,17 @@ var log = logging.Logger("share/full") // recovery technique. It is considered "full" because it is required // to download enough shares to fully reconstruct the data square. type ShareAvailability struct { - rtrv *eds.Retriever - disc *discovery.Discovery + getter share.Getter + disc *discovery.Discovery cancel context.CancelFunc } // NewShareAvailability creates a new full ShareAvailability. -func NewShareAvailability(bServ blockservice.BlockService, disc *discovery.Discovery) *ShareAvailability { +func NewShareAvailability(getter share.Getter, disc *discovery.Discovery) *ShareAvailability { return &ShareAvailability{ - rtrv: eds.NewRetriever(bServ), - disc: disc, + getter: getter, + disc: disc, } } @@ -61,7 +59,7 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro panic(err) } - _, err := fa.rtrv.Retrieve(ctx, root) + _, err := fa.getter.GetEDS(ctx, root) if err != nil { log.Errorw("availability validation failed", "root", root.Hash(), "err", err) if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { diff --git a/share/availability/full/availability_test.go b/share/availability/full/availability_test.go index ef3e653d9e..f914d3912f 100644 --- a/share/availability/full/availability_test.go +++ b/share/availability/full/availability_test.go @@ -34,7 +34,8 @@ func TestSharesAvailable_Full(t *testing.T) { defer cancel() // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it - service, dah := RandServiceWithSquare(t, 16) - err := service.SharesAvailable(ctx, dah) + getter, dah := GetterWithRandSquare(t, 16) + avail := TestAvailability(getter) + err := avail.SharesAvailable(ctx, dah) assert.NoError(t, err) } diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index f072671df0..c792c1e70a 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/ipfs/go-blockservice" mdutils "github.com/ipfs/go-merkledag/test" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/p2p/discovery/routing" @@ -12,14 +11,15 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/service" + "github.com/celestiaorg/celestia-node/share/getters" ) -// RandServiceWithSquare provides a service.ShareService filled with 'n' NMT +// GetterWithRandSquare provides a share.Getter filled with 'n' NMT // trees of 'n' random shares, essentially storing a whole square. -func RandServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { +func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.Root) { bServ := mdutils.Bserv() - return service.NewShareService(bServ, TestAvailability(bServ)), availability_test.RandFillBS(t, n, bServ) + getter := getters.NewIPLDGetter(bServ) + return getter, availability_test.RandFillBS(t, n, bServ) } // RandNode creates a Full Node filled with a random block of the given size. @@ -31,11 +31,12 @@ func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_t // Node creates a new empty Full Node. func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { nd := dn.NewTestNode() - nd.ShareService = service.NewShareService(nd.BlockService, TestAvailability(nd.BlockService)) + nd.Getter = getters.NewIPLDGetter(nd.BlockService) + nd.Availability = TestAvailability(nd.Getter) return nd } -func TestAvailability(bServ blockservice.BlockService) *ShareAvailability { +func TestAvailability(getter share.Getter) *ShareAvailability { disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) - return NewShareAvailability(bServ, disc) + return NewShareAvailability(getter, disc) } diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index e90d46e816..f804a91e32 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -5,16 +5,13 @@ import ( "errors" "math" - "github.com/libp2p/go-libp2p/core/peer" - - "github.com/celestiaorg/celestia-node/share/ipld" - - "github.com/ipfs/go-blockservice" ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/getters" ) var log = logging.Logger("share/light") @@ -24,7 +21,7 @@ var log = logging.Logger("share/light") // its availability. It is assumed that there are a lot of lightAvailability instances // on the network doing sampling over the same Root to collectively verify its availability. type ShareAvailability struct { - bserv blockservice.BlockService + getter share.Getter // disc discovers new full nodes in the network. // it is not allowed to call advertise for light nodes (Full nodes only). disc *discovery.Discovery @@ -33,12 +30,12 @@ type ShareAvailability struct { // NewShareAvailability creates a new light Availability. func NewShareAvailability( - bserv blockservice.BlockService, + getter share.Getter, disc *discovery.Discovery, ) *ShareAvailability { la := &ShareAvailability{ - bserv: bserv, - disc: disc, + getter: getter, + disc: disc, } return la } @@ -72,20 +69,20 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo return err } + // indicate to the share.Getter that a blockservice session should be created. This + // functionality is optional and must be supported by the used share.Getter. + ctx = getters.WithSession(ctx) ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) defer cancel() log.Debugw("starting sampling session", "root", dah.Hash()) - ses := blockservice.NewSession(ctx, la.bserv) errs := make(chan error, len(samples)) for _, s := range samples { go func(s Sample) { - root, leaf := ipld.Translate(dah, s.Row, s.Col) - - log.Debugw("fetching share", "root", dah.Hash(), "leaf CID", leaf) - _, err := share.GetShare(ctx, ses, root, leaf, len(dah.RowsRoots)) + log.Debugw("fetching share", "root", dah.Hash(), "row", s.Row, "col", s.Col) + _, err := la.getter.GetShare(ctx, dah, s.Row, s.Col) if err != nil { - log.Debugw("error fetching share", "root", dah.Hash(), "leaf CID", leaf) + log.Debugw("error fetching share", "root", dah.Hash(), "row", s.Row, "col", s.Col) } // we don't really care about Share bodies at this point // it also means we now saved the Share in local storage diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 6cf998bf0b..1fe77bf021 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -6,7 +6,6 @@ import ( _ "embed" "encoding/hex" "encoding/json" - "math" mrand "math/rand" "strconv" "testing" @@ -33,9 +32,9 @@ func TestSharesAvailable(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // RandServiceWithSquare creates a Light ShareAvailability inside, so we can test it - service, dah := RandServiceWithSquare(t, 16) - err := service.SharesAvailable(ctx, dah) + getter, dah := GetterWithRandSquare(t, 16) + avail := TestAvailability(getter) + err := avail.SharesAvailable(ctx, dah) assert.NoError(t, err) } @@ -43,10 +42,10 @@ func TestSharesAvailableFailed(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // RandServiceWithSquare creates a Light ShareAvailability inside, so we can test it - s, _ := RandServiceWithSquare(t, 16) + getter, _ := GetterWithRandSquare(t, 16) + avail := TestAvailability(getter) empty := header.EmptyDAH() - err := s.SharesAvailable(ctx, &empty) + err := avail.SharesAvailable(ctx, &empty) assert.Error(t, err) } @@ -68,20 +67,15 @@ func TestGetShare(t *testing.T) { defer cancel() n := 16 - serv, dah := RandServiceWithSquare(t, n) - err := serv.Start(ctx) - require.NoError(t, err) + getter, dah := GetterWithRandSquare(t, n) for i := range make([]bool, n) { for j := range make([]bool, n) { - sh, err := serv.GetShare(ctx, dah, i, j) + sh, err := getter.GetShare(ctx, dah, i, j) assert.NotNil(t, sh) assert.NoError(t, err) } } - - err = serv.Stop(ctx) - require.NoError(t, err) } func TestService_GetSharesByNamespace(t *testing.T) { @@ -96,7 +90,7 @@ func TestService_GetSharesByNamespace(t *testing.T) { for _, tt := range tests { t.Run("size: "+strconv.Itoa(tt.squareSize), func(t *testing.T) { - serv, bServ := RandService() + getter, bServ := EmptyGetter() n := tt.squareSize * tt.squareSize randShares := share.RandShares(t, n) idx1 := (n - 1) / 2 @@ -108,16 +102,18 @@ func TestService_GetSharesByNamespace(t *testing.T) { root := availability_test.FillBS(t, bServ, randShares) randNID := randShares[idx1][:8] - shares, err := serv.GetSharesByNamespace(context.Background(), root, randNID) + shares, err := getter.GetSharesByNamespace(context.Background(), root, randNID) require.NoError(t, err) - assert.Len(t, shares, tt.expectedShareCount) - for _, value := range shares { + require.NoError(t, shares.Verify(root, randNID)) + flattened := shares.Flatten() + assert.Len(t, flattened, tt.expectedShareCount) + for _, value := range flattened { assert.Equal(t, randNID, []byte(share.ID(value))) } if tt.expectedShareCount > 1 { // idx1 is always smaller than idx2 - assert.Equal(t, randShares[idx1], shares[0]) - assert.Equal(t, randShares[idx2], shares[1]) + assert.Equal(t, randShares[idx1], flattened[0]) + assert.Equal(t, randShares[idx2], flattened[1]) } }) } @@ -128,35 +124,20 @@ func TestGetShares(t *testing.T) { defer cancel() n := 16 - serv, dah := RandServiceWithSquare(t, n) - err := serv.Start(ctx) - require.NoError(t, err) + getter, dah := GetterWithRandSquare(t, n) - shares, err := serv.GetShares(ctx, dah) - require.NoError(t, err) - - flattened := make([][]byte, 0, len(shares)*2) - for _, row := range shares { - flattened = append(flattened, row...) - } - // generate DAH from shares returned by `share.GetShares` to compare - // calculated DAH to expected DAH - squareSize := uint64(math.Sqrt(float64(len(flattened)))) - eds, err := da.ExtendShares(squareSize, flattened) + eds, err := getter.GetEDS(ctx, dah) require.NoError(t, err) gotDAH := da.NewDataAvailabilityHeader(eds) require.True(t, dah.Equals(&gotDAH)) - - err = serv.Stop(ctx) - require.NoError(t, err) } func TestService_GetSharesByNamespaceNotFound(t *testing.T) { - serv, root := RandServiceWithSquare(t, 1) + getter, root := GetterWithRandSquare(t, 1) root.RowsRoots = nil - shares, err := serv.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) + shares, err := getter.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) assert.Len(t, shares, 0) assert.NoError(t, err) } @@ -173,12 +154,12 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { for _, tt := range tests { b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { t := &testing.T{} - serv, root := RandServiceWithSquare(t, tt.amountShares) + getter, root := GetterWithRandSquare(t, tt.amountShares) randNID := root.RowsRoots[(len(root.RowsRoots)-1)/2][:8] root.RowsRoots[(len(root.RowsRoots) / 2)] = root.RowsRoots[(len(root.RowsRoots)-1)/2] b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := serv.GetSharesByNamespace(context.Background(), root, randNID) + _, err := getter.GetSharesByNamespace(context.Background(), root, randNID) require.NoError(t, err) } }) @@ -186,7 +167,7 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { } func TestSharesRoundTrip(t *testing.T) { - serv, store := RandService() + getter, store := EmptyGetter() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -325,11 +306,12 @@ func TestSharesRoundTrip(t *testing.T) { require.NoError(t, err) dah := da.NewDataAvailabilityHeader(extSquare) - shares, err := serv.GetSharesByNamespace(ctx, &dah, namespace) + shares, err := getter.GetSharesByNamespace(ctx, &dah, namespace) require.NoError(t, err) + require.NoError(t, shares.Verify(&dah, namespace)) require.NotEmpty(t, shares) - msgs, err := appshares.ParseMsgs(shares) + msgs, err := appshares.ParseMsgs(shares.Flatten()) require.NoError(t, err) assert.Len(t, msgs.MessagesList, len(msgsInNamespace)) for i := range msgs.MessagesList { diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go index 9a63198b3a..0072f226c6 100644 --- a/share/availability/light/testing.go +++ b/share/availability/light/testing.go @@ -12,22 +12,23 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/service" + "github.com/celestiaorg/celestia-node/share/getters" ) -// RandServiceWithSquare provides a share.Service filled with 'n' NMT -// trees of 'n' random shares, essentially storing a whole square. -func RandServiceWithSquare(t *testing.T, n int) (*service.ShareService, *share.Root) { +// GetterWithRandSquare provides a share.Getter filled with 'n' NMT trees of 'n' random shares, +// essentially storing a whole square. +func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.Root) { bServ := mdutils.Bserv() - - return service.NewShareService(bServ, TestAvailability(bServ)), availability_test.RandFillBS(t, n, bServ) + getter := getters.NewIPLDGetter(bServ) + return getter, availability_test.RandFillBS(t, n, bServ) } -// RandService provides an unfilled share.Service with corresponding -// blockservice.BlockService than can be filled by the test. -func RandService() (*service.ShareService, blockservice.BlockService) { +// EmptyGetter provides an unfilled share.Getter with corresponding blockservice.BlockService than +// can be filled by the test. +func EmptyGetter() (share.Getter, blockservice.BlockService) { bServ := mdutils.Bserv() - return service.NewShareService(bServ, TestAvailability(bServ)), bServ + getter := getters.NewIPLDGetter(bServ) + return getter, bServ } // RandNode creates a Light Node filled with a random block of the given size. @@ -39,13 +40,14 @@ func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_t // Node creates a new empty Light Node. func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { nd := dn.NewTestNode() - nd.ShareService = service.NewShareService(nd.BlockService, TestAvailability(nd.BlockService)) + nd.Getter = getters.NewIPLDGetter(nd.BlockService) + nd.Availability = TestAvailability(nd.Getter) return nd } -func TestAvailability(bServ blockservice.BlockService) *ShareAvailability { +func TestAvailability(getter share.Getter) *ShareAvailability { disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) - return NewShareAvailability(bServ, disc) + return NewShareAvailability(getter, disc) } func SubNetNode(sn *availability_test.SubNet) *availability_test.TestNode { diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 421256428f..6e665a8a0e 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -19,7 +19,6 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/service" ) // RandFillBS fills the given BlockService with a random block of a given size. @@ -38,7 +37,8 @@ func FillBS(t *testing.T, bServ blockservice.BlockService, shares []share.Share) type TestNode struct { net *TestDagNet - *service.ShareService + share.Getter + share.Availability blockservice.BlockService host.Host } diff --git a/share/getter.go b/share/getter.go new file mode 100644 index 0000000000..fdbbcb6ea0 --- /dev/null +++ b/share/getter.go @@ -0,0 +1,97 @@ +package share + +import ( + "context" + "fmt" + + "github.com/minio/sha256-simd" + + "github.com/celestiaorg/celestia-node/share/ipld" + + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +// Getter interface provides a set of accessors for shares by the Root. +// Automatically verifies integrity of shares(exceptions possible depending on the implementation). +type Getter interface { + // GetShare gets a Share by coordinates in EDS. + GetShare(ctx context.Context, root *Root, row, col int) (Share, error) + + // GetEDS gets the full EDS identified by the given root. + GetEDS(context.Context, *Root) (*rsmt2d.ExtendedDataSquare, error) + + // GetSharesByNamespace gets all shares from an EDS within the given namespace. + // Shares are returned in a row-by-row order if the namespace spans multiple rows. + GetSharesByNamespace(context.Context, *Root, namespace.ID) (NamespacedShares, error) +} + +// NamespacedShares represents all shares with proofs within a specific namespace of an EDS. +type NamespacedShares []NamespacedRow + +// Flatten returns the concatenated slice of all NamespacedRow shares. +func (ns NamespacedShares) Flatten() []Share { + shares := make([]Share, 0) + for _, row := range ns { + shares = append(shares, row.Shares...) + } + return shares +} + +// NamespacedRow represents all shares with proofs within a specific namespace of a single EDS row. +type NamespacedRow struct { + Shares []Share + Proof *ipld.Proof +} + +// Verify validates NamespacedShares by checking every row with nmt inclusion proof. +func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { + originalRoots := make([][]byte, 0) + for _, row := range root.RowsRoots { + if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { + originalRoots = append(originalRoots, row) + } + } + + if len(originalRoots) != len(ns) { + return fmt.Errorf("amount of rows differs between root and namespace shares: expected %d, got %d", + len(originalRoots), len(ns)) + } + + for i, row := range ns { + // verify row data against row hash from original root + if !row.verify(originalRoots[i], nID) { + return fmt.Errorf("row verification failed: row %d doesn't match original root: %s", i, root.Hash()) + } + } + return nil +} + +// verify validates the row using nmt inclusion proof. +func (row *NamespacedRow) verify(rowRoot []byte, nID namespace.ID) bool { + // construct nmt leaves from shares by prepending namespace + leaves := make([][]byte, 0, len(row.Shares)) + for _, sh := range row.Shares { + leaves = append(leaves, append(sh[:NamespaceSize], sh...)) + } + + proofNodes := make([][]byte, 0, len(row.Proof.Nodes)) + for _, n := range row.Proof.Nodes { + proofNodes = append(proofNodes, ipld.NamespacedSha256FromCID(n)) + } + + // construct new proof + inclusionProof := nmt.NewInclusionProof( + row.Proof.Start, + row.Proof.End, + proofNodes, + ipld.NMTIgnoreMaxNamespace) + + // verify inclusion + return inclusionProof.VerifyNamespace( + sha256.New(), + nID, + leaves, + rowRoot) +} diff --git a/share/getters/ipld.go b/share/getters/ipld.go new file mode 100644 index 0000000000..b81f49ae76 --- /dev/null +++ b/share/getters/ipld.go @@ -0,0 +1,141 @@ +package getters + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + "golang.org/x/sync/errgroup" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" + + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +var _ share.Getter = (*IPLDGetter)(nil) + +// IPLDGetter is a share.Getter that retrieves shares from the IPLD network. Result caching is +// handled by the provided blockservice. A blockservice session will be created for retrieval if the +// passed context is wrapped with WithSession. +type IPLDGetter struct { + rtrv *eds.Retriever + bServ blockservice.BlockService +} + +// NewIPLDGetter creates a new share.Getter that retrieves shares from the IPLD network. +func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter { + return &IPLDGetter{ + rtrv: eds.NewRetriever(bServ), + bServ: bServ, + } +} + +func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { + root, leaf := ipld.Translate(dah, row, col) + blockGetter := getGetter(ctx, ig.bServ) + nd, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + if err != nil { + return nil, fmt.Errorf("getter/ipld: failed to retrieve share: %w", err) + } + + return nd, nil +} + +func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + // rtrv.Retrieve calls shares.GetShares until enough shares are retrieved to reconstruct the EDS + eds, err := ig.rtrv.Retrieve(ctx, root) + if err != nil { + return nil, fmt.Errorf("getter/ipld: failed to retrieve eds: %w", err) + } + return eds, nil +} + +func (ig *IPLDGetter) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + nID namespace.ID, +) (share.NamespacedShares, error) { + if len(nID) != share.NamespaceSize { + return nil, fmt.Errorf("getter/ipld: expected namespace ID of size %d, got %d", + share.NamespaceSize, len(nID)) + } + + rowRootCIDs := make([]cid.Cid, 0, len(root.RowsRoots)) + for _, row := range root.RowsRoots { + if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { + rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) + } + } + if len(rowRootCIDs) == 0 { + return nil, nil + } + + blockGetter := getGetter(ctx, ig.bServ) + errGroup, ctx := errgroup.WithContext(ctx) + shares := make([]share.NamespacedRow, len(rowRootCIDs)) + for i, rootCID := range rowRootCIDs { + // shadow loop variables, to ensure correct values are captured + i, rootCID := i, rootCID + errGroup.Go(func() error { + proof := new(ipld.Proof) + row, err := share.GetSharesByNamespace(ctx, blockGetter, rootCID, nID, len(root.RowsRoots), proof) + shares[i] = share.NamespacedRow{ + Shares: row, + Proof: proof, + } + if err != nil { + return fmt.Errorf("getter/ipld: retrieving nID %x for row %x: %w", nID, rootCID, err) + } + return nil + }) + } + + if err := errGroup.Wait(); err != nil { + return nil, err + } + + return shares, nil +} + +var sessionKey = &session{} + +// session is a struct that can optionally be passed by context to the share.Getter methods using +// WithSession to indicate that a blockservice session should be created. +type session struct { + sync.Mutex + atomic.Pointer[blockservice.Session] +} + +// WithSession stores an empty session in the context, indicating that a blockservice session should +// be created. +func WithSession(ctx context.Context) context.Context { + return context.WithValue(ctx, sessionKey, &session{}) +} + +func getGetter(ctx context.Context, service blockservice.BlockService) blockservice.BlockGetter { + s, ok := ctx.Value(sessionKey).(*session) + if !ok { + return service + } + + val := s.Load() + if val != nil { + return val + } + + s.Lock() + defer s.Unlock() + val = s.Load() + if val == nil { + val = blockservice.NewSession(ctx, service) + s.Store(val) + } + return val +} diff --git a/share/ipld/corrupted_data_test.go b/share/ipld/corrupted_data_test.go index 8b14f6d2aa..df1d0d7888 100644 --- a/share/ipld/corrupted_data_test.go +++ b/share/ipld/corrupted_data_test.go @@ -10,7 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/service" + "github.com/celestiaorg/celestia-node/share/getters" ) // sharesAvailableTimeout is an arbitrarily picked interval of time in which a TestNode is expected @@ -26,7 +26,7 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { requestor := full.Node(net) provider, mockBS := availability_test.MockNode(t, net) - provider.ShareService = service.NewShareService(provider.BlockService, full.TestAvailability(provider.BlockService)) + provider.Availability = full.TestAvailability(getters.NewIPLDGetter(provider.BlockService)) net.ConnectAll() // before the provider starts attacking, we should be able to retrieve successfully. We pass a size diff --git a/share/service/service.go b/share/service/service.go deleted file mode 100644 index 190e68c4ea..0000000000 --- a/share/service/service.go +++ /dev/null @@ -1,140 +0,0 @@ -package service - -import ( - "context" - "fmt" - - "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - "golang.org/x/sync/errgroup" - - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" -) - -// TODO(@Wondertan): Simple thread safety for Start and Stop would not hurt. -type ShareService struct { - share.Availability - rtrv *eds.Retriever - bServ blockservice.BlockService - // session is blockservice sub-session that applies optimization for fetching/loading related - // nodes, like shares prefer session over blockservice for fetching nodes. - session blockservice.BlockGetter - cancel context.CancelFunc -} - -// NewService creates a new basic share.Module. -func NewShareService(bServ blockservice.BlockService, avail share.Availability) *ShareService { - return &ShareService{ - rtrv: eds.NewRetriever(bServ), - Availability: avail, - bServ: bServ, - } -} - -func (s *ShareService) Start(context.Context) error { - if s.session != nil || s.cancel != nil { - return fmt.Errorf("share: service already started") - } - - // NOTE: The ctx given as param is used to control Start flow and only needed when Start is - // blocking, but this one is not. - // - // The newer context here is created to control lifecycle of the session and peer discovery. - ctx, cancel := context.WithCancel(context.Background()) - s.cancel = cancel - s.session = blockservice.NewSession(ctx, s.bServ) - return nil -} - -func (s *ShareService) Stop(context.Context) error { - if s.session == nil || s.cancel == nil { - return fmt.Errorf("share: service already stopped") - } - - s.cancel() - s.cancel = nil - s.session = nil - return nil -} - -func (s *ShareService) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { - root, leaf := ipld.Translate(dah, row, col) - nd, err := share.GetShare(ctx, s.bServ, root, leaf, len(dah.RowsRoots)) - if err != nil { - return nil, err - } - - return nd, nil -} - -func (s *ShareService) GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) { - eds, err := s.rtrv.Retrieve(ctx, root) - if err != nil { - return nil, err - } - - origWidth := int(eds.Width() / 2) - shares := make([][]share.Share, origWidth) - - for i := 0; i < origWidth; i++ { - row := eds.Row(uint(i)) - shares[i] = make([]share.Share, origWidth) - for j := 0; j < origWidth; j++ { - shares[i][j] = row[j] - } - } - - return shares, nil -} - -// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the -// given namespace.ID. -func (s *ShareService) GetSharesByNamespace( - ctx context.Context, - root *share.Root, - nID namespace.ID, -) ([]share.Share, error) { - if len(nID) != share.NamespaceSize { - return nil, fmt.Errorf("expected namespace ID of size %d, got %d", share.NamespaceSize, len(nID)) - } - - rowRootCIDs := make([]cid.Cid, 0) - for _, row := range root.RowsRoots { - if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { - rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) - } - } - if len(rowRootCIDs) == 0 { - return nil, nil - } - - errGroup, ctx := errgroup.WithContext(ctx) - shares := make([][]share.Share, len(rowRootCIDs)) - for i, rootCID := range rowRootCIDs { - // shadow loop variables, to ensure correct values are captured - i, rootCID := i, rootCID - errGroup.Go(func() (err error) { - shares[i], err = share.GetSharesByNamespace(ctx, s.bServ, rootCID, nID, len(root.RowsRoots), nil) - return - }) - } - - if err := errGroup.Wait(); err != nil { - return nil, err - } - - // we don't know the amount of shares in the namespace, so we cannot preallocate properly - // TODO(@Wondertan): Consider improving encoding schema for data in the shares that will also - // include metadata with the amount of shares. If we are talking about plenty of data here, proper - // preallocation would make a difference - var out []share.Share - for i := 0; i < len(rowRootCIDs); i++ { - out = append(out, shares[i]...) - } - - return out, nil -} From 18fe4df1c6260c1b0617b33f5390defc9e6f97e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 17:31:16 +0000 Subject: [PATCH 0287/1008] chore(deps): bump go.uber.org/multierr from 1.8.0 to 1.9.0 (#1573) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 05d4013c12..67d9b48c6d 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.11.1 go.uber.org/fx v1.18.2 - go.uber.org/multierr v1.8.0 + go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.5.0 golang.org/x/sync v0.1.0 golang.org/x/text v0.6.0 diff --git a/go.sum b/go.sum index 4c30b422ce..030746d87d 100644 --- a/go.sum +++ b/go.sum @@ -1846,8 +1846,8 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= From e4061a82810955fcc0b6fa83d273f6ff60d620fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 17:40:30 +0000 Subject: [PATCH 0288/1008] chore(deps): bump github.com/ipfs/go-ipld-cbor from 0.0.5 to 0.0.6 (#1468) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 67d9b48c6d..c919d0f2d7 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/ipfs/go-ipfs-exchange-interface v0.2.0 github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-ipfs-routing v0.3.0 - github.com/ipfs/go-ipld-cbor v0.0.5 + github.com/ipfs/go-ipld-cbor v0.0.6 github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.7.0 diff --git a/go.sum b/go.sum index 030746d87d..ccf7583cee 100644 --- a/go.sum +++ b/go.sum @@ -852,8 +852,9 @@ github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8 github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= -github.com/ipfs/go-ipld-cbor v0.0.5 h1:ovz4CHKogtG2KB/h1zUp5U0c/IzZrL435rCh5+K/5G8= github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= +github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= +github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= From e1eaa5f957e8c8ab85e3236db2e08fae406dd78f Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 10 Jan 2023 12:05:11 +0100 Subject: [PATCH 0289/1008] feat(share): `getters.StoreGetter` (#1528) Closes https://github.com/celestiaorg/celestia-node/issues/1490 --- nodebuilder/das/mocks/api.go | 3 +- nodebuilder/fraud/mocks/api.go | 3 +- nodebuilder/header/mocks/api.go | 3 +- nodebuilder/share/mocks/api.go | 3 +- nodebuilder/state/mocks/api.go | 3 +- share/getters/getter_test.go | 98 +++++++++++++++++++++++++++++++++ share/getters/ipld.go | 52 ++++------------- share/getters/store.go | 82 +++++++++++++++++++++++++++ share/getters/utils.go | 75 +++++++++++++++++++++++++ 9 files changed, 277 insertions(+), 45 deletions(-) create mode 100644 share/getters/getter_test.go create mode 100644 share/getters/store.go create mode 100644 share/getters/utils.go diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go index c4046e90e8..68ffaf3c8c 100644 --- a/nodebuilder/das/mocks/api.go +++ b/nodebuilder/das/mocks/api.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - das "github.com/celestiaorg/celestia-node/das" gomock "github.com/golang/mock/gomock" + + das "github.com/celestiaorg/celestia-node/das" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 6b31c2b851..cc94a4e794 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + fraud "github.com/celestiaorg/celestia-node/fraud" fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index c6c9ee0a88..683370078f 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - header "github.com/celestiaorg/celestia-node/header" gomock "github.com/golang/mock/gomock" + + header "github.com/celestiaorg/celestia-node/header" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 586c6dab4b..1b26273c0f 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,11 +8,12 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index f3017e4a2b..50996edb23 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,11 +9,12 @@ import ( reflect "reflect" math "cosmossdk.io/math" - namespace "github.com/celestiaorg/nmt/namespace" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" + + namespace "github.com/celestiaorg/nmt/namespace" ) // MockModule is a mock of Module interface. diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go new file mode 100644 index 0000000000..3924348ac7 --- /dev/null +++ b/share/getters/getter_test.go @@ -0,0 +1,98 @@ +package getters + +import ( + "context" + "testing" + + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" +) + +func TestStoreGetter(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + + err = edsStore.Start(ctx) + require.NoError(t, err) + + sg := NewStoreGetter(edsStore) + + t.Run("GetShare", func(t *testing.T) { + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + squareSize := int(eds.Width()) + for i := 0; i < squareSize; i++ { + for j := 0; j < squareSize; j++ { + share, err := sg.GetShare(ctx, &dah, i, j) + require.NoError(t, err) + assert.Equal(t, eds.GetCell(uint(i), uint(j)), share) + } + } + }) + + t.Run("GetEDS", func(t *testing.T) { + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + retrievedEDS, err := sg.GetEDS(ctx, &dah) + require.NoError(t, err) + assert.True(t, share.EqualEDS(eds, retrievedEDS)) + }) + + t.Run("GetSharesByNamespace", func(t *testing.T) { + eds, nID, dah := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + shares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + require.NoError(t, err) + require.NoError(t, shares.Verify(&dah, nID)) + assert.Len(t, shares.Flatten(), 2) + }) + +} + +func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + + return eds, dah +} + +// randomEDSWithDoubledNamespace generates a random EDS and ensures that there are two shares in the +// middle that share a namespace. +func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedDataSquare, []byte, share.Root) { + n := size * size + randShares := share.RandShares(t, n) + idx1 := (n - 1) / 2 + idx2 := n / 2 + // make it so that two rows have the same namespace ID + copy(randShares[idx2][:8], randShares[idx1][:8]) + + eds, err := rsmt2d.ComputeExtendedDataSquare( + randShares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(size)), + ) + require.NoError(t, err, "failure to recompute the extended data square") + dah := da.NewDataAvailabilityHeader(eds) + + return eds, randShares[idx1][:8], dah +} diff --git a/share/getters/ipld.go b/share/getters/ipld.go index b81f49ae76..cddfd6f939 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -7,21 +7,18 @@ import ( "sync/atomic" "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - "golang.org/x/sync/errgroup" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" ) var _ share.Getter = (*IPLDGetter)(nil) -// IPLDGetter is a share.Getter that retrieves shares from the IPLD network. Result caching is +// IPLDGetter is a share.Getter that retrieves shares from the bitswap network. Result caching is // handled by the provided blockservice. A blockservice session will be created for retrieval if the // passed context is wrapped with WithSession. type IPLDGetter struct { @@ -29,7 +26,7 @@ type IPLDGetter struct { bServ blockservice.BlockService } -// NewIPLDGetter creates a new share.Getter that retrieves shares from the IPLD network. +// NewIPLDGetter creates a new share.Getter that retrieves shares from the bitswap network. func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter { return &IPLDGetter{ rtrv: eds.NewRetriever(bServ), @@ -37,8 +34,11 @@ func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter { } } +// GetShare gets a single share at the given EDS coordinates from the bitswap network. func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { root, leaf := ipld.Translate(dah, row, col) + + // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) nd, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) if err != nil { @@ -62,45 +62,17 @@ func (ig *IPLDGetter) GetSharesByNamespace( root *share.Root, nID namespace.ID, ) (share.NamespacedShares, error) { - if len(nID) != share.NamespaceSize { - return nil, fmt.Errorf("getter/ipld: expected namespace ID of size %d, got %d", - share.NamespaceSize, len(nID)) - } - - rowRootCIDs := make([]cid.Cid, 0, len(root.RowsRoots)) - for _, row := range root.RowsRoots { - if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { - rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) - } - } - if len(rowRootCIDs) == 0 { - return nil, nil + err := verifyNIDSize(nID) + if err != nil { + return nil, fmt.Errorf("getter/ipld: invalid namespace ID: %w", err) } + // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - errGroup, ctx := errgroup.WithContext(ctx) - shares := make([]share.NamespacedRow, len(rowRootCIDs)) - for i, rootCID := range rowRootCIDs { - // shadow loop variables, to ensure correct values are captured - i, rootCID := i, rootCID - errGroup.Go(func() error { - proof := new(ipld.Proof) - row, err := share.GetSharesByNamespace(ctx, blockGetter, rootCID, nID, len(root.RowsRoots), proof) - shares[i] = share.NamespacedRow{ - Shares: row, - Proof: proof, - } - if err != nil { - return fmt.Errorf("getter/ipld: retrieving nID %x for row %x: %w", nID, rootCID, err) - } - return nil - }) - } - - if err := errGroup.Wait(); err != nil { - return nil, err + shares, err := collectSharesByNamespace(ctx, blockGetter, root, nID) + if err != nil { + return nil, fmt.Errorf("getter/ipld: failed to retrieve shares by namespace: %w", err) } - return shares, nil } diff --git a/share/getters/store.go b/share/getters/store.go new file mode 100644 index 0000000000..54559119ab --- /dev/null +++ b/share/getters/store.go @@ -0,0 +1,82 @@ +package getters + +import ( + "context" + "fmt" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" + + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +var _ share.Getter = (*StoreGetter)(nil) + +// StoreGetter is a share.Getter that retrieves shares from an eds.Store. No results are saved to +// the eds.Store after retrieval. +type StoreGetter struct { + store *eds.Store +} + +// NewStoreGetter creates a new share.Getter that retrieves shares from an eds.Store. +func NewStoreGetter(store *eds.Store) *StoreGetter { + return &StoreGetter{ + store: store, + } +} + +// GetShare gets a single share at the given EDS coordinates from the eds.Store through the +// corresponding CAR-level blockstore. +func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { + root, leaf := ipld.Translate(dah, row, col) + bs, err := sg.store.CARBlockstore(ctx, dah.Hash()) + if err != nil { + return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) + } + + // wrap the read-only CAR blockstore in a getter + blockGetter := eds.NewBlockGetter(bs) + share, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + if err != nil { + return nil, fmt.Errorf("getter/store: failed to retrieve share: %w", err) + } + + return share, nil +} + +// GetEDS gets the EDS identified by the given root from the EDS store. +func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + eds, err := sg.store.Get(ctx, root.Hash()) + if err != nil { + return nil, fmt.Errorf("getter/store: failed to retrieve eds: %w", err) + } + return eds, nil +} + +// GetSharesByNamespace gets all EDS shares in the given namespace from the EDS store through the +// corresponding CAR-level blockstore. +func (sg *StoreGetter) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + nID namespace.ID, +) (share.NamespacedShares, error) { + err := verifyNIDSize(nID) + if err != nil { + return nil, fmt.Errorf("getter/store: invalid namespace ID: %w", err) + } + + bs, err := sg.store.CARBlockstore(ctx, root.Hash()) + if err != nil { + return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) + } + + // wrap the read-only CAR blockstore in a getter + blockGetter := eds.NewBlockGetter(bs) + shares, err := collectSharesByNamespace(ctx, blockGetter, root, nID) + if err != nil { + return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) + } + return shares, nil +} diff --git a/share/getters/utils.go b/share/getters/utils.go new file mode 100644 index 0000000000..04648e236f --- /dev/null +++ b/share/getters/utils.go @@ -0,0 +1,75 @@ +package getters + +import ( + "context" + "fmt" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + "golang.org/x/sync/errgroup" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" + + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" +) + +// filterRootsByNamespace returns the row roots from the given share.Root that contain the passed +// namespace ID. +func filterRootsByNamespace(root *share.Root, nID namespace.ID) []cid.Cid { + rowRootCIDs := make([]cid.Cid, 0, len(root.RowsRoots)) + for _, row := range root.RowsRoots { + if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { + rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) + } + } + return rowRootCIDs +} + +// collectSharesByNamespace collects NamespaceShares within the given namespace ID from the given +// share.Root. +func collectSharesByNamespace( + ctx context.Context, + bg blockservice.BlockGetter, + root *share.Root, + nID namespace.ID, +) (share.NamespacedShares, error) { + rootCIDs := filterRootsByNamespace(root, nID) + if len(rootCIDs) == 0 { + return nil, nil + } + + errGroup, ctx := errgroup.WithContext(ctx) + shares := make([]share.NamespacedRow, len(rootCIDs)) + for i, rootCID := range rootCIDs { + // shadow loop variables, to ensure correct values are captured + i, rootCID := i, rootCID + errGroup.Go(func() error { + proof := new(ipld.Proof) + row, err := share.GetSharesByNamespace(ctx, bg, rootCID, nID, len(root.RowsRoots), proof) + shares[i] = share.NamespacedRow{ + Shares: row, + Proof: proof, + } + if err != nil { + return fmt.Errorf("retrieving nID %x for row %x: %w", nID, rootCID, err) + } + return nil + }) + } + + if err := errGroup.Wait(); err != nil { + return nil, err + } + + return shares, nil +} + +func verifyNIDSize(nID namespace.ID) error { + if len(nID) != share.NamespaceSize { + return fmt.Errorf("expected namespace ID of size %d, got %d", + share.NamespaceSize, len(nID)) + } + return nil +} From 3f2369c194e8948f355b36cf186d185fe7ff2ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?joshcs=2Eeth=20=E1=B5=8D=E1=B5=90?= <46639943+jcstein@users.noreply.github.com> Date: Wed, 11 Jan 2023 06:48:17 -0500 Subject: [PATCH 0290/1008] docker: Update entrypoint.sh for multiple networks (#1522) resolves https://github.com/celestiaorg/celestia-node/issues/1502 --- docker/Dockerfile | 1 + docker/entrypoint.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9c0ca01ddc..b51ee7fa72 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -12,6 +12,7 @@ RUN make build FROM ubuntu:20.04 # Default node type can be overwritten in deployment manifest ENV NODE_TYPE bridge +ENV P2P_NETWORK mocha COPY docker/entrypoint.sh / diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 3548288d40..f5fcb4c74a 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -3,7 +3,7 @@ set -e if [ "$1" = 'celestia' ]; then - ./celestia "${NODE_TYPE}" init + ./celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" exec ./"$@" "--" fi From 97c07a44c5966af738c138e29be6e4a65c2db481 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 11 Jan 2023 13:53:28 +0100 Subject: [PATCH 0291/1008] chore: updating rsmt2d to v0.8.0 and removing test skip (#1585) Closes https://github.com/celestiaorg/celestia-node/issues/1541 --- api/rpc_test.go | 4 ---- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/api/rpc_test.go b/api/rpc_test.go index c2e4f761f8..218f31d2de 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -289,10 +289,6 @@ func TestAllReturnValuesAreMarshalable(t *testing.T) { } func implementsMarshaler(t *testing.T, typ reflect.Type) { - // TODO(@distractedm1nd): Write marshaller for ExtendedDataSquare - if typ.Name() == "ExtendedDataSquare" { - return - } // the passed type may already implement json.Marshaler and we don't need to go deeper if typ.Implements(reflect.TypeOf(new(json.Marshaler)).Elem()) { return diff --git a/go.mod b/go.mod index c919d0f2d7..f42a141473 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v0.11.0 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.11.0 - github.com/celestiaorg/rsmt2d v0.7.0 + github.com/celestiaorg/rsmt2d v0.8.0 github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 diff --git a/go.sum b/go.sum index ccf7583cee..2a635ad09c 100644 --- a/go.sum +++ b/go.sum @@ -232,8 +232,8 @@ github.com/celestiaorg/nmt v0.11.0 h1:iqTaNwnVzM3njBmPklpHzb3A4Xy/JKahoclRPbAzxN github.com/celestiaorg/nmt v0.11.0/go.mod h1:NN3W8EEoospv8EHCw50DDNWwPLpJkFHoEFiqCEcNCH4= github.com/celestiaorg/quantum-gravity-bridge v1.2.0 h1:l/LEEUP+x8MhhXB8rrWkyUVFZgQj1Ur/TAwUpnyLK38= github.com/celestiaorg/quantum-gravity-bridge v1.2.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= -github.com/celestiaorg/rsmt2d v0.7.0 h1:r8fybOWhE2/VJ2XEJ6BncnYTSlLYx2c7dQDUD+5hBqg= -github.com/celestiaorg/rsmt2d v0.7.0/go.mod h1:hhlsTi6G3+X5jOP/8Lb/d7i5y2XNFmnyMddYbFSmrgo= +github.com/celestiaorg/rsmt2d v0.8.0 h1:ZUxTCELZCM9zMGKNF3cT+rUqMddXMeiuyleSJPZ3Wn4= +github.com/celestiaorg/rsmt2d v0.8.0/go.mod h1:hhlsTi6G3+X5jOP/8Lb/d7i5y2XNFmnyMddYbFSmrgo= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= From a96b4c436b05cf821bc3cd6e9f3e55ed16907cae Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 12 Jan 2023 17:07:40 +0200 Subject: [PATCH 0292/1008] bugfix(header/p2p): handle long live context in head request (#1587) ## Overview We should stop waiting for headers and return an error In case of Exchange service context will be canceled. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/exchange.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index e98917a59d..1a751cd13c 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -132,6 +132,8 @@ LOOP: } case <-ctx.Done(): break LOOP + case <-ex.ctx.Done(): + return nil, ctx.Err() } } From f43444e668e83403e8396c5456324272528b8466 Mon Sep 17 00:00:00 2001 From: Kevin Ji <1146876+kevinji@users.noreply.github.com> Date: Fri, 13 Jan 2023 02:19:27 -0800 Subject: [PATCH 0293/1008] fix(cel-key): update flag name to --p2p.network (#1590) --- cmd/cel-key/node_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 9f7d0b70d9..2727ec0c15 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -30,7 +30,7 @@ func DirectoryFlags() *flag.FlagSet { networkKey, defaultNetwork, "Sets key utility to use the node network's directory (e.g. "+ - "~/.celestia-light-mynetwork if --node.network MyNetwork is passed).") + "~/.celestia-light-mynetwork if --p2p.network MyNetwork is passed).") return flags } From a0fa0686b55da32b8d9e62fc8aa756b02ee18825 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 13 Jan 2023 12:28:43 +0100 Subject: [PATCH 0294/1008] feat(share): `getters.TeeGetter` (#1540) Closes https://github.com/celestiaorg/celestia-node/issues/1535 --- share/getters/getter_test.go | 56 ++++++++++++++++++++++++++++++++++ share/getters/tee.go | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 share/getters/tee.go diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 3924348ac7..65b480a3aa 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -6,6 +6,7 @@ import ( "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" + mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,6 +18,61 @@ import ( "github.com/celestiaorg/celestia-node/share/eds" ) +func TestTeeGetter(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + + err = edsStore.Start(ctx) + require.NoError(t, err) + + bServ := mdutils.Bserv() + ig := NewIPLDGetter(bServ) + tg := NewTeeGetter(ig, edsStore) + + t.Run("TeesToEDSStore", func(t *testing.T) { + eds, dah := randomEDS(t) + _, err := share.ImportShares(ctx, eds.Flattened(), bServ) + require.NoError(t, err) + + // eds store doesn't have the EDS yet + ok, err := edsStore.Has(ctx, dah.Hash()) + assert.False(t, ok) + assert.Error(t, err) + + retrievedEDS, err := tg.GetEDS(ctx, &dah) + require.NoError(t, err) + require.True(t, share.EqualEDS(eds, retrievedEDS)) + + // eds store now has the EDS and it can be retrieved + ok, err = edsStore.Has(ctx, dah.Hash()) + assert.True(t, ok) + assert.NoError(t, err) + finalEDS, err := edsStore.Get(ctx, dah.Hash()) + assert.NoError(t, err) + require.True(t, share.EqualEDS(eds, finalEDS)) + }) + + t.Run("ShardAlreadyExistsDoesntError", func(t *testing.T) { + eds, dah := randomEDS(t) + _, err := share.ImportShares(ctx, eds.Flattened(), bServ) + require.NoError(t, err) + + retrievedEDS, err := tg.GetEDS(ctx, &dah) + require.NoError(t, err) + require.True(t, share.EqualEDS(eds, retrievedEDS)) + + // no error should be returned, even though the EDS identified by the DAH already exists + retrievedEDS, err = tg.GetEDS(ctx, &dah) + require.NoError(t, err) + require.True(t, share.EqualEDS(eds, retrievedEDS)) + }) +} + func TestStoreGetter(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) diff --git a/share/getters/tee.go b/share/getters/tee.go new file mode 100644 index 0000000000..048de905db --- /dev/null +++ b/share/getters/tee.go @@ -0,0 +1,58 @@ +package getters + +import ( + "context" + "errors" + "fmt" + + "github.com/filecoin-project/dagstore" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +var _ share.Getter = (*TeeGetter)(nil) + +// TeeGetter is a share.Getter that wraps a getter and stores the results of GetEDS into an +// eds.Store. +type TeeGetter struct { + getter share.Getter + store *eds.Store +} + +// NewTeeGetter creates a new TeeGetter. +func NewTeeGetter(getter share.Getter, store *eds.Store) *TeeGetter { + return &TeeGetter{ + getter: getter, + store: store, + } +} + +func (tg *TeeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { + return tg.getter.GetShare(ctx, root, row, col) +} + +func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + eds, err := tg.getter.GetEDS(ctx, root) + if err != nil { + return nil, err + } + + err = tg.store.Put(ctx, root.Hash(), eds) + if err != nil && !errors.Is(err, dagstore.ErrShardExists) { + return nil, fmt.Errorf("getter/tee: failed to store eds: %w", err) + } + + return eds, nil +} + +func (tg *TeeGetter) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + id namespace.ID, +) (share.NamespacedShares, error) { + return tg.getter.GetSharesByNamespace(ctx, root, id) +} From 8b44f0dd7af5cfb8315358480c80f2278f1c0347 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 13 Jan 2023 17:04:30 +0300 Subject: [PATCH 0295/1008] feat(ShrEx/ND): Create client/server for namespaced data communication (#1497) - implement shrex/nd server https://github.com/celestiaorg/celestia-node/issues/1420 - implement shrex/nd client https://github.com/celestiaorg/celestia-node/issues/1419 --- share/p2p/shrexnd/client.go | 208 ++++++ share/p2p/shrexnd/options.go | 88 +++ share/p2p/shrexnd/pb/share.pb.go | 1112 ++++++++++++++++++++++++++++++ share/p2p/shrexnd/pb/share.proto | 32 + share/p2p/shrexnd/server.go | 187 +++++ share/p2p/shrexnd/shrex_test.go | 94 +++ 6 files changed, 1721 insertions(+) create mode 100644 share/p2p/shrexnd/client.go create mode 100644 share/p2p/shrexnd/options.go create mode 100644 share/p2p/shrexnd/pb/share.pb.go create mode 100644 share/p2p/shrexnd/pb/share.proto create mode 100644 share/p2p/shrexnd/server.go create mode 100644 share/p2p/shrexnd/shrex_test.go diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go new file mode 100644 index 0000000000..9c0738aed2 --- /dev/null +++ b/share/p2p/shrexnd/client.go @@ -0,0 +1,208 @@ +package shrexnd + +import ( + "context" + "errors" + "fmt" + "net" + "time" + + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" + pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/nmt/namespace" +) + +var errNoMorePeers = errors.New("shrex-nd: all peers returned invalid responses") + +// Client implements client side of shrex/nd protocol to obtain namespaced shares data from remote +// peers. +type Client struct { + params *Parameters + protocolID protocol.ID + + host host.Host +} + +// NewClient creates a new shrEx/nd client +func NewClient(host host.Host, opts ...Option) (*Client, error) { + params := DefaultParameters() + for _, opt := range opts { + opt(params) + } + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("shrex-nd: client creation failed: %w", err) + } + + return &Client{ + host: host, + protocolID: protocolID(params.protocolSuffix), + params: params, + }, nil +} + +// GetSharesByNamespace request shares with option to collect proofs from remote peers using shrex +// protocol +func (c *Client) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + nID namespace.ID, + peerIDs ...peer.ID, +) (share.NamespacedShares, error) { + for _, peerID := range peerIDs { + shares, err := c.doRequest(ctx, root, nID, peerID) + if err == nil { + return shares, err + } + + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return nil, ctx.Err() + } + // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not + // unwrap to a ctx err + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { + if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { + // stop trying peers if ctx deadline reached + return nil, context.DeadlineExceeded + } + } + + // log and try another peer + log.Errorw("client-nd: peer returned err", "peer_id", peerID.String(), "err", err) + } + + return nil, errNoMorePeers +} + +func (c *Client) doRequest( + ctx context.Context, + root *share.Root, + nID namespace.ID, + peerID peer.ID, +) (share.NamespacedShares, error) { + stream, err := c.host.NewStream(ctx, peerID, c.protocolID) + if err != nil { + return nil, err + } + defer stream.Close() + + c.setStreamDeadlines(ctx, stream) + + req := &pb.GetSharesByNamespaceRequest{ + RootHash: root.Hash(), + NamespaceId: nID, + } + + _, err = serde.Write(stream, req) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, fmt.Errorf("client-nd: writing request: %w", err) + } + + err = stream.CloseWrite() + if err != nil { + log.Debugf("client-nd: closing write side of the stream: %s", err) + } + + var resp pb.GetSharesByNamespaceResponse + _, err = serde.Read(stream, &resp) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, fmt.Errorf("client-nd: reading response: %w", err) + } + + if err = statusToErr(resp.Status); err != nil { + return nil, fmt.Errorf("client-nd: response code is not OK: %w", err) + } + + shares, err := convertToNamespacedShares(resp.Rows) + if err != nil { + return nil, fmt.Errorf("client-nd: converting response to shares: %w", err) + } + + err = shares.Verify(root, nID) + if err != nil { + return nil, fmt.Errorf("client-nd: verifying response: %w", err) + } + + return shares, nil +} + +// convertToNamespacedShares converts proto Rows to share.NamespacedShares +func convertToNamespacedShares(rows []*pb.Row) (share.NamespacedShares, error) { + shares := make([]share.NamespacedRow, 0, len(rows)) + for _, row := range rows { + var proof *ipld.Proof + if row.Proof != nil { + cids := make([]cid.Cid, 0, len(row.Proof.Nodes)) + for _, node := range row.Proof.Nodes { + cid, err := cid.Cast(node) + if err != nil { + return nil, fmt.Errorf("casting proofs node to cid: %w", err) + } + cids = append(cids, cid) + } + + proof = &ipld.Proof{ + Nodes: cids, + Start: int(row.Proof.Start), + End: int(row.Proof.End), + } + } + + shares = append(shares, share.NamespacedRow{ + Shares: row.Shares, + Proof: proof, + }) + } + return shares, nil +} + +func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) { + // set read/write deadline to use context deadline if it exists + deadline, ok := ctx.Deadline() + if ok { + err := stream.SetDeadline(deadline) + if err != nil { + log.Debugf("client-nd: set write deadline: %s", err) + } + return + } + + if c.params.readTimeout != 0 { + err := stream.SetReadDeadline(time.Now().Add(c.params.readTimeout)) + if err != nil { + log.Debugf("client-nd: set read deadline: %s", err) + } + } + + if c.params.writeTimeout != 0 { + err := stream.SetWriteDeadline(time.Now().Add(c.params.readTimeout)) + if err != nil { + log.Debugf("client-nd: set write deadline: %s", err) + } + } +} + +func statusToErr(code pb.StatusCode) error { + switch code { + case pb.StatusCode_OK: + return nil + case pb.StatusCode_INVALID, + pb.StatusCode_NOT_FOUND, + pb.StatusCode_INTERNAL, + pb.StatusCode_REFUSED: + default: + code = pb.StatusCode_INVALID + } + return errors.New(code.String()) +} diff --git a/share/p2p/shrexnd/options.go b/share/p2p/shrexnd/options.go new file mode 100644 index 0000000000..3c540a6bb6 --- /dev/null +++ b/share/p2p/shrexnd/options.go @@ -0,0 +1,88 @@ +package shrexnd + +import ( + "fmt" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/protocol" +) + +const protocolPrefix = "/shrex/nd/0.0.1/" + +var log = logging.Logger("shrex/nd") + +// Option is the functional option that is applied to the shrex/eds protocol to configure its +// parameters. +type Option func(*Parameters) + +// Parameters is the set of parameters that must be configured for the shrex/eds protocol. +type Parameters struct { + // readTimeout sets the timeout for reading messages from the stream. + readTimeout time.Duration + + // writeTimeout sets the timeout for writing messages to the stream. + writeTimeout time.Duration + + // serveTimeout defines the deadline for serving request. + serveTimeout time.Duration + + // protocolSuffix is appended to the protocolID and represents the network the protocol is + // running on. + protocolSuffix string +} + +func DefaultParameters() *Parameters { + return &Parameters{ + readTimeout: time.Second * 5, + writeTimeout: time.Second * 10, + serveTimeout: time.Second * 10, + } +} + +const errSuffix = "value should be positive and non-zero" + +func (p *Parameters) Validate() error { + if p.readTimeout <= 0 { + return fmt.Errorf("invalid stream read timeout: %v, %s", p.readTimeout, errSuffix) + } + if p.writeTimeout <= 0 { + return fmt.Errorf("invalid write timeout: %v, %s", p.writeTimeout, errSuffix) + } + if p.serveTimeout <= 0 { + return fmt.Errorf("invalid serve timeout: %v, %s", p.serveTimeout, errSuffix) + } + return nil +} + +// WithProtocolSuffix is a functional option that configures the `protocolSuffix` parameter +func WithProtocolSuffix(protocolSuffix string) Option { + return func(parameters *Parameters) { + parameters.protocolSuffix = protocolSuffix + } +} + +// WithReadTimeout is a functional option that configures the `readTimeout` parameter +func WithReadTimeout(readTimeout time.Duration) Option { + return func(parameters *Parameters) { + parameters.readTimeout = readTimeout + } +} + +// WithWriteTimeout is a functional option that configures the `writeTimeout` parameter +func WithWriteTimeout(writeTimeout time.Duration) Option { + return func(parameters *Parameters) { + parameters.writeTimeout = writeTimeout + } +} + +// WithServeTimeout is a functional option that configures the `serveTimeout` parameter +func WithServeTimeout(serveTimeout time.Duration) Option { + return func(parameters *Parameters) { + parameters.serveTimeout = serveTimeout + } +} + +func protocolID(protocolSuffix string) protocol.ID { + return protocol.ID(fmt.Sprintf("%s%s", protocolPrefix, protocolSuffix)) +} diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go new file mode 100644 index 0000000000..e703090d9a --- /dev/null +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -0,0 +1,1112 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: share/p2p/shrexnd/pb/share.proto + +package share_p2p_shrex_nd + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type StatusCode int32 + +const ( + StatusCode_INVALID StatusCode = 0 + StatusCode_OK StatusCode = 1 + StatusCode_NOT_FOUND StatusCode = 2 + StatusCode_INTERNAL StatusCode = 3 + StatusCode_REFUSED StatusCode = 4 +) + +var StatusCode_name = map[int32]string{ + 0: "INVALID", + 1: "OK", + 2: "NOT_FOUND", + 3: "INTERNAL", + 4: "REFUSED", +} + +var StatusCode_value = map[string]int32{ + "INVALID": 0, + "OK": 1, + "NOT_FOUND": 2, + "INTERNAL": 3, + "REFUSED": 4, +} + +func (x StatusCode) String() string { + return proto.EnumName(StatusCode_name, int32(x)) +} + +func (StatusCode) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_ed9f13149b0de397, []int{0} +} + +type GetSharesByNamespaceRequest struct { + RootHash []byte `protobuf:"bytes,1,opt,name=root_hash,json=rootHash,proto3" json:"root_hash,omitempty"` + NamespaceId []byte `protobuf:"bytes,2,opt,name=namespace_id,json=namespaceId,proto3" json:"namespace_id,omitempty"` +} + +func (m *GetSharesByNamespaceRequest) Reset() { *m = GetSharesByNamespaceRequest{} } +func (m *GetSharesByNamespaceRequest) String() string { return proto.CompactTextString(m) } +func (*GetSharesByNamespaceRequest) ProtoMessage() {} +func (*GetSharesByNamespaceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ed9f13149b0de397, []int{0} +} +func (m *GetSharesByNamespaceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GetSharesByNamespaceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GetSharesByNamespaceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GetSharesByNamespaceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSharesByNamespaceRequest.Merge(m, src) +} +func (m *GetSharesByNamespaceRequest) XXX_Size() int { + return m.Size() +} +func (m *GetSharesByNamespaceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetSharesByNamespaceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSharesByNamespaceRequest proto.InternalMessageInfo + +func (m *GetSharesByNamespaceRequest) GetRootHash() []byte { + if m != nil { + return m.RootHash + } + return nil +} + +func (m *GetSharesByNamespaceRequest) GetNamespaceId() []byte { + if m != nil { + return m.NamespaceId + } + return nil +} + +type GetSharesByNamespaceResponse struct { + Status StatusCode `protobuf:"varint,1,opt,name=status,proto3,enum=share.p2p.shrex.nd.StatusCode" json:"status,omitempty"` + Rows []*Row `protobuf:"bytes,2,rep,name=rows,proto3" json:"rows,omitempty"` +} + +func (m *GetSharesByNamespaceResponse) Reset() { *m = GetSharesByNamespaceResponse{} } +func (m *GetSharesByNamespaceResponse) String() string { return proto.CompactTextString(m) } +func (*GetSharesByNamespaceResponse) ProtoMessage() {} +func (*GetSharesByNamespaceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ed9f13149b0de397, []int{1} +} +func (m *GetSharesByNamespaceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GetSharesByNamespaceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GetSharesByNamespaceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GetSharesByNamespaceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSharesByNamespaceResponse.Merge(m, src) +} +func (m *GetSharesByNamespaceResponse) XXX_Size() int { + return m.Size() +} +func (m *GetSharesByNamespaceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetSharesByNamespaceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSharesByNamespaceResponse proto.InternalMessageInfo + +func (m *GetSharesByNamespaceResponse) GetStatus() StatusCode { + if m != nil { + return m.Status + } + return StatusCode_INVALID +} + +func (m *GetSharesByNamespaceResponse) GetRows() []*Row { + if m != nil { + return m.Rows + } + return nil +} + +type Row struct { + Shares [][]byte `protobuf:"bytes,1,rep,name=shares,proto3" json:"shares,omitempty"` + Proof *Proof `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` +} + +func (m *Row) Reset() { *m = Row{} } +func (m *Row) String() string { return proto.CompactTextString(m) } +func (*Row) ProtoMessage() {} +func (*Row) Descriptor() ([]byte, []int) { + return fileDescriptor_ed9f13149b0de397, []int{2} +} +func (m *Row) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Row) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Row.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Row) XXX_Merge(src proto.Message) { + xxx_messageInfo_Row.Merge(m, src) +} +func (m *Row) XXX_Size() int { + return m.Size() +} +func (m *Row) XXX_DiscardUnknown() { + xxx_messageInfo_Row.DiscardUnknown(m) +} + +var xxx_messageInfo_Row proto.InternalMessageInfo + +func (m *Row) GetShares() [][]byte { + if m != nil { + return m.Shares + } + return nil +} + +func (m *Row) GetProof() *Proof { + if m != nil { + return m.Proof + } + return nil +} + +type Proof struct { + Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + Nodes [][]byte `protobuf:"bytes,3,rep,name=Nodes,proto3" json:"Nodes,omitempty"` +} + +func (m *Proof) Reset() { *m = Proof{} } +func (m *Proof) String() string { return proto.CompactTextString(m) } +func (*Proof) ProtoMessage() {} +func (*Proof) Descriptor() ([]byte, []int) { + return fileDescriptor_ed9f13149b0de397, []int{3} +} +func (m *Proof) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Proof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Proof.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Proof) XXX_Merge(src proto.Message) { + xxx_messageInfo_Proof.Merge(m, src) +} +func (m *Proof) XXX_Size() int { + return m.Size() +} +func (m *Proof) XXX_DiscardUnknown() { + xxx_messageInfo_Proof.DiscardUnknown(m) +} + +var xxx_messageInfo_Proof proto.InternalMessageInfo + +func (m *Proof) GetStart() int64 { + if m != nil { + return m.Start + } + return 0 +} + +func (m *Proof) GetEnd() int64 { + if m != nil { + return m.End + } + return 0 +} + +func (m *Proof) GetNodes() [][]byte { + if m != nil { + return m.Nodes + } + return nil +} + +func init() { + proto.RegisterEnum("share.p2p.shrex.nd.StatusCode", StatusCode_name, StatusCode_value) + proto.RegisterType((*GetSharesByNamespaceRequest)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceRequest") + proto.RegisterType((*GetSharesByNamespaceResponse)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceResponse") + proto.RegisterType((*Row)(nil), "share.p2p.shrex.nd.Row") + proto.RegisterType((*Proof)(nil), "share.p2p.shrex.nd.Proof") +} + +func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } + +var fileDescriptor_ed9f13149b0de397 = []byte{ + // 380 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xc1, 0xae, 0xd2, 0x40, + 0x14, 0x86, 0xdb, 0x0e, 0x54, 0x38, 0xad, 0xa6, 0x99, 0x18, 0xad, 0xc1, 0x34, 0xd8, 0x15, 0xd1, + 0xa4, 0x4d, 0x6a, 0xe2, 0x1e, 0xa4, 0x68, 0x03, 0x19, 0xcc, 0x00, 0xee, 0x0c, 0x29, 0x76, 0x4c, + 0x5d, 0xd8, 0x19, 0x3b, 0x43, 0xd0, 0xb5, 0x2f, 0xe0, 0x63, 0xb9, 0x64, 0xe9, 0xd2, 0xc0, 0x8b, + 0x98, 0x4e, 0xb9, 0xf7, 0x2e, 0x2e, 0xbb, 0xfe, 0xe7, 0x7c, 0xff, 0x7f, 0xce, 0xe9, 0xc0, 0x50, + 0x96, 0x79, 0xcd, 0x62, 0x91, 0x88, 0x58, 0x96, 0x35, 0xfb, 0x51, 0x15, 0xb1, 0xd8, 0xc5, 0xba, + 0x18, 0x89, 0x9a, 0x2b, 0x8e, 0xf1, 0x45, 0x24, 0x22, 0xd2, 0x44, 0x54, 0x15, 0xe1, 0x27, 0x18, + 0xbc, 0x63, 0x6a, 0xd5, 0x34, 0xe4, 0xe4, 0x27, 0xc9, 0xbf, 0x31, 0x29, 0xf2, 0xcf, 0x8c, 0xb2, + 0xef, 0x7b, 0x26, 0x15, 0x1e, 0x40, 0xbf, 0xe6, 0x5c, 0x6d, 0xcb, 0x5c, 0x96, 0xbe, 0x39, 0x34, + 0x47, 0x2e, 0xed, 0x35, 0x85, 0xf7, 0xb9, 0x2c, 0xf1, 0x0b, 0x70, 0xab, 0x1b, 0xc3, 0xf6, 0x6b, + 0xe1, 0x5b, 0xba, 0xef, 0xdc, 0xd6, 0xb2, 0x22, 0xfc, 0x65, 0xc2, 0xf3, 0xeb, 0xf9, 0x52, 0xf0, + 0x4a, 0x32, 0xfc, 0x06, 0x6c, 0xa9, 0x72, 0xb5, 0x97, 0x3a, 0xfd, 0x51, 0x12, 0x44, 0xf7, 0x97, + 0x8c, 0x56, 0x9a, 0x78, 0xcb, 0x0b, 0x46, 0x2f, 0x34, 0x7e, 0x05, 0x9d, 0x9a, 0x1f, 0xa4, 0x6f, + 0x0d, 0xd1, 0xc8, 0x49, 0x9e, 0x5e, 0x73, 0x51, 0x7e, 0xa0, 0x1a, 0x0a, 0x09, 0x20, 0xca, 0x0f, + 0xf8, 0x09, 0xd8, 0x1a, 0x6b, 0x66, 0xa1, 0x91, 0x4b, 0x2f, 0x0a, 0xc7, 0xd0, 0x15, 0x35, 0xe7, + 0x5f, 0xf4, 0x01, 0x4e, 0xf2, 0xec, 0x5a, 0xd8, 0x87, 0x06, 0xa0, 0x2d, 0x17, 0xa6, 0xd0, 0xd5, + 0x1a, 0x3f, 0x86, 0xae, 0x54, 0x79, 0xad, 0xf4, 0xf2, 0x88, 0xb6, 0x02, 0x7b, 0x80, 0x58, 0xd5, + 0xfe, 0x0e, 0x44, 0x9b, 0xcf, 0x86, 0x23, 0xbc, 0x60, 0xd2, 0x47, 0x7a, 0x70, 0x2b, 0x5e, 0xce, + 0x01, 0xee, 0x2e, 0xc3, 0x0e, 0x3c, 0xc8, 0xc8, 0xc7, 0xf1, 0x22, 0x9b, 0x7a, 0x06, 0xb6, 0xc1, + 0x5a, 0xce, 0x3d, 0x13, 0x3f, 0x84, 0x3e, 0x59, 0xae, 0xb7, 0xb3, 0xe5, 0x86, 0x4c, 0x3d, 0x0b, + 0xbb, 0xd0, 0xcb, 0xc8, 0x3a, 0xa5, 0x64, 0xbc, 0xf0, 0x50, 0xe3, 0xa0, 0xe9, 0x6c, 0xb3, 0x4a, + 0xa7, 0x5e, 0x67, 0xe2, 0xff, 0x39, 0x05, 0xe6, 0xf1, 0x14, 0x98, 0xff, 0x4e, 0x81, 0xf9, 0xfb, + 0x1c, 0x18, 0xc7, 0x73, 0x60, 0xfc, 0x3d, 0x07, 0xc6, 0xce, 0xd6, 0xaf, 0xff, 0xfa, 0x7f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x33, 0x30, 0xe8, 0xae, 0x21, 0x02, 0x00, 0x00, +} + +func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetSharesByNamespaceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GetSharesByNamespaceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NamespaceId) > 0 { + i -= len(m.NamespaceId) + copy(dAtA[i:], m.NamespaceId) + i = encodeVarintShare(dAtA, i, uint64(len(m.NamespaceId))) + i-- + dAtA[i] = 0x12 + } + if len(m.RootHash) > 0 { + i -= len(m.RootHash) + copy(dAtA[i:], m.RootHash) + i = encodeVarintShare(dAtA, i, uint64(len(m.RootHash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GetSharesByNamespaceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetSharesByNamespaceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GetSharesByNamespaceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Rows) > 0 { + for iNdEx := len(m.Rows) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Rows[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintShare(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.Status != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Row) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Row) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Row) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Proof != nil { + { + size, err := m.Proof.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintShare(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Shares) > 0 { + for iNdEx := len(m.Shares) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Shares[iNdEx]) + copy(dAtA[i:], m.Shares[iNdEx]) + i = encodeVarintShare(dAtA, i, uint64(len(m.Shares[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Proof) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Proof) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Proof) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Nodes) > 0 { + for iNdEx := len(m.Nodes) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Nodes[iNdEx]) + copy(dAtA[i:], m.Nodes[iNdEx]) + i = encodeVarintShare(dAtA, i, uint64(len(m.Nodes[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if m.End != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.End)) + i-- + dAtA[i] = 0x10 + } + if m.Start != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.Start)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintShare(dAtA []byte, offset int, v uint64) int { + offset -= sovShare(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GetSharesByNamespaceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RootHash) + if l > 0 { + n += 1 + l + sovShare(uint64(l)) + } + l = len(m.NamespaceId) + if l > 0 { + n += 1 + l + sovShare(uint64(l)) + } + return n +} + +func (m *GetSharesByNamespaceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Status != 0 { + n += 1 + sovShare(uint64(m.Status)) + } + if len(m.Rows) > 0 { + for _, e := range m.Rows { + l = e.Size() + n += 1 + l + sovShare(uint64(l)) + } + } + return n +} + +func (m *Row) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Shares) > 0 { + for _, b := range m.Shares { + l = len(b) + n += 1 + l + sovShare(uint64(l)) + } + } + if m.Proof != nil { + l = m.Proof.Size() + n += 1 + l + sovShare(uint64(l)) + } + return n +} + +func (m *Proof) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Start != 0 { + n += 1 + sovShare(uint64(m.Start)) + } + if m.End != 0 { + n += 1 + sovShare(uint64(m.End)) + } + if len(m.Nodes) > 0 { + for _, b := range m.Nodes { + l = len(b) + n += 1 + l + sovShare(uint64(l)) + } + } + return n +} + +func sovShare(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozShare(x uint64) (n int) { + return sovShare(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GetSharesByNamespaceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetSharesByNamespaceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetSharesByNamespaceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RootHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RootHash = append(m.RootHash[:0], dAtA[iNdEx:postIndex]...) + if m.RootHash == nil { + m.RootHash = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NamespaceId", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NamespaceId = append(m.NamespaceId[:0], dAtA[iNdEx:postIndex]...) + if m.NamespaceId == nil { + m.NamespaceId = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipShare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthShare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetSharesByNamespaceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetSharesByNamespaceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetSharesByNamespaceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= StatusCode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rows", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Rows = append(m.Rows, &Row{}) + if err := m.Rows[len(m.Rows)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipShare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthShare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Row) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Row: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Row: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Shares = append(m.Shares, make([]byte, postIndex-iNdEx)) + copy(m.Shares[len(m.Shares)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Proof == nil { + m.Proof = &Proof{} + } + if err := m.Proof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipShare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthShare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Proof) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Proof: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Proof: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Start", wireType) + } + m.Start = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Start |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field End", wireType) + } + m.End = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.End |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Nodes", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Nodes = append(m.Nodes, make([]byte, postIndex-iNdEx)) + copy(m.Nodes[len(m.Nodes)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipShare(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthShare + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipShare(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowShare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowShare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowShare + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthShare + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupShare + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthShare + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthShare = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowShare = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupShare = fmt.Errorf("proto: unexpected end of group") +) diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto new file mode 100644 index 0000000000..59dac8c82e --- /dev/null +++ b/share/p2p/shrexnd/pb/share.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package share.p2p.shrex.nd; + +message GetSharesByNamespaceRequest{ + bytes root_hash = 1; + bytes namespace_id = 2; +} + +message GetSharesByNamespaceResponse{ + StatusCode status = 1; + repeated Row rows = 2; +} + +enum StatusCode { + INVALID = 0; + OK = 1; + NOT_FOUND = 2; + INTERNAL = 3; + REFUSED = 4; +}; + +message Row { + repeated bytes shares = 1; + Proof proof = 2; +} + +message Proof { + int64 start = 1; + int64 end = 2; + repeated bytes Nodes = 3; +} diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go new file mode 100644 index 0000000000..09e39a495e --- /dev/null +++ b/share/p2p/shrexnd/server.go @@ -0,0 +1,187 @@ +package shrexnd + +import ( + "context" + "fmt" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/minio/sha256-simd" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" + pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" +) + +// Server implements server side of shrex/nd protocol to serve namespaced share to remote +// peers. +type Server struct { + params *Parameters + protocolID protocol.ID + + getter share.Getter + store *eds.Store + host host.Host + + cancel context.CancelFunc +} + +// NewServer creates new Server +func NewServer(host host.Host, store *eds.Store, getter *getters.IPLDGetter, opts ...Option) (*Server, error) { + params := DefaultParameters() + for _, opt := range opts { + opt(params) + } + + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("shrex-nd: server creation failed: %w", err) + } + + srv := &Server{ + getter: getter, + store: store, + host: host, + params: params, + protocolID: protocolID(params.protocolSuffix), + } + + return srv, nil +} + +// Start starts the server +func (srv *Server) Start() { + ctx, cancel := context.WithCancel(context.Background()) + srv.cancel = cancel + + srv.host.SetStreamHandler(srv.protocolID, func(s network.Stream) { + srv.handleNamespacedData(ctx, s) + }) +} + +// Stop stops the server +func (srv *Server) Stop() { + srv.cancel() + srv.host.RemoveStreamHandler(srv.protocolID) +} + +func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { + err := stream.SetReadDeadline(time.Now().Add(srv.params.readTimeout)) + if err != nil { + log.Debugf("server: setting read deadline: %s", err) + } + + var req pb.GetSharesByNamespaceRequest + _, err = serde.Read(stream, &req) + if err != nil { + log.Errorw("server: reading request", "err", err) + stream.Reset() //nolint:errcheck + return + } + log.Debugw("server: new request", "namespaceId", string(req.NamespaceId), "roothash", string(req.RootHash)) + + err = stream.CloseRead() + if err != nil { + log.Debugf("server: closing read side of the stream: %s", err) + } + + err = validateRequest(req) + if err != nil { + log.Errorw("server: invalid request", "err", err) + stream.Reset() //nolint:errcheck + return + } + + ctx, cancel := context.WithTimeout(ctx, srv.params.serveTimeout) + defer cancel() + + dah, err := srv.store.GetDAH(ctx, req.RootHash) + if err != nil { + log.Errorw("server: retrieving DAH for datahash", "err", err) + srv.respondInternalError(stream) + return + } + + shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) + if err != nil { + log.Errorw("server: retrieving shares", "err", err) + srv.respondInternalError(stream) + return + } + + resp := namespacedSharesToResponse(shares) + srv.respond(stream, resp) + log.Debugw("server: handled request", "namespaceId", string(req.NamespaceId), "roothash", string(req.RootHash)) +} + +// validateRequest checks correctness of the request +func validateRequest(req pb.GetSharesByNamespaceRequest) error { + if len(req.NamespaceId) != ipld.NamespaceSize { + return fmt.Errorf("incorrect namespace id length: %v", len(req.NamespaceId)) + } + if len(req.RootHash) != sha256.Size { + return fmt.Errorf("incorrect root hash length: %v", len(req.RootHash)) + } + + return nil +} + +// respondInternalError sends internal error response to client +func (srv *Server) respondInternalError(stream network.Stream) { + resp := &pb.GetSharesByNamespaceResponse{ + Status: pb.StatusCode_INTERNAL, + } + srv.respond(stream, resp) +} + +// namespacedSharesToResponse encodes shares into proto and sends it to client with OK status code +func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNamespaceResponse { + rows := make([]*pb.Row, 0, len(shares)) + for _, row := range shares { + // construct proof + nodes := make([][]byte, 0, len(row.Proof.Nodes)) + for _, cid := range row.Proof.Nodes { + nodes = append(nodes, cid.Bytes()) + } + + proof := &pb.Proof{ + Start: int64(row.Proof.Start), + End: int64(row.Proof.End), + Nodes: nodes, + } + + row := &pb.Row{ + Shares: row.Shares, + Proof: proof, + } + + rows = append(rows, row) + } + + return &pb.GetSharesByNamespaceResponse{ + Status: pb.StatusCode_OK, + Rows: rows, + } +} + +func (srv *Server) respond(stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { + err := stream.SetWriteDeadline(time.Now().Add(srv.params.writeTimeout)) + if err != nil { + log.Debugf("server: seting write deadline: %s", err) + } + + _, err = serde.Write(stream, resp) + if err != nil { + log.Errorf("server: writing response: %s", err.Error()) + stream.Reset() //nolint:errcheck + return + } + + if err = stream.Close(); err != nil { + log.Errorf("server: closing stream: %s", err.Error()) + } +} diff --git a/share/p2p/shrexnd/shrex_test.go b/share/p2p/shrexnd/shrex_test.go new file mode 100644 index 0000000000..3f55ed545a --- /dev/null +++ b/share/p2p/shrexnd/shrex_test.go @@ -0,0 +1,94 @@ +package shrexnd + +import ( + "context" + "math/rand" + "testing" + "time" + + bsrv "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + mdutils "github.com/ipfs/go-merkledag/test" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +func TestGetSharesWithProofByNamespace(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + t.Cleanup(cancel) + + // create test net + net := availability_test.NewTestDAGNet(ctx, t) + + // generate test data + bServ := mdutils.Bserv() + randomEDS, nID := generateTestEDS(t, bServ) + dah := da.NewDataAvailabilityHeader(randomEDS) + + // launch eds store and put test data into it + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + err = edsStore.Put(ctx, dah.Hash(), randomEDS) + require.NoError(t, err) + + // create server and register handler + srv, err := NewServer(net.NewTestNode().Host, edsStore, nil) + require.NoError(t, err) + srv.getter = getters.NewIPLDGetter(bServ) + srv.Start() + t.Cleanup(srv.Stop) + + // create client and connect it to server + client, err := NewClient(net.NewTestNode().Host) + require.NoError(t, err) + net.ConnectAll() + + got, err := client.GetSharesByNamespace( + ctx, + &dah, + nID, + srv.host.ID()) + + require.NoError(t, err) + require.NoError(t, got.Verify(&dah, nID)) +} + +func newStore(t *testing.T) (*eds.Store, error) { + t.Helper() + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + return eds.NewStore(tmpDir, ds) +} + +func generateTestEDS(t *testing.T, bServ bsrv.BlockService) (*rsmt2d.ExtendedDataSquare, namespace.ID) { + shares := share.RandShares(t, 16) + + from := rand.Intn(len(shares)) + to := rand.Intn(len(shares)) + + if to < from { + from, to = to, from + } + + nID := shares[from][:share.NamespaceSize] + // change some shares to have same nID + for i := from; i <= to; i++ { + copy(shares[i][:share.NamespaceSize], nID) + } + + eds, err := share.AddShares(context.Background(), shares, bServ) + require.NoError(t, err) + + return eds, nID +} From 226583068ea02c1546a374cd942d33e120af3cd6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 16 Jan 2023 11:21:26 +0100 Subject: [PATCH 0296/1008] refactor(shrexeds): removing retry logic from client (#1591) --- share/p2p/shrexeds/client.go | 56 ++++++++++------------------- share/p2p/shrexeds/exchange_test.go | 13 +++---- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 57cdf789a0..50c5f359a6 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -42,52 +42,34 @@ func NewClient(host host.Host, opts ...Option) (*Client, error) { }, nil } -// RequestEDS requests the full ODS from one of the given peers and returns the EDS. -// -// The peers are requested in a round-robin manner with retries until one of them gives a valid -// response, blocking until the context is canceled or a valid response is given. +// RequestEDS requests the ODS from the given peers and returns the EDS upon success. func (c *Client) RequestEDS( ctx context.Context, dataHash share.DataHash, - peers peer.IDSlice, + peers ...peer.ID, ) (*rsmt2d.ExtendedDataSquare, error) { req := &p2p_pb.EDSRequest{Hash: dataHash} - // requests are retried for every peer until a valid response is received - excludedPeers := make(map[peer.ID]struct{}) - for { - // if no peers are left, return - if len(peers) == len(excludedPeers) { - return nil, errNoMorePeers + for _, to := range peers { + eds, err := c.doRequest(ctx, req, to) + if eds != nil { + return eds, err } - - for _, to := range peers { - // skip over excluded peers - if _, ok := excludedPeers[to]; ok { - continue - } - eds, err := c.doRequest(ctx, req, to) - if eds != nil { - return eds, err - } - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - return nil, ctx.Err() - } - // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not - // unwrap to a ctx err - var ne net.Error - if errors.As(err, &ne) && ne.Timeout() { - return nil, context.DeadlineExceeded - } - if err != nil { - // peer has misbehaved, exclude them from round-robin - excludedPeers[to] = struct{}{} - log.Errorw("client: eds request to peer failed", "peer", to, "hash", dataHash.String()) - } - - // no eds was found, continue + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return nil, ctx.Err() + } + // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not + // unwrap to a ctx err + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { + return nil, context.DeadlineExceeded + } + if err != nil { + log.Errorw("client: eds request to peer failed", "peer", to, "hash", dataHash.String()) } } + + return nil, errNoMorePeers } func (c *Client) doRequest( diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 21e90297ef..07814833f3 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -8,7 +8,6 @@ import ( "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" libhost "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,14 +35,14 @@ func TestExchange_RequestEDS(t *testing.T) { err = store.Put(ctx, dah.Hash(), eds) require.NoError(t, err) - requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), []peer.ID{server.host.ID()}) + requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), server.host.ID()) assert.NoError(t, err) assert.Equal(t, eds.Flattened(), requestedEDS.Flattened()) - }) // Testcase: EDS is unavailable initially, but is found after multiple requests t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { + t.Skip() storageDelay := time.Second eds := share.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) @@ -54,7 +53,7 @@ func TestExchange_RequestEDS(t *testing.T) { }() now := time.Now() - requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), []peer.ID{server.host.ID()}) + requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), server.host.ID()) finished := time.Now() assert.Greater(t, finished.Sub(now), storageDelay) @@ -64,19 +63,21 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: Invalid request excludes peer from round-robin, stopping request t.Run("EDS_InvalidRequest", func(t *testing.T) { + t.Skip() dataHash := []byte("invalid") - requestedEDS, err := client.RequestEDS(ctx, dataHash, []peer.ID{server.host.ID()}) + requestedEDS, err := client.RequestEDS(ctx, dataHash, server.host.ID()) assert.ErrorIs(t, err, errNoMorePeers) assert.Nil(t, requestedEDS) }) // Testcase: Valid request, which server cannot serve, waits forever t.Run("EDS_ValidTimeout", func(t *testing.T) { + t.Skip() timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) eds := share.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) - requestedEDS, err := client.RequestEDS(timeoutCtx, dah.Hash(), []peer.ID{server.host.ID()}) + requestedEDS, err := client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) assert.ErrorIs(t, err, timeoutCtx.Err()) assert.Nil(t, requestedEDS) }) From 8041370c1159a3cb38debe507af419de38c9a9f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 15:32:42 +0100 Subject: [PATCH 0297/1008] chore(deps): bump google.golang.org/grpc from 1.51.0 to 1.52.0 (#1600) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 23 ++++---- go.sum | 179 +++++++++------------------------------------------------ 2 files changed, 39 insertions(+), 163 deletions(-) diff --git a/go.mod b/go.mod index f42a141473..866fafd5cb 100644 --- a/go.mod +++ b/go.mod @@ -67,14 +67,15 @@ require ( golang.org/x/crypto v0.5.0 golang.org/x/sync v0.1.0 golang.org/x/text v0.6.0 - google.golang.org/grpc v1.51.0 + google.golang.org/grpc v1.52.0 ) require ( - cloud.google.com/go v0.100.2 // indirect - cloud.google.com/go/compute v1.6.1 // indirect - cloud.google.com/go/iam v0.3.0 // indirect - cloud.google.com/go/storage v1.14.0 // indirect + cloud.google.com/go v0.105.0 // indirect + cloud.google.com/go/compute v1.12.1 // indirect + cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go/iam v0.7.0 // indirect + cloud.google.com/go/storage v1.27.0 // indirect cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -151,11 +152,13 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gax-go/v2 v2.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.6.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -297,14 +300,14 @@ require ( golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.5.0 // indirect - golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/term v0.4.0 // indirect golang.org/x/tools v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect - google.golang.org/api v0.81.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.102.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect + google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index 2a635ad09c..91eafbba45 100644 --- a/go.sum +++ b/go.sum @@ -21,19 +21,8 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -41,16 +30,15 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -60,8 +48,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= @@ -266,7 +255,6 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= @@ -404,10 +392,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakRE22OTI12k+2LkyY= github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= @@ -555,7 +541,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -574,7 +559,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -601,8 +585,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -617,7 +601,6 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -630,11 +613,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -644,16 +622,14 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -1934,7 +1910,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= @@ -2010,7 +1985,6 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -2020,10 +1994,6 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2037,17 +2007,11 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2060,7 +2024,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2138,15 +2101,12 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2157,30 +2117,18 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= @@ -2272,9 +2220,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= @@ -2283,10 +2228,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= @@ -2316,26 +2259,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.81.0 h1:o8WF5AvfidafWbFjsRyupxyEQJNUWxLZJCK5NXrxZZ8= -google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= +google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2392,48 +2317,10 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2462,23 +2349,10 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2492,7 +2366,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From c6672df9f482168d32a769023ee6b39c428da25d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 14:36:01 +0000 Subject: [PATCH 0298/1008] chore(deps): bump github.com/libp2p/go-libp2p-routing-helpers from 0.4.0 to 0.6.0 (#1601) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 866fafd5cb..0ced26a5c8 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/libp2p/go-libp2p-kad-dht v0.20.0 github.com/libp2p/go-libp2p-pubsub v0.8.2 github.com/libp2p/go-libp2p-record v0.2.0 - github.com/libp2p/go-libp2p-routing-helpers v0.4.0 + github.com/libp2p/go-libp2p-routing-helpers v0.6.0 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 diff --git a/go.sum b/go.sum index 91eafbba45..366be3b6f0 100644 --- a/go.sum +++ b/go.sum @@ -1078,8 +1078,8 @@ github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= -github.com/libp2p/go-libp2p-routing-helpers v0.4.0 h1:b7y4aixQ7AwbqYfcOQ6wTw8DQvuRZeTAA0Od3YYN5yc= -github.com/libp2p/go-libp2p-routing-helpers v0.4.0/go.mod h1:dYEAgkVhqho3/YKxfOEGdFMIcWfAFNlZX8iAIihYA2E= +github.com/libp2p/go-libp2p-routing-helpers v0.6.0 h1:Rfyd+wp/cU0PjNjCphGzLYzd7Q51fjOMs5Sjj6zWGT0= +github.com/libp2p/go-libp2p-routing-helpers v0.6.0/go.mod h1:wwK/XSLt6njjO7sRbjhf8w7PGBOfdntMQ2mOQPZ5s/Q= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= From 5b00bef146f1e967eb698f4ff9810a619bae8b5c Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 16 Jan 2023 16:17:01 +0100 Subject: [PATCH 0299/1008] fix(fraud): fix race by using correct peer id in log statement (#1605) --- fraud/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fraud/sync.go b/fraud/sync.go index cb51810748..ce7b7b88d3 100644 --- a/fraud/sync.go +++ b/fraud/sync.go @@ -79,7 +79,7 @@ func (f *ProofService) syncFraudProofs(ctx context.Context) { span.SetStatus(codes.Ok, "") return } - log.Debugw("got fraud proofs from peer", "pid", connStatus.Peer) + log.Debugw("got fraud proofs from peer", "pid", pid) for _, data := range respProofs { f.topicsLk.RLock() topic, ok := f.topics[ProofType(data.Type)] From ffe383386c3aa99a0e0a23b2e0cecc0b8395e88e Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 16 Jan 2023 17:28:48 +0200 Subject: [PATCH 0300/1008] improvement(header/p2p): change store to getter inside headerEx server (#1604) ## Overview We do not need to use all `header.Store `methods in HeaderEx server. Instead, we can use `header.Getter` ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- header/p2p/server.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/header/p2p/server.go b/header/p2p/server.go index f59f8b0a58..961372a876 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -21,8 +21,8 @@ import ( type ExchangeServer struct { protocolID protocol.ID - host host.Host - store header.Store + host host.Host + getter header.Getter ctx context.Context cancel context.CancelFunc @@ -34,7 +34,7 @@ type ExchangeServer struct { // header-related requests. func NewExchangeServer( host host.Host, - store header.Store, + getter header.Getter, protocolSuffix string, opts ...Option[ServerParameters], ) (*ExchangeServer, error) { @@ -49,7 +49,7 @@ func NewExchangeServer( return &ExchangeServer{ protocolID: protocolID(protocolSuffix), host: host, - store: store, + getter: getter, Params: params, }, nil } @@ -152,7 +152,7 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.ExtendedHeader, error) { log.Debugw("server: handling header request", "hash", tmbytes.HexBytes(hash).String()) - h, err := serv.store.Get(serv.ctx, hash) + h, err := serv.getter.Get(serv.ctx, hash) if err != nil { log.Errorw("server: getting header by hash", "hash", tmbytes.HexBytes(hash).String(), "err", err) return nil, err @@ -165,7 +165,7 @@ func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.Extended func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHeader, error) { if from == uint64(0) { log.Debug("server: handling head request") - head, err := serv.store.Head(serv.ctx) + head, err := serv.getter.Head(serv.ctx) if err != nil { log.Errorw("server: getting head", "err", err) return nil, err @@ -180,7 +180,7 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe log.Debugw("server: handling headers request", "from", from, "to", to) ctx, cancel := context.WithTimeout(serv.ctx, time.Second*5) defer cancel() - headersByRange, err := serv.store.GetRangeByHeight(ctx, from, to) + headersByRange, err := serv.getter.GetRangeByHeight(ctx, from, to) if err != nil { if errors.Is(err, context.DeadlineExceeded) { log.Warnw("server: requested headers not found", "from", from, "to", to) From bb3e7a38694049efa9ec34e13857e41db5050742 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 17 Jan 2023 15:07:15 +0100 Subject: [PATCH 0301/1008] fix(header/store): cleanup subscription early if was cancelled (#1606) --- header/store/heightsub.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/header/store/heightsub.go b/header/store/heightsub.go index ddae9e4f55..cb031aafda 100644 --- a/header/store/heightsub.go +++ b/header/store/heightsub.go @@ -62,6 +62,10 @@ func (hs *heightSub) Sub(ctx context.Context, height uint64) (*header.ExtendedHe case resp := <-resp: return resp, nil case <-ctx.Done(): + // no need to keep the request, if the op is canceled + hs.heightReqsLk.Lock() + delete(hs.heightReqs, height) + hs.heightReqsLk.Unlock() return nil, ctx.Err() } } From b6c7d4e622ece98aeed08441f8b6ba22b0cc8b99 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 17 Jan 2023 17:14:10 +0200 Subject: [PATCH 0302/1008] share/sharexpush: decrease ttl for seen messages in pubsub (#1580) --- share/p2p/shrexpush/pubsub.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/p2p/shrexpush/pubsub.go b/share/p2p/shrexpush/pubsub.go index 00d50a3e3c..cdeaf7ec70 100644 --- a/share/p2p/shrexpush/pubsub.go +++ b/share/p2p/shrexpush/pubsub.go @@ -36,7 +36,8 @@ type PubSub struct { // NewPubSub creates a libp2p.PubSub wrapper. func NewPubSub(ctx context.Context, h host.Host, suffix string) (*PubSub, error) { - pubsub, err := pubsub.NewFloodSub(ctx, h) + // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same msgId) + pubsub, err := pubsub.NewFloodSub(ctx, h, pubsub.WithSeenMessagesTTL(0)) if err != nil { return nil, err } From e5637e9ad184b47589313c17bc0a7c65d1f996db Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 17 Jan 2023 16:28:42 +0100 Subject: [PATCH 0303/1008] improvement(header/p2p): Implement context deadline on requests to store inside server (#1588) This PR introduces server-side context deadlines for **all** requests down to store with a config parameter for the server `RequestTimeout` --- header/p2p/options.go | 17 +++++++++++++++++ header/p2p/server.go | 13 +++++++++---- header/store/store.go | 2 +- nodebuilder/header/module.go | 1 + nodebuilder/header/module_test.go | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/header/p2p/options.go b/header/p2p/options.go index 3b6d4634a1..646d1a5a23 100644 --- a/header/p2p/options.go +++ b/header/p2p/options.go @@ -23,6 +23,8 @@ type ServerParameters struct { ReadDeadline time.Duration // MaxRequestSize defines the max amount of headers that can be handled at once. MaxRequestSize uint64 + // ServeTimeout defines the deadline for serving requests. + ServeTimeout time.Duration } // DefaultServerParameters returns the default params to configure the store. @@ -31,6 +33,7 @@ func DefaultServerParameters() ServerParameters { WriteDeadline: time.Second * 5, ReadDeadline: time.Minute, MaxRequestSize: 512, + ServeTimeout: time.Second * 5, } } @@ -44,6 +47,9 @@ func (p *ServerParameters) Validate() error { if p.MaxRequestSize == 0 { return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) } + if p.ServeTimeout == time.Duration(0) { + return fmt.Errorf("invalid request timeout: %d", p.ServeTimeout) + } return nil } @@ -82,6 +88,17 @@ func WithMaxRequestSize[T parameters](size uint64) Option[T] { } } +// WithServeTimeout is a functional option that configures the +// `ServeTimeout` parameter. +func WithServeTimeout[T parameters](timeout time.Duration) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ServerParameters: + t.ServeTimeout = timeout + } + } +} + // ClientParameters is the set of parameters that must be configured for the exchange. type ClientParameters struct { // the target minimum amount of responses with the same chain head diff --git a/header/p2p/server.go b/header/p2p/server.go index 961372a876..9829cf8d53 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -152,7 +152,10 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.ExtendedHeader, error) { log.Debugw("server: handling header request", "hash", tmbytes.HexBytes(hash).String()) - h, err := serv.getter.Get(serv.ctx, hash) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) + defer cancel() + + h, err := serv.getter.Get(ctx, hash) if err != nil { log.Errorw("server: getting header by hash", "hash", tmbytes.HexBytes(hash).String(), "err", err) return nil, err @@ -163,9 +166,12 @@ func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.Extended // handleRequest fetches the ExtendedHeader at the given origin and // writes it to the stream. func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHeader, error) { + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) + defer cancel() + if from == uint64(0) { log.Debug("server: handling head request") - head, err := serv.getter.Head(serv.ctx) + head, err := serv.getter.Head(ctx) if err != nil { log.Errorw("server: getting head", "err", err) return nil, err @@ -177,9 +183,8 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe log.Errorw("server: skip request for too many headers.", "amount", to-from) return nil, header.ErrHeadersLimitExceeded } + log.Debugw("server: handling headers request", "from", from, "to", to) - ctx, cancel := context.WithTimeout(serv.ctx, time.Second*5) - defer cancel() headersByRange, err := serv.getter.GetRangeByHeight(ctx, from, to) if err != nil { if errors.Is(err, context.DeadlineExceeded) { diff --git a/header/store/store.go b/header/store/store.go index b3859c56b4..868a51de0c 100644 --- a/header/store/store.go +++ b/header/store/store.go @@ -23,7 +23,7 @@ var ( errStoppedStore = errors.New("stopped store") ) -// store implements the Store interface for ExtendedHeaders over Datastore. +// Store implements the Store interface for ExtendedHeaders over Datastore. type Store struct { // header storing // diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index c51accb612..eebaa0f470 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -42,6 +42,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithWriteDeadline(cfg.Server.WriteDeadline), p2p.WithReadDeadline(cfg.Server.ReadDeadline), p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), + p2p.WithServeTimeout[p2p.ServerParameters](cfg.Server.ServeTimeout), } }), fx.Provide(NewHeaderService), diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 0a0f074f25..8a87352ae8 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -121,4 +121,5 @@ func TestConstructModule_ExchangeParams(t *testing.T) { require.Equal(t, exchangeServer.Params.WriteDeadline, cfg.Server.WriteDeadline) require.Equal(t, exchangeServer.Params.ReadDeadline, cfg.Server.ReadDeadline) require.Equal(t, exchangeServer.Params.MaxRequestSize, cfg.Server.MaxRequestSize) + require.Equal(t, exchangeServer.Params.ServeTimeout, cfg.Server.ServeTimeout) } From b7feb12f962126551503f2dff9f82f90abdf2874 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 17 Jan 2023 16:37:32 +0100 Subject: [PATCH 0304/1008] chore(header/p2p): improve logging and error wrapping (#1608) --- header/p2p/exchange.go | 2 ++ header/p2p/helpers.go | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 1a751cd13c..bec05664d9 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -228,8 +228,10 @@ func (ex *Exchange) request( to peer.ID, req *p2p_pb.ExtendedHeaderRequest, ) ([]*header.ExtendedHeader, error) { + log.Debugw("requesting peer", "peer", to) responses, _, _, err := sendMessage(ctx, ex.host, to, ex.protocolID, req) if err != nil { + log.Debugw("err sending request", "peer", to, "err", err) return nil, err } if len(responses) == 0 { diff --git a/header/p2p/helpers.go b/header/p2p/helpers.go index 8b497a6e6d..8c493866d2 100644 --- a/header/p2p/helpers.go +++ b/header/p2p/helpers.go @@ -33,7 +33,7 @@ func sendMessage( startTime := time.Now() stream, err := host.NewStream(ctx, to, protocol) if err != nil { - return nil, 0, 0, err + return nil, 0, 0, fmt.Errorf("header/p2p: failed to open a new stream: %w", err) } // set stream deadline from the context deadline. @@ -49,12 +49,12 @@ func sendMessage( _, err = serde.Write(stream, req) if err != nil { stream.Reset() //nolint:errcheck - return nil, 0, 0, err + return nil, 0, 0, fmt.Errorf("header/p2p: failed to write a request: %w", err) } err = stream.CloseWrite() if err != nil { - return nil, 0, 0, nil + return nil, 0, 0, err } headers := make([]*p2p_pb.ExtendedHeaderResponse, 0) @@ -67,7 +67,7 @@ func sendMessage( break } stream.Reset() //nolint:errcheck - return nil, 0, 0, err + return nil, 0, 0, fmt.Errorf("header/p2p: failed to read a response: %w", err) } totalRequestSize += uint64(msgSize) From 2d6d13867ade15b851042bf3b215a12d342db858 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 18 Jan 2023 12:40:11 +0200 Subject: [PATCH 0305/1008] feat(header/p2p): add traces to server (#1589) --- header/p2p/server.go | 66 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/header/p2p/server.go b/header/p2p/server.go index 9829cf8d53..be251ff3ed 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -9,6 +9,10 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" tmbytes "github.com/tendermint/tendermint/libs/bytes" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -16,6 +20,10 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" ) +var ( + tracer = otel.Tracer("header/server") +) + // ExchangeServer represents the server-side component for // responding to inbound header-related requests. type ExchangeServer struct { @@ -151,42 +159,53 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { // if it exists. func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.ExtendedHeader, error) { log.Debugw("server: handling header request", "hash", tmbytes.HexBytes(hash).String()) - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) defer cancel() + ctx, span := tracer.Start(ctx, "request-by-hash", trace.WithAttributes( + attribute.String("hash", tmbytes.HexBytes(hash).String()), + )) + defer span.End() h, err := serv.getter.Get(ctx, hash) if err != nil { log.Errorw("server: getting header by hash", "hash", tmbytes.HexBytes(hash).String(), "err", err) + span.SetStatus(codes.Error, err.Error()) return nil, err } + + span.AddEvent("fetched-header-from-store", trace.WithAttributes( + attribute.String("hash", tmbytes.HexBytes(hash).String()), + attribute.Int64("height", h.Height)), + ) + span.SetStatus(codes.Ok, "") return []*header.ExtendedHeader{h}, nil } // handleRequest fetches the ExtendedHeader at the given origin and // writes it to the stream. func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHeader, error) { + if from == uint64(0) { + return serv.handleHeadRequest() + } + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) defer cancel() - if from == uint64(0) { - log.Debug("server: handling head request") - head, err := serv.getter.Head(ctx) - if err != nil { - log.Errorw("server: getting head", "err", err) - return nil, err - } - return []*header.ExtendedHeader{head}, nil - } + ctx, span := tracer.Start(ctx, "request-range", trace.WithAttributes( + attribute.Int64("from", int64(from)), + attribute.Int64("to", int64(to)))) + defer span.End() if to-from > serv.Params.MaxRequestSize { log.Errorw("server: skip request for too many headers.", "amount", to-from) + span.SetStatus(codes.Error, header.ErrHeadersLimitExceeded.Error()) return nil, header.ErrHeadersLimitExceeded } log.Debugw("server: handling headers request", "from", from, "to", to) headersByRange, err := serv.getter.GetRangeByHeight(ctx, from, to) if err != nil { + span.SetStatus(codes.Error, err.Error()) if errors.Is(err, context.DeadlineExceeded) { log.Warnw("server: requested headers not found", "from", from, "to", to) return nil, header.ErrNotFound @@ -194,5 +213,32 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe log.Errorw("server: getting headers", "from", from, "to", to, "err", err) return nil, err } + + span.AddEvent("fetched-range-of-headers", trace.WithAttributes( + attribute.Int("amount", len(headersByRange)))) + span.SetStatus(codes.Ok, "") return headersByRange, nil } + +// handleHeadRequest returns the latest stored head. +func (serv *ExchangeServer) handleHeadRequest() ([]*header.ExtendedHeader, error) { + log.Debug("server: handling head request") + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) + defer cancel() + ctx, span := tracer.Start(ctx, "request-head") + defer span.End() + + head, err := serv.getter.Head(ctx) + if err != nil { + log.Errorw("server: getting head", "err", err) + span.SetStatus(codes.Error, err.Error()) + return nil, err + } + + span.AddEvent("fetched-head", trace.WithAttributes( + attribute.String("hash", head.Hash().String()), + attribute.Int64("height", head.Height)), + ) + span.SetStatus(codes.Ok, "") + return []*header.ExtendedHeader{head}, nil +} From 534ec0df88fd885d69c04daf41c0ba10e4e1512f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 18 Jan 2023 14:47:42 +0200 Subject: [PATCH 0306/1008] fix(header/p2p): add peerScore decreasing in case of failed request (#1603) --- header/p2p/exchange.go | 4 +- header/p2p/exchange_test.go | 81 +++++++++++++++++++++++++++++++ header/p2p/options.go | 33 ++++++++----- header/p2p/peer_stats.go | 11 +++++ header/p2p/peer_stats_test.go | 10 ++++ header/p2p/server.go | 6 +-- header/p2p/session.go | 28 ++++++----- nodebuilder/header/module.go | 3 +- nodebuilder/header/module_test.go | 3 +- 9 files changed, 148 insertions(+), 31 deletions(-) diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index bec05664d9..4bdc4a35b9 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -167,7 +167,7 @@ func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( if amount > ex.Params.MaxRequestSize { return nil, header.ErrHeadersLimitExceeded } - session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID) + session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout) defer session.close() return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRequest) } @@ -179,7 +179,7 @@ func (ex *Exchange) GetVerifiedRange( from *header.ExtendedHeader, amount uint64, ) ([]*header.ExtendedHeader, error) { - session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID, withValidation(from)) + session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout, withValidation(from)) defer session.close() return session.getRangeByHeight(ctx, uint64(from.Height)+1, amount, ex.Params.MaxHeadersPerRequest) diff --git a/header/p2p/exchange_test.go b/header/p2p/exchange_test.go index 3adca5a78d..3458bd42ed 100644 --- a/header/p2p/exchange_test.go +++ b/header/p2p/exchange_test.go @@ -8,14 +8,19 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" libhost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + blankhost "github.com/libp2p/go-libp2p/p2p/host/blank" "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/go-libp2p-messenger/serde" + swarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" + "github.com/celestiaorg/celestia-node/header" headerMock "github.com/celestiaorg/celestia-node/header/mocks" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" @@ -293,6 +298,49 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.Equal(t, resp.StatusCode, p2p_pb.StatusCode_NOT_FOUND) } +// TestExchange_RequestHeadersFromAnotherPeer tests that the Exchange instance will request range +// from another peer with lower score after receiving header.ErrNotFound +func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { + // create blankhost because mocknet does not support deadlines + swarm0 := swarm.GenSwarm(t) + host0 := blankhost.NewBlankHost(swarm0) + swarm1 := swarm.GenSwarm(t) + host1 := blankhost.NewBlankHost(swarm1) + swarm2 := swarm.GenSwarm(t) + host2 := blankhost.NewBlankHost(swarm2) + dial := func(a, b network.Network) { + swarm.DivulgeAddresses(b, a) + if _, err := a.DialPeer(context.Background(), b.LocalPeer()); err != nil { + t.Fatalf("Failed to dial: %s", err) + } + } + // dial peers + dial(swarm0, swarm1) + dial(swarm0, swarm2) + dial(swarm1, swarm2) + + // create client + server(it does not have needed headers) + exchg, _ := createP2PExAndServer(t, host0, host1) + exchg.Params.RequestTimeout = time.Millisecond * 100 + // create one more server(with more headers in the store) + serverSideEx, err := NewExchangeServer(host2, headerMock.NewStore(t, 10), "private") + require.NoError(t, err) + // change store implementation + serverSideEx.getter = &timedOutStore{exchg.Params.RequestTimeout} + require.NoError(t, serverSideEx.Start(context.Background())) + t.Cleanup(func() { + serverSideEx.Stop(context.Background()) //nolint:errcheck + }) + exchg.peerTracker.peerLk.Lock() + exchg.peerTracker.trackedPeers[host2.ID()] = &peerStat{peerID: host2.ID(), peerScore: 200} + exchg.peerTracker.peerLk.Unlock() + _, err = exchg.GetRangeByHeight(context.Background(), 1, 3) + require.NoError(t, err) + // ensure that peerScore for the first peer was decrease by 20% + newPeerScore := exchg.peerTracker.trackedPeers[host2.ID()].score() + assert.Less(t, newPeerScore, float32(200)) +} + func createMocknet(t *testing.T, amount int) []libhost.Host { net, err := mocknet.FullMeshConnected(amount) require.NoError(t, err) @@ -322,3 +370,36 @@ func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (*Exchange, *h }) return ex, store } + +type timedOutStore struct { + timeout time.Duration +} + +func (t *timedOutStore) Head(context.Context) (*header.ExtendedHeader, error) { + // TODO implement me + panic("implement me") +} + +func (t *timedOutStore) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { + // TODO implement me + panic("implement me") +} + +func (t *timedOutStore) GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) { + // TODO implement me + panic("implement me") +} + +func (t *timedOutStore) GetRangeByHeight(_ context.Context, _, _ uint64) ([]*header.ExtendedHeader, error) { + time.Sleep(t.timeout + 1) + return []*header.ExtendedHeader{}, nil +} + +func (t *timedOutStore) GetVerifiedRange( + context.Context, + *header.ExtendedHeader, + uint64, +) ([]*header.ExtendedHeader, error) { + // TODO implement me + panic("implement me") +} diff --git a/header/p2p/options.go b/header/p2p/options.go index 646d1a5a23..0f72477158 100644 --- a/header/p2p/options.go +++ b/header/p2p/options.go @@ -23,8 +23,9 @@ type ServerParameters struct { ReadDeadline time.Duration // MaxRequestSize defines the max amount of headers that can be handled at once. MaxRequestSize uint64 - // ServeTimeout defines the deadline for serving requests. - ServeTimeout time.Duration + // RequestTimeout defines a timeout after which the session will try to re-request headers + // from another peer. + RequestTimeout time.Duration } // DefaultServerParameters returns the default params to configure the store. @@ -33,7 +34,7 @@ func DefaultServerParameters() ServerParameters { WriteDeadline: time.Second * 5, ReadDeadline: time.Minute, MaxRequestSize: 512, - ServeTimeout: time.Second * 5, + RequestTimeout: time.Second * 5, } } @@ -47,8 +48,9 @@ func (p *ServerParameters) Validate() error { if p.MaxRequestSize == 0 { return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) } - if p.ServeTimeout == time.Duration(0) { - return fmt.Errorf("invalid request timeout: %d", p.ServeTimeout) + if p.RequestTimeout == 0 { + return fmt.Errorf("invalid request duration for session: "+ + "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) } return nil } @@ -88,13 +90,15 @@ func WithMaxRequestSize[T parameters](size uint64) Option[T] { } } -// WithServeTimeout is a functional option that configures the -// `ServeTimeout` parameter. -func WithServeTimeout[T parameters](timeout time.Duration) Option[T] { +// WithRequestTimeout is a functional option that configures the +// `RequestTimeout` parameter. +func WithRequestTimeout[T parameters](duration time.Duration) Option[T] { return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic + switch t := any(p).(type) { + case *ClientParameters: + t.RequestTimeout = duration case *ServerParameters: - t.ServeTimeout = timeout + t.RequestTimeout = duration } } } @@ -112,7 +116,9 @@ type ClientParameters struct { MaxAwaitingTime time.Duration // DefaultScore specifies the score for newly connected peers. DefaultScore float32 - + // RequestTimeout defines a timeout after which the session will try to re-request headers + // from another peer. + RequestTimeout time.Duration // MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker. MaxPeerTrackerSize int } @@ -125,6 +131,7 @@ func DefaultClientParameters() ClientParameters { MaxHeadersPerRequest: 64, MaxAwaitingTime: time.Hour, DefaultScore: 1, + RequestTimeout: time.Second * 3, MaxPeerTrackerSize: 100, } } @@ -156,6 +163,10 @@ func (p *ClientParameters) Validate() error { if p.DefaultScore <= 0 { return fmt.Errorf("invalid DefaultScore: %s. %s: %f", greaterThenZero, providedSuffix, p.DefaultScore) } + if p.RequestTimeout == 0 { + return fmt.Errorf("invalid request duration for session: "+ + "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) + } if p.MaxPeerTrackerSize <= 0 { return fmt.Errorf("invalid MaxTrackerSize: %s. %s: %d", greaterThenZero, providedSuffix, p.MaxPeerTrackerSize) } diff --git a/header/p2p/peer_stats.go b/header/p2p/peer_stats.go index 89d44085ff..ced0592996 100644 --- a/header/p2p/peer_stats.go +++ b/header/p2p/peer_stats.go @@ -40,6 +40,17 @@ func (p *peerStat) updateStats(amount uint64, time uint64) { p.peerScore = (p.peerScore + averageSpeed) / 2 } +// decreaseScore decreases peerScore by 20% of the peer that failed the request by any reason. +// NOTE: decreasing peerScore in one session will not affect its position in queue in another +// session(as we can have multiple sessions running concurrently). +// TODO(vgonkivs): to figure out the better scoring increments/decrements +func (p *peerStat) decreaseScore() { + p.Lock() + defer p.Unlock() + + p.peerScore -= p.peerScore / 100 * 20 +} + // score reads a peer's latest score from the queue func (p *peerStat) score() float32 { p.RLock() diff --git a/header/p2p/peer_stats_test.go b/header/p2p/peer_stats_test.go index e020bb5d95..d5a875d12d 100644 --- a/header/p2p/peer_stats_test.go +++ b/header/p2p/peer_stats_test.go @@ -99,3 +99,13 @@ func Test_StatsUpdateStats(t *testing.T) { heap.Push(&pQueue.stats, updatedStat) } } + +func Test_StatDecreaseScore(t *testing.T) { + pStats := &peerStat{ + peerID: peer.ID("test"), + peerScore: 100, + } + // will decrease score by 20% + pStats.decreaseScore() + require.Equal(t, pStats.score(), float32(80.0)) +} diff --git a/header/p2p/server.go b/header/p2p/server.go index be251ff3ed..723d488d6e 100644 --- a/header/p2p/server.go +++ b/header/p2p/server.go @@ -159,7 +159,7 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { // if it exists. func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.ExtendedHeader, error) { log.Debugw("server: handling header request", "hash", tmbytes.HexBytes(hash).String()) - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-by-hash", trace.WithAttributes( attribute.String("hash", tmbytes.HexBytes(hash).String()), @@ -188,7 +188,7 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe return serv.handleHeadRequest() } - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-range", trace.WithAttributes( @@ -223,7 +223,7 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe // handleHeadRequest returns the latest stored head. func (serv *ExchangeServer) handleHeadRequest() ([]*header.ExtendedHeader, error) { log.Debug("server: handling head request") - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.ServeTimeout) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-head") defer span.End() diff --git a/header/p2p/session.go b/header/p2p/session.go index beb45d1c7d..bb5a35b9b3 100644 --- a/header/p2p/session.go +++ b/header/p2p/session.go @@ -4,6 +4,7 @@ import ( "context" "errors" "sort" + "time" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" @@ -31,7 +32,8 @@ type session struct { // `from` is set when additional validation for range is needed. // Otherwise, it will be nil. - from *header.ExtendedHeader + from *header.ExtendedHeader + requestTimeout time.Duration ctx context.Context cancel context.CancelFunc @@ -43,16 +45,18 @@ func newSession( h host.Host, peerTracker *peerTracker, protocolID protocol.ID, + requestTimeout time.Duration, options ...option, ) *session { ctx, cancel := context.WithCancel(ctx) ses := &session{ - ctx: ctx, - cancel: cancel, - protocolID: protocolID, - host: h, - queue: newPeerQueue(ctx, peerTracker.peers()), - peerTracker: peerTracker, + ctx: ctx, + cancel: cancel, + protocolID: protocolID, + host: h, + queue: newPeerQueue(ctx, peerTracker.peers()), + peerTracker: peerTracker, + requestTimeout: requestTimeout, } for _, opt := range options { @@ -130,17 +134,16 @@ func (s *session) doRequest( req *p2p_pb.ExtendedHeaderRequest, headers chan []*header.ExtendedHeader, ) { + ctx, cancel := context.WithTimeout(ctx, s.requestTimeout) + defer cancel() + r, size, duration, err := sendMessage(ctx, s.host, stat.peerID, s.protocolID, req) if err != nil { - if err == context.Canceled || err == context.DeadlineExceeded { - return - } log.Errorw("requesting headers from peer failed.", "failed peer", stat.peerID, "err", err) select { - case <-ctx.Done(): case <-s.ctx.Done(): - // retry request case s.reqCh <- req: + stat.decreaseScore() log.Debug("Retrying the request from different peer") } return @@ -154,7 +157,6 @@ func (s *session) doRequest( s.peerTracker.blockPeer(stat.peerID, err) } select { - case <-ctx.Done(): case <-s.ctx.Done(): case s.reqCh <- req: } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index eebaa0f470..f539eaa735 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -42,7 +42,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithWriteDeadline(cfg.Server.WriteDeadline), p2p.WithReadDeadline(cfg.Server.ReadDeadline), p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), - p2p.WithServeTimeout[p2p.ServerParameters](cfg.Server.ServeTimeout), + p2p.WithRequestTimeout[p2p.ServerParameters](cfg.Server.RequestTimeout), } }), fx.Provide(NewHeaderService), @@ -111,6 +111,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithMaxHeadersPerRequest(cfg.Client.MaxHeadersPerRequest), p2p.WithMaxAwaitingTime(cfg.Client.MaxAwaitingTime), p2p.WithDefaultScore(cfg.Client.DefaultScore), + p2p.WithRequestTimeout[p2p.ClientParameters](cfg.Client.RequestTimeout), p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), } }, diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 8a87352ae8..045a526f4b 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -117,9 +117,10 @@ func TestConstructModule_ExchangeParams(t *testing.T) { require.Equal(t, exchange.Params.MaxAwaitingTime, cfg.Client.MaxAwaitingTime) require.Equal(t, exchange.Params.DefaultScore, cfg.Client.DefaultScore) require.Equal(t, exchange.Params.MaxPeerTrackerSize, cfg.Client.MaxPeerTrackerSize) + require.Equal(t, exchange.Params.RequestTimeout, cfg.Client.RequestTimeout) require.Equal(t, exchangeServer.Params.WriteDeadline, cfg.Server.WriteDeadline) require.Equal(t, exchangeServer.Params.ReadDeadline, cfg.Server.ReadDeadline) require.Equal(t, exchangeServer.Params.MaxRequestSize, cfg.Server.MaxRequestSize) - require.Equal(t, exchangeServer.Params.ServeTimeout, cfg.Server.ServeTimeout) + require.Equal(t, exchangeServer.Params.RequestTimeout, cfg.Server.RequestTimeout) } From fb274bce7d12c1c4781259fb939a4af5ae3f93c7 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 19 Jan 2023 15:08:31 +0200 Subject: [PATCH 0307/1008] fix(header/p2p): fix error string (#1618) --- header/p2p/options.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/header/p2p/options.go b/header/p2p/options.go index 0f72477158..a644bc547e 100644 --- a/header/p2p/options.go +++ b/header/p2p/options.go @@ -49,7 +49,7 @@ func (p *ServerParameters) Validate() error { return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) } if p.RequestTimeout == 0 { - return fmt.Errorf("invalid request duration for session: "+ + return fmt.Errorf("invalid request timeout for session: "+ "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) } return nil @@ -164,7 +164,7 @@ func (p *ClientParameters) Validate() error { return fmt.Errorf("invalid DefaultScore: %s. %s: %f", greaterThenZero, providedSuffix, p.DefaultScore) } if p.RequestTimeout == 0 { - return fmt.Errorf("invalid request duration for session: "+ + return fmt.Errorf("invalid request timeout for session: "+ "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) } if p.MaxPeerTrackerSize <= 0 { From a3137ba70055926db23234a7145392054882bd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Thu, 19 Jan 2023 14:43:43 +0100 Subject: [PATCH 0308/1008] feat(header)!: introduce `header.Header` interface (#1304) Most important changes in this PR: * `tmbytes.HexBytes` replaced by `header.Hash` * `header.Header` used in interfaces in `header` package * `IsBefore` inlined, so we don't need to include this method in interface (it's a oneliner used once) * `VerifyNonAdjacent` added to `Header` interface (at least for now) * Generics introduced to avoid casting Resolves https://github.com/celestiaorg/rollmint/issues/575. --- api/gateway/share.go | 2 +- cmd/cel-shed/header.go | 5 +- core/fetcher.go | 5 +- core/fetcher_test.go | 8 +- das/coordinator.go | 5 +- das/coordinator_test.go | 12 +- das/daser.go | 15 +- das/daser_test.go | 18 +- das/subscriber.go | 7 +- das/worker.go | 9 +- fraud/testing.go | 6 +- header/core/exchange.go | 7 +- header/core/listener.go | 7 +- header/core/listener_test.go | 4 +- header/header.go | 55 +++- header/header_test.go | 2 +- header/local/exchange.go | 53 ---- header/metrics.go | 6 +- header/mocks/store.go | 100 ------ header/p2p/subscriber.go | 116 ------- header/p2p/subscription.go | 51 ---- header/serde.go | 40 ++- header/testing.go | 32 +- header/verify.go | 66 ++-- header/verify_test.go | 8 +- libs/header/doc.go | 10 + libs/header/errors.go | 12 + libs/header/hash.go | 38 +++ libs/header/header.go | 33 ++ {header => libs/header}/interface.go | 87 +++--- libs/header/local/exchange.go | 51 ++++ libs/header/mocks/store.go | 98 ++++++ {header => libs/header}/p2p/exchange.go | 144 +++++---- {header => libs/header}/p2p/exchange_test.go | 101 +++--- {header => libs/header}/p2p/helpers.go | 16 +- {header => libs/header}/p2p/options.go | 0 .../header/p2p/pb/header_request.pb.go | 284 ++++++++--------- .../header/p2p/pb/header_request.proto | 4 +- {header => libs/header}/p2p/peer_stats.go | 0 .../header}/p2p/peer_stats_test.go | 0 {header => libs/header}/p2p/peer_tracker.go | 0 .../header}/p2p/peer_tracker_test.go | 0 {header => libs/header}/p2p/server.go | 69 +++-- {header => libs/header}/p2p/server_test.go | 7 +- {header => libs/header}/p2p/session.go | 72 ++--- {header => libs/header}/p2p/session_test.go | 0 libs/header/p2p/subscriber.go | 82 +++++ libs/header/p2p/subscription.go | 54 ++++ .../header}/p2p/subscription_test.go | 17 +- {header => libs/header}/store/batch.go | 46 +-- .../header}/store/height_indexer.go | 19 +- {header => libs/header}/store/heightsub.go | 31 +- .../header}/store/heightsub_test.go | 16 +- {header => libs/header}/store/init.go | 6 +- {header => libs/header}/store/init_test.go | 21 +- {header => libs/header}/store/keys.go | 4 +- {header => libs/header}/store/options.go | 0 {header => libs/header}/store/store.go | 149 ++++----- {header => libs/header}/store/store_test.go | 20 +- {header => libs/header}/store/testing.go | 5 +- {header => libs/header}/sync/options.go | 0 {header => libs/header}/sync/ranges.go | 54 ++-- {header => libs/header}/sync/sync.go | 68 +++-- {header => libs/header}/sync/sync_head.go | 80 +++-- {header => libs/header}/sync/sync_test.go | 78 ++--- libs/header/test/testing.go | 287 ++++++++++++++++++ nodebuilder/core/module.go | 3 +- nodebuilder/das/constructors.go | 5 +- nodebuilder/fraud/constructors.go | 5 +- nodebuilder/header/config.go | 10 +- nodebuilder/header/constructors.go | 34 ++- nodebuilder/header/module.go | 40 ++- nodebuilder/header/module_test.go | 25 +- nodebuilder/header/service.go | 25 +- nodebuilder/state/core.go | 5 +- nodebuilder/testing.go | 7 +- nodebuilder/tests/swamp/swamp.go | 14 +- share/eds/byzantine/bad_encoding.go | 2 +- share/eds/byzantine/bad_encoding_test.go | 2 +- share/eds/retriever_test.go | 2 +- state/core_access.go | 9 +- 81 files changed, 1679 insertions(+), 1211 deletions(-) delete mode 100644 header/local/exchange.go delete mode 100644 header/mocks/store.go delete mode 100644 header/p2p/subscriber.go delete mode 100644 header/p2p/subscription.go create mode 100644 libs/header/doc.go create mode 100644 libs/header/errors.go create mode 100644 libs/header/hash.go create mode 100644 libs/header/header.go rename {header => libs/header}/interface.go (52%) create mode 100644 libs/header/local/exchange.go create mode 100644 libs/header/mocks/store.go rename {header => libs/header}/p2p/exchange.go (54%) rename {header => libs/header}/p2p/exchange_test.go (78%) rename {header => libs/header}/p2p/helpers.go (81%) rename {header => libs/header}/p2p/options.go (100%) rename header/p2p/pb/extended_header_request.pb.go => libs/header/p2p/pb/header_request.pb.go (50%) rename header/p2p/pb/extended_header_request.proto => libs/header/p2p/pb/header_request.proto (78%) rename {header => libs/header}/p2p/peer_stats.go (100%) rename {header => libs/header}/p2p/peer_stats_test.go (100%) rename {header => libs/header}/p2p/peer_tracker.go (100%) rename {header => libs/header}/p2p/peer_tracker_test.go (100%) rename {header => libs/header}/p2p/server.go (74%) rename {header => libs/header}/p2p/server_test.go (63%) rename {header => libs/header}/p2p/session.go (74%) rename {header => libs/header}/p2p/session_test.go (100%) create mode 100644 libs/header/p2p/subscriber.go create mode 100644 libs/header/p2p/subscription.go rename {header => libs/header}/p2p/subscription_test.go (81%) rename {header => libs/header}/store/batch.go (66%) rename {header => libs/header}/store/height_indexer.go (59%) rename {header => libs/header}/store/heightsub.go (81%) rename {header => libs/header}/store/heightsub_test.go (70%) rename {header => libs/header}/store/init.go (65%) rename {header => libs/header}/store/init_test.go (65%) rename {header => libs/header}/store/keys.go (74%) rename {header => libs/header}/store/options.go (100%) rename {header => libs/header}/store/store.go (70%) rename {header => libs/header}/store/store_test.go (83%) rename {header => libs/header}/store/testing.go (70%) rename {header => libs/header}/sync/options.go (100%) rename {header => libs/header}/sync/ranges.go (68%) rename {header => libs/header}/sync/sync.go (82%) rename {header => libs/header}/sync/sync_head.go (73%) rename {header => libs/header}/sync/sync_test.go (69%) create mode 100644 libs/header/test/testing.go diff --git a/api/gateway/share.go b/api/gateway/share.go index 7161ae121e..3046346e0f 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -108,7 +108,7 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID } // perform request shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, nID) - return shares.Flatten(), header.Height, err + return shares.Flatten(), header.Height(), err } func dataFromShares(shares []share.Share) ([][]byte, error) { diff --git a/cmd/cel-shed/header.go b/cmd/cel-shed/header.go index ea2584405d..f9b2edbb2a 100644 --- a/cmd/cel-shed/header.go +++ b/cmd/cel-shed/header.go @@ -7,7 +7,8 @@ import ( "github.com/spf13/cobra" - "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header/store" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -54,7 +55,7 @@ Custom store path is not supported yet.`, return err } - hstore, err := store.NewStore(ds) + hstore, err := store.NewStore[*header.ExtendedHeader](ds) if err != nil { return err } diff --git a/core/fetcher.go b/core/fetcher.go index 62876ec175..ab1c45158d 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -5,8 +5,9 @@ import ( "fmt" logging "github.com/ipfs/go-log/v2" - tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/types" + + libhead "github.com/celestiaorg/celestia-node/libs/header" ) const newBlockSubscriber = "NewBlock/Events" @@ -64,7 +65,7 @@ func (f *BlockFetcher) GetBlock(ctx context.Context, height *int64) (*types.Bloc return res.Block, nil } -func (f *BlockFetcher) GetBlockByHash(ctx context.Context, hash tmbytes.HexBytes) (*types.Block, error) { +func (f *BlockFetcher) GetBlockByHash(ctx context.Context, hash libhead.Hash) (*types.Block, error) { res, err := f.client.BlockByHash(ctx, hash) if err != nil { return nil, err diff --git a/core/fetcher_test.go b/core/fetcher_test.go index ece9660c39..b71f7568e4 100644 --- a/core/fetcher_test.go +++ b/core/fetcher_test.go @@ -8,8 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" - - "github.com/tendermint/tendermint/libs/bytes" ) func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { @@ -76,9 +74,7 @@ func TestBlockFetcherHeaderValues(t *testing.T) { assert.Equal(t, nextBlock.LastCommit.Height, commit.Height) assert.Equal(t, nextBlock.LastCommit.Signatures, commit.Signatures) // compare ValidatorSet hash to the ValidatorsHash from first block height - hexBytes := bytes.HexBytes{} - err = hexBytes.Unmarshal(valSet.Hash()) - require.NoError(t, err) - assert.Equal(t, nextBlock.ValidatorsHash, hexBytes) + hexBytes := valSet.Hash() + assert.Equal(t, nextBlock.ValidatorsHash.Bytes(), hexBytes) require.NoError(t, fetcher.UnsubscribeNewBlockEvent(ctx)) } diff --git a/das/coordinator.go b/das/coordinator.go index 809d7550ec..d8e149ebd3 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -5,13 +5,14 @@ import ( "sync" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" ) // samplingCoordinator runs and coordinates sampling workers and updates current sampling state type samplingCoordinator struct { concurrencyLimit int - getter header.Getter + getter libhead.Getter[*header.ExtendedHeader] sampleFn sampleFn state coordinatorState @@ -37,7 +38,7 @@ type result struct { func newSamplingCoordinator( params Parameters, - getter header.Getter, + getter libhead.Getter[*header.ExtendedHeader], sample sampleFn, ) *samplingCoordinator { return &samplingCoordinator{ diff --git a/das/coordinator_test.go b/das/coordinator_test.go index 1fd47a6440..a37ea73f08 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -302,7 +302,7 @@ func (m *mockSampler) sample(ctx context.Context, h *header.ExtendedHeader) erro m.lock.Lock() defer m.lock.Unlock() - height := uint64(h.Height) + height := uint64(h.Height()) m.done[height]++ if len(m.done) > int(m.NetworkHead-m.SampleFrom) && !m.isFinished { @@ -435,7 +435,7 @@ func (o *checkOrder) middleWare(out sampleFn) sampleFn { if len(o.queue) > 0 { // check last item in queue to be same as input - if o.queue[0] != uint64(h.Height) { + if o.queue[0] != uint64(h.Height()) { o.lock.Unlock() return fmt.Errorf("expected height: %v,got: %v", o.queue[0], h) } @@ -505,7 +505,7 @@ func (l *lock) releaseAll(except ...uint64) { func (l *lock) middleWare(out sampleFn) sampleFn { return func(ctx context.Context, h *header.ExtendedHeader) error { l.m.Lock() - ch, blocked := l.blockList[uint64(h.Height)] + ch, blocked := l.blockList[uint64(h.Height())] l.m.Unlock() if !blocked { return out(ctx, h) @@ -525,10 +525,10 @@ func onceMiddleWare(out sampleFn) sampleFn { m := sync.Mutex{} return func(ctx context.Context, h *header.ExtendedHeader) error { m.Lock() - db[h.Height]++ - if db[h.Height] > 1 { + db[h.Height()]++ + if db[h.Height()] > 1 { m.Unlock() - return fmt.Errorf("header sampled more than once: %v", h.Height) + return fmt.Errorf("header sampled more than once: %v", h.Height()) } m.Unlock() return out(ctx, h) diff --git a/das/daser.go b/das/daser.go index ce4b41225e..b9c425f9da 100644 --- a/das/daser.go +++ b/das/daser.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) @@ -23,8 +24,8 @@ type DASer struct { da share.Availability bcast fraud.Broadcaster - hsub header.Subscriber // listens for new headers in the network - getter header.Getter // retrieves past headers + hsub libhead.Subscriber[*header.ExtendedHeader] // listens for new headers in the network + getter libhead.Getter[*header.ExtendedHeader] // retrieves past headers sampler *samplingCoordinator store checkpointStore @@ -41,8 +42,8 @@ type sampleFn func(context.Context, *header.ExtendedHeader) error // NewDASer creates a new DASer. func NewDASer( da share.Availability, - hsub header.Subscriber, - getter header.Getter, + hsub libhead.Subscriber[*header.ExtendedHeader], + getter libhead.Getter[*header.ExtendedHeader], dstore datastore.Datastore, bcast fraud.Broadcaster, options ...Option, @@ -95,7 +96,7 @@ func (d *DASer) Start(ctx context.Context) error { // attempt to get head info. No need to handle error, later DASer // will be able to find new head from subscriber after it is started if h, err := d.getter.Head(ctx); err == nil { - cp.NetworkHead = uint64(h.Height) + cp.NetworkHead = uint64(h.Height()) } } log.Info("starting DASer from checkpoint: ", cp.String()) @@ -151,13 +152,13 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { var byzantineErr *byzantine.ErrByzantine if errors.As(err, &byzantineErr) { log.Warn("Propagating proof...") - sendErr := d.bcast.Broadcast(ctx, byzantine.CreateBadEncodingProof(h.Hash(), uint64(h.Height), byzantineErr)) + sendErr := d.bcast.Broadcast(ctx, byzantine.CreateBadEncodingProof(h.Hash(), uint64(h.Height()), byzantineErr)) if sendErr != nil { log.Errorw("fraud proof propagating failed", "err", sendErr) } } - log.Errorw("sampling failed", "height", h.Height, "hash", h.Hash(), + log.Errorw("sampling failed", "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err) return err } diff --git a/das/daser_test.go b/das/daser_test.go index c829171161..21b3dc70b6 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -5,12 +5,6 @@ import ( "testing" "time" - "github.com/celestiaorg/celestia-node/share/availability/full" - "github.com/celestiaorg/celestia-node/share/availability/light" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - - "github.com/tendermint/tendermint/types" - "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" @@ -19,10 +13,14 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share/availability/full" + "github.com/celestiaorg/celestia-node/share/availability/light" + availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -232,7 +230,7 @@ func (m *mockGetter) fillSubWithHeaders( randHeader := header.RandExtendedHeader(t) randHeader.DataHash = dah.Hash() randHeader.DAH = dah - randHeader.Height = int64(i + 1) + randHeader.RawHeader.Height = int64(i + 1) sub.Headers[index] = randHeader // also checkpointStore to mock getter for duplicate sampling @@ -260,7 +258,7 @@ func (m *mockGetter) generateHeaders(t *testing.T, bServ blockservice.BlockServi randHeader := header.RandExtendedHeader(t) randHeader.DataHash = dah.Hash() randHeader.DAH = dah - randHeader.Height = int64(i + 1) + randHeader.RawHeader.Height = int64(i + 1) m.headers[int64(i+1)] = randHeader } @@ -332,6 +330,6 @@ func (m getterStub) GetVerifiedRange( return nil, nil } -func (m getterStub) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { +func (m getterStub) Get(context.Context, libhead.Hash) (*header.ExtendedHeader, error) { return nil, nil } diff --git a/das/subscriber.go b/das/subscriber.go index 862cb6a2e4..121301eb7f 100644 --- a/das/subscriber.go +++ b/das/subscriber.go @@ -4,6 +4,7 @@ import ( "context" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" ) // subscriber subscribes to notifications about new headers in the network to keep @@ -16,7 +17,7 @@ func newSubscriber() subscriber { return subscriber{newDone("subscriber")} } -func (s *subscriber) run(ctx context.Context, sub header.Subscription, emit listenFn) { +func (s *subscriber) run(ctx context.Context, sub libhead.Subscription[*header.ExtendedHeader], emit listenFn) { defer s.indicateDone() defer sub.Cancel() @@ -30,8 +31,8 @@ func (s *subscriber) run(ctx context.Context, sub header.Subscription, emit list log.Errorw("failed to get next header", "err", err) continue } - log.Infow("new header received via subscription", "height", h.Height) + log.Infow("new header received via subscription", "height", h.Height()) - emit(ctx, uint64(h.Height)) + emit(ctx, uint64(h.Height())) } } diff --git a/das/worker.go b/das/worker.go index 427f08a0a5..7eb73ac703 100644 --- a/das/worker.go +++ b/das/worker.go @@ -10,6 +10,7 @@ import ( "go.uber.org/multierr" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" ) type worker struct { @@ -36,7 +37,7 @@ type job struct { func (w *worker) run( ctx context.Context, - getter header.Getter, + getter libhead.Getter[*header.ExtendedHeader], sample sampleFn, metrics *metrics, resultCh chan<- result) { @@ -59,7 +60,7 @@ func (w *worker) run( } metrics.observeGetHeader(ctx, time.Since(startGet)) - log.Debugw("got header from header store", "height", h.Height, "hash", h.Hash(), + log.Debugw("got header from header store", "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startGet)) startSample := time.Now() @@ -71,10 +72,10 @@ func (w *worker) run( w.setResult(curr, err) metrics.observeSample(ctx, h, time.Since(startSample), err) if err != nil { - log.Debugw("failed to sampled header", "height", h.Height, "hash", h.Hash(), + log.Debugw("failed to sampled header", "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err) } else { - log.Debugw("sampled header", "height", h.Height, "hash", h.Hash(), + log.Debugw("sampled header", "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startSample)) } } diff --git a/fraud/testing.go b/fraud/testing.go index ae7e7cd8f2..d891cf45d3 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -49,10 +49,10 @@ func createStore(t *testing.T, numHeaders int) *mockStore { for i := 0; i < numHeaders; i++ { header := suite.GenExtendedHeader() - store.headers[header.Height] = header + store.headers[header.Height()] = header - if header.Height > store.headHeight { - store.headHeight = header.Height + if header.Height() > store.headHeight { + store.headHeight = header.Height() } } return store diff --git a/header/core/exchange.go b/header/core/exchange.go index 9689f84f00..de895cbffe 100644 --- a/header/core/exchange.go +++ b/header/core/exchange.go @@ -8,10 +8,9 @@ import ( "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" ) var log = logging.Logger("header/core") @@ -57,7 +56,7 @@ func (ce *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( func (ce *Exchange) GetVerifiedRange(ctx context.Context, from *header.ExtendedHeader, amount uint64, ) ([]*header.ExtendedHeader, error) { - headers, err := ce.GetRangeByHeight(ctx, uint64(from.Height)+1, amount) + headers, err := ce.GetRangeByHeight(ctx, uint64(from.Height())+1, amount) if err != nil { return nil, err } @@ -72,7 +71,7 @@ func (ce *Exchange) GetVerifiedRange(ctx context.Context, from *header.ExtendedH return headers, nil } -func (ce *Exchange) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { +func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) { log.Debugw("requesting header", "hash", hash.String()) block, err := ce.fetcher.GetBlockByHash(ctx, hash) if err != nil { diff --git a/header/core/listener.go b/header/core/listener.go index ee11021580..d06178f7fc 100644 --- a/header/core/listener.go +++ b/header/core/listener.go @@ -10,6 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" ) // Listener is responsible for listening to Core for @@ -20,7 +21,7 @@ import ( // broadcasts the new `ExtendedHeader` to the header-sub gossipsub // network. type Listener struct { - bcast header.Broadcaster + bcast libhead.Broadcaster[*header.ExtendedHeader] fetcher *core.BlockFetcher bServ blockservice.BlockService construct header.ConstructFn @@ -28,7 +29,7 @@ type Listener struct { } func NewListener( - bcast header.Broadcaster, + bcast libhead.Broadcaster[*header.ExtendedHeader], fetcher *core.BlockFetcher, bServ blockservice.BlockService, construct header.ConstructFn, @@ -98,7 +99,7 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers err = cl.bcast.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) if err != nil { - log.Errorw("listener: broadcasting next header", "height", eh.Height, + log.Errorw("listener: broadcasting next header", "height", eh.Height(), "err", err) } case <-ctx.Done(): diff --git a/header/core/listener_test.go b/header/core/listener_test.go index 5fa22aa1d7..2d561c4903 100644 --- a/header/core/listener_test.go +++ b/header/core/listener_test.go @@ -14,7 +14,7 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/p2p" ) // TestListener tests the lifecycle of the core listener. @@ -94,7 +94,7 @@ func createListener( fetcher *core.BlockFetcher, ps *pubsub.PubSub, ) *Listener { - p2pSub := p2p.NewSubscriber(ps) + p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID) err := p2pSub.Start(ctx) require.NoError(t, err) t.Cleanup(func() { diff --git a/header/header.go b/header/header.go index ac3ab6b8d8..ba4600eb90 100644 --- a/header/header.go +++ b/header/header.go @@ -5,11 +5,11 @@ import ( "context" "encoding/json" "fmt" + "time" "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" - bts "github.com/tendermint/tendermint/libs/bytes" amino "github.com/tendermint/tendermint/libs/json" core "github.com/tendermint/tendermint/types" @@ -17,11 +17,21 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" + libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" ) var log = logging.Logger("header") +// ConstructFn aliases a function that creates an ExtendedHeader. +type ConstructFn = func( + context.Context, + *core.Block, + *core.Commit, + *core.ValidatorSet, + blockservice.BlockService, +) (*ExtendedHeader, error) + type DataAvailabilityHeader = da.DataAvailabilityHeader // EmptyDAH provides DAH of the empty block. @@ -42,6 +52,28 @@ type ExtendedHeader struct { DAH *DataAvailabilityHeader `json:"dah"` } +func (eh *ExtendedHeader) New() libhead.Header { + return new(ExtendedHeader) +} + +func (eh *ExtendedHeader) IsZero() bool { + return eh == nil +} + +func (eh *ExtendedHeader) ChainID() string { + return eh.RawHeader.ChainID +} + +func (eh *ExtendedHeader) Height() int64 { + return eh.RawHeader.Height +} + +func (eh *ExtendedHeader) Time() time.Time { + return eh.RawHeader.Time +} + +var _ libhead.Header = &ExtendedHeader{} + // MakeExtendedHeader assembles new ExtendedHeader. func MakeExtendedHeader( ctx context.Context, @@ -73,33 +105,30 @@ func MakeExtendedHeader( Commit: comm, ValidatorSet: vals, } - return eh, eh.ValidateBasic() + return eh, eh.Validate() } // Hash returns Hash of the wrapped RawHeader. // NOTE: It purposely overrides Hash method of RawHeader to get it directly from Commit without // recomputing. -func (eh *ExtendedHeader) Hash() bts.HexBytes { - return eh.Commit.BlockID.Hash +func (eh *ExtendedHeader) Hash() libhead.Hash { + return libhead.Hash(eh.Commit.BlockID.Hash) } // LastHeader returns the Hash of the last wrapped RawHeader. -func (eh *ExtendedHeader) LastHeader() bts.HexBytes { - return eh.RawHeader.LastBlockID.Hash +func (eh *ExtendedHeader) LastHeader() libhead.Hash { + return libhead.Hash(eh.RawHeader.LastBlockID.Hash) } // IsBefore returns whether the given header is of a higher height. -func (eh *ExtendedHeader) IsBefore(h *ExtendedHeader) bool { - return eh.Height < h.Height -} // Equals returns whether the hash and height of the given header match. func (eh *ExtendedHeader) Equals(header *ExtendedHeader) bool { - return eh.Height == header.Height && bytes.Equal(eh.Hash(), header.Hash()) + return eh.Height() == header.Height() && bytes.Equal(eh.Hash(), header.Hash()) } -// ValidateBasic performs *basic* validation to check for missed/incorrect fields. -func (eh *ExtendedHeader) ValidateBasic() error { +// Validate performs *basic* validation to check for missed/incorrect fields. +func (eh *ExtendedHeader) Validate() error { err := eh.RawHeader.ValidateBasic() if err != nil { return err @@ -122,7 +151,7 @@ func (eh *ExtendedHeader) ValidateBasic() error { ) } - if err := eh.ValidatorSet.VerifyCommitLight(eh.ChainID, eh.Commit.BlockID, eh.Height, eh.Commit); err != nil { + if err := eh.ValidatorSet.VerifyCommitLight(eh.ChainID(), eh.Commit.BlockID, eh.Height(), eh.Commit); err != nil { return err } diff --git a/header/header_test.go b/header/header_test.go index 47b8543121..43f0204a2a 100644 --- a/header/header_test.go +++ b/header/header_test.go @@ -43,6 +43,6 @@ func TestMismatchedDataHash_ComputedRoot(t *testing.T) { header.DataHash = rand.Bytes(32) - err := header.ValidateBasic() + err := header.Validate() assert.ErrorContains(t, err, "mismatch between data hash") } diff --git a/header/local/exchange.go b/header/local/exchange.go deleted file mode 100644 index 3bd978f483..0000000000 --- a/header/local/exchange.go +++ /dev/null @@ -1,53 +0,0 @@ -package local - -import ( - "context" - - "github.com/tendermint/tendermint/libs/bytes" - - "github.com/celestiaorg/celestia-node/header" -) - -// NewExchange is a simple Exchange that reads Headers from Store without any networking. -type Exchange struct { - store header.Store -} - -// NewExchange creates a new local Exchange. -func NewExchange(store header.Store) header.Exchange { - return &Exchange{ - store: store, - } -} - -func (l *Exchange) Start(context.Context) error { - return nil -} - -func (l *Exchange) Stop(context.Context) error { - return nil -} - -func (l *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { - return l.store.Head(ctx) -} - -func (l *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { - return l.store.GetByHeight(ctx, height) -} - -func (l *Exchange) GetRangeByHeight(ctx context.Context, origin, amount uint64) ([]*header.ExtendedHeader, error) { - if amount == 0 { - return nil, nil - } - return l.store.GetRangeByHeight(ctx, origin, origin+amount) -} - -func (l *Exchange) GetVerifiedRange(ctx context.Context, from *header.ExtendedHeader, amount uint64, -) ([]*header.ExtendedHeader, error) { - return l.store.GetVerifiedRange(ctx, from, uint64(from.Height)+amount) -} - -func (l *Exchange) Get(ctx context.Context, hash bytes.HexBytes) (*header.ExtendedHeader, error) { - return l.store.Get(ctx, hash) -} diff --git a/header/metrics.go b/header/metrics.go index 3284d85867..64ff448c7f 100644 --- a/header/metrics.go +++ b/header/metrics.go @@ -7,12 +7,14 @@ import ( "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/unit" + + libhead "github.com/celestiaorg/celestia-node/libs/header" ) var meter = global.MeterProvider().Meter("header") // WithMetrics enables Otel metrics to monitor head. -func WithMetrics(store Store) { +func WithMetrics(store libhead.Store[*ExtendedHeader]) { headC, _ := meter.AsyncInt64().Counter( "head", instrument.WithUnit(unit.Dimensionless), @@ -32,7 +34,7 @@ func WithMetrics(store Store) { headC.Observe( ctx, - head.Height, + head.Height(), attribute.Int("square_size", len(head.DAH.RowsRoots)), ) }, diff --git a/header/mocks/store.go b/header/mocks/store.go deleted file mode 100644 index 5cbb65dbe9..0000000000 --- a/header/mocks/store.go +++ /dev/null @@ -1,100 +0,0 @@ -package mocks - -import ( - "bytes" - "context" - "testing" - - tmbytes "github.com/tendermint/tendermint/libs/bytes" - - "github.com/celestiaorg/celestia-node/header" -) - -type MockStore struct { - Headers map[int64]*header.ExtendedHeader - HeadHeight int64 -} - -// NewStore creates a mock store and adds several random -// headers -func NewStore(t *testing.T, numHeaders int) *MockStore { - store := &MockStore{ - Headers: make(map[int64]*header.ExtendedHeader), - HeadHeight: 0, - } - - suite := header.NewTestSuite(t, numHeaders) - - for i := 0; i < numHeaders; i++ { - header := suite.GenExtendedHeader() - store.Headers[header.Height] = header - - if header.Height > store.HeadHeight { - store.HeadHeight = header.Height - } - } - return store -} - -func (m *MockStore) Init(context.Context, *header.ExtendedHeader) error { return nil } -func (m *MockStore) Start(context.Context) error { return nil } -func (m *MockStore) Stop(context.Context) error { return nil } - -func (m *MockStore) Height() uint64 { - return uint64(m.HeadHeight) -} - -func (m *MockStore) Head(context.Context) (*header.ExtendedHeader, error) { - return m.Headers[m.HeadHeight], nil -} - -func (m *MockStore) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { - for _, header := range m.Headers { - if bytes.Equal(header.Hash(), hash) { - return header, nil - } - } - return nil, header.ErrNotFound -} - -func (m *MockStore) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { - return m.Headers[int64(height)], nil -} - -func (m *MockStore) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { - headers := make([]*header.ExtendedHeader, to-from) - // As the requested range is [from; to), - // check that (to-1) height in request is less than - // the biggest header height in store. - if to-1 > m.Height() { - return nil, header.ErrNotFound - } - for i := range headers { - headers[i] = m.Headers[int64(from)] - from++ - } - return headers, nil -} - -func (m *MockStore) GetVerifiedRange( - ctx context.Context, - h *header.ExtendedHeader, - to uint64, -) ([]*header.ExtendedHeader, error) { - return m.GetRangeByHeight(ctx, uint64(h.Height)+1, to) -} - -func (m *MockStore) Has(context.Context, tmbytes.HexBytes) (bool, error) { - return false, nil -} - -func (m *MockStore) Append(ctx context.Context, headers ...*header.ExtendedHeader) (int, error) { - for _, header := range headers { - m.Headers[header.Height] = header - // set head - if header.Height > m.HeadHeight { - m.HeadHeight = header.Height - } - } - return len(headers), nil -} diff --git a/header/p2p/subscriber.go b/header/p2p/subscriber.go deleted file mode 100644 index ed33c14d30..0000000000 --- a/header/p2p/subscriber.go +++ /dev/null @@ -1,116 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - pb "github.com/libp2p/go-libp2p-pubsub/pb" - "github.com/libp2p/go-libp2p/core/peer" - "golang.org/x/crypto/blake2b" - - "github.com/celestiaorg/celestia-node/header" -) - -// Subscriber manages the lifecycle and relationship of header Module -// with the "header-sub" gossipsub topic. -type Subscriber struct { - pubsub *pubsub.PubSub - topic *pubsub.Topic -} - -// NewSubscriber returns a Subscriber that manages the header Module's -// relationship with the "header-sub" gossipsub topic. -func NewSubscriber(ps *pubsub.PubSub) *Subscriber { - return &Subscriber{ - pubsub: ps, - } -} - -// Start starts the Subscriber, registering a topic validator for the "header-sub" -// topic and joining it. -func (p *Subscriber) Start(context.Context) (err error) { - p.topic, err = p.pubsub.Join(PubSubTopic, pubsub.WithTopicMessageIdFn(msgID)) - return err -} - -// Stop closes the topic and unregisters its validator. -func (p *Subscriber) Stop(context.Context) error { - err := p.pubsub.UnregisterTopicValidator(PubSubTopic) - if err != nil { - log.Warnf("unregistering validator: %s", err) - } - - return p.topic.Close() -} - -// AddValidator applies basic pubsub validator for the topic. -func (p *Subscriber) AddValidator(val header.Validator) error { - pval := func(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - maybeHead, err := header.UnmarshalExtendedHeader(msg.Data) - if err != nil { - log.Errorw("unmarshalling header", - "from", p.ShortString(), - "err", err) - return pubsub.ValidationReject - } - msg.ValidatorData = maybeHead - return val(ctx, maybeHead) - } - return p.pubsub.RegisterTopicValidator(PubSubTopic, pval) -} - -// Subscribe returns a new subscription to the Subscriber's -// topic. -func (p *Subscriber) Subscribe() (header.Subscription, error) { - if p.topic == nil { - return nil, fmt.Errorf("header topic is not instantiated, service must be started before subscribing") - } - - return newSubscription(p.topic) -} - -// Broadcast broadcasts the given ExtendedHeader to the topic. -func (p *Subscriber) Broadcast(ctx context.Context, header *header.ExtendedHeader, opts ...pubsub.PubOpt) error { - bin, err := header.MarshalBinary() - if err != nil { - return err - } - return p.topic.Publish(ctx, bin, opts...) -} - -// msgID computes an id for a pubsub message -// TODO(@Wondertan): This cause additional allocations per each recvd message in the topic. Find a -// way to avoid those. -func msgID(pmsg *pb.Message) string { - mID := func(data []byte) string { - hash := blake2b.Sum256(data) - return string(hash[:]) - } - - h, err := header.UnmarshalExtendedHeader(pmsg.Data) - if err != nil { - // There is nothing we can do about the error, and it will be anyway caught during validation. - // We also *have* to return some ID for the msg, so give the hash of even faulty msg - return mID(pmsg.Data) - } - - // IMPORTANT NOTE: - // Due to the nature of the Tendermint consensus, validators don't necessarily collect commit - // signatures from the entire validator set, but only the minimum required amount of them (>2/3 of - // voting power). In addition, signatures are collected asynchronously. Therefore, each validator - // may have a different set of signatures that pass the minimum required voting power threshold, - // causing nondeterminism in the header message gossiped over the network. Subsequently, this - // causes message duplicates as each Bridge Node, connected to a personal validator, sends the - // validator's own view of commits of effectively the same header. - // To solve the problem above, we exclude nondeterministic value from message id calculation - h.Commit.Signatures = nil - - data, err := header.MarshalExtendedHeader(h) - if err != nil { - // See the note under unmarshalling step - return mID(pmsg.Data) - } - - return mID(data) -} diff --git a/header/p2p/subscription.go b/header/p2p/subscription.go deleted file mode 100644 index a3ac7d1454..0000000000 --- a/header/p2p/subscription.go +++ /dev/null @@ -1,51 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - "reflect" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/celestiaorg/celestia-node/header" -) - -// subscription handles retrieving ExtendedHeaders from the header pubsub topic. -type subscription struct { - subscription *pubsub.Subscription -} - -// newSubscription creates a new ExtendedHeader event subscription -// on the given host. -func newSubscription(topic *pubsub.Topic) (*subscription, error) { - sub, err := topic.Subscribe() - if err != nil { - return nil, err - } - - return &subscription{ - subscription: sub, - }, nil -} - -// NextHeader returns the next (latest) verified ExtendedHeader from the network. -func (s *subscription) NextHeader(ctx context.Context) (*header.ExtendedHeader, error) { - msg, err := s.subscription.Next(ctx) - if err != nil { - return nil, err - } - log.Debugw("received message", "topic", msg.Message.GetTopic(), "sender", msg.ReceivedFrom) - - header, ok := msg.ValidatorData.(*header.ExtendedHeader) - if !ok { - panic(fmt.Sprintf("invalid type received %s", reflect.TypeOf(msg.ValidatorData))) - } - - log.Debugw("received new ExtendedHeader", "height", header.Height, "hash", header.Hash()) - return header, nil -} - -// Cancel cancels the subscription to new ExtendedHeaders from the network. -func (s *subscription) Cancel() { - s.subscription.Cancel() -} diff --git a/header/serde.go b/header/serde.go index 320229b40d..510e46e1fc 100644 --- a/header/serde.go +++ b/header/serde.go @@ -1,7 +1,9 @@ package header import ( + pb "github.com/libp2p/go-libp2p-pubsub/pb" core "github.com/tendermint/tendermint/types" + "golang.org/x/crypto/blake2b" "github.com/celestiaorg/celestia-app/pkg/da" @@ -59,7 +61,7 @@ func UnmarshalExtendedHeader(data []byte) (*ExtendedHeader, error) { return nil, err } - return out, out.ValidateBasic() + return out, out.Validate() } func ExtendedHeaderToProto(eh *ExtendedHeader) (*header_pb.ExtendedHeader, error) { @@ -92,3 +94,39 @@ func ProtoToExtendedHeader(pb *header_pb.ExtendedHeader) (*ExtendedHeader, error } return header, nil } + +// msgID computes an id for a pubsub message +// TODO(@Wondertan): This cause additional allocations per each recvd message in the topic. Find a +// way to avoid those. +func MsgID(pmsg *pb.Message) string { + mID := func(data []byte) string { + hash := blake2b.Sum256(data) + return string(hash[:]) + } + + h, err := UnmarshalExtendedHeader(pmsg.Data) + if err != nil { + // There is nothing we can do about the error, and it will be anyway caught during validation. + // We also *have* to return some ID for the msg, so give the hash of even faulty msg + return mID(pmsg.Data) + } + + // IMPORTANT NOTE: + // Due to the nature of the Tendermint consensus, validators don't necessarily collect commit + // signatures from the entire validator set, but only the minimum required amount of them (>2/3 of + // voting power). In addition, signatures are collected asynchronously. Therefore, each validator + // may have a different set of signatures that pass the minimum required voting power threshold, + // causing nondeterminism in the header message gossiped over the network. Subsequently, this + // causes message duplicates as each Bridge Node, connected to a personal validator, sends the + // validator's own view of commits of effectively the same header. + // To solve the problem above, we exclude nondeterministic value from message id calculation + h.Commit.Signatures = nil + + data, err := MarshalExtendedHeader(h) + if err != nil { + // See the note under unmarshalling step + return mID(pmsg.Data) + } + + return mID(data) +} diff --git a/header/testing.go b/header/testing.go index 03880fcf28..abf2db387b 100644 --- a/header/testing.go +++ b/header/testing.go @@ -6,13 +6,12 @@ package header import ( "context" - "github.com/celestiaorg/celestia-node/share" - mrand "math/rand" "testing" "time" "github.com/ipfs/go-blockservice" + pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bytes" @@ -25,6 +24,9 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/core" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/test" + "github.com/celestiaorg/celestia-node/share" ) // TestSuite provides everything you need to test chain of Headers. @@ -68,7 +70,7 @@ func (s *TestSuite) genesis() *ExtendedHeader { ValidatorSet: s.valSet, DAH: &dah, } - require.NoError(s.t, eh.ValidateBasic()) + require.NoError(s.t, eh.Validate()) return eh } @@ -87,6 +89,12 @@ func (s *TestSuite) GenExtendedHeaders(num int) []*ExtendedHeader { return headers } +func (s *TestSuite) GetRandomHeader() *ExtendedHeader { + return s.GenExtendedHeader() +} + +var _ test.Generator[*ExtendedHeader] = &TestSuite{} + func (s *TestSuite) GenExtendedHeader() *ExtendedHeader { if s.head == nil { s.head = s.genesis() @@ -94,26 +102,26 @@ func (s *TestSuite) GenExtendedHeader() *ExtendedHeader { } dah := da.MinDataAvailabilityHeader() - height := s.Head().Height + 1 - rh := s.GenRawHeader(height, s.Head().Hash(), s.Head().Commit.Hash(), dah.Hash()) + height := s.Head().Height() + 1 + rh := s.GenRawHeader(height, s.Head().Hash(), libhead.Hash(s.Head().Commit.Hash()), dah.Hash()) s.head = &ExtendedHeader{ RawHeader: *rh, Commit: s.Commit(rh), ValidatorSet: s.valSet, DAH: &dah, } - require.NoError(s.t, s.head.ValidateBasic()) + require.NoError(s.t, s.head.Validate()) return s.head } func (s *TestSuite) GenRawHeader( - height int64, lastHeader, lastCommit, dataHash bytes.HexBytes) *RawHeader { + height int64, lastHeader, lastCommit, dataHash libhead.Hash) *RawHeader { rh := RandRawHeader(s.t) rh.Height = height rh.Time = time.Now() - rh.LastBlockID = types.BlockID{Hash: lastHeader} - rh.LastCommitHash = lastCommit - rh.DataHash = dataHash + rh.LastBlockID = types.BlockID{Hash: bytes.HexBytes(lastHeader)} + rh.LastCommitHash = bytes.HexBytes(lastCommit) + rh.DataHash = bytes.HexBytes(dataHash) rh.ValidatorsHash = s.valSet.Hash() rh.NextValidatorsHash = s.valSet.Hash() rh.ProposerAddress = s.nextProposer().Address @@ -250,11 +258,11 @@ type DummySubscriber struct { Headers []*ExtendedHeader } -func (mhs *DummySubscriber) AddValidator(Validator) error { +func (mhs *DummySubscriber) AddValidator(func(context.Context, *ExtendedHeader) pubsub.ValidationResult) error { return nil } -func (mhs *DummySubscriber) Subscribe() (Subscription, error) { +func (mhs *DummySubscriber) Subscribe() (libhead.Subscription[*ExtendedHeader], error) { return mhs, nil } diff --git a/header/verify.go b/header/verify.go index a753dd11d3..e9a12e2a98 100644 --- a/header/verify.go +++ b/header/verify.go @@ -6,54 +6,53 @@ import ( "time" "github.com/tendermint/tendermint/light" + + libhead "github.com/celestiaorg/celestia-node/libs/header" ) // TODO(@Wondertan): We should request TrustingPeriod from the network's state params or // listen for network params changes to always have a topical value. -// IsExpired checks if header is expired against trusting period. -func (eh *ExtendedHeader) IsExpired(period time.Duration) bool { - expirationTime := eh.Time.Add(period) - return !expirationTime.After(time.Now()) -} - -// IsRecent checks if header is recent against the given blockTime. -func (eh *ExtendedHeader) IsRecent(blockTime time.Duration) bool { - return time.Since(eh.Time) <= blockTime // TODO @renaynay: should we allow for a 5-10 block drift here? -} - // VerifyNonAdjacent validates non-adjacent untrusted header against trusted 'eh'. -func (eh *ExtendedHeader) VerifyNonAdjacent(untrst *ExtendedHeader) error { +func (eh *ExtendedHeader) VerifyNonAdjacent(untrusted libhead.Header) error { + untrst, ok := untrusted.(*ExtendedHeader) + if !ok { + return &libhead.VerifyError{Reason: fmt.Errorf("invalid header type: expected %T, got %T", eh, untrusted)} + } if err := eh.verify(untrst); err != nil { - return &VerifyError{Reason: err} + return &libhead.VerifyError{Reason: err} } // Ensure that untrusted commit has enough of trusted commit's power. - err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID, untrst.Commit, light.DefaultTrustLevel) + err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel) if err != nil { - return &VerifyError{err} + return &libhead.VerifyError{Reason: err} } return nil } // VerifyAdjacent validates adjacent untrusted header against trusted 'eh'. -func (eh *ExtendedHeader) VerifyAdjacent(untrst *ExtendedHeader) error { - if untrst.Height != eh.Height+1 { - return &ErrNonAdjacent{ - Head: eh.Height, - Attempted: untrst.Height, +func (eh *ExtendedHeader) VerifyAdjacent(untrusted libhead.Header) error { + untrst, ok := untrusted.(*ExtendedHeader) + if !ok { + return &libhead.VerifyError{Reason: fmt.Errorf("invalid header type: expected %T, got %T", eh, untrusted)} + } + if untrst.Height() != eh.Height()+1 { + return &libhead.ErrNonAdjacent{ + Head: eh.Height(), + Attempted: untrst.Height(), } } if err := eh.verify(untrst); err != nil { - return &VerifyError{Reason: err} + return &libhead.VerifyError{Reason: err} } // Check the validator hashes are the same if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) { - return &VerifyError{ - fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", + return &libhead.VerifyError{ + Reason: fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", eh.NextValidatorsHash, untrst.ValidatorsHash, ), @@ -69,28 +68,19 @@ var clockDrift = 10 * time.Second // verify performs basic verification of untrusted header. func (eh *ExtendedHeader) verify(untrst *ExtendedHeader) error { - if untrst.ChainID != eh.ChainID { - return fmt.Errorf("new untrusted header has different chain %s, not %s", untrst.ChainID, eh.ChainID) + if untrst.ChainID() != eh.ChainID() { + return fmt.Errorf("new untrusted header has different chain %s, not %s", untrst.ChainID(), eh.ChainID()) } - if !untrst.Time.After(eh.Time) { - return fmt.Errorf("expected new untrusted header time %v to be after old header time %v", untrst.Time, eh.Time) + if !untrst.Time().After(eh.Time()) { + return fmt.Errorf("expected new untrusted header time %v to be after old header time %v", untrst.Time(), eh.Time()) } now := time.Now() - if !untrst.Time.Before(now.Add(clockDrift)) { + if !untrst.Time().Before(now.Add(clockDrift)) { return fmt.Errorf( - "new untrusted header has a time from the future %v (now: %v, clockDrift: %v)", untrst.Time, now, clockDrift) + "new untrusted header has a time from the future %v (now: %v, clockDrift: %v)", untrst.Time(), now, clockDrift) } return nil } - -// VerifyError is thrown on during VerifyAdjacent and VerifyNonAdjacent if verification fails. -type VerifyError struct { - Reason error -} - -func (vr *VerifyError) Error() string { - return fmt.Sprintf("header: verify: %s", vr.Reason.Error()) -} diff --git a/header/verify_test.go b/header/verify_test.go index 499624bb1d..b735076628 100644 --- a/header/verify_test.go +++ b/header/verify_test.go @@ -29,25 +29,25 @@ func TestVerifyAdjacent(t *testing.T) { }, { prepare: func() { - untrusted.Time = untrusted.Time.Add(time.Minute) + untrusted.RawHeader.Time = untrusted.RawHeader.Time.Add(time.Minute) }, err: true, }, { prepare: func() { - untrusted.Time = untrusted.Time.Truncate(time.Hour) + untrusted.RawHeader.Time = untrusted.RawHeader.Time.Truncate(time.Hour) }, err: true, }, { prepare: func() { - untrusted.ChainID = "toaster" + untrusted.RawHeader.ChainID = "toaster" }, err: true, }, { prepare: func() { - untrusted.Height++ + untrusted.RawHeader.Height++ }, err: true, }, diff --git a/libs/header/doc.go b/libs/header/doc.go new file mode 100644 index 0000000000..a2505a5c8a --- /dev/null +++ b/libs/header/doc.go @@ -0,0 +1,10 @@ +/* +Package header contains all services related to generating, requesting, syncing and storing Headers. + +There are 4 main components in the header package: + 1. p2p.Subscriber listens for new Headers from the P2P network (via the HeaderSub) + 2. p2p.Exchange request Headers from other nodes + 3. Syncer manages syncing of past and recent Headers from either the P2P network + 4. Store manages storing Headers and making them available for access by other dependent services. +*/ +package header diff --git a/libs/header/errors.go b/libs/header/errors.go new file mode 100644 index 0000000000..6f00876a93 --- /dev/null +++ b/libs/header/errors.go @@ -0,0 +1,12 @@ +package header + +import "fmt" + +// VerifyError is thrown on during VerifyAdjacent and VerifyNonAdjacent if verification fails. +type VerifyError struct { + Reason error +} + +func (vr *VerifyError) Error() string { + return fmt.Sprintf("header: verify: %s", vr.Reason.Error()) +} diff --git a/libs/header/hash.go b/libs/header/hash.go new file mode 100644 index 0000000000..156cd2bb05 --- /dev/null +++ b/libs/header/hash.go @@ -0,0 +1,38 @@ +package header + +import ( + "encoding/hex" + "fmt" + "strings" +) + +// Hash represents cryptographic hash and provides basic serialization functions. +type Hash []byte + +// String implements fmt.Stringer interface. +func (h Hash) String() string { + return strings.ToUpper(hex.EncodeToString(h)) +} + +// MarshalJSON serializes Hash into valid JSON. +func (h Hash) MarshalJSON() ([]byte, error) { + s := strings.ToUpper(hex.EncodeToString(h)) + jbz := make([]byte, len(s)+2) + jbz[0] = '"' + copy(jbz[1:], s) + jbz[len(jbz)-1] = '"' + return jbz, nil +} + +// UnmarshalJSON deserializes JSON representation of a Hash into object. +func (h *Hash) UnmarshalJSON(data []byte) error { + if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' { + return fmt.Errorf("invalid hex string: %s", data) + } + bz2, err := hex.DecodeString(string(data[1 : len(data)-1])) + if err != nil { + return err + } + *h = bz2 + return nil +} diff --git a/libs/header/header.go b/libs/header/header.go new file mode 100644 index 0000000000..72d1698f4d --- /dev/null +++ b/libs/header/header.go @@ -0,0 +1,33 @@ +package header + +import ( + "encoding" + "time" +) + +// Header abstracts all methods required to perform header sync. +type Header interface { + // New creates new instance of a header. + New() Header + // IsZero reports whether Header is a zero value of it's concrete type. + IsZero() bool + // ChainID returns identifier of the chain (ChainID). + ChainID() string + // Hash returns hash of a header. + Hash() Hash + // Height returns the height of a header. + Height() int64 + // LastHeader returns the hash of last header before this header (aka. previous header hash). + LastHeader() Hash + // Time returns time when header was created. + Time() time.Time + // VerifyAdjacent validates adjacent untrusted header against trusted header. + VerifyAdjacent(Header) error + // VerifyNonAdjacent validates non-adjacent untrusted header against trusted header. + VerifyNonAdjacent(Header) error + // Validate performs basic validation to check for missed/incorrect fields. + Validate() error + + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler +} diff --git a/header/interface.go b/libs/header/interface.go similarity index 52% rename from header/interface.go rename to libs/header/interface.go index 897e5aec93..387159d92c 100644 --- a/header/interface.go +++ b/libs/header/interface.go @@ -5,57 +5,42 @@ import ( "errors" "fmt" - "github.com/ipfs/go-blockservice" pubsub "github.com/libp2p/go-libp2p-pubsub" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - core "github.com/tendermint/tendermint/types" ) -// ConstructFn aliases a function that creates an ExtendedHeader. -type ConstructFn = func( - context.Context, - *core.Block, - *core.Commit, - *core.ValidatorSet, - blockservice.BlockService, -) (*ExtendedHeader, error) - -// Validator aliases a func that validates ExtendedHeader. -type Validator = func(context.Context, *ExtendedHeader) pubsub.ValidationResult - // Subscriber encompasses the behavior necessary to -// subscribe/unsubscribe from new ExtendedHeader events from the +// subscribe/unsubscribe from new Header events from the // network. -type Subscriber interface { - // Subscribe creates long-living Subscription for validated ExtendedHeaders. +type Subscriber[H Header] interface { + // Subscribe creates long-living Subscription for validated Headers. // Multiple Subscriptions can be created. - Subscribe() (Subscription, error) + Subscribe() (Subscription[H], error) // AddValidator registers a Validator for all Subscriptions. - // Registered Validators screen ExtendedHeaders for their validity + // Registered Validators screen Headers for their validity // before they are sent through Subscriptions. // Multiple validators can be registered. - AddValidator(Validator) error + AddValidator(func(context.Context, H) pubsub.ValidationResult) error } -// Subscription can retrieve the next ExtendedHeader from the +// Subscription can retrieve the next Header from the // network. -type Subscription interface { - // NextHeader returns the newest verified and valid ExtendedHeader +type Subscription[H Header] interface { + // NextHeader returns the newest verified and valid Header // in the network. - NextHeader(ctx context.Context) (*ExtendedHeader, error) + NextHeader(ctx context.Context) (H, error) // Cancel cancels the subscription. Cancel() } -// Broadcaster broadcasts an ExtendedHeader to the network. -type Broadcaster interface { - Broadcast(ctx context.Context, header *ExtendedHeader, opts ...pubsub.PubOpt) error +// Broadcaster broadcasts a Header to the network. +type Broadcaster[H Header] interface { + Broadcast(ctx context.Context, header H, opts ...pubsub.PubOpt) error } -// Exchange encompasses the behavior necessary to request ExtendedHeaders +// Exchange encompasses the behavior necessary to request Headers // from the network. -type Exchange interface { - Getter +type Exchange[H Header] interface { + Getter[H] } var ( @@ -80,9 +65,9 @@ func (ena *ErrNonAdjacent) Error() string { return fmt.Sprintf("header/store: non-adjacent: head %d, attempted %d", ena.Head, ena.Attempted) } -// Store encompasses the behavior necessary to store and retrieve ExtendedHeaders +// Store encompasses the behavior necessary to store and retrieve Headers // from a node's local storage. -type Store interface { +type Store[H Header] interface { // Start starts the store. Start(context.Context) error @@ -91,48 +76,48 @@ type Store interface { Stop(context.Context) error // Getter encompasses all getter methods for headers. - Getter + Getter[H] // Init initializes Store with the given head, meaning it is initialized with the genesis header. - Init(context.Context, *ExtendedHeader) error + Init(context.Context, H) error // Height reports current height of the chain head. Height() uint64 - // Has checks whether ExtendedHeader is already stored. - Has(context.Context, tmbytes.HexBytes) (bool, error) + // Has checks whether Header is already stored. + Has(context.Context, Hash) (bool, error) - // Append stores and verifies the given ExtendedHeader(s). + // Append stores and verifies the given Header(s). // It requires them to be adjacent and in ascending order, // as it applies them contiguously on top of the current head height. // It returns the amount of successfully applied headers, // so caller can understand what given header was invalid, if any. - Append(context.Context, ...*ExtendedHeader) (int, error) + Append(context.Context, ...H) (int, error) } // Getter contains the behavior necessary for a component to retrieve // headers that have been processed during header sync. -type Getter interface { - Head +type Getter[H Header] interface { + Head[H] - // Get returns the ExtendedHeader corresponding to the given hash. - Get(context.Context, tmbytes.HexBytes) (*ExtendedHeader, error) + // Get returns the Header corresponding to the given hash. + Get(context.Context, Hash) (H, error) - // GetByHeight returns the ExtendedHeader corresponding to the given block height. - GetByHeight(context.Context, uint64) (*ExtendedHeader, error) + // GetByHeight returns the Header corresponding to the given block height. + GetByHeight(context.Context, uint64) (H, error) - // GetRangeByHeight returns the given range of ExtendedHeaders. - GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*ExtendedHeader, error) + // GetRangeByHeight returns the given range of Headers. + GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) - // GetVerifiedRange requests the header range from the provided ExtendedHeader and + // GetVerifiedRange requests the header range from the provided Header and // verifies that the returned headers are adjacent to each other. - GetVerifiedRange(ctx context.Context, from *ExtendedHeader, amount uint64) ([]*ExtendedHeader, error) + GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) } // Head contains the behavior necessary for a component to retrieve // the chain head. Note that "chain head" is subjective to the component // reporting it. -type Head interface { +type Head[H Header] interface { // Head returns the latest known header. - Head(context.Context) (*ExtendedHeader, error) + Head(context.Context) (H, error) } diff --git a/libs/header/local/exchange.go b/libs/header/local/exchange.go new file mode 100644 index 0000000000..a2aa0e3eb4 --- /dev/null +++ b/libs/header/local/exchange.go @@ -0,0 +1,51 @@ +package local + +import ( + "context" + + "github.com/celestiaorg/celestia-node/libs/header" +) + +// Exchange is a simple Exchange that reads Headers from Store without any networking. +type Exchange[H header.Header] struct { + store header.Store[H] +} + +// NewExchange creates a new local Exchange. +func NewExchange[H header.Header](store header.Store[H]) header.Exchange[H] { + return &Exchange[H]{ + store: store, + } +} + +func (l *Exchange[H]) Start(context.Context) error { + return nil +} + +func (l *Exchange[H]) Stop(context.Context) error { + return nil +} + +func (l *Exchange[H]) Head(ctx context.Context) (H, error) { + return l.store.Head(ctx) +} + +func (l *Exchange[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { + return l.store.GetByHeight(ctx, height) +} + +func (l *Exchange[H]) GetRangeByHeight(ctx context.Context, origin, amount uint64) ([]H, error) { + if amount == 0 { + return nil, nil + } + return l.store.GetRangeByHeight(ctx, origin, origin+amount) +} + +func (l *Exchange[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64, +) ([]H, error) { + return l.store.GetVerifiedRange(ctx, from, uint64(from.Height())+amount) +} + +func (l *Exchange[H]) Get(ctx context.Context, hash header.Hash) (H, error) { + return l.store.Get(ctx, hash) +} diff --git a/libs/header/mocks/store.go b/libs/header/mocks/store.go new file mode 100644 index 0000000000..a6227a60c9 --- /dev/null +++ b/libs/header/mocks/store.go @@ -0,0 +1,98 @@ +package mocks + +import ( + "bytes" + "context" + "testing" + + "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/test" +) + +type MockStore[H header.Header] struct { + Headers map[int64]H + HeadHeight int64 +} + +// NewStore creates a mock store and adds several random +// headers +func NewStore[H header.Header](t *testing.T, gen test.Generator[H], numHeaders int) *MockStore[H] { + store := &MockStore[H]{ + Headers: make(map[int64]H), + HeadHeight: 0, + } + + for i := 0; i < numHeaders; i++ { + header := gen.GetRandomHeader() + store.Headers[header.Height()] = header + + if header.Height() > store.HeadHeight { + store.HeadHeight = header.Height() + } + } + return store +} + +func (m *MockStore[H]) Init(context.Context, H) error { return nil } +func (m *MockStore[H]) Start(context.Context) error { return nil } +func (m *MockStore[H]) Stop(context.Context) error { return nil } + +func (m *MockStore[H]) Height() uint64 { + return uint64(m.HeadHeight) +} + +func (m *MockStore[H]) Head(context.Context) (H, error) { + return m.Headers[m.HeadHeight], nil +} + +func (m *MockStore[H]) Get(ctx context.Context, hash header.Hash) (H, error) { + for _, header := range m.Headers { + if bytes.Equal(header.Hash(), hash) { + return header, nil + } + } + var zero H + return zero, header.ErrNotFound +} + +func (m *MockStore[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { + return m.Headers[int64(height)], nil +} + +func (m *MockStore[H]) GetRangeByHeight(ctx context.Context, from, to uint64) ([]H, error) { + headers := make([]H, to-from) + // As the requested range is [from; to), + // check that (to-1) height in request is less than + // the biggest header height in store. + if to-1 > m.Height() { + return nil, header.ErrNotFound + } + for i := range headers { + headers[i] = m.Headers[int64(from)] + from++ + } + return headers, nil +} + +func (m *MockStore[H]) GetVerifiedRange( + ctx context.Context, + h H, + to uint64, +) ([]H, error) { + return m.GetRangeByHeight(ctx, uint64(h.Height())+1, to) +} + +func (m *MockStore[H]) Has(context.Context, header.Hash) (bool, error) { + return false, nil +} + +func (m *MockStore[H]) Append(ctx context.Context, headers ...H) (int, error) { + for _, header := range headers { + m.Headers[header.Height()] = header + // set head + if header.Height() > m.HeadHeight { + m.HeadHeight = header.Height() + } + } + return len(headers), nil +} diff --git a/header/p2p/exchange.go b/libs/header/p2p/exchange.go similarity index 54% rename from header/p2p/exchange.go rename to libs/header/p2p/exchange.go index 4bdc4a35b9..5ea9b6379d 100644 --- a/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -12,21 +12,20 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/p2p/net/conngater" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/celestia-node/header" - p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" + "github.com/celestiaorg/celestia-node/libs/header" + p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) var log = logging.Logger("header/p2p") -// PubSubTopic hardcodes the name of the ExtendedHeader +// PubSubTopic hardcodes the name of the Header // gossipsub topic. const PubSubTopic = "header-sub" -// Exchange enables sending outbound ExtendedHeaderRequests to the network as well as -// handling inbound ExtendedHeaderRequests from the network. -type Exchange struct { +// Exchange enables sending outbound HeaderRequests to the network as well as +// handling inbound HeaderRequests from the network. +type Exchange[H header.Header] struct { ctx context.Context cancel context.CancelFunc @@ -39,13 +38,13 @@ type Exchange struct { Params ClientParameters } -func NewExchange( +func NewExchange[H header.Header]( host host.Host, peers peer.IDSlice, protocolSuffix string, connGater *conngater.BasicConnectionGater, opts ...Option[ClientParameters], -) (*Exchange, error) { +) (*Exchange[H], error) { params := DefaultClientParameters() for _, opt := range opts { opt(¶ms) @@ -56,7 +55,7 @@ func NewExchange( return nil, err } - return &Exchange{ + return &Exchange[H]{ host: host, protocolID: protocolID(protocolSuffix), trustedPeers: peers, @@ -71,7 +70,7 @@ func NewExchange( }, nil } -func (ex *Exchange) Start(context.Context) error { +func (ex *Exchange[H]) Start(context.Context) error { ex.ctx, ex.cancel = context.WithCancel(context.Background()) for _, p := range ex.trustedPeers { @@ -85,7 +84,7 @@ func (ex *Exchange) Start(context.Context) error { return nil } -func (ex *Exchange) Stop(context.Context) error { +func (ex *Exchange[H]) Stop(context.Context) error { // cancel the session if it exists ex.cancel() // stop the peerTracker @@ -93,124 +92,132 @@ func (ex *Exchange) Stop(context.Context) error { return nil } -// Head requests the latest ExtendedHeader. Note that the ExtendedHeader -// must be verified thereafter. +// Head requests the latest Header. Note that the Header must be verified thereafter. // NOTE: // It is fine to continue handling head request if the timeout will be reached. // As we are requesting head from multiple trusted peers, // we may already have some headers when the timeout will be reached. -func (ex *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (ex *Exchange[H]) Head(ctx context.Context) (H, error) { log.Debug("requesting head") // create request - req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: uint64(0)}, + req := &p2p_pb.HeaderRequest{ + Data: &p2p_pb.HeaderRequest_Origin{Origin: uint64(0)}, Amount: 1, } - headerCh := make(chan *header.ExtendedHeader) + var ( + zero H + headerCh = make(chan H) + ) + // request head from each trusted peer for _, from := range ex.trustedPeers { go func(from peer.ID) { headers, err := ex.request(ctx, from, req) if err != nil { log.Errorw("head request to trusted peer failed", "trustedPeer", from, "err", err) - headerCh <- nil + var zero H + headerCh <- zero return } - // doRequest ensures that the result slice will have at least one ExtendedHeader + // doRequest ensures that the result slice will have at least one Header headerCh <- headers[0] }(from) } - result := make([]*header.ExtendedHeader, 0, len(ex.trustedPeers)) + result := make([]H, 0, len(ex.trustedPeers)) LOOP: for range ex.trustedPeers { select { case h := <-headerCh: - if h != nil { + if !h.IsZero() { result = append(result, h) } case <-ctx.Done(): break LOOP case <-ex.ctx.Done(): - return nil, ctx.Err() + return zero, ctx.Err() } } - return bestHead(result, ex.Params.MinResponses) + return bestHead[H](result, ex.Params.MinResponses) } -// GetByHeight performs a request for the ExtendedHeader at the given -// height to the network. Note that the ExtendedHeader must be verified +// GetByHeight performs a request for the Header at the given +// height to the network. Note that the Header must be verified // thereafter. -func (ex *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { +func (ex *Exchange[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { log.Debugw("requesting header", "height", height) + var zero H // sanity check height if height == 0 { - return nil, fmt.Errorf("specified request height must be greater than 0") + return zero, fmt.Errorf("specified request height must be greater than 0") } // create request - req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: height}, + req := &p2p_pb.HeaderRequest{ + Data: &p2p_pb.HeaderRequest_Origin{Origin: height}, Amount: 1, } headers, err := ex.performRequest(ctx, req) if err != nil { - return nil, err + return zero, err } return headers[0], nil } -// GetRangeByHeight performs a request for the given range of ExtendedHeaders -// to the network. Note that the ExtendedHeaders must be verified thereafter. -func (ex *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { +// GetRangeByHeight performs a request for the given range of Headers +// to the network. Note that the Headers must be verified thereafter. +func (ex *Exchange[H]) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) { if amount > ex.Params.MaxRequestSize { return nil, header.ErrHeadersLimitExceeded } - session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout) + session := newSession[H](ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout) defer session.close() return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRequest) } -// GetVerifiedRange performs a request for the given range of ExtendedHeaders to the network and +// GetVerifiedRange performs a request for the given range of Headers to the network and // ensures that returned headers are correct against the passed one. -func (ex *Exchange) GetVerifiedRange( +func (ex *Exchange[H]) GetVerifiedRange( ctx context.Context, - from *header.ExtendedHeader, + from H, amount uint64, -) ([]*header.ExtendedHeader, error) { - session := newSession(ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout, withValidation(from)) +) ([]H, error) { + session := newSession[H]( + ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout, withValidation(from), + ) defer session.close() - return session.getRangeByHeight(ctx, uint64(from.Height)+1, amount, ex.Params.MaxHeadersPerRequest) + return session.getRangeByHeight(ctx, uint64(from.Height())+1, amount, ex.Params.MaxHeadersPerRequest) } -// Get performs a request for the ExtendedHeader by the given hash corresponding -// to the RawHeader. Note that the ExtendedHeader must be verified thereafter. -func (ex *Exchange) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { +// Get performs a request for the Header by the given hash corresponding +// to the RawHeader. Note that the Header must be verified thereafter. +func (ex *Exchange[H]) Get(ctx context.Context, hash header.Hash) (H, error) { log.Debugw("requesting header", "hash", hash.String()) + var zero H // create request - req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: hash.Bytes()}, + req := &p2p_pb.HeaderRequest{ + Data: &p2p_pb.HeaderRequest_Hash{Hash: hash}, Amount: 1, } headers, err := ex.performRequest(ctx, req) if err != nil { - return nil, err + return zero, err } - if !bytes.Equal(headers[0].Hash().Bytes(), hash) { - return nil, fmt.Errorf("incorrect hash in header: expected %x, got %x", hash, headers[0].Hash().Bytes()) + if !bytes.Equal(headers[0].Hash(), hash) { + return zero, fmt.Errorf("incorrect hash in header: expected %x, got %x", hash, headers[0].Hash()) } return headers[0], nil } -func (ex *Exchange) performRequest( +func (ex *Exchange[H]) performRequest( ctx context.Context, - req *p2p_pb.ExtendedHeaderRequest, -) ([]*header.ExtendedHeader, error) { + req *p2p_pb.HeaderRequest, +) ([]H, error) { if req.Amount == 0 { - return make([]*header.ExtendedHeader, 0), nil + return make([]H, 0), nil } if len(ex.trustedPeers) == 0 { @@ -222,12 +229,12 @@ func (ex *Exchange) performRequest( return ex.request(ctx, ex.trustedPeers[index], req) } -// request sends the ExtendedHeaderRequest to a remote peer. -func (ex *Exchange) request( +// request sends the HeaderRequest to a remote peer. +func (ex *Exchange[H]) request( ctx context.Context, to peer.ID, - req *p2p_pb.ExtendedHeaderRequest, -) ([]*header.ExtendedHeader, error) { + req *p2p_pb.HeaderRequest, +) ([]H, error) { log.Debugw("requesting peer", "peer", to) responses, _, _, err := sendMessage(ctx, ex.host, to, ex.protocolID, req) if err != nil { @@ -237,40 +244,43 @@ func (ex *Exchange) request( if len(responses) == 0 { return nil, header.ErrNotFound } - headers := make([]*header.ExtendedHeader, 0, len(responses)) + headers := make([]H, 0, len(responses)) for _, response := range responses { if err = convertStatusCodeToError(response.StatusCode); err != nil { return nil, err } - header, err := header.UnmarshalExtendedHeader(response.Body) + var empty H + header := empty.New() + err := header.UnmarshalBinary(response.Body) if err != nil { return nil, err } - headers = append(headers, header) + headers = append(headers, header.(H)) } return headers, nil } -// bestHead chooses ExtendedHeader that matches the conditions: +// bestHead chooses Header that matches the conditions: // * should have max height among received; // * should be received at least from 2 peers; -// If neither condition is met, then latest ExtendedHeader will be returned (header of the highest +// If neither condition is met, then latest Header will be returned (header of the highest // height). -func bestHead(result []*header.ExtendedHeader, minResponses int) (*header.ExtendedHeader, error) { +func bestHead[H header.Header](result []H, minResponses int) (H, error) { if len(result) == 0 { - return nil, header.ErrNotFound + var zero H + return zero, header.ErrNotFound } counter := make(map[string]int) - // go through all of ExtendedHeaders and count the number of headers with a specific hash + // go through all of Headers and count the number of headers with a specific hash for _, res := range result { counter[res.Hash().String()]++ } // sort results in a decreasing order sort.Slice(result, func(i, j int) bool { - return result[i].Height > result[j].Height + return result[i].Height() > result[j].Height() }) - // try to find ExtendedHeader with the maximum height that was received at least from 2 peers + // try to find Header with the maximum height that was received at least from 2 peers for _, res := range result { if counter[res.Hash().String()] >= minResponses { return res, nil diff --git a/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go similarity index 78% rename from header/p2p/exchange_test.go rename to libs/header/p2p/exchange_test.go index 3458bd42ed..430cbd2a0f 100644 --- a/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -15,15 +15,15 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/go-libp2p-messenger/serde" swarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" - "github.com/celestiaorg/celestia-node/header" - headerMock "github.com/celestiaorg/celestia-node/header/mocks" - p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" + "github.com/celestiaorg/celestia-node/libs/header" + headerMock "github.com/celestiaorg/celestia-node/libs/header/mocks" + p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" + "github.com/celestiaorg/celestia-node/libs/header/test" ) var privateProtocolID = protocolID("private") @@ -35,7 +35,7 @@ func TestExchange_RequestHead(t *testing.T) { header, err := exchg.Head(context.Background()) require.NoError(t, err) - assert.Equal(t, store.Headers[store.HeadHeight].Height, header.Height) + assert.Equal(t, store.Headers[store.HeadHeight].Height(), header.Height()) assert.Equal(t, store.Headers[store.HeadHeight].Hash(), header.Hash()) } @@ -45,7 +45,7 @@ func TestExchange_RequestHeader(t *testing.T) { // perform expected request header, err := exchg.GetByHeight(context.Background(), 5) require.NoError(t, err) - assert.Equal(t, store.Headers[5].Height, header.Height) + assert.Equal(t, store.Headers[5].Height(), header.Height()) assert.Equal(t, store.Headers[5].Hash(), header.Hash()) } @@ -56,8 +56,8 @@ func TestExchange_RequestHeaders(t *testing.T) { gotHeaders, err := exchg.GetRangeByHeight(context.Background(), 1, 5) require.NoError(t, err) for _, got := range gotHeaders { - assert.Equal(t, store.Headers[got.Height].Height, got.Height) - assert.Equal(t, store.Headers[got.Height].Hash(), got.Hash()) + assert.Equal(t, store.Headers[got.Height()].Height(), got.Height()) + assert.Equal(t, store.Headers[got.Height()].Hash(), got.Hash()) } } @@ -93,19 +93,20 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { // create mocknet with 5 peers hosts := createMocknet(t, 5) totalAmount := 80 - store := headerMock.NewStore(t, totalAmount) + store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), totalAmount) protocolSuffix := "private" connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) // create new exchange - exchange, err := NewExchange(hosts[len(hosts)-1], []peer.ID{}, protocolSuffix, connGater) + exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{}, protocolSuffix, connGater) require.NoError(t, err) exchange.Params.MaxHeadersPerRequest = 10 exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) t.Cleanup(exchange.cancel) - servers := make([]*ExchangeServer, len(hosts)-1) // amount of servers is len(hosts)-1 because one peer acts as a client + // amount of servers is len(hosts)-1 because one peer acts as a client + servers := make([]*ExchangeServer[*test.DummyHeader], len(hosts)-1) for index := range servers { - servers[index], err = NewExchangeServer(hosts[index], store, protocolSuffix) + servers[index], err = NewExchangeServer[*test.DummyHeader](hosts[index], store, protocolSuffix) require.NoError(t, err) servers[index].Start(context.Background()) //nolint:errcheck exchange.peerTracker.peerLk.Lock() @@ -138,7 +139,9 @@ func TestExchange_RequestHeadersFromAnotherPeer(t *testing.T) { // create client + server(it does not have needed headers) exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) // create one more server(with more headers in the store) - serverSideEx, err := NewExchangeServer(hosts[2], headerMock.NewStore(t, 10), "private") + serverSideEx, err := NewExchangeServer[*test.DummyHeader]( + hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), "private", + ) require.NoError(t, err) require.NoError(t, serverSideEx.Start(context.Background())) t.Cleanup(func() { @@ -155,7 +158,7 @@ func TestExchange_RequestHeadersFromAnotherPeer(t *testing.T) { } // TestExchange_RequestByHash tests that the Exchange instance can -// respond to an ExtendedHeaderRequest for a hash instead of a height. +// respond to an HeaderRequest for a hash instead of a height. func TestExchange_RequestByHash(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -165,8 +168,8 @@ func TestExchange_RequestByHash(t *testing.T) { // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] // create and start the ExchangeServer - store := headerMock.NewStore(t, 5) - serv, err := NewExchangeServer(host, store, "private") + store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) + serv, err := NewExchangeServer[*test.DummyHeader](host, store, "private") require.NoError(t, err) err = serv.Start(ctx) require.NoError(t, err) @@ -179,37 +182,38 @@ func TestExchange_RequestByHash(t *testing.T) { require.NoError(t, err) // create request for a header at a random height reqHeight := store.HeadHeight - 2 - req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: store.Headers[reqHeight].Hash()}, + req := &p2p_pb.HeaderRequest{ + Data: &p2p_pb.HeaderRequest_Hash{Hash: store.Headers[reqHeight].Hash()}, Amount: 1, } // send request _, err = serde.Write(stream, req) require.NoError(t, err) // read resp - resp := new(p2p_pb.ExtendedHeaderResponse) + resp := new(p2p_pb.HeaderResponse) _, err = serde.Read(stream, resp) require.NoError(t, err) // compare - eh, err := header.UnmarshalExtendedHeader(resp.Body) + var eh test.DummyHeader + err = eh.UnmarshalBinary(resp.Body) require.NoError(t, err) - assert.Equal(t, store.Headers[reqHeight].Height, eh.Height) + assert.Equal(t, store.Headers[reqHeight].Height(), eh.Height()) assert.Equal(t, store.Headers[reqHeight].Hash(), eh.Hash()) } func Test_bestHead(t *testing.T) { params := DefaultClientParameters() - gen := func() []*header.ExtendedHeader { - suite := header.NewTestSuite(t, 3) - res := make([]*header.ExtendedHeader, 0) + gen := func() []*test.DummyHeader { + suite := test.NewTestSuite(t) + res := make([]*test.DummyHeader, 0) for i := 0; i < 3; i++ { - res = append(res, suite.GenExtendedHeader()) + res = append(res, suite.GetRandomHeader()) } return res } testCases := []struct { - precondition func() []*header.ExtendedHeader + precondition func() []*test.DummyHeader expectedHeight int64 }{ /* @@ -231,7 +235,7 @@ func Test_bestHead(t *testing.T) { result -> headerHeight[0] */ { - precondition: func() []*header.ExtendedHeader { + precondition: func() []*test.DummyHeader { res := gen() res = append(res, res[0]) return res @@ -246,7 +250,7 @@ func Test_bestHead(t *testing.T) { result -> headerHeight[1] */ { - precondition: func() []*header.ExtendedHeader { + precondition: func() []*test.DummyHeader { res := gen() res = append(res, res[0]) res = append(res, res[0]) @@ -260,7 +264,7 @@ func Test_bestHead(t *testing.T) { res := tt.precondition() header, err := bestHead(res, params.MinResponses) require.NoError(t, err) - require.True(t, header.Height == tt.expectedHeight) + require.True(t, header.Height() == tt.expectedHeight) } } @@ -274,7 +278,9 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.NoError(t, err) // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] - serv, err := NewExchangeServer(host, headerMock.NewStore(t, 0), "private") + serv, err := NewExchangeServer[*test.DummyHeader]( + host, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 0), "private", + ) require.NoError(t, err) err = serv.Start(ctx) require.NoError(t, err) @@ -284,15 +290,15 @@ func TestExchange_RequestByHashFails(t *testing.T) { stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, privateProtocolID) require.NoError(t, err) - req := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Hash{Hash: []byte("dummy_hash")}, + req := &p2p_pb.HeaderRequest{ + Data: &p2p_pb.HeaderRequest_Hash{Hash: []byte("dummy_hash")}, Amount: 1, } // send request _, err = serde.Write(stream, req) require.NoError(t, err) // read resp - resp := new(p2p_pb.ExtendedHeaderResponse) + resp := new(p2p_pb.HeaderResponse) _, err = serde.Read(stream, resp) require.NoError(t, err) require.Equal(t, resp.StatusCode, p2p_pb.StatusCode_NOT_FOUND) @@ -323,7 +329,9 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { exchg, _ := createP2PExAndServer(t, host0, host1) exchg.Params.RequestTimeout = time.Millisecond * 100 // create one more server(with more headers in the store) - serverSideEx, err := NewExchangeServer(host2, headerMock.NewStore(t, 10), "private") + serverSideEx, err := NewExchangeServer[*test.DummyHeader]( + host2, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), "private", + ) require.NoError(t, err) // change store implementation serverSideEx.getter = &timedOutStore{exchg.Params.RequestTimeout} @@ -349,15 +357,18 @@ func createMocknet(t *testing.T, amount int) []libhost.Host { } // createP2PExAndServer creates a Exchange with 5 headers already in its store. -func createP2PExAndServer(t *testing.T, host, tpeer libhost.Host) (*Exchange, *headerMock.MockStore) { - store := headerMock.NewStore(t, 5) - serverSideEx, err := NewExchangeServer(tpeer, store, "private") +func createP2PExAndServer( + t *testing.T, + host, tpeer libhost.Host, +) (*Exchange[*test.DummyHeader], *headerMock.MockStore[*test.DummyHeader]) { + store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) + serverSideEx, err := NewExchangeServer[*test.DummyHeader](tpeer, store, "private") require.NoError(t, err) err = serverSideEx.Start(context.Background()) require.NoError(t, err) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - ex, err := NewExchange(host, []peer.ID{tpeer.ID()}, "private", connGater) + ex, err := NewExchange[*test.DummyHeader](host, []peer.ID{tpeer.ID()}, "private", connGater) require.NoError(t, err) require.NoError(t, ex.Start(context.Background())) time.Sleep(time.Millisecond * 100) // give peerTracker time to add a trusted peer @@ -375,31 +386,31 @@ type timedOutStore struct { timeout time.Duration } -func (t *timedOutStore) Head(context.Context) (*header.ExtendedHeader, error) { +func (t *timedOutStore) Head(context.Context) (*test.DummyHeader, error) { // TODO implement me panic("implement me") } -func (t *timedOutStore) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { +func (t *timedOutStore) Get(context.Context, header.Hash) (*test.DummyHeader, error) { // TODO implement me panic("implement me") } -func (t *timedOutStore) GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) { +func (t *timedOutStore) GetByHeight(context.Context, uint64) (*test.DummyHeader, error) { // TODO implement me panic("implement me") } -func (t *timedOutStore) GetRangeByHeight(_ context.Context, _, _ uint64) ([]*header.ExtendedHeader, error) { +func (t *timedOutStore) GetRangeByHeight(_ context.Context, _, _ uint64) ([]*test.DummyHeader, error) { time.Sleep(t.timeout + 1) - return []*header.ExtendedHeader{}, nil + return []*test.DummyHeader{}, nil } func (t *timedOutStore) GetVerifiedRange( context.Context, - *header.ExtendedHeader, + *test.DummyHeader, uint64, -) ([]*header.ExtendedHeader, error) { +) ([]*test.DummyHeader, error) { // TODO implement me panic("implement me") } diff --git a/header/p2p/helpers.go b/libs/header/p2p/helpers.go similarity index 81% rename from header/p2p/helpers.go rename to libs/header/p2p/helpers.go index 8c493866d2..3b48362479 100644 --- a/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -12,24 +12,24 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" - "github.com/celestiaorg/celestia-node/header" - p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" + "github.com/celestiaorg/celestia-node/libs/header" + p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) func protocolID(protocolSuffix string) protocol.ID { return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) } -// sendMessage opens the stream to the given peers and sends ExtendedHeaderRequest to fetch -// ExtendedHeaders. As a result sendMessage returns ExtendedHeaderResponse, the size of fetched +// sendMessage opens the stream to the given peers and sends HeaderRequest to fetch +// Headers. As a result sendMessage returns HeaderResponse, the size of fetched // data, the duration of the request and an error. func sendMessage( ctx context.Context, host host.Host, to peer.ID, protocol protocol.ID, - req *p2p_pb.ExtendedHeaderRequest, -) ([]*p2p_pb.ExtendedHeaderResponse, uint64, uint64, error) { + req *p2p_pb.HeaderRequest, +) ([]*p2p_pb.HeaderResponse, uint64, uint64, error) { startTime := time.Now() stream, err := host.NewStream(ctx, to, protocol) if err != nil { @@ -57,10 +57,10 @@ func sendMessage( return nil, 0, 0, err } - headers := make([]*p2p_pb.ExtendedHeaderResponse, 0) + headers := make([]*p2p_pb.HeaderResponse, 0) totalRequestSize := uint64(0) for i := 0; i < int(req.Amount); i++ { - resp := new(p2p_pb.ExtendedHeaderResponse) + resp := new(p2p_pb.HeaderResponse) msgSize, err := serde.Read(stream, resp) if err != nil { if err == io.EOF { diff --git a/header/p2p/options.go b/libs/header/p2p/options.go similarity index 100% rename from header/p2p/options.go rename to libs/header/p2p/options.go diff --git a/header/p2p/pb/extended_header_request.pb.go b/libs/header/p2p/pb/header_request.pb.go similarity index 50% rename from header/p2p/pb/extended_header_request.pb.go rename to libs/header/p2p/pb/header_request.pb.go index cdb80d0e10..ee90374d6e 100644 --- a/header/p2p/pb/extended_header_request.pb.go +++ b/libs/header/p2p/pb/header_request.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: header/p2p/pb/extended_header_request.proto +// source: pkg/header/p2p/pb/header_request.proto package p2p_pb @@ -47,29 +47,30 @@ func (x StatusCode) String() string { } func (StatusCode) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_ea2a1467b965216e, []int{0} + return fileDescriptor_bd9664a357f22804, []int{0} } -type ExtendedHeaderRequest struct { +type HeaderRequest struct { // Types that are valid to be assigned to Data: - // *ExtendedHeaderRequest_Origin - // *ExtendedHeaderRequest_Hash - Data isExtendedHeaderRequest_Data `protobuf_oneof:"data"` - Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + // + // *HeaderRequest_Origin + // *HeaderRequest_Hash + Data isHeaderRequest_Data `protobuf_oneof:"data"` + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` } -func (m *ExtendedHeaderRequest) Reset() { *m = ExtendedHeaderRequest{} } -func (m *ExtendedHeaderRequest) String() string { return proto.CompactTextString(m) } -func (*ExtendedHeaderRequest) ProtoMessage() {} -func (*ExtendedHeaderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_ea2a1467b965216e, []int{0} +func (m *HeaderRequest) Reset() { *m = HeaderRequest{} } +func (m *HeaderRequest) String() string { return proto.CompactTextString(m) } +func (*HeaderRequest) ProtoMessage() {} +func (*HeaderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_bd9664a357f22804, []int{0} } -func (m *ExtendedHeaderRequest) XXX_Unmarshal(b []byte) error { +func (m *HeaderRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *ExtendedHeaderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *HeaderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_ExtendedHeaderRequest.Marshal(b, m, deterministic) + return xxx_messageInfo_HeaderRequest.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -79,56 +80,56 @@ func (m *ExtendedHeaderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byt return b[:n], nil } } -func (m *ExtendedHeaderRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ExtendedHeaderRequest.Merge(m, src) +func (m *HeaderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HeaderRequest.Merge(m, src) } -func (m *ExtendedHeaderRequest) XXX_Size() int { +func (m *HeaderRequest) XXX_Size() int { return m.Size() } -func (m *ExtendedHeaderRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ExtendedHeaderRequest.DiscardUnknown(m) +func (m *HeaderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HeaderRequest.DiscardUnknown(m) } -var xxx_messageInfo_ExtendedHeaderRequest proto.InternalMessageInfo +var xxx_messageInfo_HeaderRequest proto.InternalMessageInfo -type isExtendedHeaderRequest_Data interface { - isExtendedHeaderRequest_Data() +type isHeaderRequest_Data interface { + isHeaderRequest_Data() MarshalTo([]byte) (int, error) Size() int } -type ExtendedHeaderRequest_Origin struct { +type HeaderRequest_Origin struct { Origin uint64 `protobuf:"varint,1,opt,name=origin,proto3,oneof" json:"origin,omitempty"` } -type ExtendedHeaderRequest_Hash struct { +type HeaderRequest_Hash struct { Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3,oneof" json:"hash,omitempty"` } -func (*ExtendedHeaderRequest_Origin) isExtendedHeaderRequest_Data() {} -func (*ExtendedHeaderRequest_Hash) isExtendedHeaderRequest_Data() {} +func (*HeaderRequest_Origin) isHeaderRequest_Data() {} +func (*HeaderRequest_Hash) isHeaderRequest_Data() {} -func (m *ExtendedHeaderRequest) GetData() isExtendedHeaderRequest_Data { +func (m *HeaderRequest) GetData() isHeaderRequest_Data { if m != nil { return m.Data } return nil } -func (m *ExtendedHeaderRequest) GetOrigin() uint64 { - if x, ok := m.GetData().(*ExtendedHeaderRequest_Origin); ok { +func (m *HeaderRequest) GetOrigin() uint64 { + if x, ok := m.GetData().(*HeaderRequest_Origin); ok { return x.Origin } return 0 } -func (m *ExtendedHeaderRequest) GetHash() []byte { - if x, ok := m.GetData().(*ExtendedHeaderRequest_Hash); ok { +func (m *HeaderRequest) GetHash() []byte { + if x, ok := m.GetData().(*HeaderRequest_Hash); ok { return x.Hash } return nil } -func (m *ExtendedHeaderRequest) GetAmount() uint64 { +func (m *HeaderRequest) GetAmount() uint64 { if m != nil { return m.Amount } @@ -136,30 +137,30 @@ func (m *ExtendedHeaderRequest) GetAmount() uint64 { } // XXX_OneofWrappers is for the internal use of the proto package. -func (*ExtendedHeaderRequest) XXX_OneofWrappers() []interface{} { +func (*HeaderRequest) XXX_OneofWrappers() []interface{} { return []interface{}{ - (*ExtendedHeaderRequest_Origin)(nil), - (*ExtendedHeaderRequest_Hash)(nil), + (*HeaderRequest_Origin)(nil), + (*HeaderRequest_Hash)(nil), } } -type ExtendedHeaderResponse struct { +type HeaderResponse struct { Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` StatusCode StatusCode `protobuf:"varint,2,opt,name=statusCode,proto3,enum=p2p.pb.StatusCode" json:"statusCode,omitempty"` } -func (m *ExtendedHeaderResponse) Reset() { *m = ExtendedHeaderResponse{} } -func (m *ExtendedHeaderResponse) String() string { return proto.CompactTextString(m) } -func (*ExtendedHeaderResponse) ProtoMessage() {} -func (*ExtendedHeaderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_ea2a1467b965216e, []int{1} +func (m *HeaderResponse) Reset() { *m = HeaderResponse{} } +func (m *HeaderResponse) String() string { return proto.CompactTextString(m) } +func (*HeaderResponse) ProtoMessage() {} +func (*HeaderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bd9664a357f22804, []int{1} } -func (m *ExtendedHeaderResponse) XXX_Unmarshal(b []byte) error { +func (m *HeaderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *ExtendedHeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *HeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_ExtendedHeaderResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_HeaderResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -169,26 +170,26 @@ func (m *ExtendedHeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]by return b[:n], nil } } -func (m *ExtendedHeaderResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ExtendedHeaderResponse.Merge(m, src) +func (m *HeaderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_HeaderResponse.Merge(m, src) } -func (m *ExtendedHeaderResponse) XXX_Size() int { +func (m *HeaderResponse) XXX_Size() int { return m.Size() } -func (m *ExtendedHeaderResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ExtendedHeaderResponse.DiscardUnknown(m) +func (m *HeaderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_HeaderResponse.DiscardUnknown(m) } -var xxx_messageInfo_ExtendedHeaderResponse proto.InternalMessageInfo +var xxx_messageInfo_HeaderResponse proto.InternalMessageInfo -func (m *ExtendedHeaderResponse) GetBody() []byte { +func (m *HeaderResponse) GetBody() []byte { if m != nil { return m.Body } return nil } -func (m *ExtendedHeaderResponse) GetStatusCode() StatusCode { +func (m *HeaderResponse) GetStatusCode() StatusCode { if m != nil { return m.StatusCode } @@ -197,37 +198,36 @@ func (m *ExtendedHeaderResponse) GetStatusCode() StatusCode { func init() { proto.RegisterEnum("p2p.pb.StatusCode", StatusCode_name, StatusCode_value) - proto.RegisterType((*ExtendedHeaderRequest)(nil), "p2p.pb.ExtendedHeaderRequest") - proto.RegisterType((*ExtendedHeaderResponse)(nil), "p2p.pb.ExtendedHeaderResponse") + proto.RegisterType((*HeaderRequest)(nil), "p2p.pb.HeaderRequest") + proto.RegisterType((*HeaderResponse)(nil), "p2p.pb.HeaderResponse") } func init() { - proto.RegisterFile("header/p2p/pb/extended_header_request.proto", fileDescriptor_ea2a1467b965216e) -} - -var fileDescriptor_ea2a1467b965216e = []byte{ - // 278 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xcd, 0x4a, 0xf3, 0x40, - 0x14, 0x86, 0x67, 0xfa, 0x85, 0xf9, 0xf0, 0x58, 0xa5, 0x0c, 0x5a, 0xb2, 0x1a, 0x4a, 0x57, 0x45, - 0x21, 0x95, 0x78, 0x05, 0xd6, 0x2a, 0x2d, 0x4a, 0x03, 0xe3, 0xcf, 0x36, 0x4e, 0x98, 0xa1, 0xe9, - 0xc2, 0xcc, 0x98, 0x99, 0x80, 0xde, 0x85, 0x97, 0xe5, 0xb2, 0x4b, 0x97, 0x92, 0xdc, 0x88, 0x38, - 0x09, 0x2a, 0xee, 0xce, 0x7b, 0xde, 0x07, 0x9e, 0xc3, 0x81, 0xe3, 0x5c, 0x09, 0xa9, 0xca, 0xa9, - 0x89, 0xcd, 0xd4, 0x64, 0x53, 0xf5, 0xec, 0x54, 0x21, 0x95, 0x4c, 0xdb, 0x75, 0x5a, 0xaa, 0xa7, - 0x4a, 0x59, 0x17, 0x99, 0x52, 0x3b, 0x4d, 0x89, 0x89, 0x4d, 0x64, 0xb2, 0xf1, 0x1a, 0x0e, 0x2f, - 0x3a, 0x70, 0xe1, 0x39, 0xde, 0x62, 0x34, 0x04, 0xa2, 0xcb, 0xcd, 0x7a, 0x53, 0x84, 0x78, 0x84, - 0x27, 0xc1, 0x02, 0xf1, 0x2e, 0xd3, 0x03, 0x08, 0x72, 0x61, 0xf3, 0xb0, 0x37, 0xc2, 0x93, 0xfe, - 0x02, 0x71, 0x9f, 0xe8, 0x10, 0x88, 0x78, 0xd4, 0x55, 0xe1, 0xc2, 0x7f, 0x5f, 0x3c, 0xef, 0xd2, - 0x8c, 0x40, 0x20, 0x85, 0x13, 0xe3, 0x07, 0x18, 0xfe, 0x15, 0x59, 0xa3, 0x0b, 0xab, 0x28, 0x85, - 0x20, 0xd3, 0xf2, 0xc5, 0x7b, 0xfa, 0xdc, 0xcf, 0x34, 0x06, 0xb0, 0x4e, 0xb8, 0xca, 0x9e, 0x6b, - 0xa9, 0xbc, 0x69, 0x3f, 0xa6, 0x51, 0x7b, 0x73, 0x74, 0xf3, 0xdd, 0xf0, 0x5f, 0xd4, 0xd1, 0x09, - 0xc0, 0x4f, 0x43, 0x77, 0xe1, 0xff, 0x72, 0x75, 0x7f, 0x76, 0xbd, 0x9c, 0x0f, 0x10, 0x25, 0xd0, - 0x4b, 0xae, 0x06, 0x98, 0xee, 0xc1, 0xce, 0x2a, 0xb9, 0x4d, 0x2f, 0x93, 0xbb, 0xd5, 0x7c, 0xd0, - 0x9b, 0x85, 0x6f, 0x35, 0xc3, 0xdb, 0x9a, 0xe1, 0x8f, 0x9a, 0xe1, 0xd7, 0x86, 0xa1, 0x6d, 0xc3, - 0xd0, 0x7b, 0xc3, 0x50, 0x46, 0xfc, 0x97, 0x4e, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x3d, 0x8f, - 0x64, 0x4b, 0x54, 0x01, 0x00, 0x00, -} - -func (m *ExtendedHeaderRequest) Marshal() (dAtA []byte, err error) { + proto.RegisterFile("pkg/header/p2p/pb/header_request.proto", fileDescriptor_bd9664a357f22804) +} + +var fileDescriptor_bd9664a357f22804 = []byte{ + // 270 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xcb, 0x4a, 0xc3, 0x40, + 0x14, 0x86, 0x67, 0x6a, 0x18, 0xf1, 0xd8, 0x96, 0x32, 0x88, 0x64, 0x35, 0x94, 0x2e, 0xa4, 0xb8, + 0x48, 0x24, 0x3e, 0x81, 0xb5, 0x48, 0x8a, 0x92, 0xc0, 0x78, 0xc1, 0x5d, 0x98, 0x90, 0xa1, 0x29, + 0x62, 0x66, 0xcc, 0x4c, 0x16, 0xbe, 0x85, 0x8f, 0xe5, 0xb2, 0x4b, 0x97, 0x92, 0xbc, 0x88, 0x38, + 0x8d, 0x97, 0xdd, 0xf9, 0xf8, 0x7e, 0xce, 0x7f, 0x38, 0x70, 0xa2, 0x9f, 0xd6, 0x61, 0x29, 0x45, + 0x21, 0xeb, 0x50, 0x47, 0x3a, 0xd4, 0x79, 0x4f, 0x59, 0x2d, 0x5f, 0x1a, 0x69, 0x6c, 0xa0, 0x6b, + 0x65, 0x15, 0x25, 0x3a, 0xd2, 0x81, 0xce, 0x67, 0x19, 0x8c, 0x62, 0xe7, 0xf9, 0x4e, 0x53, 0x1f, + 0x88, 0xaa, 0x37, 0xeb, 0x4d, 0xe5, 0xe3, 0x29, 0x9e, 0x7b, 0x31, 0xe2, 0x3d, 0xd3, 0x23, 0xf0, + 0x4a, 0x61, 0x4a, 0x7f, 0x30, 0xc5, 0xf3, 0x61, 0x8c, 0xb8, 0x23, 0x7a, 0x0c, 0x44, 0x3c, 0xab, + 0xa6, 0xb2, 0xfe, 0xde, 0x77, 0x9e, 0xf7, 0xb4, 0x20, 0xe0, 0x15, 0xc2, 0x8a, 0xd9, 0x23, 0x8c, + 0x7f, 0x0a, 0x8c, 0x56, 0x95, 0x91, 0x94, 0x82, 0x97, 0xab, 0xe2, 0xd5, 0xed, 0x1f, 0x72, 0x37, + 0xd3, 0x08, 0xc0, 0x58, 0x61, 0x1b, 0x73, 0xa9, 0x0a, 0xe9, 0x1a, 0xc6, 0x11, 0x0d, 0x76, 0x37, + 0x06, 0xb7, 0xbf, 0x86, 0xff, 0x4b, 0x9d, 0x9e, 0x01, 0xfc, 0x19, 0x7a, 0x08, 0xfb, 0xab, 0xe4, + 0xe1, 0xe2, 0x66, 0xb5, 0x9c, 0x20, 0x4a, 0x60, 0x90, 0x5e, 0x4f, 0x30, 0x1d, 0xc1, 0x41, 0x92, + 0xde, 0x65, 0x57, 0xe9, 0x7d, 0xb2, 0x9c, 0x0c, 0x16, 0xfe, 0x7b, 0xcb, 0xf0, 0xb6, 0x65, 0xf8, + 0xb3, 0x65, 0xf8, 0xad, 0x63, 0x68, 0xdb, 0x31, 0xf4, 0xd1, 0x31, 0x94, 0x13, 0xf7, 0x95, 0xf3, + 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x41, 0x37, 0xb7, 0xe6, 0x3f, 0x01, 0x00, 0x00, +} + +func (m *HeaderRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -237,18 +237,18 @@ func (m *ExtendedHeaderRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ExtendedHeaderRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *HeaderRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ExtendedHeaderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *HeaderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.Amount != 0 { - i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(m.Amount)) + i = encodeVarintHeaderRequest(dAtA, i, uint64(m.Amount)) i-- dAtA[i] = 0x18 } @@ -264,35 +264,35 @@ func (m *ExtendedHeaderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ExtendedHeaderRequest_Origin) MarshalTo(dAtA []byte) (int, error) { +func (m *HeaderRequest_Origin) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ExtendedHeaderRequest_Origin) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *HeaderRequest_Origin) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) - i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(m.Origin)) + i = encodeVarintHeaderRequest(dAtA, i, uint64(m.Origin)) i-- dAtA[i] = 0x8 return len(dAtA) - i, nil } -func (m *ExtendedHeaderRequest_Hash) MarshalTo(dAtA []byte) (int, error) { +func (m *HeaderRequest_Hash) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ExtendedHeaderRequest_Hash) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *HeaderRequest_Hash) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) if m.Hash != nil { i -= len(m.Hash) copy(dAtA[i:], m.Hash) - i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(len(m.Hash))) + i = encodeVarintHeaderRequest(dAtA, i, uint64(len(m.Hash))) i-- dAtA[i] = 0x12 } return len(dAtA) - i, nil } -func (m *ExtendedHeaderResponse) Marshal() (dAtA []byte, err error) { +func (m *HeaderResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -302,33 +302,33 @@ func (m *ExtendedHeaderResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ExtendedHeaderResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *HeaderResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ExtendedHeaderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *HeaderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.StatusCode != 0 { - i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(m.StatusCode)) + i = encodeVarintHeaderRequest(dAtA, i, uint64(m.StatusCode)) i-- dAtA[i] = 0x10 } if len(m.Body) > 0 { i -= len(m.Body) copy(dAtA[i:], m.Body) - i = encodeVarintExtendedHeaderRequest(dAtA, i, uint64(len(m.Body))) + i = encodeVarintHeaderRequest(dAtA, i, uint64(len(m.Body))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func encodeVarintExtendedHeaderRequest(dAtA []byte, offset int, v uint64) int { - offset -= sovExtendedHeaderRequest(v) +func encodeVarintHeaderRequest(dAtA []byte, offset int, v uint64) int { + offset -= sovHeaderRequest(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) @@ -338,7 +338,7 @@ func encodeVarintExtendedHeaderRequest(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *ExtendedHeaderRequest) Size() (n int) { +func (m *HeaderRequest) Size() (n int) { if m == nil { return 0 } @@ -348,21 +348,21 @@ func (m *ExtendedHeaderRequest) Size() (n int) { n += m.Data.Size() } if m.Amount != 0 { - n += 1 + sovExtendedHeaderRequest(uint64(m.Amount)) + n += 1 + sovHeaderRequest(uint64(m.Amount)) } return n } -func (m *ExtendedHeaderRequest_Origin) Size() (n int) { +func (m *HeaderRequest_Origin) Size() (n int) { if m == nil { return 0 } var l int _ = l - n += 1 + sovExtendedHeaderRequest(uint64(m.Origin)) + n += 1 + sovHeaderRequest(uint64(m.Origin)) return n } -func (m *ExtendedHeaderRequest_Hash) Size() (n int) { +func (m *HeaderRequest_Hash) Size() (n int) { if m == nil { return 0 } @@ -370,11 +370,11 @@ func (m *ExtendedHeaderRequest_Hash) Size() (n int) { _ = l if m.Hash != nil { l = len(m.Hash) - n += 1 + l + sovExtendedHeaderRequest(uint64(l)) + n += 1 + l + sovHeaderRequest(uint64(l)) } return n } -func (m *ExtendedHeaderResponse) Size() (n int) { +func (m *HeaderResponse) Size() (n int) { if m == nil { return 0 } @@ -382,21 +382,21 @@ func (m *ExtendedHeaderResponse) Size() (n int) { _ = l l = len(m.Body) if l > 0 { - n += 1 + l + sovExtendedHeaderRequest(uint64(l)) + n += 1 + l + sovHeaderRequest(uint64(l)) } if m.StatusCode != 0 { - n += 1 + sovExtendedHeaderRequest(uint64(m.StatusCode)) + n += 1 + sovHeaderRequest(uint64(m.StatusCode)) } return n } -func sovExtendedHeaderRequest(x uint64) (n int) { +func sovHeaderRequest(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } -func sozExtendedHeaderRequest(x uint64) (n int) { - return sovExtendedHeaderRequest(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +func sozHeaderRequest(x uint64) (n int) { + return sovHeaderRequest(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { +func (m *HeaderRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -404,7 +404,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -419,10 +419,10 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ExtendedHeaderRequest: wiretype end group for non-group") + return fmt.Errorf("proto: HeaderRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ExtendedHeaderRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HeaderRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -432,7 +432,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { var v uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -444,7 +444,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { break } } - m.Data = &ExtendedHeaderRequest_Origin{v} + m.Data = &HeaderRequest_Origin{v} case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) @@ -452,7 +452,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -465,18 +465,18 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { } } if byteLen < 0 { - return ErrInvalidLengthExtendedHeaderRequest + return ErrInvalidLengthHeaderRequest } postIndex := iNdEx + byteLen if postIndex < 0 { - return ErrInvalidLengthExtendedHeaderRequest + return ErrInvalidLengthHeaderRequest } if postIndex > l { return io.ErrUnexpectedEOF } v := make([]byte, postIndex-iNdEx) copy(v, dAtA[iNdEx:postIndex]) - m.Data = &ExtendedHeaderRequest_Hash{v} + m.Data = &HeaderRequest_Hash{v} iNdEx = postIndex case 3: if wireType != 0 { @@ -485,7 +485,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { m.Amount = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -499,12 +499,12 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { } default: iNdEx = preIndex - skippy, err := skipExtendedHeaderRequest(dAtA[iNdEx:]) + skippy, err := skipHeaderRequest(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthExtendedHeaderRequest + return ErrInvalidLengthHeaderRequest } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF @@ -518,7 +518,7 @@ func (m *ExtendedHeaderRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { +func (m *HeaderResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -526,7 +526,7 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -541,10 +541,10 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ExtendedHeaderResponse: wiretype end group for non-group") + return fmt.Errorf("proto: HeaderResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ExtendedHeaderResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HeaderResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -554,7 +554,7 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -567,11 +567,11 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { } } if byteLen < 0 { - return ErrInvalidLengthExtendedHeaderRequest + return ErrInvalidLengthHeaderRequest } postIndex := iNdEx + byteLen if postIndex < 0 { - return ErrInvalidLengthExtendedHeaderRequest + return ErrInvalidLengthHeaderRequest } if postIndex > l { return io.ErrUnexpectedEOF @@ -588,7 +588,7 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { m.StatusCode = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return ErrIntOverflowExtendedHeaderRequest + return ErrIntOverflowHeaderRequest } if iNdEx >= l { return io.ErrUnexpectedEOF @@ -602,12 +602,12 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { } default: iNdEx = preIndex - skippy, err := skipExtendedHeaderRequest(dAtA[iNdEx:]) + skippy, err := skipHeaderRequest(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthExtendedHeaderRequest + return ErrInvalidLengthHeaderRequest } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF @@ -621,7 +621,7 @@ func (m *ExtendedHeaderResponse) Unmarshal(dAtA []byte) error { } return nil } -func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { +func skipHeaderRequest(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 @@ -629,7 +629,7 @@ func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { - return 0, ErrIntOverflowExtendedHeaderRequest + return 0, ErrIntOverflowHeaderRequest } if iNdEx >= l { return 0, io.ErrUnexpectedEOF @@ -646,7 +646,7 @@ func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { - return 0, ErrIntOverflowExtendedHeaderRequest + return 0, ErrIntOverflowHeaderRequest } if iNdEx >= l { return 0, io.ErrUnexpectedEOF @@ -662,7 +662,7 @@ func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { - return 0, ErrIntOverflowExtendedHeaderRequest + return 0, ErrIntOverflowHeaderRequest } if iNdEx >= l { return 0, io.ErrUnexpectedEOF @@ -675,14 +675,14 @@ func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { } } if length < 0 { - return 0, ErrInvalidLengthExtendedHeaderRequest + return 0, ErrInvalidLengthHeaderRequest } iNdEx += length case 3: depth++ case 4: if depth == 0 { - return 0, ErrUnexpectedEndOfGroupExtendedHeaderRequest + return 0, ErrUnexpectedEndOfGroupHeaderRequest } depth-- case 5: @@ -691,7 +691,7 @@ func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { - return 0, ErrInvalidLengthExtendedHeaderRequest + return 0, ErrInvalidLengthHeaderRequest } if depth == 0 { return iNdEx, nil @@ -701,7 +701,7 @@ func skipExtendedHeaderRequest(dAtA []byte) (n int, err error) { } var ( - ErrInvalidLengthExtendedHeaderRequest = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowExtendedHeaderRequest = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupExtendedHeaderRequest = fmt.Errorf("proto: unexpected end of group") + ErrInvalidLengthHeaderRequest = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHeaderRequest = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupHeaderRequest = fmt.Errorf("proto: unexpected end of group") ) diff --git a/header/p2p/pb/extended_header_request.proto b/libs/header/p2p/pb/header_request.proto similarity index 78% rename from header/p2p/pb/extended_header_request.proto rename to libs/header/p2p/pb/header_request.proto index 875c9d89dc..ee764c7ab1 100644 --- a/header/p2p/pb/extended_header_request.proto +++ b/libs/header/p2p/pb/header_request.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package p2p.pb; -message ExtendedHeaderRequest { +message HeaderRequest { oneof data { uint64 origin = 1; bytes hash = 2; @@ -16,7 +16,7 @@ enum StatusCode { NOT_FOUND = 2; }; -message ExtendedHeaderResponse { +message HeaderResponse { bytes body = 1; StatusCode statusCode = 2; } diff --git a/header/p2p/peer_stats.go b/libs/header/p2p/peer_stats.go similarity index 100% rename from header/p2p/peer_stats.go rename to libs/header/p2p/peer_stats.go diff --git a/header/p2p/peer_stats_test.go b/libs/header/p2p/peer_stats_test.go similarity index 100% rename from header/p2p/peer_stats_test.go rename to libs/header/p2p/peer_stats_test.go diff --git a/header/p2p/peer_tracker.go b/libs/header/p2p/peer_tracker.go similarity index 100% rename from header/p2p/peer_tracker.go rename to libs/header/p2p/peer_tracker.go diff --git a/header/p2p/peer_tracker_test.go b/libs/header/p2p/peer_tracker_test.go similarity index 100% rename from header/p2p/peer_tracker_test.go rename to libs/header/p2p/peer_tracker_test.go diff --git a/header/p2p/server.go b/libs/header/p2p/server.go similarity index 74% rename from header/p2p/server.go rename to libs/header/p2p/server.go index 723d488d6e..673dcd1185 100644 --- a/header/p2p/server.go +++ b/libs/header/p2p/server.go @@ -8,7 +8,6 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" - tmbytes "github.com/tendermint/tendermint/libs/bytes" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -16,8 +15,8 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" - "github.com/celestiaorg/celestia-node/header" - p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" + "github.com/celestiaorg/celestia-node/libs/header" + p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) var ( @@ -26,11 +25,11 @@ var ( // ExchangeServer represents the server-side component for // responding to inbound header-related requests. -type ExchangeServer struct { +type ExchangeServer[H header.Header] struct { protocolID protocol.ID host host.Host - getter header.Getter + getter header.Getter[H] ctx context.Context cancel context.CancelFunc @@ -40,12 +39,12 @@ type ExchangeServer struct { // NewExchangeServer returns a new P2P server that handles inbound // header-related requests. -func NewExchangeServer( +func NewExchangeServer[H header.Header]( host host.Host, - getter header.Getter, + getter header.Getter[H], protocolSuffix string, opts ...Option[ServerParameters], -) (*ExchangeServer, error) { +) (*ExchangeServer[H], error) { params := DefaultServerParameters() for _, opt := range opts { opt(¶ms) @@ -54,7 +53,7 @@ func NewExchangeServer( return nil, err } - return &ExchangeServer{ + return &ExchangeServer[H]{ protocolID: protocolID(protocolSuffix), host: host, getter: getter, @@ -63,7 +62,7 @@ func NewExchangeServer( } // Start sets the stream handler for inbound header-related requests. -func (serv *ExchangeServer) Start(context.Context) error { +func (serv *ExchangeServer[H]) Start(context.Context) error { serv.ctx, serv.cancel = context.WithCancel(context.Background()) log.Info("server: listening for inbound header requests") @@ -73,21 +72,21 @@ func (serv *ExchangeServer) Start(context.Context) error { } // Stop removes the stream handler for serving header-related requests. -func (serv *ExchangeServer) Stop(context.Context) error { +func (serv *ExchangeServer[H]) Stop(context.Context) error { log.Info("server: stopping server") serv.cancel() serv.host.RemoveStreamHandler(serv.protocolID) return nil } -// requestHandler handles inbound ExtendedHeaderRequests. -func (serv *ExchangeServer) requestHandler(stream network.Stream) { +// requestHandler handles inbound HeaderRequests. +func (serv *ExchangeServer[H]) requestHandler(stream network.Stream) { err := stream.SetReadDeadline(time.Now().Add(serv.Params.ReadDeadline)) if err != nil { log.Debugf("error setting deadline: %s", err) } // unmarshal request - pbreq := new(p2p_pb.ExtendedHeaderRequest) + pbreq := new(p2p_pb.HeaderRequest) _, err = serde.Read(stream, pbreq) if err != nil { log.Errorw("server: reading header request from stream", "err", err) @@ -98,12 +97,12 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { log.Error(err) } - var headers []*header.ExtendedHeader - // retrieve and write ExtendedHeaders + var headers []H + // retrieve and write Headers switch pbreq.Data.(type) { - case *p2p_pb.ExtendedHeaderRequest_Hash: + case *p2p_pb.HeaderRequest_Hash: headers, err = serv.handleRequestByHash(pbreq.GetHash()) - case *p2p_pb.ExtendedHeaderRequest_Origin: + case *p2p_pb.HeaderRequest_Origin: headers, err = serv.handleRequest(pbreq.GetOrigin(), pbreq.GetOrigin()+pbreq.Amount) default: log.Error("server: invalid data type received") @@ -121,9 +120,9 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { return } - // reallocate headers with 1 nil ExtendedHeader if code is not StatusCode_OK + // reallocate headers with 1 nil Header if code is not StatusCode_OK if code != p2p_pb.StatusCode_OK { - headers = make([]*header.ExtendedHeader, 1) + headers = make([]H, 1) } // write all headers to stream for _, h := range headers { @@ -133,7 +132,7 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { var bin []byte // if header is not nil, then marshal it to []byte. // if header is nil, then error was received,so we will set empty []byte to proto. - if h != nil { + if !h.IsZero() { bin, err = h.MarshalBinary() if err != nil { log.Errorw("server: marshaling header to proto", "height", h.Height, "err", err) @@ -141,7 +140,7 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { return } } - _, err = serde.Write(stream, &p2p_pb.ExtendedHeaderResponse{Body: bin, StatusCode: code}) + _, err = serde.Write(stream, &p2p_pb.HeaderResponse{Body: bin, StatusCode: code}) if err != nil { log.Errorw("server: writing header to stream", "err", err) stream.Reset() //nolint:errcheck @@ -155,35 +154,35 @@ func (serv *ExchangeServer) requestHandler(stream network.Stream) { } } -// handleRequestByHash returns the ExtendedHeader at the given hash +// handleRequestByHash returns the Header at the given hash // if it exists. -func (serv *ExchangeServer) handleRequestByHash(hash []byte) ([]*header.ExtendedHeader, error) { - log.Debugw("server: handling header request", "hash", tmbytes.HexBytes(hash).String()) +func (serv *ExchangeServer[H]) handleRequestByHash(hash []byte) ([]H, error) { + log.Debugw("server: handling header request", "hash", header.Hash(hash).String()) ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-by-hash", trace.WithAttributes( - attribute.String("hash", tmbytes.HexBytes(hash).String()), + attribute.String("hash", header.Hash(hash).String()), )) defer span.End() h, err := serv.getter.Get(ctx, hash) if err != nil { - log.Errorw("server: getting header by hash", "hash", tmbytes.HexBytes(hash).String(), "err", err) + log.Errorw("server: getting header by hash", "hash", header.Hash(hash).String(), "err", err) span.SetStatus(codes.Error, err.Error()) return nil, err } span.AddEvent("fetched-header-from-store", trace.WithAttributes( - attribute.String("hash", tmbytes.HexBytes(hash).String()), - attribute.Int64("height", h.Height)), + attribute.String("hash", header.Hash(hash).String()), + attribute.Int64("height", h.Height())), ) span.SetStatus(codes.Ok, "") - return []*header.ExtendedHeader{h}, nil + return []H{h}, nil } -// handleRequest fetches the ExtendedHeader at the given origin and +// handleRequest fetches the Header at the given origin and // writes it to the stream. -func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHeader, error) { +func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { if from == uint64(0) { return serv.handleHeadRequest() } @@ -221,7 +220,7 @@ func (serv *ExchangeServer) handleRequest(from, to uint64) ([]*header.ExtendedHe } // handleHeadRequest returns the latest stored head. -func (serv *ExchangeServer) handleHeadRequest() ([]*header.ExtendedHeader, error) { +func (serv *ExchangeServer[H]) handleHeadRequest() ([]H, error) { log.Debug("server: handling head request") ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) defer cancel() @@ -237,8 +236,8 @@ func (serv *ExchangeServer) handleHeadRequest() ([]*header.ExtendedHeader, error span.AddEvent("fetched-head", trace.WithAttributes( attribute.String("hash", head.Hash().String()), - attribute.Int64("height", head.Height)), + attribute.Int64("height", head.Height())), ) span.SetStatus(codes.Ok, "") - return []*header.ExtendedHeader{head}, nil + return []H{head}, nil } diff --git a/header/p2p/server_test.go b/libs/header/p2p/server_test.go similarity index 63% rename from header/p2p/server_test.go rename to libs/header/p2p/server_test.go index a1bdbaf51a..ff7d33ddc9 100644 --- a/header/p2p/server_test.go +++ b/libs/header/p2p/server_test.go @@ -7,14 +7,15 @@ import ( "github.com/ipfs/go-datastore" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/libs/header/store" + "github.com/celestiaorg/celestia-node/libs/header/test" ) func TestExchangeServer_handleRequestTimeout(t *testing.T) { peer := createMocknet(t, 1) - s, err := store.NewStore(datastore.NewMapDatastore()) + s, err := store.NewStore[*test.DummyHeader](datastore.NewMapDatastore()) require.NoError(t, err) - server, err := NewExchangeServer(peer[0], s, "private") + server, err := NewExchangeServer[*test.DummyHeader](peer[0], s, "private") require.NoError(t, err) err = server.Start(context.Background()) require.NoError(t, err) diff --git a/header/p2p/session.go b/libs/header/p2p/session.go similarity index 74% rename from header/p2p/session.go rename to libs/header/p2p/session.go index bb5a35b9b3..e00cc8a05f 100644 --- a/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -9,21 +9,21 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" - "github.com/celestiaorg/celestia-node/header" - p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" + "github.com/celestiaorg/celestia-node/libs/header" + p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) -type option func(*session) +type option[H header.Header] func(*session[H]) -func withValidation(from *header.ExtendedHeader) option { - return func(s *session) { +func withValidation[H header.Header](from H) option[H] { + return func(s *session[H]) { s.from = from } } // session aims to divide a range of headers // into several smaller requests among different peers. -type session struct { +type session[H header.Header] struct { host host.Host protocolID protocol.ID queue *peerQueue @@ -32,24 +32,24 @@ type session struct { // `from` is set when additional validation for range is needed. // Otherwise, it will be nil. - from *header.ExtendedHeader + from H requestTimeout time.Duration ctx context.Context cancel context.CancelFunc - reqCh chan *p2p_pb.ExtendedHeaderRequest + reqCh chan *p2p_pb.HeaderRequest } -func newSession( +func newSession[H header.Header]( ctx context.Context, h host.Host, peerTracker *peerTracker, protocolID protocol.ID, requestTimeout time.Duration, - options ...option, -) *session { + options ...option[H], +) *session[H] { ctx, cancel := context.WithCancel(ctx) - ses := &session{ + ses := &session[H]{ ctx: ctx, cancel: cancel, protocolID: protocolID, @@ -66,22 +66,22 @@ func newSession( } // GetRangeByHeight requests headers from different peers. -func (s *session) getRangeByHeight( +func (s *session[H]) getRangeByHeight( ctx context.Context, from, amount, headersPerPeer uint64, -) ([]*header.ExtendedHeader, error) { +) ([]H, error) { log.Debugw("requesting headers", "from", from, "to", from+amount) requests := prepareRequests(from, amount, headersPerPeer) - result := make(chan []*header.ExtendedHeader, len(requests)) - s.reqCh = make(chan *p2p_pb.ExtendedHeaderRequest, len(requests)) + result := make(chan []H, len(requests)) + s.reqCh = make(chan *p2p_pb.HeaderRequest, len(requests)) go s.handleOutgoingRequests(ctx, result) for _, req := range requests { s.reqCh <- req } - headers := make([]*header.ExtendedHeader, 0, amount) + headers := make([]H, 0, amount) for i := 0; i < len(requests); i++ { select { case <-s.ctx.Done(): @@ -93,13 +93,13 @@ func (s *session) getRangeByHeight( } } sort.Slice(headers, func(i, j int) bool { - return headers[i].Height < headers[j].Height + return headers[i].Height() < headers[j].Height() }) return headers, nil } // close stops the session. -func (s *session) close() { +func (s *session[H]) close() { if s.cancel != nil { s.cancel() s.cancel = nil @@ -108,7 +108,7 @@ func (s *session) close() { // handleOutgoingRequests pops a peer from the queue and sends a prepared request to the peer. // Will exit via canceled session context or when all request are processed. -func (s *session) handleOutgoingRequests(ctx context.Context, result chan []*header.ExtendedHeader) { +func (s *session[H]) handleOutgoingRequests(ctx context.Context, result chan []H) { for { select { case <-ctx.Done(): @@ -128,11 +128,11 @@ func (s *session) handleOutgoingRequests(ctx context.Context, result chan []*hea // doRequest chooses the best peer to fetch headers and sends a request in range of available // maxRetryAttempts. -func (s *session) doRequest( +func (s *session[H]) doRequest( ctx context.Context, stat *peerStat, - req *p2p_pb.ExtendedHeaderRequest, - headers chan []*header.ExtendedHeader, + req *p2p_pb.HeaderRequest, + headers chan []H, ) { ctx, cancel := context.WithTimeout(ctx, s.requestTimeout) defer cancel() @@ -170,19 +170,21 @@ func (s *session) doRequest( s.queue.push(stat) } -// processResponse converts ExtendedHeaderResponse to ExtendedHeader. -func (s *session) processResponse(responses []*p2p_pb.ExtendedHeaderResponse) ([]*header.ExtendedHeader, error) { - headers := make([]*header.ExtendedHeader, 0) +// processResponse converts HeaderResponse to Header. +func (s *session[H]) processResponse(responses []*p2p_pb.HeaderResponse) ([]H, error) { + headers := make([]H, 0) for _, resp := range responses { err := convertStatusCodeToError(resp.StatusCode) if err != nil { return nil, err } - header, err := header.UnmarshalExtendedHeader(resp.Body) + var empty H + header := empty.New() + err = header.UnmarshalBinary(resp.Body) if err != nil { return nil, errors.New("unmarshalling error") } - headers = append(headers, header) + headers = append(headers, header.(H)) } if len(headers) == 0 { return nil, header.ErrNotFound @@ -193,9 +195,9 @@ func (s *session) processResponse(responses []*p2p_pb.ExtendedHeaderResponse) ([ } // validate checks that the received range of headers is valid against the provided header. -func (s *session) validate(headers []*header.ExtendedHeader) error { +func (s *session[H]) validate(headers []H) error { // if `from` is empty, then additional validation for the header`s range is not needed. - if s.from == nil { + if s.from.IsZero() { return nil } @@ -221,13 +223,13 @@ func (s *session) validate(headers []*header.ExtendedHeader) error { return nil } -// prepareRequests converts incoming range into separate ExtendedHeaderRequest. -func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.ExtendedHeaderRequest { - requests := make([]*p2p_pb.ExtendedHeaderRequest, 0, amount/headersPerPeer) +// prepareRequests converts incoming range into separate HeaderRequest. +func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.HeaderRequest { + requests := make([]*p2p_pb.HeaderRequest, 0, amount/headersPerPeer) for amount > uint64(0) { var requestSize uint64 - request := &p2p_pb.ExtendedHeaderRequest{ - Data: &p2p_pb.ExtendedHeaderRequest_Origin{Origin: from}, + request := &p2p_pb.HeaderRequest{ + Data: &p2p_pb.HeaderRequest_Origin{Origin: from}, } if amount < headersPerPeer { requestSize = amount diff --git a/header/p2p/session_test.go b/libs/header/p2p/session_test.go similarity index 100% rename from header/p2p/session_test.go rename to libs/header/p2p/session_test.go diff --git a/libs/header/p2p/subscriber.go b/libs/header/p2p/subscriber.go new file mode 100644 index 0000000000..7abd0d1864 --- /dev/null +++ b/libs/header/p2p/subscriber.go @@ -0,0 +1,82 @@ +package p2p + +import ( + "context" + "fmt" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/celestiaorg/celestia-node/libs/header" +) + +// Subscriber manages the lifecycle and relationship of header Module +// with the "header-sub" gossipsub topic. +type Subscriber[H header.Header] struct { + pubsub *pubsub.PubSub + topic *pubsub.Topic + msgID pubsub.MsgIdFunction +} + +// NewSubscriber returns a Subscriber that manages the header Module's +// relationship with the "header-sub" gossipsub topic. +func NewSubscriber[H header.Header](ps *pubsub.PubSub, msgID pubsub.MsgIdFunction) *Subscriber[H] { + return &Subscriber[H]{ + pubsub: ps, + msgID: msgID, + } +} + +// Start starts the Subscriber, registering a topic validator for the "header-sub" +// topic and joining it. +func (p *Subscriber[H]) Start(context.Context) (err error) { + p.topic, err = p.pubsub.Join(PubSubTopic, pubsub.WithTopicMessageIdFn(p.msgID)) + return err +} + +// Stop closes the topic and unregisters its validator. +func (p *Subscriber[H]) Stop(context.Context) error { + err := p.pubsub.UnregisterTopicValidator(PubSubTopic) + if err != nil { + log.Warnf("unregistering validator: %s", err) + } + + return p.topic.Close() +} + +// AddValidator applies basic pubsub validator for the topic. +func (p *Subscriber[H]) AddValidator(val func(context.Context, H) pubsub.ValidationResult) error { + pval := func(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { + var empty H + maybeHead := empty.New() + err := maybeHead.UnmarshalBinary(msg.Data) + if err != nil { + log.Errorw("unmarshalling header", + "from", p.ShortString(), + "err", err) + return pubsub.ValidationReject + } + msg.ValidatorData = maybeHead + return val(ctx, maybeHead.(H)) + } + return p.pubsub.RegisterTopicValidator(PubSubTopic, pval) +} + +// Subscribe returns a new subscription to the Subscriber's +// topic. +func (p *Subscriber[H]) Subscribe() (header.Subscription[H], error) { + if p.topic == nil { + return nil, fmt.Errorf("header topic is not instantiated, service must be started before subscribing") + } + + return newSubscription[H](p.topic) +} + +// Broadcast broadcasts the given Header to the topic. +func (p *Subscriber[H]) Broadcast(ctx context.Context, header H, opts ...pubsub.PubOpt) error { + bin, err := header.MarshalBinary() + if err != nil { + return err + } + return p.topic.Publish(ctx, bin, opts...) +} diff --git a/libs/header/p2p/subscription.go b/libs/header/p2p/subscription.go new file mode 100644 index 0000000000..0986434fef --- /dev/null +++ b/libs/header/p2p/subscription.go @@ -0,0 +1,54 @@ +package p2p + +import ( + "context" + "fmt" + "reflect" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/celestiaorg/celestia-node/libs/header" +) + +// subscription handles retrieving Headers from the header pubsub topic. +type subscription[H header.Header] struct { + topic *pubsub.Topic + subscription *pubsub.Subscription +} + +// newSubscription creates a new Header event subscription +// on the given host. +func newSubscription[H header.Header](topic *pubsub.Topic) (*subscription[H], error) { + sub, err := topic.Subscribe() + if err != nil { + return nil, err + } + + return &subscription[H]{ + topic: topic, + subscription: sub, + }, nil +} + +// NextHeader returns the next (latest) verified Header from the network. +func (s *subscription[H]) NextHeader(ctx context.Context) (H, error) { + msg, err := s.subscription.Next(ctx) + if err != nil { + var zero H + return zero, err + } + log.Debugw("received message", "topic", msg.Message.GetTopic(), "sender", msg.ReceivedFrom) + + header, ok := msg.ValidatorData.(H) + if !ok { + panic(fmt.Sprintf("invalid type received %s", reflect.TypeOf(msg.ValidatorData))) + } + + log.Debugw("received new Header", "height", header.Height(), "hash", header.Hash()) + return header, nil +} + +// Cancel cancels the subscription to new Headers from the network. +func (s *subscription[H]) Cancel() { + s.subscription.Cancel() +} diff --git a/header/p2p/subscription_test.go b/libs/header/p2p/subscription_test.go similarity index 81% rename from header/p2p/subscription_test.go rename to libs/header/p2p/subscription_test.go index 9c51846614..06b0622ad3 100644 --- a/header/p2p/subscription_test.go +++ b/libs/header/p2p/subscription_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header/test" ) // TestSubscriber tests the header Module's implementation of Subscriber. @@ -23,14 +23,14 @@ func TestSubscriber(t *testing.T) { net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) // get mock host and create new gossipsub on it pubsub1, err := pubsub.NewGossipSub(ctx, net.Hosts()[0], pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) // create sub-service lifecycles for header service 1 - p2pSub1 := NewSubscriber(pubsub1) + p2pSub1 := NewSubscriber[*test.DummyHeader](pubsub1, pubsub.DefaultMsgIdFn) err = p2pSub1.Start(context.Background()) require.NoError(t, err) @@ -40,7 +40,7 @@ func TestSubscriber(t *testing.T) { require.NoError(t, err) // create sub-service lifecycles for header service 2 - p2pSub2 := NewSubscriber(pubsub2) + p2pSub2 := NewSubscriber[*test.DummyHeader](pubsub2, pubsub.DefaultMsgIdFn) err = p2pSub2.Start(context.Background()) require.NoError(t, err) @@ -66,24 +66,23 @@ func TestSubscriber(t *testing.T) { _, err = p2pSub2.Subscribe() require.NoError(t, err) - p2pSub1.AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult { //nolint:errcheck + p2pSub1.AddValidator(func(context.Context, *test.DummyHeader) pubsub.ValidationResult { //nolint:errcheck return pubsub.ValidationAccept }) subscription, err := p2pSub1.Subscribe() require.NoError(t, err) - expectedHeader := suite.GenExtendedHeaders(1)[0] + expectedHeader := suite.GenDummyHeaders(1)[0] bin, err := expectedHeader.MarshalBinary() require.NoError(t, err) err = p2pSub2.topic.Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) require.NoError(t, err) - // get next ExtendedHeader from network + // get next Header from network header, err := subscription.NextHeader(ctx) require.NoError(t, err) - assert.Equal(t, expectedHeader.Height, header.Height) + assert.Equal(t, expectedHeader.Height(), header.Height()) assert.Equal(t, expectedHeader.Hash(), header.Hash()) - assert.Equal(t, expectedHeader.DAH.Hash(), header.DAH.Hash()) } diff --git a/header/store/batch.go b/libs/header/store/batch.go similarity index 66% rename from header/store/batch.go rename to libs/header/store/batch.go index f1f33dc38e..89518be679 100644 --- a/header/store/batch.go +++ b/libs/header/store/batch.go @@ -3,9 +3,7 @@ package store import ( "sync" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) // batch keeps an adjacent range of headers and loosely mimics the Store @@ -15,80 +13,84 @@ import ( // unlike the Store which keeps 'hash -> header' and 'height -> hash'. // The approach simplifies implementation for the batch and // makes it better optimized for the GetByHeight case which is what we need. -type batch struct { +type batch[H header.Header] struct { lk sync.RWMutex heights map[string]uint64 - headers []*header.ExtendedHeader + headers []H } // newBatch creates the batch with the given pre-allocated size. -func newBatch(size int) *batch { - return &batch{ +func newBatch[H header.Header](size int) *batch[H] { + return &batch[H]{ heights: make(map[string]uint64, size), - headers: make([]*header.ExtendedHeader, 0, size), + headers: make([]H, 0, size), } } // Len gives current length of the batch. -func (b *batch) Len() int { +func (b *batch[H]) Len() int { b.lk.RLock() defer b.lk.RUnlock() return len(b.headers) } // GetAll returns a slice of all the headers in the batch. -func (b *batch) GetAll() []*header.ExtendedHeader { +func (b *batch[H]) GetAll() []H { b.lk.RLock() defer b.lk.RUnlock() return b.headers } // Get returns a header by its hash. -func (b *batch) Get(hash tmbytes.HexBytes) *header.ExtendedHeader { +func (b *batch[H]) Get(hash header.Hash) H { b.lk.RLock() defer b.lk.RUnlock() height, ok := b.heights[hash.String()] if !ok { - return nil + var zero H + return zero } return b.getByHeight(height) } // GetByHeight returns a header by its height. -func (b *batch) GetByHeight(height uint64) *header.ExtendedHeader { +func (b *batch[H]) GetByHeight(height uint64) H { b.lk.RLock() defer b.lk.RUnlock() return b.getByHeight(height) } -func (b *batch) getByHeight(height uint64) *header.ExtendedHeader { - ln := uint64(len(b.headers)) +func (b *batch[H]) getByHeight(height uint64) H { + var ( + ln = uint64(len(b.headers)) + zero H + ) if ln == 0 { - return nil + return zero } - head := uint64(b.headers[ln-1].Height) + head := uint64(b.headers[ln-1].Height()) base := head - ln if height > head || height <= base { - return nil + return zero } return b.headers[height-base-1] } // Append appends new headers to the batch. -func (b *batch) Append(headers ...*header.ExtendedHeader) { +func (b *batch[H]) Append(headers ...H) { b.lk.Lock() defer b.lk.Unlock() for _, h := range headers { b.headers = append(b.headers, h) - b.heights[h.Hash().String()] = uint64(h.Height) + b.heights[h.Hash().String()] = uint64(h.Height()) } } // Has checks whether header by the hash is present in the batch. -func (b *batch) Has(hash tmbytes.HexBytes) bool { +func (b *batch[H]) Has(hash header.Hash) bool { b.lk.RLock() defer b.lk.RUnlock() _, ok := b.heights[hash.String()] @@ -96,7 +98,7 @@ func (b *batch) Has(hash tmbytes.HexBytes) bool { } // Reset cleans references to batched headers. -func (b *batch) Reset() { +func (b *batch[H]) Reset() { b.lk.Lock() defer b.lk.Unlock() b.headers = b.headers[:0] diff --git a/header/store/height_indexer.go b/libs/header/store/height_indexer.go similarity index 59% rename from header/store/height_indexer.go rename to libs/header/store/height_indexer.go index f8e0697a22..fd0b1cb436 100644 --- a/header/store/height_indexer.go +++ b/libs/header/store/height_indexer.go @@ -5,36 +5,35 @@ import ( lru "github.com/hashicorp/golang-lru" "github.com/ipfs/go-datastore" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) // TODO(@Wondertan): There should be a more clever way to index heights, than just storing // HeightToHash pair... heightIndexer simply stores and cashes mappings between header Height and // Hash. -type heightIndexer struct { +type heightIndexer[H header.Header] struct { ds datastore.Batching cache *lru.ARCCache } // newHeightIndexer creates new heightIndexer. -func newHeightIndexer(ds datastore.Batching, indexCacheSize int) (*heightIndexer, error) { +func newHeightIndexer[H header.Header](ds datastore.Batching, indexCacheSize int) (*heightIndexer[H], error) { cache, err := lru.NewARC(indexCacheSize) if err != nil { return nil, err } - return &heightIndexer{ + return &heightIndexer[H]{ ds: ds, cache: cache, }, nil } // HashByHeight loads a header hash corresponding to the given height. -func (hi *heightIndexer) HashByHeight(ctx context.Context, h uint64) (tmbytes.HexBytes, error) { +func (hi *heightIndexer[H]) HashByHeight(ctx context.Context, h uint64) (header.Hash, error) { if v, ok := hi.cache.Get(h); ok { - return v.(tmbytes.HexBytes), nil + return v.(header.Hash), nil } val, err := hi.ds.Get(ctx, heightKey(h)) @@ -42,14 +41,14 @@ func (hi *heightIndexer) HashByHeight(ctx context.Context, h uint64) (tmbytes.He return nil, err } - hi.cache.Add(h, tmbytes.HexBytes(val)) + hi.cache.Add(h, header.Hash(val)) return val, nil } // IndexTo saves mapping between header Height and Hash to the given batch. -func (hi *heightIndexer) IndexTo(ctx context.Context, batch datastore.Batch, headers ...*header.ExtendedHeader) error { +func (hi *heightIndexer[H]) IndexTo(ctx context.Context, batch datastore.Batch, headers ...H) error { for _, h := range headers { - err := batch.Put(ctx, heightKey(uint64(h.Height)), h.Hash()) + err := batch.Put(ctx, heightKey(uint64(h.Height())), h.Hash()) if err != nil { return err } diff --git a/header/store/heightsub.go b/libs/header/store/heightsub.go similarity index 81% rename from header/store/heightsub.go rename to libs/header/store/heightsub.go index cb031aafda..55cb63f509 100644 --- a/header/store/heightsub.go +++ b/libs/header/store/heightsub.go @@ -6,44 +6,45 @@ import ( "sync" "sync/atomic" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) // errElapsedHeight is thrown when a requested height was already provided to heightSub. var errElapsedHeight = errors.New("elapsed height") // heightSub provides a minimalistic mechanism to wait till header for a height becomes available. -type heightSub struct { +type heightSub[H header.Header] struct { // height refers to the latest locally available header height // that has been fully verified and inserted into the subjective chain height uint64 // atomic heightReqsLk sync.Mutex - heightReqs map[uint64][]chan *header.ExtendedHeader + heightReqs map[uint64][]chan H } // newHeightSub instantiates new heightSub. -func newHeightSub() *heightSub { - return &heightSub{ - heightReqs: make(map[uint64][]chan *header.ExtendedHeader), +func newHeightSub[H header.Header]() *heightSub[H] { + return &heightSub[H]{ + heightReqs: make(map[uint64][]chan H), } } // Height reports current height. -func (hs *heightSub) Height() uint64 { +func (hs *heightSub[H]) Height() uint64 { return atomic.LoadUint64(&hs.height) } // SetHeight sets the new head height for heightSub. -func (hs *heightSub) SetHeight(height uint64) { +func (hs *heightSub[H]) SetHeight(height uint64) { atomic.StoreUint64(&hs.height, height) } // Sub subscribes for a header of a given height. // It can return errElapsedHeight, which means a requested header was already provided // and caller should get it elsewhere. -func (hs *heightSub) Sub(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { +func (hs *heightSub[H]) Sub(ctx context.Context, height uint64) (H, error) { + var zero H if hs.Height() >= height { - return nil, errElapsedHeight + return zero, errElapsedHeight } hs.heightReqsLk.Lock() @@ -52,9 +53,9 @@ func (hs *heightSub) Sub(ctx context.Context, height uint64) (*header.ExtendedHe // The lock above can park a goroutine long enough for hs.height to change for a requested height, // leaving the request never fulfilled and the goroutine deadlocked. hs.heightReqsLk.Unlock() - return nil, errElapsedHeight + return zero, errElapsedHeight } - resp := make(chan *header.ExtendedHeader, 1) + resp := make(chan H, 1) hs.heightReqs[height] = append(hs.heightReqs[height], resp) hs.heightReqsLk.Unlock() @@ -66,7 +67,7 @@ func (hs *heightSub) Sub(ctx context.Context, height uint64) (*header.ExtendedHe hs.heightReqsLk.Lock() delete(hs.heightReqs, height) hs.heightReqsLk.Unlock() - return nil, ctx.Err() + return zero, ctx.Err() } } @@ -74,14 +75,14 @@ func (hs *heightSub) Sub(ctx context.Context, height uint64) (*header.ExtendedHe // Pub is only safe when called from one goroutine. // For Pub to work correctly, heightSub has to be initialized with SetHeight // so that given headers are contiguous to the height on heightSub. -func (hs *heightSub) Pub(headers ...*header.ExtendedHeader) { +func (hs *heightSub[H]) Pub(headers ...H) { ln := len(headers) if ln == 0 { return } height := hs.Height() - from, to := uint64(headers[0].Height), uint64(headers[ln-1].Height) + from, to := uint64(headers[0].Height()), uint64(headers[ln-1].Height()) if height+1 != from { log.Fatal("PLEASE FILE A BUG REPORT: headers given to the heightSub are in the wrong order") return diff --git a/header/store/heightsub_test.go b/libs/header/store/heightsub_test.go similarity index 70% rename from header/store/heightsub_test.go rename to libs/header/store/heightsub_test.go index 5be45d17ef..990f4714ba 100644 --- a/header/store/heightsub_test.go +++ b/libs/header/store/heightsub_test.go @@ -7,19 +7,19 @@ import ( "github.com/stretchr/testify/assert" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header/test" ) func TestHeightSub(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - hs := newHeightSub() + hs := newHeightSub[*test.DummyHeader]() // assert subscription returns nil for past heights { - h := header.RandExtendedHeader(t) - h.Height = 100 + h := test.RandDummyHeader(t) + h.Raw.Height = 100 hs.SetHeight(99) hs.Pub(h) @@ -34,10 +34,10 @@ func TestHeightSub(t *testing.T) { // fixes flakiness on CI time.Sleep(time.Millisecond) - h1 := header.RandExtendedHeader(t) - h1.Height = 101 - h2 := header.RandExtendedHeader(t) - h2.Height = 102 + h1 := test.RandDummyHeader(t) + h1.Raw.Height = 101 + h2 := test.RandDummyHeader(t) + h2.Raw.Height = 102 hs.Pub(h1, h2) }() diff --git a/header/store/init.go b/libs/header/store/init.go similarity index 65% rename from header/store/init.go rename to libs/header/store/init.go index 8017e4922c..b44eee1cc1 100644 --- a/header/store/init.go +++ b/libs/header/store/init.go @@ -3,14 +3,12 @@ package store import ( "context" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) // Init ensures a Store is initialized. If it is not already initialized, // it initializes the Store by requesting the header with the given hash. -func Init(ctx context.Context, store header.Store, ex header.Exchange, hash tmbytes.HexBytes) error { +func Init[H header.Header](ctx context.Context, store header.Store[H], ex header.Exchange[H], hash header.Hash) error { _, err := store.Head(ctx) switch err { default: diff --git a/header/store/init_test.go b/libs/header/store/init_test.go similarity index 65% rename from header/store/init_test.go rename to libs/header/store/init_test.go index df24553bbc..3da167e71d 100644 --- a/header/store/init_test.go +++ b/libs/header/store/init_test.go @@ -10,38 +10,35 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/local" + "github.com/celestiaorg/celestia-node/libs/header/local" + "github.com/celestiaorg/celestia-node/libs/header/test" ) func TestInitStore_NoReinit(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) head := suite.Head() exchange := local.NewExchange(NewTestStore(ctx, t, head)) ds := sync.MutexWrap(datastore.NewMapDatastore()) - store, err := NewStore(ds) + store, err := NewStore[*test.DummyHeader](ds) require.NoError(t, err) - err = Init(ctx, store, exchange, head.Hash()) + err = Init[*test.DummyHeader](ctx, store, exchange, head.Hash()) assert.NoError(t, err) err = store.Start(ctx) require.NoError(t, err) - _, err = store.Append(ctx, suite.GenExtendedHeaders(10)...) + _, err = store.Append(ctx, suite.GenDummyHeaders(10)...) require.NoError(t, err) err = store.Stop(ctx) require.NoError(t, err) - reopenedStore, err := NewStore(ds) - require.NoError(t, err) - - err = Init(ctx, reopenedStore, exchange, head.Hash()) + reopenedStore, err := NewStore[*test.DummyHeader](ds) assert.NoError(t, err) err = reopenedStore.Start(ctx) @@ -51,8 +48,8 @@ func TestInitStore_NoReinit(t *testing.T) { require.NoError(t, err) // check that reopened head changed and the store wasn't reinitialized - assert.Equal(t, suite.Head().Height, reopenedHead.Height) - assert.NotEqual(t, head.Height, reopenedHead.Height) + assert.Equal(t, suite.Head().Height(), reopenedHead.Height()) + assert.NotEqual(t, head.Height(), reopenedHead.Height()) err = reopenedStore.Stop(ctx) require.NoError(t, err) diff --git a/header/store/keys.go b/libs/header/store/keys.go similarity index 74% rename from header/store/keys.go rename to libs/header/store/keys.go index 6f22326015..087be71a8d 100644 --- a/header/store/keys.go +++ b/libs/header/store/keys.go @@ -5,7 +5,7 @@ import ( "github.com/ipfs/go-datastore" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) var ( @@ -17,6 +17,6 @@ func heightKey(h uint64) datastore.Key { return datastore.NewKey(strconv.Itoa(int(h))) } -func headerKey(h *header.ExtendedHeader) datastore.Key { +func headerKey(h header.Header) datastore.Key { return datastore.NewKey(h.Hash().String()) } diff --git a/header/store/options.go b/libs/header/store/options.go similarity index 100% rename from header/store/options.go rename to libs/header/store/options.go diff --git a/header/store/store.go b/libs/header/store/store.go similarity index 70% rename from header/store/store.go rename to libs/header/store/store.go index 868a51de0c..59c8897323 100644 --- a/header/store/store.go +++ b/libs/header/store/store.go @@ -6,14 +6,12 @@ import ( "fmt" "sync/atomic" - logging "github.com/ipfs/go-log/v2" - lru "github.com/hashicorp/golang-lru" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" - tmbytes "github.com/tendermint/tendermint/libs/bytes" + logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) var log = logging.Logger("header/store") @@ -23,8 +21,8 @@ var ( errStoppedStore = errors.New("stopped store") ) -// Store implements the Store interface for ExtendedHeaders over Datastore. -type Store struct { +// Store implements the Store interface for Headers over Datastore. +type Store[H header.Header] struct { // header storing // // underlying KV store @@ -35,21 +33,21 @@ type Store struct { // header heights management // // maps heights to hashes - heightIndex *heightIndexer + heightIndex *heightIndexer[H] // manages current store read head height (1) and // allows callers to wait until header for a height is stored (2) - heightSub *heightSub + heightSub *heightSub[H] // writing to datastore // // queue of headers to be written - writes chan []*header.ExtendedHeader + writes chan []H // signals when writes are finished writesDn chan struct{} // writeHead maintains the current write head - writeHead atomic.Pointer[header.ExtendedHeader] + writeHead atomic.Pointer[H] // pending keeps headers pending to be written in one batch - pending *batch + pending *batch[H] Params Parameters } @@ -57,18 +55,18 @@ type Store struct { // NewStore constructs a Store over datastore. // The datastore must have a head there otherwise Start will error. // For first initialization of Store use NewStoreWithHead. -func NewStore(ds datastore.Batching, opts ...Option) (*Store, error) { - return newStore(ds, opts...) +func NewStore[H header.Header](ds datastore.Batching, opts ...Option) (*Store[H], error) { + return newStore[H](ds, opts...) } // NewStoreWithHead initiates a new Store and forcefully sets a given trusted header as head. -func NewStoreWithHead( +func NewStoreWithHead[H header.Header]( ctx context.Context, ds datastore.Batching, - head *header.ExtendedHeader, + head H, opts ...Option, -) (*Store, error) { - store, err := newStore(ds, opts...) +) (*Store[H], error) { + store, err := newStore[H](ds, opts...) if err != nil { return nil, err } @@ -76,7 +74,7 @@ func NewStoreWithHead( return store, store.Init(ctx, head) } -func newStore(ds datastore.Batching, opts ...Option) (*Store, error) { +func newStore[H header.Header](ds datastore.Batching, opts ...Option) (*Store[H], error) { params := DefaultParameters() for _, opt := range opts { opt(¶ms) @@ -92,40 +90,40 @@ func newStore(ds datastore.Batching, opts ...Option) (*Store, error) { } wrappedStore := namespace.Wrap(ds, storePrefix) - index, err := newHeightIndexer(wrappedStore, params.IndexCacheSize) + index, err := newHeightIndexer[H](wrappedStore, params.IndexCacheSize) if err != nil { return nil, fmt.Errorf("failed to create height indexer: %w", err) } - return &Store{ + return &Store[H]{ Params: params, ds: wrappedStore, - heightSub: newHeightSub(), - writes: make(chan []*header.ExtendedHeader, 16), + heightSub: newHeightSub[H](), + writes: make(chan []H, 16), writesDn: make(chan struct{}), cache: cache, heightIndex: index, - pending: newBatch(params.WriteBatchSize), + pending: newBatch[H](params.WriteBatchSize), }, nil } -func (s *Store) Init(ctx context.Context, initial *header.ExtendedHeader) error { +func (s *Store[H]) Init(ctx context.Context, initial H) error { // trust the given header as the initial head err := s.flush(ctx, initial) if err != nil { return err } - log.Infow("initialized head", "height", initial.Height, "hash", initial.Hash()) + log.Infow("initialized head", "height", initial.Height(), "hash", initial.Hash()) return nil } -func (s *Store) Start(context.Context) error { +func (s *Store[H]) Start(context.Context) error { go s.flushLoop() return nil } -func (s *Store) Stop(ctx context.Context) error { +func (s *Store[H]) Stop(ctx context.Context) error { select { case <-s.writesDn: return errStoppedStore @@ -145,59 +143,64 @@ func (s *Store) Stop(ctx context.Context) error { return nil } -func (s *Store) Height() uint64 { +func (s *Store[H]) Height() uint64 { return s.heightSub.Height() } -func (s *Store) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Store[H]) Head(ctx context.Context) (H, error) { head, err := s.GetByHeight(ctx, s.heightSub.Height()) if err == nil { return head, nil } + var zero H head, err = s.readHead(ctx) switch err { default: - return nil, err + return zero, err case datastore.ErrNotFound, header.ErrNotFound: - return nil, header.ErrNoHead + return zero, header.ErrNoHead case nil: - s.heightSub.SetHeight(uint64(head.Height)) - log.Infow("loaded head", "height", head.Height, "hash", head.Hash()) + s.heightSub.SetHeight(uint64(head.Height())) + log.Infow("loaded head", "height", head.Height(), "hash", head.Hash()) return head, nil } } -func (s *Store) Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) { +func (s *Store[H]) Get(ctx context.Context, hash header.Hash) (H, error) { + var zero H if v, ok := s.cache.Get(hash.String()); ok { - return v.(*header.ExtendedHeader), nil + return v.(H), nil } // check if the requested header is not yet written on disk - if h := s.pending.Get(hash); h != nil { + if h := s.pending.Get(hash); !h.IsZero() { return h, nil } b, err := s.ds.Get(ctx, datastore.NewKey(hash.String())) if err != nil { if err == datastore.ErrNotFound { - return nil, header.ErrNotFound + return zero, header.ErrNotFound } - return nil, err + return zero, err } - h, err := header.UnmarshalExtendedHeader(b) + var empty H + h := empty.New() + err = h.UnmarshalBinary(b) if err != nil { - return nil, err + return zero, err } s.cache.Add(h.Hash().String(), h) - return h, nil + return h.(H), nil } -func (s *Store) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { +func (s *Store[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { + var zero H if height == 0 { - return nil, fmt.Errorf("header/store: height must be bigger than zero") + return zero, fmt.Errorf("header/store: height must be bigger than zero") } // if the requested 'height' was not yet published // we subscribe to it @@ -209,30 +212,30 @@ func (s *Store) GetByHeight(ctx context.Context, height uint64) (*header.Extende // which means the requested 'height' should be present // // check if the requested header is not yet written on disk - if h := s.pending.GetByHeight(height); h != nil { + if h := s.pending.GetByHeight(height); !h.IsZero() { return h, nil } hash, err := s.heightIndex.HashByHeight(ctx, height) if err != nil { if err == datastore.ErrNotFound { - return nil, header.ErrNotFound + return zero, header.ErrNotFound } - return nil, err + return zero, err } return s.Get(ctx, hash) } -func (s *Store) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { +func (s *Store[H]) GetRangeByHeight(ctx context.Context, from, to uint64) ([]H, error) { h, err := s.GetByHeight(ctx, to-1) if err != nil { return nil, err } ln := to - from - headers := make([]*header.ExtendedHeader, ln) + headers := make([]H, ln) for i := ln - 1; i > 0; i-- { headers[i] = h h, err = s.Get(ctx, h.LastHeader()) @@ -245,15 +248,15 @@ func (s *Store) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*heade return headers, nil } -func (s *Store) GetVerifiedRange( +func (s *Store[H]) GetVerifiedRange( ctx context.Context, - from *header.ExtendedHeader, + from H, to uint64, -) ([]*header.ExtendedHeader, error) { - if uint64(from.Height) >= to { - return nil, fmt.Errorf("header/store: invalid range(%d,%d)", from.Height, to) +) ([]H, error) { + if uint64(from.Height()) >= to { + return nil, fmt.Errorf("header/store: invalid range(%d,%d)", from.Height(), to) } - headers, err := s.GetRangeByHeight(ctx, uint64(from.Height)+1, to) + headers, err := s.GetRangeByHeight(ctx, uint64(from.Height())+1, to) if err != nil { return nil, err } @@ -268,7 +271,7 @@ func (s *Store) GetVerifiedRange( return headers, nil } -func (s *Store) Has(ctx context.Context, hash tmbytes.HexBytes) (bool, error) { +func (s *Store[H]) Has(ctx context.Context, hash header.Hash) (bool, error) { if ok := s.cache.Contains(hash.String()); ok { return ok, nil } @@ -280,7 +283,7 @@ func (s *Store) Has(ctx context.Context, hash tmbytes.HexBytes) (bool, error) { return s.ds.Has(ctx, datastore.NewKey(hash.String())) } -func (s *Store) Append(ctx context.Context, headers ...*header.ExtendedHeader) (int, error) { +func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { lh := len(headers) if lh == 0 { return 0, nil @@ -288,25 +291,28 @@ func (s *Store) Append(ctx context.Context, headers ...*header.ExtendedHeader) ( var err error // take current write head to verify headers against - head := s.writeHead.Load() - if head == nil { + var head H + headPtr := s.writeHead.Load() + if headPtr == nil { head, err = s.Head(ctx) if err != nil { return 0, err } + } else { + head = *headPtr } // collect valid headers - verified := make([]*header.ExtendedHeader, 0, lh) + verified := make([]H, 0, lh) for i, h := range headers { err = head.VerifyAdjacent(h) if err != nil { var verErr *header.VerifyError if errors.As(err, &verErr) { log.Errorw("invalid header", - "height_of_head", head.Height, + "height_of_head", head.Height(), "hash_of_head", head.Hash(), - "height_of_invalid", h.Height, + "height_of_invalid", h.Height(), "hash_of_invalid", h.Hash(), "reason", verErr.Reason) } @@ -325,9 +331,9 @@ func (s *Store) Append(ctx context.Context, headers ...*header.ExtendedHeader) ( select { case s.writes <- verified: ln := len(verified) - s.writeHead.Store(verified[ln-1]) - wh := s.writeHead.Load() - log.Infow("new head", "height", wh.Height, "hash", wh.Hash()) + s.writeHead.Store(&verified[ln-1]) + wh := *s.writeHead.Load() + log.Infow("new head", "height", wh.Height(), "hash", wh.Hash()) // we return an error here after writing, // as there might be an invalid header in between of a given range return ln, err @@ -342,7 +348,7 @@ func (s *Store) Append(ctx context.Context, headers ...*header.ExtendedHeader) ( // This way writes are controlled and manageable from one place allowing // (1) Appends not to be blocked on long disk IO writes and underlying DB compactions // (2) Batching header writes -func (s *Store) flushLoop() { +func (s *Store[H]) flushLoop() { defer close(s.writesDn) ctx := context.Background() for headers := range s.writes { @@ -361,7 +367,7 @@ func (s *Store) flushLoop() { err := s.flush(ctx, s.pending.GetAll()...) if err != nil { // TODO(@Wondertan): Should this be a fatal error case with os.Exit? - from, to := uint64(headers[0].Height), uint64(headers[len(headers)-1].Height) + from, to := uint64(headers[0].Height()), uint64(headers[len(headers)-1].Height()) log.Errorw("writing header batch", "from", from, "to", to) continue } @@ -376,7 +382,7 @@ func (s *Store) flushLoop() { } // flush writes the given batch to datastore. -func (s *Store) flush(ctx context.Context, headers ...*header.ExtendedHeader) error { +func (s *Store[H]) flush(ctx context.Context, headers ...H) error { ln := len(headers) if ln == 0 { return nil @@ -422,16 +428,17 @@ func (s *Store) flush(ctx context.Context, headers ...*header.ExtendedHeader) er } // readHead loads the head from the datastore. -func (s *Store) readHead(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Store[H]) readHead(ctx context.Context) (H, error) { + var zero H b, err := s.ds.Get(ctx, headKey) if err != nil { - return nil, err + return zero, err } - var head tmbytes.HexBytes + var head header.Hash err = head.UnmarshalJSON(b) if err != nil { - return nil, err + return zero, err } return s.Get(ctx, head) diff --git a/header/store/store_test.go b/libs/header/store/store_test.go similarity index 83% rename from header/store/store_test.go rename to libs/header/store/store_test.go index 460c5e78cd..e9aca94f59 100644 --- a/header/store/store_test.go +++ b/libs/header/store/store_test.go @@ -10,16 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - tmrand "github.com/tendermint/tendermint/libs/rand" - - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header/test" ) func TestStore(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) ds := sync.MutexWrap(datastore.NewMapDatastore()) store, err := NewStoreWithHead(ctx, ds, suite.Head()) @@ -32,7 +30,7 @@ func TestStore(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, suite.Head().Hash(), head.Hash()) - in := suite.GenExtendedHeaders(10) + in := suite.GenDummyHeaders(10) ln, err := store.Append(ctx, in...) require.NoError(t, err) assert.Equal(t, 10, ln) @@ -51,12 +49,12 @@ func TestStore(t *testing.T) { require.NoError(t, err) assert.True(t, ok) - ok, err = store.Has(ctx, tmrand.Bytes(32)) + ok, err = store.Has(ctx, test.RandBytes(32)) require.NoError(t, err) assert.False(t, ok) go func() { - ln, err := store.Append(ctx, suite.GenExtendedHeaders(1)...) + ln, err := store.Append(ctx, suite.GenDummyHeaders(1)...) require.NoError(t, err) assert.Equal(t, 1, ln) }() @@ -70,7 +68,7 @@ func TestStore(t *testing.T) { // check that the store can be successfully started after previous stop // with all data being flushed. - store, err = NewStore(ds) + store, err = NewStore[*test.DummyHeader](ds) require.NoError(t, err) err = store.Start(ctx) @@ -92,7 +90,7 @@ func TestStorePendingCacheMiss(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) ds := sync.MutexWrap(datastore.NewMapDatastore()) @@ -104,10 +102,10 @@ func TestStorePendingCacheMiss(t *testing.T) { err = store.Start(ctx) require.NoError(t, err) - _, err = store.Append(ctx, suite.GenExtendedHeaders(100)...) + _, err = store.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) - _, err = store.Append(ctx, suite.GenExtendedHeaders(50)...) + _, err = store.Append(ctx, suite.GenDummyHeaders(50)...) require.NoError(t, err) _, err = store.GetRangeByHeight(ctx, 1, 101) diff --git a/header/store/testing.go b/libs/header/store/testing.go similarity index 70% rename from header/store/testing.go rename to libs/header/store/testing.go index 2a6a3acb0c..a472193bda 100644 --- a/header/store/testing.go +++ b/libs/header/store/testing.go @@ -8,11 +8,12 @@ import ( "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/test" ) // NewTestStore creates initialized and started in memory header Store which is useful for testing. -func NewTestStore(ctx context.Context, t *testing.T, head *header.ExtendedHeader) header.Store { +func NewTestStore(ctx context.Context, t *testing.T, head *test.DummyHeader) header.Store[*test.DummyHeader] { store, err := NewStoreWithHead(ctx, sync.MutexWrap(datastore.NewMapDatastore()), head) require.NoError(t, err) diff --git a/header/sync/options.go b/libs/header/sync/options.go similarity index 100% rename from header/sync/options.go rename to libs/header/sync/options.go diff --git a/header/sync/ranges.go b/libs/header/sync/ranges.go similarity index 68% rename from header/sync/ranges.go rename to libs/header/sync/ranges.go index c4f38b709f..99047c4093 100644 --- a/header/sync/ranges.go +++ b/libs/header/sync/ranges.go @@ -3,38 +3,39 @@ package sync import ( "sync" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) // ranges keeps non-overlapping and non-adjacent header ranges which are used to cache headers (in // ascending order). This prevents unnecessary / duplicate network requests for additional headers // during sync. -type ranges struct { +type ranges[H header.Header] struct { lk sync.RWMutex - ranges []*headerRange + ranges []*headerRange[H] } -// Head returns the highest ExtendedHeader in all ranges if any. -func (rs *ranges) Head() *header.ExtendedHeader { +// Head returns the highest Header in all ranges if any. +func (rs *ranges[H]) Head() H { rs.lk.RLock() defer rs.lk.RUnlock() ln := len(rs.ranges) if ln == 0 { - return nil + var zero H + return zero } head := rs.ranges[ln-1] return head.Head() } -// Add appends the new ExtendedHeader to existing range or starts a new one. -// It starts a new one if the new ExtendedHeader is not adjacent to any of existing ranges. -func (rs *ranges) Add(h *header.ExtendedHeader) { +// Add appends the new Header to existing range or starts a new one. +// It starts a new one if the new Header is not adjacent to any of existing ranges. +func (rs *ranges[H]) Add(h H) { head := rs.Head() // short-circuit if header is from the past - if head != nil && head.Height >= h.Height { + if !head.IsZero() && head.Height() >= h.Height() { // TODO(@Wondertan): Technically, we can still apply the header: // * Headers here are verified, so we can trust them // * PubSub does not guarantee the ordering of msgs @@ -50,12 +51,12 @@ func (rs *ranges) Add(h *header.ExtendedHeader) { defer rs.lk.Unlock() // if the new header is adjacent to head - if head != nil && h.Height == head.Height+1 { + if !head.IsZero() && h.Height() == head.Height()+1 { // append it to the last known range rs.ranges[len(rs.ranges)-1].Append(h) } else { // otherwise, start a new range - rs.ranges = append(rs.ranges, newRange(h)) + rs.ranges = append(rs.ranges, newRange[H](h)) // it is possible to miss a header or few from PubSub, due to quick disconnects or sleep // once we start rcving them again we save those in new range @@ -65,7 +66,7 @@ func (rs *ranges) Add(h *header.ExtendedHeader) { // FirstRangeWithin checks if the first range is within a given height span [start:end] // and returns it. -func (rs *ranges) FirstRangeWithin(start, end uint64) (*headerRange, bool) { +func (rs *ranges[H]) FirstRangeWithin(start, end uint64) (*headerRange[H], bool) { r, ok := rs.First() if !ok { return nil, false @@ -79,7 +80,7 @@ func (rs *ranges) FirstRangeWithin(start, end uint64) (*headerRange, bool) { } // First provides a first non-empty range, while cleaning up empty ones. -func (rs *ranges) First() (*headerRange, bool) { +func (rs *ranges[H]) First() (*headerRange[H], bool) { rs.lk.Lock() defer rs.lk.Unlock() @@ -97,46 +98,47 @@ func (rs *ranges) First() (*headerRange, bool) { } } -type headerRange struct { +type headerRange[H header.Header] struct { lk sync.RWMutex - headers []*header.ExtendedHeader + headers []H start uint64 } -func newRange(h *header.ExtendedHeader) *headerRange { - return &headerRange{ - start: uint64(h.Height), - headers: []*header.ExtendedHeader{h}, +func newRange[H header.Header](h H) *headerRange[H] { + return &headerRange[H]{ + start: uint64(h.Height()), + headers: []H{h}, } } // Append appends new headers. -func (r *headerRange) Append(h ...*header.ExtendedHeader) { +func (r *headerRange[H]) Append(h ...H) { r.lk.Lock() r.headers = append(r.headers, h...) r.lk.Unlock() } // Empty reports if range is empty. -func (r *headerRange) Empty() bool { +func (r *headerRange[H]) Empty() bool { r.lk.RLock() defer r.lk.RUnlock() return len(r.headers) == 0 } // Head reports the head of range if any. -func (r *headerRange) Head() *header.ExtendedHeader { +func (r *headerRange[H]) Head() H { r.lk.RLock() defer r.lk.RUnlock() ln := len(r.headers) if ln == 0 { - return nil + var zero H + return zero } return r.headers[ln-1] } // Before truncates all the headers before height 'end' - [r.Start:end] -func (r *headerRange) Before(end uint64) ([]*header.ExtendedHeader, uint64) { +func (r *headerRange[H]) Before(end uint64) ([]H, uint64) { r.lk.Lock() defer r.lk.Unlock() @@ -148,7 +150,7 @@ func (r *headerRange) Before(end uint64) ([]*header.ExtendedHeader, uint64) { out := r.headers[:amnt] r.headers = r.headers[amnt:] if len(r.headers) != 0 { - r.start = uint64(r.headers[0].Height) + r.start = uint64(r.headers[0].Height()) } return out, amnt } diff --git a/header/sync/sync.go b/libs/header/sync/sync.go similarity index 82% rename from header/sync/sync.go rename to libs/header/sync/sync.go index c2b90d6f02..ecd9475c3e 100644 --- a/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -7,9 +7,8 @@ import ( "time" logging "github.com/ipfs/go-log/v2" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) var log = logging.Logger("header/sync") @@ -32,10 +31,10 @@ var log = logging.Logger("header/sync") // verifies against the latest known trusted header // adds the header to pending cache(making it the latest known trusted header) // and triggers syncing loop to catch up to that point. -type Syncer struct { - sub header.Subscriber - exchange header.Exchange - store header.Store +type Syncer[H header.Header] struct { + sub header.Subscriber[H] + exchange header.Exchange[H] + store header.Store[H] // stateLk protects state which represents the current or latest sync stateLk sync.RWMutex @@ -43,7 +42,7 @@ type Syncer struct { // signals to start syncing triggerSync chan struct{} // pending keeps ranges of valid new network headers awaiting to be appended to store - pending ranges + pending ranges[H] // netReqLk ensures only one network head is requested at any moment netReqLk sync.RWMutex @@ -55,7 +54,12 @@ type Syncer struct { } // NewSyncer creates a new instance of Syncer. -func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscriber, opts ...Options) (*Syncer, error) { +func NewSyncer[H header.Header]( + exchange header.Exchange[H], + store header.Store[H], + sub header.Subscriber[H], + opts ...Options, +) (*Syncer[H], error) { params := DefaultParameters() for _, opt := range opts { opt(¶ms) @@ -64,7 +68,7 @@ func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscrib return nil, err } - return &Syncer{ + return &Syncer[H]{ sub: sub, exchange: exchange, store: store, @@ -74,7 +78,7 @@ func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscrib } // Start starts the syncing routine. -func (s *Syncer) Start(ctx context.Context) error { +func (s *Syncer[H]) Start(ctx context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) // register validator for header subscriptions // syncer does not subscribe itself and syncs headers together with validation @@ -93,13 +97,13 @@ func (s *Syncer) Start(ctx context.Context) error { } // Stop stops Syncer. -func (s *Syncer) Stop(context.Context) error { +func (s *Syncer[H]) Stop(context.Context) error { s.cancel() return nil } // WaitSync blocks until ongoing sync is done. -func (s *Syncer) WaitSync(ctx context.Context) error { +func (s *Syncer[H]) WaitSync(ctx context.Context) error { state := s.State() if state.Finished() { return nil @@ -115,7 +119,7 @@ type State struct { ID uint64 // incrementing ID of a sync Height uint64 // height at the moment when State is requested for a sync FromHeight, ToHeight uint64 // the starting and the ending point of a sync - FromHash, ToHash tmbytes.HexBytes + FromHash, ToHash header.Hash Start, End time.Time Error error // the error that might happen within a sync } @@ -134,7 +138,7 @@ func (s State) Duration() time.Duration { // Note that throughout the whole Syncer lifetime there might an initial sync and multiple // catch-ups. All of them are treated as different syncs with different state IDs and other // information. -func (s *Syncer) State() State { +func (s *Syncer[H]) State() State { s.stateLk.RLock() state := s.state s.stateLk.RUnlock() @@ -143,7 +147,7 @@ func (s *Syncer) State() State { } // wantSync will trigger the syncing loop (non-blocking). -func (s *Syncer) wantSync() { +func (s *Syncer[H]) wantSync() { select { case s.triggerSync <- struct{}{}: default: @@ -151,7 +155,7 @@ func (s *Syncer) wantSync() { } // syncLoop controls syncing process. -func (s *Syncer) syncLoop() { +func (s *Syncer[H]) syncLoop() { for { select { case <-s.triggerSync: @@ -163,9 +167,9 @@ func (s *Syncer) syncLoop() { } // sync ensures we are synced from the Store's head up to the new subjective head -func (s *Syncer) sync(ctx context.Context) { +func (s *Syncer[H]) sync(ctx context.Context) { newHead := s.pending.Head() - if newHead == nil { + if newHead.IsZero() { return } @@ -175,18 +179,18 @@ func (s *Syncer) sync(ctx context.Context) { return } - if head.Height >= newHead.Height { + if head.Height() >= newHead.Height() { log.Warnw("sync attempt to an already synced header", - "synced_height", head.Height, - "attempted_height", newHead.Height, + "synced_height", head.Height(), + "attempted_height", newHead.Height(), ) log.Warn("PLEASE REPORT THIS AS A BUG") return // should never happen, but just in case } log.Infow("syncing headers", - "from", head.Height, - "to", newHead.Height) + "from", head.Height(), + "to", newHead.Height()) err = s.doSync(ctx, head, newHead) if err != nil { if errors.Is(err, context.Canceled) { @@ -195,21 +199,21 @@ func (s *Syncer) sync(ctx context.Context) { } log.Errorw("syncing headers", - "from", head.Height, - "to", newHead.Height, + "from", head.Height(), + "to", newHead.Height(), "err", err) return } log.Infow("finished syncing", - "from", head.Height, - "to", newHead.Height, + "from", head.Height(), + "to", newHead.Height(), "elapsed time", s.state.End.Sub(s.state.Start)) } // doSync performs actual syncing updating the internal State -func (s *Syncer) doSync(ctx context.Context, fromHead, toHead *header.ExtendedHeader) (err error) { - from, to := uint64(fromHead.Height)+1, uint64(toHead.Height) +func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) { + from, to := uint64(fromHead.Height())+1, uint64(toHead.Height()) s.stateLk.Lock() s.state.ID++ @@ -236,7 +240,7 @@ func (s *Syncer) doSync(ctx context.Context, fromHead, toHead *header.ExtendedHe // processHeaders gets and stores headers starting at the given 'from' height up to 'to' height - // [from:to] -func (s *Syncer) processHeaders(ctx context.Context, from, to uint64) (int, error) { +func (s *Syncer[H]) processHeaders(ctx context.Context, from, to uint64) (int, error) { headers, err := s.findHeaders(ctx, from, to) if err != nil { return 0, err @@ -247,13 +251,13 @@ func (s *Syncer) processHeaders(ctx context.Context, from, to uint64) (int, erro // findHeaders gets headers from either remote peers or from local cache of headers received by // PubSub - [from:to] -func (s *Syncer) findHeaders(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { +func (s *Syncer[H]) findHeaders(ctx context.Context, from, to uint64) ([]H, error) { amount := to - from + 1 // + 1 to include 'to' height as well if amount > s.Params.MaxRequestSize { to, amount = from+s.Params.MaxRequestSize, s.Params.MaxRequestSize } - out := make([]*header.ExtendedHeader, 0, amount) + out := make([]H, 0, amount) for from < to { // if we have some range cached - use it r, ok := s.pending.FirstRangeWithin(from, to) diff --git a/header/sync/sync_head.go b/libs/header/sync/sync_head.go similarity index 73% rename from header/sync/sync_head.go rename to libs/header/sync/sync_head.go index 1ec33b17f7..1bdb818c4e 100644 --- a/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -3,56 +3,60 @@ package sync import ( "context" "errors" + "time" pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) // Head returns the Syncer's latest known header. It calls 'networkHead' in order to // either return or eagerly fetch the most recent header. -func (s *Syncer) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Syncer[H]) Head(ctx context.Context) (H, error) { return s.networkHead(ctx) } // subjectiveHead returns the latest known local header that is not expired(within trusting period). // If the header is expired, it is retrieved from a trusted peer without validation; // in other words, an automatic subjective initialization is performed. -func (s *Syncer) subjectiveHead(ctx context.Context) (*header.ExtendedHeader, error) { - // pending head is the latest known subjective head Syncer syncs to, so try to get it - // NOTES: - // * Empty when no sync is in progress - // * Pending cannot be expired, guaranteed - pendHead := s.pending.Head() - if pendHead != nil { +func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) { + var ( + // pending head is the latest known subjective head Syncer syncs to, so try to get it + // NOTES: + // * Empty when no sync is in progress + // * Pending cannot be expired, guaranteed + pendHead = s.pending.Head() + zero H + ) + if !pendHead.IsZero() { return pendHead, nil } // if empty, get subjective head out of the store netHead, err := s.store.Head(ctx) if err != nil { - return nil, err + return zero, err } // check if our subjective header is not expired and use it - if !netHead.IsExpired(s.Params.TrustingPeriod) { + if !isExpired(netHead, s.Params.TrustingPeriod) { return netHead, nil } - log.Infow("subjective header expired", "height", netHead.Height) + log.Infow("subjective header expired", "height", netHead.Height()) // otherwise, request network head from a trusted peer netHead, err = s.exchange.Head(ctx) if err != nil { - return nil, err + return zero, err } // and set as the new subjective head without validation, // or, in other words, do 'automatic subjective initialization' s.newNetHead(ctx, netHead, true) switch { default: - log.Infow("subjective initialization finished", "height", netHead.Height) + log.Infow("subjective initialization finished", "height", netHead.Height()) return netHead, nil - case netHead.IsExpired(s.Params.TrustingPeriod): - log.Warnw("subjective initialization with an expired header", "height", netHead.Height) - case !netHead.IsRecent(s.Params.blockTime): - log.Warnw("subjective initialization with an old header", "height", netHead.Height) + case isExpired(netHead, s.Params.TrustingPeriod): + log.Warnw("subjective initialization with an expired header", "height", netHead.Height()) + case !isRecent(netHead, s.Params.blockTime): + log.Warnw("subjective initialization with an old header", "height", netHead.Height()) } log.Warn("trusted peer is out of sync") return netHead, nil @@ -62,13 +66,14 @@ func (s *Syncer) subjectiveHead(ctx context.Context) (*header.ExtendedHeader, er // Known subjective head is considered network head if it is recent // enough(now-timestamp<=blocktime). Otherwise, network header is requested from a trusted peer and // set as the new subjective head, assuming that trusted peer is always synced. -func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Syncer[H]) networkHead(ctx context.Context) (H, error) { + var zero H sbjHead, err := s.subjectiveHead(ctx) if err != nil { - return nil, err + return zero, err } // if subjective header is recent enough (relative to the network's block time) - just use it - if sbjHead.IsRecent(s.Params.blockTime) { + if isRecent(sbjHead, s.Params.blockTime) { return sbjHead, nil } // otherwise, request head from a trusted peer, as we assume it is fully synced @@ -88,7 +93,7 @@ func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error // * This way we don't request as we know the new network header arrives exactly netHead, err := s.exchange.Head(ctx) if err != nil { - return nil, err + return zero, err } // process netHead returned from the trusted peer and validate against the subjective head // NOTE: We could trust the netHead like we do during 'automatic subjective initialization' @@ -101,7 +106,7 @@ func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error } // incomingNetHead processes new gossiped network headers. -func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHeader) pubsub.ValidationResult { +func (s *Syncer[H]) incomingNetHead(ctx context.Context, netHead H) pubsub.ValidationResult { // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network // header _, err := s.store.Append(ctx, netHead) @@ -121,7 +126,7 @@ func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHe } // might be a storage error or something else, but we can still try to continue processing netHead log.Errorw("appending network header", - "height", netHead.Height, + "height", netHead.Height(), "hash", netHead.Hash().String(), "err", err) } @@ -131,7 +136,7 @@ func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHe // newNetHead sets the network header as the new subjective head with preceding validation(per // request). -func (s *Syncer) newNetHead(ctx context.Context, netHead *header.ExtendedHeader, trust bool) pubsub.ValidationResult { +func (s *Syncer[H]) newNetHead(ctx context.Context, netHead H, trust bool) pubsub.ValidationResult { // validate netHead against subjective head if !trust { if res := s.validate(ctx, netHead); res != pubsub.ValidationAccept { @@ -142,22 +147,22 @@ func (s *Syncer) newNetHead(ctx context.Context, netHead *header.ExtendedHeader, // and if valid, set it as new subjective head s.pending.Add(netHead) s.wantSync() - log.Infow("new network head", "height", netHead.Height, "hash", netHead.Hash()) + log.Infow("new network head", "height", netHead.Height(), "hash", netHead.Hash()) return pubsub.ValidationAccept } // validate checks validity of the given header against the subjective head. -func (s *Syncer) validate(ctx context.Context, new *header.ExtendedHeader) pubsub.ValidationResult { +func (s *Syncer[H]) validate(ctx context.Context, new H) pubsub.ValidationResult { sbjHead, err := s.subjectiveHead(ctx) if err != nil { log.Errorw("getting subjective head during validation", "err", err) return pubsub.ValidationIgnore // local error, so ignore } // ignore header if it's from the past - if !sbjHead.IsBefore(new) { + if new.Height() <= sbjHead.Height() { log.Warnw("received known network header", - "current_height", sbjHead.Height, - "header_height", new.Height, + "current_height", sbjHead.Height(), + "header_height", new.Height(), "header_hash", new.Hash()) return pubsub.ValidationIgnore } @@ -166,9 +171,9 @@ func (s *Syncer) validate(ctx context.Context, new *header.ExtendedHeader) pubsu var verErr *header.VerifyError if errors.As(err, &verErr) { log.Errorw("invalid network header", - "height_of_invalid", new.Height, + "height_of_invalid", new.Height(), "hash_of_invalid", new.Hash(), - "height_of_subjective", sbjHead.Height, + "height_of_subjective", sbjHead.Height(), "hash_of_subjective", sbjHead.Hash(), "reason", verErr.Reason) return pubsub.ValidationReject @@ -176,3 +181,14 @@ func (s *Syncer) validate(ctx context.Context, new *header.ExtendedHeader) pubsu // and accept if the header is good return pubsub.ValidationAccept } + +// isExpired checks if header is expired against trusting period. +func isExpired(header header.Header, period time.Duration) bool { + expirationTime := header.Time().Add(period) + return !expirationTime.After(time.Now()) +} + +// isRecent checks if header is recent against the given blockTime. +func isRecent(header header.Header, blockTime time.Duration) bool { + return time.Since(header.Time()) <= blockTime // TODO @renaynay: should we allow for a 5-10 block drift here? +} diff --git a/header/sync/sync_test.go b/libs/header/sync/sync_test.go similarity index 69% rename from header/sync/sync_test.go rename to libs/header/sync/sync_test.go index 174312b193..3f937ee00c 100644 --- a/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -8,32 +8,32 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/bytes" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/local" - "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/local" + "github.com/celestiaorg/celestia-node/libs/header/store" + "github.com/celestiaorg/celestia-node/libs/header/test" ) func TestSyncSimpleRequestingHead(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) head := suite.Head() remoteStore := store.NewTestStore(ctx, t, head) - _, err := remoteStore.Append(ctx, suite.GenExtendedHeaders(100)...) + _, err := remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) _, err = remoteStore.GetByHeight(ctx, 100) require.NoError(t, err) localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer( + syncer, err := NewSyncer[*test.DummyHeader]( local.NewExchange(remoteStore), localStore, - &header.DummySubscriber{}, + &test.DummySubscriber{}, WithBlockTime(time.Second*30), WithTrustingPeriod(time.Microsecond), WithMaxRequestSize(13), @@ -51,13 +51,13 @@ func TestSyncSimpleRequestingHead(t *testing.T) { have, err := localStore.Head(ctx) require.NoError(t, err) - assert.Equal(t, exp.Height, have.Height) + assert.Equal(t, exp.Height(), have.Height()) assert.Empty(t, syncer.pending.Head()) state := syncer.State() - assert.Equal(t, uint64(exp.Height), state.Height) + assert.Equal(t, uint64(exp.Height()), state.Height) assert.Equal(t, uint64(2), state.FromHeight) - assert.Equal(t, uint64(exp.Height), state.ToHeight) + assert.Equal(t, uint64(exp.Height()), state.ToHeight) assert.True(t, state.Finished(), state) } @@ -65,15 +65,15 @@ func TestSyncCatchUp(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) head := suite.Head() remoteStore := store.NewTestStore(ctx, t, head) localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer( + syncer, err := NewSyncer[*test.DummyHeader]( local.NewExchange(remoteStore), localStore, - &header.DummySubscriber{}, + &test.DummySubscriber{}, WithTrustingPeriod(time.Minute), ) require.NoError(t, err) @@ -82,11 +82,11 @@ func TestSyncCatchUp(t *testing.T) { require.NoError(t, err) // 2. chain grows and syncer misses that - _, err = remoteStore.Append(ctx, suite.GenExtendedHeaders(100)...) + _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) // 3. syncer rcvs header from the future and starts catching-up - res := syncer.incomingNetHead(ctx, suite.GenExtendedHeaders(1)[0]) + res := syncer.incomingNetHead(ctx, suite.GenDummyHeaders(1)[0]) assert.Equal(t, pubsub.ValidationAccept, res) time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing @@ -99,13 +99,13 @@ func TestSyncCatchUp(t *testing.T) { // 4. assert syncer caught-up have, err := localStore.Head(ctx) require.NoError(t, err) - assert.Equal(t, exp.Height+1, have.Height) // plus one as we didn't add last header to remoteStore + assert.Equal(t, exp.Height()+1, have.Height()) // plus one as we didn't add last header to remoteStore assert.Empty(t, syncer.pending.Head()) state := syncer.State() - assert.Equal(t, uint64(exp.Height+1), state.Height) + assert.Equal(t, uint64(exp.Height()+1), state.Height) assert.Equal(t, uint64(2), state.FromHeight) - assert.Equal(t, uint64(exp.Height+1), state.ToHeight) + assert.Equal(t, uint64(exp.Height()+1), state.ToHeight) assert.True(t, state.Finished(), state) } @@ -113,15 +113,15 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) head := suite.Head() remoteStore := store.NewTestStore(ctx, t, head) localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer( + syncer, err := NewSyncer[*test.DummyHeader]( local.NewExchange(remoteStore), localStore, - &header.DummySubscriber{}, + &test.DummySubscriber{}, WithTrustingPeriod(time.Minute), ) require.NoError(t, err) @@ -129,18 +129,18 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { require.NoError(t, err) // miss 1 (helps to test that Syncer properly requests missed Headers from Exchange) - _, err = remoteStore.Append(ctx, suite.GenExtendedHeaders(1)...) + _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(1)...) require.NoError(t, err) - range1 := suite.GenExtendedHeaders(15) + range1 := suite.GenDummyHeaders(15) _, err = remoteStore.Append(ctx, range1...) require.NoError(t, err) // miss 2 - _, err = remoteStore.Append(ctx, suite.GenExtendedHeaders(3)...) + _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(3)...) require.NoError(t, err) - range2 := suite.GenExtendedHeaders(23) + range2 := suite.GenDummyHeaders(23) _, err = remoteStore.Append(ctx, range2...) require.NoError(t, err) @@ -163,7 +163,7 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { have, err := localStore.Head(ctx) require.NoError(t, err) - assert.Equal(t, exp.Height, have.Height) + assert.Equal(t, exp.Height(), have.Height()) assert.Empty(t, syncer.pending.Head()) // assert all cache from pending is used } @@ -172,14 +172,14 @@ func TestSyncer_OnlyOneRecentRequest(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - suite := header.NewTestSuite(t, 3) + suite := test.NewTestSuite(t) store := store.NewTestStore(ctx, t, suite.Head()) - newHead := suite.GenExtendedHeader() + newHead := suite.GetRandomHeader() exchange := &exchangeCountingHead{header: newHead} - syncer, err := NewSyncer(exchange, store, &header.DummySubscriber{}, WithBlockTime(time.Nanosecond)) + syncer, err := NewSyncer[*test.DummyHeader](exchange, store, &test.DummySubscriber{}, WithBlockTime(time.Nanosecond)) require.NoError(t, err) - res := make(chan *header.ExtendedHeader) + res := make(chan *test.DummyHeader) for i := 0; i < 10; i++ { go func() { head, err := syncer.networkHead(ctx) @@ -196,38 +196,38 @@ func TestSyncer_OnlyOneRecentRequest(t *testing.T) { for i := 0; i < 10; i++ { head := <-res - assert.True(t, exchange.header.Equals(head)) + assert.Equal(t, exchange.header, head) } assert.Equal(t, 1, exchange.counter) } type exchangeCountingHead struct { - header *header.ExtendedHeader + header *test.DummyHeader counter int } -func (e *exchangeCountingHead) Head(context.Context) (*header.ExtendedHeader, error) { +func (e *exchangeCountingHead) Head(context.Context) (*test.DummyHeader, error) { e.counter++ time.Sleep(time.Millisecond * 100) // simulate requesting something return e.header, nil } -func (e *exchangeCountingHead) Get(ctx context.Context, bytes bytes.HexBytes) (*header.ExtendedHeader, error) { +func (e *exchangeCountingHead) Get(ctx context.Context, bytes header.Hash) (*test.DummyHeader, error) { panic("implement me") } -func (e *exchangeCountingHead) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { +func (e *exchangeCountingHead) GetByHeight(ctx context.Context, u uint64) (*test.DummyHeader, error) { panic("implement me") } func (e *exchangeCountingHead) GetRangeByHeight( c context.Context, from, amount uint64, -) ([]*header.ExtendedHeader, error) { +) ([]*test.DummyHeader, error) { panic("implement me") } -func (e *exchangeCountingHead) GetVerifiedRange(c context.Context, from *header.ExtendedHeader, amount uint64, -) ([]*header.ExtendedHeader, error) { +func (e *exchangeCountingHead) GetVerifiedRange(c context.Context, from *test.DummyHeader, amount uint64, +) ([]*test.DummyHeader, error) { panic("implement me") } diff --git a/libs/header/test/testing.go b/libs/header/test/testing.go new file mode 100644 index 0000000000..c17eaed242 --- /dev/null +++ b/libs/header/test/testing.go @@ -0,0 +1,287 @@ +package test + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/binary" + "encoding/gob" + "errors" + "fmt" + "math" + "testing" + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + "golang.org/x/crypto/sha3" + + "github.com/celestiaorg/celestia-node/libs/header" +) + +type Raw struct { + ChainID string + PreviousHash header.Hash + + Height int64 + Time time.Time +} + +type DummyHeader struct { + Raw + + hash header.Hash +} + +func (d *DummyHeader) New() header.Header { + return new(DummyHeader) +} + +func (d *DummyHeader) IsZero() bool { + return d == nil +} + +func (d *DummyHeader) ChainID() string { + return d.Raw.ChainID +} + +func (d *DummyHeader) Hash() header.Hash { + if len(d.hash) == 0 { + if err := d.rehash(); err != nil { + panic(err) + } + } + return d.hash +} + +func (d *DummyHeader) rehash() error { + b, err := d.MarshalBinary() + if err != nil { + return err + } + hash := sha3.Sum512(b) + d.hash = hash[:] + return nil +} + +func (d *DummyHeader) Height() int64 { + return d.Raw.Height +} + +func (d *DummyHeader) LastHeader() header.Hash { + return d.Raw.PreviousHash +} + +func (d *DummyHeader) Time() time.Time { + return d.Raw.Time +} + +func (d *DummyHeader) IsRecent(blockTime time.Duration) bool { + return time.Since(d.Time()) <= blockTime +} + +func (d *DummyHeader) IsExpired(period time.Duration) bool { + expirationTime := d.Time().Add(period) + return expirationTime.Before(time.Now()) +} + +func (d *DummyHeader) VerifyAdjacent(other header.Header) error { + if other.Height() != d.Height()+1 { + return fmt.Errorf("invalid Height, expected: %d, got: %d", d.Height()+1, other.Height()) + } + + if err := d.verify(other); err != nil { + return err + } + + return nil +} + +func (d *DummyHeader) VerifyNonAdjacent(other header.Header) error { + return d.verify(other) +} + +func (d *DummyHeader) verify(header header.Header) error { + // wee1 + epsilon := 10 * time.Second + if header.Time().After(time.Now().Add(epsilon)) { + return errors.New("header Time too far in the future") + } + + if header.Height() <= d.Height() { + return errors.New("expected new header Height to be larger than old header Time") + } + + if header.Time().Before(d.Time()) { + return errors.New("expected new header Time to be after old header Time") + } + + return nil +} + +func (d *DummyHeader) Validate() error { + return nil +} + +func (d *DummyHeader) MarshalBinary() ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(d.Raw) + return buf.Bytes(), err +} + +func (d *DummyHeader) UnmarshalBinary(data []byte) error { + dec := gob.NewDecoder(bytes.NewReader(data)) + err := dec.Decode(&d.Raw) + if err != nil { + return err + } + err = d.rehash() + if err != nil { + return err + } + + return nil +} + +// RandBytes returns slice of n-bytes, or nil in case of error +func RandBytes(n int) []byte { + buf := make([]byte, n) + + c, err := rand.Read(buf) + if err != nil || c != n { + return nil + } + + return buf +} + +func randInt63() int64 { + var buf [8]byte + + _, err := rand.Read(buf[:]) + if err != nil { + return math.MaxInt64 + } + + return int64(binary.BigEndian.Uint64(buf[:]) & math.MaxInt64) +} + +func RandDummyHeader(t *testing.T) *DummyHeader { + t.Helper() + dh, err := randDummyHeader() + if err != nil { + t.Fatal(err) + } + return dh +} + +func randDummyHeader() (*DummyHeader, error) { + dh := &DummyHeader{ + Raw{ + PreviousHash: RandBytes(32), + Height: randInt63(), + Time: time.Now().UTC(), + }, + nil, + } + err := dh.rehash() + return dh, err +} + +func mustRandDummyHeader() *DummyHeader { + dh, err := randDummyHeader() + if err != nil { + panic(err) + } + return dh +} + +// Suite provides everything you need to test chain of Headers. +// If not, please don't hesitate to extend it for your case. +type Suite struct { + t *testing.T + + head *DummyHeader +} + +type Generator[H header.Header] interface { + GetRandomHeader() H +} + +// NewTestSuite setups a new test suite. +func NewTestSuite(t *testing.T) *Suite { + return &Suite{ + t: t, + } +} + +func (s *Suite) genesis() *DummyHeader { + return &DummyHeader{ + hash: nil, + Raw: Raw{ + PreviousHash: nil, + Height: 1, + Time: time.Now().Add(-10 * time.Second).UTC(), + }, + } +} + +func (s *Suite) Head() *DummyHeader { + if s.head == nil { + s.head = s.genesis() + } + return s.head +} + +func (s *Suite) GenDummyHeaders(num int) []*DummyHeader { + headers := make([]*DummyHeader, num) + for i := range headers { + headers[i] = s.GetRandomHeader() + } + return headers +} + +func (s *Suite) GetRandomHeader() *DummyHeader { + if s.head == nil { + s.head = s.genesis() + return s.head + } + + dh := mustRandDummyHeader() + dh.Raw.Height = s.head.Height() + 1 + dh.Raw.PreviousHash = s.head.Hash() + _ = dh.rehash() + s.head = dh + return s.head +} + +type DummySubscriber struct { + Headers []*DummyHeader +} + +func (mhs *DummySubscriber) AddValidator(func(context.Context, *DummyHeader) pubsub.ValidationResult) error { + return nil +} + +func (mhs *DummySubscriber) Subscribe() (header.Subscription[*DummyHeader], error) { + return mhs, nil +} + +func (mhs *DummySubscriber) NextHeader(ctx context.Context) (*DummyHeader, error) { + defer func() { + if len(mhs.Headers) > 1 { + // pop the already-returned header + cp := mhs.Headers + mhs.Headers = cp[1:] + } else { + mhs.Headers = make([]*DummyHeader, 0) + } + }() + if len(mhs.Headers) == 0 { + return nil, context.Canceled + } + return mhs.Headers[0], nil +} + +func (mhs *DummySubscriber) Stop(context.Context) error { return nil } +func (mhs *DummySubscriber) Cancel() {} diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index fb33c86a42..d6aa48f2a3 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/header" headercore "github.com/celestiaorg/celestia-node/header/core" "github.com/celestiaorg/celestia-node/libs/fxutil" + libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -31,7 +32,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module("core", baseComponents, fx.Provide(core.NewBlockFetcher), - fxutil.ProvideAs(headercore.NewExchange, new(header.Exchange)), + fxutil.ProvideAs(headercore.NewExchange, new(libhead.Exchange[*header.ExtendedHeader])), fx.Invoke(fx.Annotate( headercore.NewListener, fx.OnStart(func(ctx context.Context, listener *headercore.Listener) error { diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index b6b44758b6..c643eef6ef 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" ) @@ -34,8 +35,8 @@ func newDaserStub() Module { func NewDASer( da share.Availability, - hsub header.Subscriber, - store header.Store, + hsub libhead.Subscriber[*header.ExtendedHeader], + store libhead.Store[*header.ExtendedHeader], batching datastore.Batching, fraudServ fraud.Service, options ...das.Option, diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index fa1ea2f881..8399b1b477 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -8,6 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -15,7 +16,7 @@ func newFraudService(syncerEnabled bool) func( fx.Lifecycle, *pubsub.PubSub, host.Host, - header.Store, + libhead.Store[*header.ExtendedHeader], datastore.Batching, p2p.Network, ) (Module, fraud.Service, error) { @@ -23,7 +24,7 @@ func newFraudService(syncerEnabled bool) func( lc fx.Lifecycle, sub *pubsub.PubSub, host host.Host, - hstore header.Store, + hstore libhead.Store[*header.ExtendedHeader], ds datastore.Batching, network p2p.Network, ) (Module, fraud.Service, error) { diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 1a695e881e..85a7fa5d78 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -6,11 +6,11 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - p2p_exchange "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" + libhead "github.com/celestiaorg/celestia-node/libs/header" + p2p_exchange "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/store" + "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -73,7 +73,7 @@ func (cfg *Config) trustedPeers(bpeers p2p.Bootstrappers) (infos []peer.AddrInfo return } -func (cfg *Config) trustedHash(net p2p.Network) (tmbytes.HexBytes, error) { +func (cfg *Config) trustedHash(net p2p.Network) (libhead.Hash, error) { if cfg.TrustedHash == "" { gen, err := p2p.GenesisFor(net) if err != nil { diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 35e0ac8303..480885afa9 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -10,20 +10,21 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/store" + "github.com/celestiaorg/celestia-node/libs/header/sync" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // newP2PServer constructs a new ExchangeServer using the given Network as a protocolID suffix. func newP2PServer( host host.Host, - store header.Store, + store libhead.Store[*header.ExtendedHeader], network modp2p.Network, opts []p2p.Option[p2p.ServerParameters], -) (*p2p.ExchangeServer, error) { - return p2p.NewExchangeServer(host, store, string(network), opts...) +) (*p2p.ExchangeServer[*header.ExtendedHeader], error) { + return p2p.NewExchangeServer[*header.ExtendedHeader](host, store, string(network), opts...) } // newP2PExchange constructs a new Exchange for headers. @@ -34,7 +35,7 @@ func newP2PExchange(cfg Config) func( host.Host, *conngater.BasicConnectionGater, []p2p.Option[p2p.ClientParameters], -) (header.Exchange, error) { +) (libhead.Exchange[*header.ExtendedHeader], error) { return func( lc fx.Lifecycle, bpeers modp2p.Bootstrappers, @@ -42,7 +43,7 @@ func newP2PExchange(cfg Config) func( host host.Host, conngater *conngater.BasicConnectionGater, opts []p2p.Option[p2p.ClientParameters], - ) (header.Exchange, error) { + ) (libhead.Exchange[*header.ExtendedHeader], error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { return nil, err @@ -52,7 +53,7 @@ func newP2PExchange(cfg Config) func( ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - exchange, err := p2p.NewExchange(host, ids, string(network), conngater, opts...) + exchange, err := p2p.NewExchange[*header.ExtendedHeader](host, ids, string(network), conngater, opts...) if err != nil { return nil, err } @@ -69,21 +70,26 @@ func newP2PExchange(cfg Config) func( } // newSyncer constructs new Syncer for headers. -func newSyncer(ex header.Exchange, store InitStore, sub header.Subscriber, opts []sync.Options) (*sync.Syncer, error) { - return sync.NewSyncer(ex, store, sub, opts...) +func newSyncer( + ex libhead.Exchange[*header.ExtendedHeader], + store InitStore, + sub libhead.Subscriber[*header.ExtendedHeader], + opts []sync.Options, +) (*sync.Syncer[*header.ExtendedHeader], error) { + return sync.NewSyncer[*header.ExtendedHeader](ex, store, sub, opts...) } // InitStore is a type representing initialized header store. // NOTE: It is needed to ensure that Store is always initialized before Syncer is started. -type InitStore header.Store +type InitStore libhead.Store[*header.ExtendedHeader] // newInitStore constructs an initialized store func newInitStore( lc fx.Lifecycle, cfg Config, net modp2p.Network, - s header.Store, - ex header.Exchange, + s libhead.Store[*header.ExtendedHeader], + ex libhead.Exchange[*header.ExtendedHeader], ) (InitStore, error) { trustedHash, err := cfg.trustedHash(net) if err != nil { diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index f539eaa735..791d5536e6 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -5,13 +5,15 @@ import ( "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" + pubsub "github.com/libp2p/go-libp2p-pubsub" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/store" + "github.com/celestiaorg/celestia-node/libs/header/sync" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -47,18 +49,18 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), fx.Provide(NewHeaderService), fx.Provide(fx.Annotate( - func(ds datastore.Batching, opts []store.Option) (header.Store, error) { - return store.NewStore(ds, opts...) + func(ds datastore.Batching, opts []store.Option) (libhead.Store[*header.ExtendedHeader], error) { + return store.NewStore[*header.ExtendedHeader](ds, opts...) }, - fx.OnStart(func(ctx context.Context, store header.Store) error { + fx.OnStart(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { return store.Start(ctx) }), - fx.OnStop(func(ctx context.Context, store header.Store) error { + fx.OnStop(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { return store.Stop(ctx) }), )), fx.Provide(newInitStore), - fx.Provide(func(subscriber *p2p.Subscriber) header.Subscriber { + fx.Provide(func(subscriber *p2p.Subscriber[*header.ExtendedHeader]) libhead.Subscriber[*header.ExtendedHeader] { return subscriber }), fx.Provide(func(cfg Config) []sync.Options { @@ -70,29 +72,35 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), fx.Provide(fx.Annotate( newSyncer, - fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, syncer *sync.Syncer) error { + fx.OnStart(func( + startCtx, ctx context.Context, + fservice fraud.Service, + syncer *sync.Syncer[*header.ExtendedHeader], + ) error { return modfraud.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, syncer.Start, syncer.Stop) }), - fx.OnStop(func(ctx context.Context, syncer *sync.Syncer) error { + fx.OnStop(func(ctx context.Context, syncer *sync.Syncer[*header.ExtendedHeader]) error { return syncer.Stop(ctx) }), )), fx.Provide(fx.Annotate( - p2p.NewSubscriber, - fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber) error { + func(ps *pubsub.PubSub) *p2p.Subscriber[*header.ExtendedHeader] { + return p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID) + }, + fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber[*header.ExtendedHeader]) error { return sub.Start(ctx) }), - fx.OnStop(func(ctx context.Context, sub *p2p.Subscriber) error { + fx.OnStop(func(ctx context.Context, sub *p2p.Subscriber[*header.ExtendedHeader]) error { return sub.Stop(ctx) }), )), fx.Provide(fx.Annotate( newP2PServer, - fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer) error { + fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer[*header.ExtendedHeader]) error { return server.Start(ctx) }), - fx.OnStop(func(ctx context.Context, server *p2p.ExchangeServer) error { + fx.OnStop(func(ctx context.Context, server *p2p.ExchangeServer[*header.ExtendedHeader]) error { return server.Stop(ctx) }), )), @@ -122,7 +130,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "header", baseComponents, - fx.Provide(func(subscriber *p2p.Subscriber) header.Broadcaster { + fx.Provide(func(subscriber *p2p.Subscriber[*header.ExtendedHeader]) libhead.Broadcaster[*header.ExtendedHeader] { return subscriber }), fx.Supply(header.MakeExtendedHeader), diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 045a526f4b..d64bf46266 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -15,9 +15,10 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/store" + "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -29,7 +30,7 @@ func TestConstructModule_StoreParams(t *testing.T) { cfg.Store.StoreCacheSize = 15 cfg.Store.IndexCacheSize = 25 cfg.Store.WriteBatchSize = 35 - var headerStore *store.Store + var headerStore *store.Store[*header.ExtendedHeader] app := fxtest.New(t, fx.Provide(func() datastore.Batching { @@ -37,8 +38,8 @@ func TestConstructModule_StoreParams(t *testing.T) { }), ConstructModule(node.Light, &cfg), fx.Invoke( - func(s header.Store) { - ss := s.(*store.Store) + func(s libhead.Store[*header.ExtendedHeader]) { + ss := s.(*store.Store[*header.ExtendedHeader]) headerStore = ss }), ) @@ -54,7 +55,7 @@ func TestConstructModule_SyncerParams(t *testing.T) { cfg := DefaultConfig(node.Light) cfg.Syncer.TrustingPeriod = time.Hour cfg.Syncer.MaxRequestSize = 1 - var syncer *sync.Syncer + var syncer *sync.Syncer[*header.ExtendedHeader] app := fxtest.New(t, fx.Supply(modp2p.Private), fx.Supply(modp2p.Bootstrappers{}), @@ -73,7 +74,7 @@ func TestConstructModule_SyncerParams(t *testing.T) { return nil }), ConstructModule(node.Light, &cfg), - fx.Invoke(func(s *sync.Syncer) { + fx.Invoke(func(s *sync.Syncer[*header.ExtendedHeader]) { syncer = s }), ) @@ -89,8 +90,8 @@ func TestConstructModule_ExchangeParams(t *testing.T) { cfg.Client.MinResponses = 10 cfg.Client.MaxRequestSize = 200 cfg.Client.MaxHeadersPerRequest = 15 - var exchange *p2p.Exchange - var exchangeServer *p2p.ExchangeServer + var exchange *p2p.Exchange[*header.ExtendedHeader] + var exchangeServer *p2p.ExchangeServer[*header.ExtendedHeader] app := fxtest.New(t, fx.Supply(modp2p.Private), @@ -104,8 +105,8 @@ func TestConstructModule_ExchangeParams(t *testing.T) { return conngater.NewBasicConnectionGater(b) }), fx.Invoke( - func(e header.Exchange, server *p2p.ExchangeServer) { - ex := e.(*p2p.Exchange) + func(e libhead.Exchange[*header.ExtendedHeader], server *p2p.ExchangeServer[*header.ExtendedHeader]) { + ex := e.(*p2p.Exchange[*header.ExtendedHeader]) exchange = ex exchangeServer = server }), diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 1cdaaf17d3..1948c09315 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -4,29 +4,30 @@ import ( "context" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/sync" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/sync" ) // Service represents the header Service that can be started / stopped on a node. // Service's main function is to manage its sub-services. Service can contain several // sub-services, such as Exchange, ExchangeServer, Syncer, and so forth. type Service struct { - ex header.Exchange + ex libhead.Exchange[*header.ExtendedHeader] - syncer *sync.Syncer - sub header.Subscriber - p2pServer *p2p.ExchangeServer - store header.Store + syncer *sync.Syncer[*header.ExtendedHeader] + sub libhead.Subscriber[*header.ExtendedHeader] + p2pServer *p2p.ExchangeServer[*header.ExtendedHeader] + store libhead.Store[*header.ExtendedHeader] } // NewHeaderService creates a new instance of header Service. func NewHeaderService( - syncer *sync.Syncer, - sub header.Subscriber, - p2pServer *p2p.ExchangeServer, - ex header.Exchange, - store header.Store) Module { + syncer *sync.Syncer[*header.ExtendedHeader], + sub libhead.Subscriber[*header.ExtendedHeader], + p2pServer *p2p.ExchangeServer[*header.ExtendedHeader], + ex libhead.Exchange[*header.ExtendedHeader], + store libhead.Store[*header.ExtendedHeader]) Module { return &Service{ syncer: syncer, sub: sub, diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index e44dddef66..069cb07673 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -2,7 +2,8 @@ package state import ( apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - "github.com/celestiaorg/celestia-node/header/sync" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/state" ) @@ -12,7 +13,7 @@ import ( func CoreAccessor( corecfg core.Config, signer *apptypes.KeyringSigner, - sync *sync.Syncer, + sync *sync.Syncer[*header.ExtendedHeader], ) *state.CoreAccessor { return state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) } diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index d8b66e8baf..8803037afb 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -13,8 +13,9 @@ import ( apptypes "github.com/celestiaorg/celestia-app/x/payment/types" "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-node/header/mocks" + coreheader "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/libs/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -43,7 +44,9 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti // temp dir for the eds store FIXME: Should be in mem fx.Replace(node.StorePath(t.TempDir())), // avoid requesting trustedPeer during initialization - fxutil.ReplaceAs(mocks.NewStore(t, 20), new(header.InitStore)), + fxutil.ReplaceAs(mocks.NewStore[*coreheader.ExtendedHeader]( + t, coreheader.NewTestSuite(t, 5), 20), new(header.InitStore), + ), ) // in fact, we don't need core.Client in tests, but Bridge requires is a valid one diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 005805ae11..4dbf6e3958 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -14,7 +14,6 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/bytes" "go.uber.org/fx" "github.com/celestiaorg/celestia-app/testutil/testnode" @@ -22,6 +21,7 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" headercore "github.com/celestiaorg/celestia-node/header/core" + libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" @@ -101,15 +101,15 @@ func (s *Swamp) stopAllNodes(ctx context.Context, allNodes ...[]*nodebuilder.Nod } // GetCoreBlockHashByHeight returns a tendermint block's hash by provided height -func (s *Swamp) GetCoreBlockHashByHeight(ctx context.Context, height int64) bytes.HexBytes { +func (s *Swamp) GetCoreBlockHashByHeight(ctx context.Context, height int64) libhead.Hash { b, err := s.ClientContext.Client.Block(ctx, &height) require.NoError(s.t, err) - return b.BlockID.Hash + return libhead.Hash(b.BlockID.Hash) } // WaitTillHeight holds the test execution until the given amount of blocks // has been produced by the CoreClient. -func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) bytes.HexBytes { +func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) libhead.Hash { require.Greater(s.t, height, int64(0)) t := time.NewTicker(time.Millisecond * 50) @@ -125,11 +125,11 @@ func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) bytes.HexBytes latest := status.SyncInfo.LatestBlockHeight switch { case latest == height: - return status.SyncInfo.LatestBlockHash + return libhead.Hash(status.SyncInfo.LatestBlockHash) case latest > height: res, err := s.ClientContext.Client.Block(ctx, &height) require.NoError(s.t, err) - return res.BlockID.Hash + return libhead.Hash(res.BlockID.Hash) } } } @@ -254,7 +254,7 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti options = append(options, p2p.WithHost(s.createPeer(ks)), fx.Replace(node.StorePath(tempDir)), - fx.Invoke(func(ctx context.Context, store header.Store) error { + fx.Invoke(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { return store.Init(ctx, s.genesis) }), ) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 5734745e67..7c3367026a 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -105,7 +105,7 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { // rebuilds bad row or col from received shares, computes Merkle Root // and compares it with block's Merkle Root. func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { - if header.Height != int64(p.BlockHeight) { + if header.Height() != int64(p.BlockHeight) { return errors.New("fraud: incorrect block height") } merkleRowRoots := header.DAH.RowsRoots diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 2e5a326c06..80cb8083e9 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -57,7 +57,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { }, } - proof := CreateBadEncodingProof(h.Hash(), uint64(h.Height), &fakeError) + proof := CreateBadEncodingProof(h.Hash(), uint64(h.Height()), &fakeError) err = proof.Validate(h) require.Error(t, err) } diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 2f8dd0c64b..725f0f161f 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -141,7 +141,7 @@ func TestFraudProofValidation(t *testing.T) { faultHeader, err := generateByzantineError(ctx, t, bServ) require.True(t, errors.As(err, &errByz)) - p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(faultHeader.Height), errByz) + p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(faultHeader.Height()), errByz) err = p.Validate(faultHeader) require.NoError(t, err) } diff --git a/state/core_access.go b/state/core_access.go index e820f4eed2..6f6be0a2d4 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -25,6 +25,7 @@ import ( "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" ) var ( @@ -39,7 +40,7 @@ type CoreAccessor struct { cancel context.CancelFunc signer *apptypes.KeyringSigner - getter header.Head + getter libhead.Head[*header.ExtendedHeader] queryCli banktypes.QueryClient stakingCli stakingtypes.QueryClient @@ -61,7 +62,7 @@ type CoreAccessor struct { // connection. func NewCoreAccessor( signer *apptypes.KeyringSigner, - getter header.Head, + getter libhead.Head[*header.ExtendedHeader], coreIP, rpcPort string, grpcPort string, @@ -200,7 +201,7 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B abciReq := abci.RequestQuery{ // TODO @renayay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use const instead Path: fmt.Sprintf("store/%s/key", banktypes.StoreKey), - Height: head.Height - 1, + Height: head.Height() - 1, Data: prefixedAccountKey, Prove: true, } @@ -219,7 +220,7 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B value := result.Response.Value // if the value returned is empty, the account balance does not yet exist if len(value) == 0 { - log.Errorf("balance for account %s does not exist at block height %d", addr.String(), head.Height-1) + log.Errorf("balance for account %s does not exist at block height %d", addr.String(), head.Height()-1) return &Balance{ Denom: app.BondDenom, Amount: sdktypes.NewInt(0), From f71eb531ace61f3552eb2b6485cab6dbcbbf7c28 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 20 Jan 2023 11:11:15 +0100 Subject: [PATCH 0309/1008] refactor!: naming PFD/PayForData to PayForBlob (#1582) Closes https://github.com/celestiaorg/celestia-node/issues/1447 --- api/gateway/endpoints.go | 2 +- api/gateway/state.go | 24 ++++++++++++------------ docs/adr/adr-009-public-api.md | 8 ++++---- nodebuilder/state/mocks/api.go | 6 +++--- nodebuilder/state/state.go | 10 +++++----- state/core_access.go | 12 ++++++------ state/metrics.go | 18 +++++++++--------- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/api/gateway/endpoints.go b/api/gateway/endpoints.go index 9e1c1b1c7e..c923bb8f5d 100644 --- a/api/gateway/endpoints.go +++ b/api/gateway/endpoints.go @@ -11,7 +11,7 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) - rpc.RegisterHandlerFunc(submitPFDEndpoint, h.handleSubmitPFD, http.MethodPost) + rpc.RegisterHandlerFunc(submitPFBEndpoint, h.handleSubmitPFB, http.MethodPost) rpc.RegisterHandlerFunc(transferEndpoint, h.handleTransfer, http.MethodPost) rpc.RegisterHandlerFunc(delegationEndpoint, h.handleDelegation, http.MethodPost) rpc.RegisterHandlerFunc(undelegationEndpoint, h.handleUndelegation, http.MethodPost) diff --git a/api/gateway/state.go b/api/gateway/state.go index 6bb472f5f9..87288d7d13 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -15,7 +15,7 @@ import ( const ( balanceEndpoint = "/balance" submitTxEndpoint = "/submit_tx" - submitPFDEndpoint = "/submit_pfd" + submitPFBEndpoint = "/submit_pfb" transferEndpoint = "/transfer" delegationEndpoint = "/delegate" undelegationEndpoint = "/begin_unbonding" @@ -38,9 +38,9 @@ type submitTxRequest struct { Tx string `json:"tx"` } -// submitPFDRequest represents a request to submit a PayForData +// submitPFBRequest represents a request to submit a PayForBlob // transaction. -type submitPFDRequest struct { +type submitPFBRequest struct { NamespaceID string `json:"namespace_id"` Data string `json:"data"` Fee int64 `json:"fee"` @@ -165,39 +165,39 @@ func (h *Handler) handleSubmitTx(w http.ResponseWriter, r *http.Request) { } } -func (h *Handler) handleSubmitPFD(w http.ResponseWriter, r *http.Request) { +func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { // decode request - var req submitPFDRequest + var req submitPFBRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - writeError(w, http.StatusBadRequest, submitPFDEndpoint, err) + writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) return } nID, err := hex.DecodeString(req.NamespaceID) if err != nil { - writeError(w, http.StatusBadRequest, submitPFDEndpoint, err) + writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) return } data, err := hex.DecodeString(req.Data) if err != nil { - writeError(w, http.StatusBadRequest, submitPFDEndpoint, err) + writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) return } fee := types.NewInt(req.Fee) // perform request - txResp, err := h.state.SubmitPayForData(r.Context(), nID, data, fee, req.GasLimit) + txResp, err := h.state.SubmitPayForBlob(r.Context(), nID, data, fee, req.GasLimit) if err != nil { - writeError(w, http.StatusInternalServerError, submitPFDEndpoint, err) + writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) return } resp, err := json.Marshal(txResp) if err != nil { - writeError(w, http.StatusInternalServerError, submitPFDEndpoint, err) + writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) return } _, err = w.Write(resp) if err != nil { - log.Errorw("writing response", "endpoint", submitPFDEndpoint, "err", err) + log.Errorw("writing response", "endpoint", submitPFBEndpoint, "err", err) } } diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 7eb8dd8076..7670647140 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -244,8 +244,8 @@ SyncHead(ctx context.Context) (*header.ExtendedHeader, error) // SubmitTx submits the given transaction/message to the Celestia network // and blocks until the tx is included in a block. SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) - // SubmitPayForData builds, signs and submits a PayForData transaction. - SubmitPayForData( + // SubmitPayForBlob builds, signs and submits a PayForBlob transaction. + SubmitPayForBlob( ctx context.Context, nID namespace.ID, data []byte, @@ -397,8 +397,8 @@ type BankModule interface { // address and verifies the returned balance against the corresponding // block's AppHash. BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) - // SubmitPayForData builds, signs and submits a PayForData transaction. - SubmitPayForData( + // SubmitPayForBlob builds, signs and submits a PayForBlob transaction. + SubmitPayForBlob( ctx context.Context, nID namespace.ID, data []byte, diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 50996edb23..3658c7ebf8 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -190,9 +190,9 @@ func (mr *MockModuleMockRecorder) QueryUnbonding(arg0, arg1 interface{}) *gomock } // SubmitPayForData mocks base method. -func (m *MockModule) SubmitPayForData(arg0 context.Context, arg1 namespace.ID, arg2 []byte, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { +func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 namespace.ID, arg2 []byte, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubmitPayForData", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "SubmitPayForBlob", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 @@ -201,7 +201,7 @@ func (m *MockModule) SubmitPayForData(arg0 context.Context, arg1 namespace.ID, a // SubmitPayForData indicates an expected call of SubmitPayForData. func (mr *MockModuleMockRecorder) SubmitPayForData(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForData", reflect.TypeOf((*MockModule)(nil).SubmitPayForData), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForBlob", reflect.TypeOf((*MockModule)(nil).SubmitPayForBlob), arg0, arg1, arg2, arg3, arg4) } // SubmitTx mocks base method. diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index b79e59015d..36965050d8 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -41,8 +41,8 @@ type Module interface { // Celestia network and blocks until the tx is included in // a block. SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) - // SubmitPayForData builds, signs and submits a PayForData transaction. - SubmitPayForData( + // SubmitPayForBlob builds, signs and submits a PayForBlob transaction. + SubmitPayForBlob( ctx context.Context, nID namespace.ID, data []byte, @@ -111,7 +111,7 @@ type API struct { gasLimit uint64, ) (*state.TxResponse, error) `perm:"write"` SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"write"` - SubmitPayForData func( + SubmitPayForBlob func( ctx context.Context, nID namespace.ID, data []byte, @@ -190,14 +190,14 @@ func (api *API) SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, e return api.Internal.SubmitTx(ctx, tx) } -func (api *API) SubmitPayForData( +func (api *API) SubmitPayForBlob( ctx context.Context, nID namespace.ID, data []byte, fee state.Int, gasLim uint64, ) (*state.TxResponse, error) { - return api.Internal.SubmitPayForData(ctx, nID, data, fee, gasLim) + return api.Internal.SubmitPayForBlob(ctx, nID, data, fee, gasLim) } func (api *API) CancelUnbondingDelegation( diff --git a/state/core_access.go b/state/core_access.go index 6f6be0a2d4..73c75d2fd5 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -53,8 +53,8 @@ type CoreAccessor struct { rpcPort string grpcPort string - lastPayForData int64 - payForDataCount int64 + lastPayForBlob int64 + payForBlobCount int64 } // NewCoreAccessor dials the given celestia-core endpoint and @@ -155,7 +155,7 @@ func (ca *CoreAccessor) constructSignedTx( return ca.signer.EncodeTx(tx) } -func (ca *CoreAccessor) SubmitPayForData( +func (ca *CoreAccessor) SubmitPayForBlob( ctx context.Context, nID namespace.ID, data []byte, @@ -163,10 +163,10 @@ func (ca *CoreAccessor) SubmitPayForData( gasLim uint64, ) (*TxResponse, error) { response, err := payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim, withFee(fee)) - // metrics should only be counted on a successful PFD tx + // metrics should only be counted on a successful PFB tx if err == nil && response.Code == 0 { - ca.lastPayForData = time.Now().UnixMilli() - ca.payForDataCount++ + ca.lastPayForBlob = time.Now().UnixMilli() + ca.payForBlobCount++ } return response, err } diff --git a/state/metrics.go b/state/metrics.go index 6fecb034cb..e465e2833d 100644 --- a/state/metrics.go +++ b/state/metrics.go @@ -11,22 +11,22 @@ import ( var meter = global.MeterProvider().Meter("state") func WithMetrics(ca *CoreAccessor) { - pfdCounter, _ := meter.AsyncInt64().Counter( - "pfd_count", + pfbCounter, _ := meter.AsyncInt64().Counter( + "pfb_count", instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription("Total count of submitted PayForData transactions"), + instrument.WithDescription("Total count of submitted PayForBlob transactions"), ) - lastPfdTimestamp, _ := meter.AsyncInt64().Counter( - "last_pfd_timestamp", + lastPfbTimestamp, _ := meter.AsyncInt64().Counter( + "last_pfb_timestamp", instrument.WithUnit(unit.Milliseconds), - instrument.WithDescription("Timestamp of the last submitted PayForData transaction"), + instrument.WithDescription("Timestamp of the last submitted PayForBlob transaction"), ) err := meter.RegisterCallback( - []instrument.Asynchronous{pfdCounter, lastPfdTimestamp}, + []instrument.Asynchronous{pfbCounter, lastPfbTimestamp}, func(ctx context.Context) { - pfdCounter.Observe(ctx, ca.payForDataCount) - lastPfdTimestamp.Observe(ctx, ca.lastPayForData) + pfbCounter.Observe(ctx, ca.payForBlobCount) + lastPfbTimestamp.Observe(ctx, ca.lastPayForBlob) }, ) if err != nil { From 8a101d9907007be92303c261169aa980bda189da Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 20 Jan 2023 11:21:29 +0100 Subject: [PATCH 0310/1008] chore(nodebuilder/p2p): Add Kaarina to arabica (#1619) --- nodebuilder/p2p/bootstrap.go | 1 + 1 file changed, 1 insertion(+) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 2cff9ea899..6b4f5fc877 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -32,6 +32,7 @@ var bootstrapList = map[Network][]string{ "/dns4/limani.celestia-devops.dev/tcp/2121/p2p/12D3KooWDgG69kXfmSiHjUErN2ahpUC1SXpSfB2urrqMZ6aWC8NS", "/dns4/marsellesa.celestia-devops.dev/tcp/2121/p2p/12D3KooWHr2wqFAsMXnPzpFsgxmePgXb8BqpkePebwUgLyZc95bd", "/dns4/parainem.celestia-devops.dev/tcp/2121/p2p/12D3KooWHX8xpwg8qkP7kLKmKGtgZvmsopvgxc6Fwtu665QC7G8q", + "/dns4/kaarina.celestia-devops.dev/tcp/2121/p2p/12D3KooWRSaqC5H77PGMC7rLx5JBXiutJu7ouUyCToF8d6J1Scfh", }, Mocha: { "/dns4/andromeda.celestia-devops.dev/tcp/2121/p2p/12D3KooWKvPXtV1yaQ6e3BRNUHa5Phh8daBwBi3KkGaSSkUPys6D", From 7305099fbf5c273348d3ece8a5c1f01f4767928c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 20 Jan 2023 14:29:43 +0300 Subject: [PATCH 0311/1008] feat(share/discovery): Add callback on peers set updates. (#1609) notify Discovery clients about peers set changes in async manner by calling provided callback. Co-authored-by: Ryan --- share/availability/discovery/discovery.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index afb89f00e5..95e8e3b7a8 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -39,8 +39,12 @@ type Discovery struct { discoveryInterval time.Duration // advertiseInterval is an interval between advertising sessions. advertiseInterval time.Duration + // onUpdatedPeers will be called on peer set changes + onUpdatedPeers OnUpdatedPeers } +type OnUpdatedPeers func(peerID peer.ID, isAdded bool) + // NewDiscovery constructs a new discovery. func NewDiscovery( h host.Host, @@ -57,6 +61,15 @@ func NewDiscovery( peersLimit, discInterval, advertiseInterval, + func(peer.ID, bool) {}, + } +} + +// WithOnPeersUpdate adds OnPeersUpdate callback call on every update of discovered peers list. +func (d *Discovery) WithOnPeersUpdate(f OnUpdatedPeers) { + d.onUpdatedPeers = func(peerID peer.ID, isAdded bool) { + d.onUpdatedPeers(peerID, isAdded) + f(peerID, isAdded) } } @@ -78,6 +91,8 @@ func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer d.set.Remove(peer.ID) return } + + d.onUpdatedPeers(peer.ID, true) log.Debugw("added peer to set", "id", peer.ID) // add tag to protect peer of being killed by ConnManager d.host.ConnManager().TagPeer(peer.ID, topic, peerWeight) @@ -132,6 +147,7 @@ func (d *Discovery) EnsurePeers(ctx context.Context) { if d.set.Contains(connStatus.Peer) { d.connector.RestartBackoff(connStatus.Peer) d.set.Remove(connStatus.Peer) + d.onUpdatedPeers(connStatus.Peer, false) d.host.ConnManager().UntagPeer(connStatus.Peer, topic) t.Reset(d.discoveryInterval) } From e62120f2db72f9dc8b20a27e124be2f2984068aa Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 20 Jan 2023 13:02:47 +0100 Subject: [PATCH 0312/1008] feat(share/eds): adding traces to edsstore, write/read eds, and getters (#1594) Closes https://github.com/celestiaorg/celestia-node/issues/1361 --- libs/utils/traces.go | 17 +++++++++++++++++ share/eds/eds.go | 18 ++++++++++++++---- share/eds/store.go | 41 +++++++++++++++++++++++++++++++++++------ share/getters/ipld.go | 38 +++++++++++++++++++++++++++++++++----- share/getters/store.go | 39 ++++++++++++++++++++++++++++++++++----- share/getters/tee.go | 35 +++++++++++++++++++++++++++++++---- share/getters/utils.go | 20 ++++++++++++++++++-- 7 files changed, 182 insertions(+), 26 deletions(-) create mode 100644 libs/utils/traces.go diff --git a/libs/utils/traces.go b/libs/utils/traces.go new file mode 100644 index 0000000000..c7bd6b72f5 --- /dev/null +++ b/libs/utils/traces.go @@ -0,0 +1,17 @@ +package utils + +import ( + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +// SetStatusAndEnd sets the status of the span depending on the contents of the passed error and +// ends it. +func SetStatusAndEnd(span trace.Span, err error) { + defer span.End() + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return + } + span.SetStatus(codes.Ok, "") +} diff --git a/share/eds/eds.go b/share/eds/eds.go index 8b92d5fc74..faa64f7e15 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -21,10 +21,10 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -44,7 +44,12 @@ type writingSession struct { // This includes all shares in quadrant order, followed by all inner nodes of the NMT tree. // Order: [ Carv1Header | Q1 | Q2 | Q3 | Q4 | inner nodes ] // For more information about the header: https://ipld.io/specs/transport/car/carv1/#header -func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { +func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) (err error) { + ctx, span := tracer.Start(ctx, "write-eds") + defer func() { + utils.SetStatusAndEnd(span, err) + }() + // 1. Reimport EDS. This is needed to traverse the NMT tree and cache the inner nodes (proofs) writer, err := initializeWriter(ctx, eds, w) if err != nil { @@ -234,7 +239,12 @@ func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) { // Only the first quadrant will be read, which represents the original data. // The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS // errors. -func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) { +func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (eds *rsmt2d.ExtendedDataSquare, err error) { + _, span := tracer.Start(ctx, "read-eds") + defer func() { + utils.SetStatusAndEnd(span, err) + }() + carReader, err := car.NewCarReader(r) if err != nil { return nil, fmt.Errorf("share: reading car file: %w", err) @@ -256,7 +266,7 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (*rsmt2d.Ext shares[i] = block.RawData()[ipld.NamespaceSize:] } - eds, err := rsmt2d.ComputeExtendedDataSquare( + eds, err = rsmt2d.ComputeExtendedDataSquare( shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(odsWidth)), diff --git a/share/eds/store.go b/share/eds/store.go index 84313f3d4b..68e7795cfb 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -17,10 +17,13 @@ import ( "github.com/ipfs/go-datastore" bstore "github.com/ipfs/go-ipfs-blockstore" carv1 "github.com/ipld/go-car" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -147,7 +150,15 @@ func (s *Store) gc(ctx context.Context) { // The square is verified on the Exchange level, and Put only stores the square, trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. // Additionally, the file gets indexed s.t. store.Blockstore can access them. -func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) error { +func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) (err error) { + ctx, span := tracer.Start(ctx, "store/put", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.Int("width", int(square.Width())), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + key := root.String() f, err := os.OpenFile(s.basepath+blocksPath+key, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { @@ -186,6 +197,9 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext // // Caller must Close returned reader after reading. func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) { + ctx, span := tracer.Start(ctx, "store/get-car", trace.WithAttributes(attribute.String("root", root.String()))) + defer span.End() + key := root.String() accessor, err := s.getAccessor(ctx, shard.KeyFromString(key)) if err != nil { @@ -220,6 +234,9 @@ func (s *Store) CARBlockstore( // GetDAH returns the DataAvailabilityHeader for the EDS identified by DataHash. func (s *Store) GetDAH(ctx context.Context, root share.DataHash) (*share.Root, error) { + ctx, span := tracer.Start(ctx, "store/get-dah", trace.WithAttributes(attribute.String("root", root.String()))) + defer span.End() + key := shard.KeyFromString(root.String()) accessor, err := s.getAccessor(ctx, key) if err != nil { @@ -290,11 +307,15 @@ func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessor // Remove removes EDS from Store by the given share.Root hash and cleans up all // the indexing. -func (s *Store) Remove(ctx context.Context, root share.DataHash) error { - key := root.String() +func (s *Store) Remove(ctx context.Context, root share.DataHash) (err error) { + ctx, span := tracer.Start(ctx, "store/remove", trace.WithAttributes(attribute.String("root", root.String()))) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + key := root.String() ch := make(chan dagstore.ShardResult, 1) - err := s.dgstr.DestroyShard(ctx, shard.KeyFromString(key), ch, dagstore.DestroyOpts{}) + err = s.dgstr.DestroyShard(ctx, shard.KeyFromString(key), ch, dagstore.DestroyOpts{}) if err != nil { return fmt.Errorf("failed to initiate shard destruction: %w", err) } @@ -327,12 +348,17 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) error { // // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by // recomputing it. -func (s *Store) Get(ctx context.Context, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) { +func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.ExtendedDataSquare, err error) { + ctx, span := tracer.Start(ctx, "store/get", trace.WithAttributes(attribute.String("root", root.String()))) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + f, err := s.GetCAR(ctx, root) if err != nil { return nil, fmt.Errorf("failed to get CAR file: %w", err) } - eds, err := ReadEDS(ctx, f, root) + eds, err = ReadEDS(ctx, f, root) if err != nil { return nil, fmt.Errorf("failed to read EDS from CAR file: %w", err) } @@ -341,6 +367,9 @@ func (s *Store) Get(ctx context.Context, root share.DataHash) (*rsmt2d.ExtendedD // Has checks if EDS exists by the given share.Root hash. func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { + _, span := tracer.Start(ctx, "store/has", trace.WithAttributes(attribute.String("root", root.String()))) + defer span.End() + key := root.String() info, err := s.dgstr.GetShardInfo(shard.KeyFromString(key)) if err == dagstore.ErrShardUnknown { diff --git a/share/getters/ipld.go b/share/getters/ipld.go index cddfd6f939..0c5c1fa63e 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -7,7 +7,10 @@ import ( "sync/atomic" "github.com/ipfs/go-blockservice" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" @@ -36,6 +39,16 @@ func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter { // GetShare gets a single share at the given EDS coordinates from the bitswap network. func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { + var err error + ctx, span := tracer.Start(ctx, "ipld/get-share", trace.WithAttributes( + attribute.String("root", dah.String()), + attribute.Int("row", row), + attribute.Int("col", col), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + root, leaf := ipld.Translate(dah, row, col) // wrap the blockservice in a session if it has been signaled in the context. @@ -48,9 +61,16 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in return nd, nil } -func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { +func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { + ctx, span := tracer.Start(ctx, "ipld/get-eds", trace.WithAttributes( + attribute.String("root", root.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + // rtrv.Retrieve calls shares.GetShares until enough shares are retrieved to reconstruct the EDS - eds, err := ig.rtrv.Retrieve(ctx, root) + eds, err = ig.rtrv.Retrieve(ctx, root) if err != nil { return nil, fmt.Errorf("getter/ipld: failed to retrieve eds: %w", err) } @@ -61,15 +81,23 @@ func (ig *IPLDGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, nID namespace.ID, -) (share.NamespacedShares, error) { - err := verifyNIDSize(nID) +) (shares share.NamespacedShares, err error) { + ctx, span := tracer.Start(ctx, "ipld/get-shares-by-namespace", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.String("nID", nID.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + + err = verifyNIDSize(nID) if err != nil { return nil, fmt.Errorf("getter/ipld: invalid namespace ID: %w", err) } // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - shares, err := collectSharesByNamespace(ctx, blockGetter, root, nID) + shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) if err != nil { return nil, fmt.Errorf("getter/ipld: failed to retrieve shares by namespace: %w", err) } diff --git a/share/getters/store.go b/share/getters/store.go index 54559119ab..f2a222894e 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -4,6 +4,10 @@ import ( "context" "fmt" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" @@ -30,6 +34,16 @@ func NewStoreGetter(store *eds.Store) *StoreGetter { // GetShare gets a single share at the given EDS coordinates from the eds.Store through the // corresponding CAR-level blockstore. func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { + var err error + ctx, span := tracer.Start(ctx, "store/get-share", trace.WithAttributes( + attribute.String("root", dah.String()), + attribute.Int("row", row), + attribute.Int("col", col), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + root, leaf := ipld.Translate(dah, row, col) bs, err := sg.store.CARBlockstore(ctx, dah.Hash()) if err != nil { @@ -47,8 +61,15 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i } // GetEDS gets the EDS identified by the given root from the EDS store. -func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - eds, err := sg.store.Get(ctx, root.Hash()) +func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { + ctx, span := tracer.Start(ctx, "store/get-eds", trace.WithAttributes( + attribute.String("root", root.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + + eds, err = sg.store.Get(ctx, root.Hash()) if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve eds: %w", err) } @@ -61,8 +82,16 @@ func (sg *StoreGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, nID namespace.ID, -) (share.NamespacedShares, error) { - err := verifyNIDSize(nID) +) (shares share.NamespacedShares, err error) { + ctx, span := tracer.Start(ctx, "store/get-shares-by-namespace", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.String("nID", nID.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + + err = verifyNIDSize(nID) if err != nil { return nil, fmt.Errorf("getter/store: invalid namespace ID: %w", err) } @@ -74,7 +103,7 @@ func (sg *StoreGetter) GetSharesByNamespace( // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) - shares, err := collectSharesByNamespace(ctx, blockGetter, root, nID) + shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) } diff --git a/share/getters/tee.go b/share/getters/tee.go index 048de905db..08c5e6eb68 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -6,7 +6,10 @@ import ( "fmt" "github.com/filecoin-project/dagstore" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" @@ -31,12 +34,28 @@ func NewTeeGetter(getter share.Getter, store *eds.Store) *TeeGetter { } } -func (tg *TeeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { +func (tg *TeeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share share.Share, err error) { + ctx, span := tracer.Start(ctx, "tee/get-share", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.Int("row", row), + attribute.Int("col", col), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + return tg.getter.GetShare(ctx, root, row, col) } -func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - eds, err := tg.getter.GetEDS(ctx, root) +func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { + ctx, span := tracer.Start(ctx, "tee/get-eds", trace.WithAttributes( + attribute.String("root", root.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + + eds, err = tg.getter.GetEDS(ctx, root) if err != nil { return nil, err } @@ -53,6 +72,14 @@ func (tg *TeeGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, id namespace.ID, -) (share.NamespacedShares, error) { +) (shares share.NamespacedShares, err error) { + ctx, span := tracer.Start(ctx, "tee/get-shares-by-namespace", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.String("nID", id.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + return tg.getter.GetSharesByNamespace(ctx, root, id) } diff --git a/share/getters/utils.go b/share/getters/utils.go index 04648e236f..5cc17e5289 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -6,8 +6,12 @@ import ( "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" @@ -15,6 +19,10 @@ import ( "github.com/celestiaorg/nmt/namespace" ) +var ( + tracer = otel.Tracer("share/getters") +) + // filterRootsByNamespace returns the row roots from the given share.Root that contain the passed // namespace ID. func filterRootsByNamespace(root *share.Root, nID namespace.ID) []cid.Cid { @@ -34,14 +42,22 @@ func collectSharesByNamespace( bg blockservice.BlockGetter, root *share.Root, nID namespace.ID, -) (share.NamespacedShares, error) { +) (shares share.NamespacedShares, err error) { + ctx, span := tracer.Start(ctx, "collect-shares-by-namespace", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.String("nid", nID.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + rootCIDs := filterRootsByNamespace(root, nID) if len(rootCIDs) == 0 { return nil, nil } errGroup, ctx := errgroup.WithContext(ctx) - shares := make([]share.NamespacedRow, len(rootCIDs)) + shares = make([]share.NamespacedRow, len(rootCIDs)) for i, rootCID := range rootCIDs { // shadow loop variables, to ensure correct values are captured i, rootCID := i, rootCID From 452326fce43aaea4d3338d1da0e212f578deccd6 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 20 Jan 2023 17:26:00 +0300 Subject: [PATCH 0313/1008] improvement(das): Add timeout for sampling in DASer (#1615) Adds timeout on every sampling call inside DASer, in case share.Available is blocked or taking too long. --- das/daser.go | 3 + das/daser_test.go | 44 ++++++++++++++- das/options.go | 24 +++++++- nodebuilder/das/module.go | 1 + share/availability.go | 2 + share/availability/mocks/availability.go | 70 ++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 share/availability/mocks/availability.go diff --git a/das/daser.go b/das/daser.go index b9c425f9da..1be0b2af87 100644 --- a/das/daser.go +++ b/das/daser.go @@ -144,6 +144,9 @@ func (d *DASer) Stop(ctx context.Context) error { } func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { + ctx, cancel := context.WithTimeout(ctx, d.params.SampleTimeout) + defer cancel() + err := d.da.SharesAvailable(ctx, h.DAH) if err != nil { if err == context.Canceled { diff --git a/das/daser_test.go b/das/daser_test.go index 21b3dc70b6..7d0863839b 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -5,11 +5,13 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" mdutils "github.com/ipfs/go-merkledag/test" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,8 +20,10 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/share/availability/mocks" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -178,6 +182,44 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { require.True(t, daser.running == 0) } +func TestDASerSampleTimeout(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + getter := getterStub{} + avail := mocks.NewMockAvailability(gomock.NewController(t)) + avail.EXPECT().SharesAvailable(gomock.Any(), gomock.Any()).DoAndReturn( + func(sampleCtx context.Context, h *share.Root, peers ...peer.ID) error { + select { + case <-sampleCtx.Done(): + return sampleCtx.Err() + case <-ctx.Done(): + t.Fatal("call context didn't timeout in time") + return ctx.Err() + } + }) + + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + sub := new(header.DummySubscriber) + f := new(fraud.DummyService) + + // create and start DASer + daser, err := NewDASer(avail, sub, getter, ds, f) + require.NoError(t, err) + + // assign directly to avoid params validation + daser.params.SampleTimeout = 0 + + require.NoError(t, daser.Start(ctx)) + require.NoError(t, daser.sampler.state.waitCatchUp(ctx)) + + stats, err := daser.SamplingStats(ctx) + require.NoError(t, err) + + // failed map should contain first header as failed + require.Equal(t, stats.Failed[1], 1) +} + // createDASerSubcomponents takes numGetter (number of headers // to store in mockGetter) and numSub (number of headers to store // in the mock header.Subscriber), returning a newly instantiated @@ -308,7 +350,7 @@ func (m benchGetterStub) GetByHeight(_ context.Context, height uint64) (*header. type getterStub struct{} func (m getterStub) Head(context.Context) (*header.ExtendedHeader, error) { - return nil, nil + return &header.ExtendedHeader{RawHeader: header.RawHeader{Height: 1}}, nil } func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { diff --git a/das/options.go b/das/options.go index 5662bf281e..55be4c9e1f 100644 --- a/das/options.go +++ b/das/options.go @@ -35,8 +35,11 @@ type Parameters struct { // PriorityQueueSize defines the size limit of the priority queue PriorityQueueSize int - // SampleFrom is the height sampling will start from + // SampleFrom is the height sampling will start from if no previous checkpoint was saved SampleFrom uint64 + + // SampleTimeout is a maximum amount time sampling of single block may take until it will be canceled + SampleTimeout time.Duration } // DefaultParameters returns the default configuration values for the daser parameters @@ -49,6 +52,7 @@ func DefaultParameters() Parameters { BackgroundStoreInterval: 10 * time.Minute, PriorityQueueSize: 16 * 4, SampleFrom: 1, + SampleTimeout: time.Minute, } } @@ -80,7 +84,15 @@ func (p *Parameters) Validate() error { // which does not exist therefore breaking the DASer. if p.SampleFrom <= 0 { return errInvalidOptionValue( - "SampleFrome", + "SampleFrom", + "negative or 0", + ) + } + + // SampleTimeout = 0 would fail every sample operation with timeout error + if p.SampleTimeout <= 0 { + return errInvalidOptionValue( + "SampleTimeout", "negative or 0", ) } @@ -141,3 +153,11 @@ func WithSampleFrom(sampleFrom uint64) Option { d.params.SampleFrom = sampleFrom } } + +// WithSampleFrom is a functional option to configure the daser's `SampleTimeout` parameter +// Refer to WithSamplingRange documentation to see an example of how to use this +func WithSampleTimeout(sampleTimeout time.Duration) Option { + return func(d *DASer) { + d.params.SampleTimeout = sampleTimeout + } +} diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 6110cf4a53..6bba4423a9 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -30,6 +30,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { das.WithPriorityQueueSize(c.PriorityQueueSize), das.WithBackgroundStoreInterval(c.BackgroundStoreInterval), das.WithSampleFrom(c.SampleFrom), + das.WithSampleTimeout(c.SampleTimeout), } }, ), diff --git a/share/availability.go b/share/availability.go index 3f2091699d..190fb31dd1 100644 --- a/share/availability.go +++ b/share/availability.go @@ -23,6 +23,8 @@ const AvailabilityTimeout = 20 * time.Minute type Root = da.DataAvailabilityHeader // Availability defines interface for validation of Shares' availability. +// +//go:generate mockgen -destination=availability/mocks/availability.go -package=mocks . Availability type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on // the Network by requesting the EDS from the provided peers. diff --git a/share/availability/mocks/availability.go b/share/availability/mocks/availability.go new file mode 100644 index 0000000000..51c2acc391 --- /dev/null +++ b/share/availability/mocks/availability.go @@ -0,0 +1,70 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/share (interfaces: Availability) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + da "github.com/celestiaorg/celestia-app/pkg/da" + gomock "github.com/golang/mock/gomock" + peer "github.com/libp2p/go-libp2p/core/peer" +) + +// MockAvailability is a mock of Availability interface. +type MockAvailability struct { + ctrl *gomock.Controller + recorder *MockAvailabilityMockRecorder +} + +// MockAvailabilityMockRecorder is the mock recorder for MockAvailability. +type MockAvailabilityMockRecorder struct { + mock *MockAvailability +} + +// NewMockAvailability creates a new mock instance. +func NewMockAvailability(ctrl *gomock.Controller) *MockAvailability { + mock := &MockAvailability{ctrl: ctrl} + mock.recorder = &MockAvailabilityMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAvailability) EXPECT() *MockAvailabilityMockRecorder { + return m.recorder +} + +// ProbabilityOfAvailability mocks base method. +func (m *MockAvailability) ProbabilityOfAvailability(arg0 context.Context) float64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProbabilityOfAvailability", arg0) + ret0, _ := ret[0].(float64) + return ret0 +} + +// ProbabilityOfAvailability indicates an expected call of ProbabilityOfAvailability. +func (mr *MockAvailabilityMockRecorder) ProbabilityOfAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProbabilityOfAvailability", reflect.TypeOf((*MockAvailability)(nil).ProbabilityOfAvailability), arg0) +} + +// SharesAvailable mocks base method. +func (m *MockAvailability) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 ...peer.ID) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SharesAvailable", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// SharesAvailable indicates an expected call of SharesAvailable. +func (mr *MockAvailabilityMockRecorder) SharesAvailable(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SharesAvailable", reflect.TypeOf((*MockAvailability)(nil).SharesAvailable), varargs...) +} From 88f3df2396a9292769aac872a540a66979d849b3 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 20 Jan 2023 15:35:18 +0100 Subject: [PATCH 0314/1008] refactor(share/p2p): ironing out differences between shrex protocols (#1611) --- share/{p2p/shrexnd => getters}/shrex_test.go | 15 +++-- share/p2p/errors.go | 15 +++++ share/p2p/shrexeds/client.go | 66 +++++++++---------- share/p2p/shrexeds/exchange_test.go | 6 +- .../shrexeds/pb/extended_data_square.pb.go | 66 +++++++++---------- .../shrexeds/pb/extended_data_square.proto | 6 +- share/p2p/shrexnd/client.go | 63 ++++++++---------- share/p2p/shrexnd/server.go | 4 +- 8 files changed, 127 insertions(+), 114 deletions(-) rename share/{p2p/shrexnd => getters}/shrex_test.go (88%) create mode 100644 share/p2p/errors.go diff --git a/share/p2p/shrexnd/shrex_test.go b/share/getters/shrex_test.go similarity index 88% rename from share/p2p/shrexnd/shrex_test.go rename to share/getters/shrex_test.go index 3f55ed545a..37cb864bb7 100644 --- a/share/p2p/shrexnd/shrex_test.go +++ b/share/getters/shrex_test.go @@ -1,4 +1,4 @@ -package shrexnd +package getters import ( "context" @@ -16,7 +16,8 @@ import ( "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" + "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" ) @@ -42,22 +43,22 @@ func TestGetSharesWithProofByNamespace(t *testing.T) { require.NoError(t, err) // create server and register handler - srv, err := NewServer(net.NewTestNode().Host, edsStore, nil) + srvHost := net.NewTestNode().Host + srv, err := shrexnd.NewServer(srvHost, edsStore, NewIPLDGetter(bServ)) require.NoError(t, err) - srv.getter = getters.NewIPLDGetter(bServ) srv.Start() t.Cleanup(srv.Stop) // create client and connect it to server - client, err := NewClient(net.NewTestNode().Host) + client, err := shrexnd.NewClient(net.NewTestNode().Host) require.NoError(t, err) net.ConnectAll() - got, err := client.GetSharesByNamespace( + got, err := client.RequestND( ctx, &dah, nID, - srv.host.ID()) + srvHost.ID()) require.NoError(t, err) require.NoError(t, got.Verify(&dah, nID)) diff --git a/share/p2p/errors.go b/share/p2p/errors.go new file mode 100644 index 0000000000..b3c1b6e503 --- /dev/null +++ b/share/p2p/errors.go @@ -0,0 +1,15 @@ +package p2p + +import ( + "errors" +) + +// ErrUnavailable is returned when a peer doesn't have the requested data or doesn't have the +// capacity to serve it at the moment. It is used to signal that the peer couldn't serve the data +// successfully, but should be retried later. +var ErrUnavailable = errors.New("server cannot serve the requested data at this time") + +// ErrInvalidResponse is returned when a peer returns an invalid response or caused an internal +// error. It is used to signal that the peer couldn't serve the data successfully, and should not be +// retried. +var ErrInvalidResponse = errors.New("server returned an invalid response or caused an internal error") diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 50c5f359a6..3de122227a 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "time" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" @@ -12,13 +13,13 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" - p2p_pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" + "github.com/celestiaorg/celestia-node/share/p2p" + pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" "github.com/celestiaorg/rsmt2d" ) -var errNoMorePeers = errors.New("all peers returned invalid responses") - // Client is responsible for requesting EDSs for blocksync over the ShrEx/EDS protocol. type Client struct { protocolID protocol.ID @@ -46,50 +47,49 @@ func NewClient(host host.Host, opts ...Option) (*Client, error) { func (c *Client) RequestEDS( ctx context.Context, dataHash share.DataHash, - peers ...peer.ID, + peer peer.ID, ) (*rsmt2d.ExtendedDataSquare, error) { - req := &p2p_pb.EDSRequest{Hash: dataHash} - - for _, to := range peers { - eds, err := c.doRequest(ctx, req, to) - if eds != nil { - return eds, err - } - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - return nil, ctx.Err() - } - // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not - // unwrap to a ctx err - var ne net.Error - if errors.As(err, &ne) && ne.Timeout() { + eds, err := c.doRequest(ctx, dataHash, peer) + if err == nil { + return eds, nil + } + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return nil, ctx.Err() + } + // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not + // unwrap to a ctx err + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { + if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { return nil, context.DeadlineExceeded } - if err != nil { - log.Errorw("client: eds request to peer failed", "peer", to, "hash", dataHash.String()) - } } - - return nil, errNoMorePeers + if err != p2p.ErrUnavailable { + log.Errorw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) + } + return nil, err } func (c *Client) doRequest( ctx context.Context, - req *p2p_pb.EDSRequest, + dataHash share.DataHash, to peer.ID, ) (*rsmt2d.ExtendedDataSquare, error) { - dataHash := share.DataHash(req.Hash) - log.Debugf("client: requesting eds %s from peer %s", dataHash.String(), to) stream, err := c.host.NewStream(ctx, to, c.protocolID) if err != nil { return nil, fmt.Errorf("failed to open stream: %w", err) } + if dl, ok := ctx.Deadline(); ok { if err = stream.SetDeadline(dl); err != nil { log.Debugw("error setting deadline: %s", err) } } + req := &pb.EDSRequest{Hash: dataHash} + // request ODS + log.Debugf("client: requesting ods %s from peer %s", dataHash.String(), to) _, err = serde.Write(stream, req) if err != nil { stream.Reset() //nolint:errcheck @@ -102,7 +102,7 @@ func (c *Client) doRequest( } // read and parse status from peer - resp := new(p2p_pb.EDSResponse) + resp := new(pb.EDSResponse) _, err = serde.Read(stream, resp) if err != nil { stream.Reset() //nolint:errcheck @@ -110,20 +110,20 @@ func (c *Client) doRequest( } switch resp.Status { - case p2p_pb.Status_OK: + case pb.Status_OK: // use header and ODS bytes to construct EDS and verify it against dataHash eds, err := eds.ReadEDS(ctx, stream, dataHash) if err != nil { return nil, fmt.Errorf("failed to read eds from ods bytes: %w", err) } return eds, nil - case p2p_pb.Status_NOT_FOUND, p2p_pb.Status_REFUSED: + case pb.Status_NOT_FOUND, pb.Status_REFUSED: log.Debugf("client: peer %s couldn't serve eds %s with status %s", to.String(), dataHash.String(), resp.GetStatus()) - // no eds was returned, but the request was valid and should be retried - return nil, nil - case p2p_pb.Status_INVALID: + return nil, p2p.ErrUnavailable + case pb.Status_INVALID: fallthrough default: - return nil, fmt.Errorf("request status %s returned for root %s", resp.GetStatus(), dataHash.String()) + log.Errorf("request status %s returned for root %s", resp.Status.String(), dataHash.String()) + return nil, p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 07814833f3..bfc8f076ee 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -12,9 +12,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/p2p" + + "github.com/celestiaorg/celestia-app/pkg/da" ) func TestExchange_RequestEDS(t *testing.T) { @@ -66,7 +68,7 @@ func TestExchange_RequestEDS(t *testing.T) { t.Skip() dataHash := []byte("invalid") requestedEDS, err := client.RequestEDS(ctx, dataHash, server.host.ID()) - assert.ErrorIs(t, err, errNoMorePeers) + assert.ErrorIs(t, err, p2p.ErrInvalidResponse) assert.Nil(t, requestedEDS) }) diff --git a/share/p2p/shrexeds/pb/extended_data_square.pb.go b/share/p2p/shrexeds/pb/extended_data_square.pb.go index 9492cb298b..2a62e4b503 100644 --- a/share/p2p/shrexeds/pb/extended_data_square.pb.go +++ b/share/p2p/shrexeds/pb/extended_data_square.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: share/eds/p2p/pb/extended_data_square.proto +// source: share/p2p/shrexeds/pb/extended_data_square.proto package extended_data_square @@ -26,23 +26,23 @@ type Status int32 const ( Status_INVALID Status = 0 - Status_OK Status = 100 - Status_NOT_FOUND Status = 200 - Status_REFUSED Status = 201 + Status_OK Status = 1 + Status_NOT_FOUND Status = 2 + Status_REFUSED Status = 3 ) var Status_name = map[int32]string{ - 0: "INVALID", - 100: "OK", - 200: "NOT_FOUND", - 201: "REFUSED", + 0: "INVALID", + 1: "OK", + 2: "NOT_FOUND", + 3: "REFUSED", } var Status_value = map[string]int32{ "INVALID": 0, - "OK": 100, - "NOT_FOUND": 200, - "REFUSED": 201, + "OK": 1, + "NOT_FOUND": 2, + "REFUSED": 3, } func (x Status) String() string { @@ -50,7 +50,7 @@ func (x Status) String() string { } func (Status) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_e8ddcd8d207cc22e, []int{0} + return fileDescriptor_49d42aa96098056e, []int{0} } type EDSRequest struct { @@ -61,7 +61,7 @@ func (m *EDSRequest) Reset() { *m = EDSRequest{} } func (m *EDSRequest) String() string { return proto.CompactTextString(m) } func (*EDSRequest) ProtoMessage() {} func (*EDSRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e8ddcd8d207cc22e, []int{0} + return fileDescriptor_49d42aa96098056e, []int{0} } func (m *EDSRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -105,7 +105,7 @@ func (m *EDSResponse) Reset() { *m = EDSResponse{} } func (m *EDSResponse) String() string { return proto.CompactTextString(m) } func (*EDSResponse) ProtoMessage() {} func (*EDSResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e8ddcd8d207cc22e, []int{1} + return fileDescriptor_49d42aa96098056e, []int{1} } func (m *EDSResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -148,25 +148,25 @@ func init() { } func init() { - proto.RegisterFile("share/eds/p2p/pb/extended_data_square.proto", fileDescriptor_e8ddcd8d207cc22e) -} - -var fileDescriptor_e8ddcd8d207cc22e = []byte{ - // 223 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2e, 0xce, 0x48, 0x2c, - 0x4a, 0xd5, 0x4f, 0x4d, 0x29, 0xd6, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, - 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x4f, 0x49, 0x2c, 0x49, 0x8c, 0x2f, 0x2e, 0x2c, 0x4d, 0x2c, - 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x52, 0xe0, 0xe2, 0x72, 0x75, 0x09, 0x0e, 0x4a, - 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x48, 0x2c, 0xce, 0x90, 0x60, 0x54, - 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xb8, 0xc1, 0x2a, 0x8a, 0x0b, 0xf2, 0xf3, - 0x8a, 0x53, 0x85, 0xe4, 0xb9, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, 0x4a, 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, - 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x35, 0x17, 0x1b, 0x44, 0x44, 0x88, 0x9b, - 0x8b, 0xdd, 0xd3, 0x2f, 0xcc, 0xd1, 0xc7, 0xd3, 0x45, 0x80, 0x41, 0x88, 0x8d, 0x8b, 0xc9, 0xdf, - 0x5b, 0x20, 0x45, 0x88, 0x8f, 0x8b, 0xd3, 0xcf, 0x3f, 0x24, 0xde, 0xcd, 0x3f, 0xd4, 0xcf, 0x45, - 0xe0, 0x04, 0xa3, 0x10, 0x0f, 0x17, 0x7b, 0x90, 0xab, 0x5b, 0x68, 0xb0, 0xab, 0x8b, 0xc0, 0x49, - 0x46, 0x27, 0x89, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, - 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xbb, - 0xd7, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x3c, 0x20, 0x59, 0x9d, 0xde, 0x00, 0x00, 0x00, + proto.RegisterFile("share/p2p/shrexeds/pb/extended_data_square.proto", fileDescriptor_49d42aa96098056e) +} + +var fileDescriptor_49d42aa96098056e = []byte{ + // 224 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x28, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0xce, 0x28, 0x4a, 0xad, 0x48, 0x4d, 0x29, 0xd6, 0x2f, + 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x4f, 0x49, 0x2c, 0x49, 0x8c, + 0x2f, 0x2e, 0x2c, 0x4d, 0x2c, 0x4a, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x52, 0xe0, 0xe2, + 0x72, 0x75, 0x09, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x48, + 0x2c, 0xce, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xb8, 0xc1, + 0x2a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xe4, 0xb9, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, 0x4a, + 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x25, 0x17, + 0x1b, 0x44, 0x44, 0x88, 0x9b, 0x8b, 0xdd, 0xd3, 0x2f, 0xcc, 0xd1, 0xc7, 0xd3, 0x45, 0x80, 0x41, + 0x88, 0x8d, 0x8b, 0xc9, 0xdf, 0x5b, 0x80, 0x51, 0x88, 0x97, 0x8b, 0xd3, 0xcf, 0x3f, 0x24, 0xde, + 0xcd, 0x3f, 0xd4, 0xcf, 0x45, 0x80, 0x09, 0xa4, 0x26, 0xc8, 0xd5, 0x2d, 0x34, 0xd8, 0xd5, 0x45, + 0x80, 0xd9, 0x49, 0xe2, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, + 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0x92, 0xd8, 0xc0, + 0xae, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x57, 0xb5, 0xbb, 0xe1, 0x00, 0x00, 0x00, } func (m *EDSRequest) Marshal() (dAtA []byte, err error) { diff --git a/share/p2p/shrexeds/pb/extended_data_square.proto b/share/p2p/shrexeds/pb/extended_data_square.proto index c7f826aea5..f96493bf66 100644 --- a/share/p2p/shrexeds/pb/extended_data_square.proto +++ b/share/p2p/shrexeds/pb/extended_data_square.proto @@ -6,9 +6,9 @@ message EDSRequest { enum Status { INVALID = 0; - OK = 100; // data found - NOT_FOUND = 200; // data not found - REFUSED = 201; // request refused + OK = 1; // data found + NOT_FOUND = 2; // data not found + REFUSED = 3; // request refused } message EDSResponse { diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 9c0738aed2..171c83d45d 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -15,13 +15,13 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" "github.com/celestiaorg/nmt/namespace" ) -var errNoMorePeers = errors.New("shrex-nd: all peers returned invalid responses") - // Client implements client side of shrex/nd protocol to obtain namespaced shares data from remote // peers. type Client struct { @@ -49,38 +49,33 @@ func NewClient(host host.Host, opts ...Option) (*Client, error) { }, nil } -// GetSharesByNamespace request shares with option to collect proofs from remote peers using shrex -// protocol -func (c *Client) GetSharesByNamespace( +// RequestND requests namespaced data from the given peer. +// Returns valid data with its verified inclusion against the share.Root. +func (c *Client) RequestND( ctx context.Context, root *share.Root, nID namespace.ID, - peerIDs ...peer.ID, + peer peer.ID, ) (share.NamespacedShares, error) { - for _, peerID := range peerIDs { - shares, err := c.doRequest(ctx, root, nID, peerID) - if err == nil { - return shares, err + shares, err := c.doRequest(ctx, root, nID, peer) + if err == nil { + return shares, err + } + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return nil, ctx.Err() + } + // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not + // unwrap to a ctx err + var ne net.Error + if errors.As(err, &ne) && ne.Timeout() { + if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { + return nil, context.DeadlineExceeded } - - if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - return nil, ctx.Err() - } - // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not - // unwrap to a ctx err - var ne net.Error - if errors.As(err, &ne) && ne.Timeout() { - if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { - // stop trying peers if ctx deadline reached - return nil, context.DeadlineExceeded - } - } - - // log and try another peer - log.Errorw("client-nd: peer returned err", "peer_id", peerID.String(), "err", err) } - - return nil, errNoMorePeers + if err != p2p.ErrUnavailable { + log.Errorw("client-nd: peer returned err", "peer", peer, "err", err) + } + return nil, err } func (c *Client) doRequest( @@ -197,12 +192,12 @@ func statusToErr(code pb.StatusCode) error { switch code { case pb.StatusCode_OK: return nil - case pb.StatusCode_INVALID, - pb.StatusCode_NOT_FOUND, - pb.StatusCode_INTERNAL, - pb.StatusCode_REFUSED: + case pb.StatusCode_NOT_FOUND, pb.StatusCode_REFUSED: + return p2p.ErrUnavailable + case pb.StatusCode_INTERNAL, pb.StatusCode_INVALID: + fallthrough default: - code = pb.StatusCode_INVALID + log.Errorf("client-nd: request status %s returned", code.String()) + return p2p.ErrInvalidResponse } - return errors.New(code.String()) } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 09e39a495e..df767b7f9d 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -12,9 +12,9 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/ipld" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" + "github.com/celestiaorg/go-libp2p-messenger/serde" ) @@ -32,7 +32,7 @@ type Server struct { } // NewServer creates new Server -func NewServer(host host.Host, store *eds.Store, getter *getters.IPLDGetter, opts ...Option) (*Server, error) { +func NewServer(host host.Host, store *eds.Store, getter share.Getter, opts ...Option) (*Server, error) { params := DefaultParameters() for _, opt := range opts { opt(params) From b38b651ebfbec66688c4591d88a03c2c5e860a0b Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 23 Jan 2023 12:15:01 +0200 Subject: [PATCH 0315/1008] feat: implement broadcasting a data hash through eds-sub (#1473) --- header/core/listener.go | 32 ++++++++++++----- header/core/listener_test.go | 34 +++++++++++++++---- nodebuilder/core/module.go | 10 +++++- nodebuilder/share/module.go | 19 +++++++++-- share/p2p/{shrexpush => shrexsub}/pubsub.go | 10 +++--- .../{shrexpush => shrexsub}/pubsub_test.go | 2 +- .../{shrexpush => shrexsub}/subscription.go | 2 +- 7 files changed, 85 insertions(+), 24 deletions(-) rename share/p2p/{shrexpush => shrexsub}/pubsub.go (92%) rename share/p2p/{shrexpush => shrexsub}/pubsub_test.go (98%) rename share/p2p/{shrexpush => shrexsub}/subscription.go (98%) diff --git a/header/core/listener.go b/header/core/listener.go index d06178f7fc..c1e4d6f0df 100644 --- a/header/core/listener.go +++ b/header/core/listener.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) // Listener is responsible for listening to Core for @@ -21,24 +22,30 @@ import ( // broadcasts the new `ExtendedHeader` to the header-sub gossipsub // network. type Listener struct { - bcast libhead.Broadcaster[*header.ExtendedHeader] - fetcher *core.BlockFetcher - bServ blockservice.BlockService + fetcher *core.BlockFetcher + bServ blockservice.BlockService + construct header.ConstructFn - cancel context.CancelFunc + + headerBroadcaster libhead.Broadcaster[*header.ExtendedHeader] + hashBroadcaster shrexsub.BroadcastFn + + cancel context.CancelFunc } func NewListener( bcast libhead.Broadcaster[*header.ExtendedHeader], fetcher *core.BlockFetcher, + extHeaderBroadcaster shrexsub.BroadcastFn, bServ blockservice.BlockService, construct header.ConstructFn, ) *Listener { return &Listener{ - bcast: bcast, - fetcher: fetcher, - bServ: bServ, - construct: construct, + headerBroadcaster: bcast, + fetcher: fetcher, + hashBroadcaster: extHeaderBroadcaster, + bServ: bServ, + construct: construct, } } @@ -97,11 +104,18 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { } // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers - err = cl.bcast.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) + err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) if err != nil { log.Errorw("listener: broadcasting next header", "height", eh.Height(), "err", err) } + + // notify network of new EDS hash + err = cl.hashBroadcaster(ctx, eh.DataHash.Bytes()) + if err != nil { + log.Errorw("listener: broadcasting data hash", "height", eh.Height(), + "hash", eh.Hash(), "err", err) + } case <-ctx.Done(): return } diff --git a/header/core/listener_test.go b/header/core/listener_test.go index 2d561c4903..9a8e07b01d 100644 --- a/header/core/listener_test.go +++ b/header/core/listener_test.go @@ -15,6 +15,7 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) // TestListener tests the lifecycle of the core listener. @@ -32,13 +33,17 @@ func TestListener(t *testing.T) { // create one block to store as Head in local store and then unsubscribe from block events fetcher := createCoreFetcher(t) - + eds := createEdsPubSub(ctx, t) // create Listener and start listening - cl := createListener(ctx, t, fetcher, ps0) + cl := createListener(ctx, t, fetcher, ps0, eds) err = cl.Start(ctx) require.NoError(t, err) - // ensure headers are getting broadcasted to the gossipsub topic + edsSubs, err := eds.Subscribe() + require.NoError(t, err) + defer edsSubs.Cancel() + + // ensure headers and dataHash are getting broadcasted to the relevant topics for i := 1; i < 6; i++ { msg, err := sub.Next(ctx) require.NoError(t, err) @@ -46,6 +51,11 @@ func TestListener(t *testing.T) { var resp header.ExtendedHeader err = resp.UnmarshalBinary(msg.Data) require.NoError(t, err) + + dataHash, err := edsSubs.Next(ctx) + require.NoError(t, err) + + require.Equal(t, resp.DataHash.Bytes(), []byte(dataHash)) } err = cl.Stop(ctx) @@ -93,14 +103,26 @@ func createListener( t *testing.T, fetcher *core.BlockFetcher, ps *pubsub.PubSub, + edsSub *shrexsub.PubSub, ) *Listener { p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID) err := p2pSub.Start(ctx) require.NoError(t, err) t.Cleanup(func() { - err := p2pSub.Stop(ctx) - require.NoError(t, err) + require.NoError(t, p2pSub.Stop(ctx)) }) - return NewListener(p2pSub, fetcher, mdutils.Bserv(), header.MakeExtendedHeader) + return NewListener(p2pSub, fetcher, edsSub.Broadcast, mdutils.Bserv(), header.MakeExtendedHeader) +} + +func createEdsPubSub(ctx context.Context, t *testing.T) *shrexsub.PubSub { + net, err := mocknet.FullMeshLinked(1) + require.NoError(t, err) + edsSub, err := shrexsub.NewPubSub(ctx, net.Hosts()[0], "eds-test") + require.NoError(t, err) + require.NoError(t, edsSub.Start(ctx)) + t.Cleanup(func() { + require.NoError(t, edsSub.Stop(ctx)) + }) + return edsSub } diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index d6aa48f2a3..165e0150fb 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -3,6 +3,7 @@ package core import ( "context" + "github.com/ipfs/go-blockservice" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/core" @@ -11,6 +12,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/fxutil" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) // ConstructModule collects all the components and services related to managing the relationship @@ -34,7 +36,13 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(core.NewBlockFetcher), fxutil.ProvideAs(headercore.NewExchange, new(libhead.Exchange[*header.ExtendedHeader])), fx.Invoke(fx.Annotate( - headercore.NewListener, + func(bcast libhead.Broadcaster[*header.ExtendedHeader], + fetcher *core.BlockFetcher, + pubsub *shrexsub.PubSub, + bServ blockservice.BlockService, + construct header.ConstructFn) *headercore.Listener { + return headercore.NewListener(bcast, fetcher, pubsub.Broadcast, bServ, construct) + }, fx.OnStart(func(ctx context.Context, listener *headercore.Listener) error { return listener.Start(ctx) }), diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 24f6d265b4..0db7fab2c3 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -7,6 +7,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" @@ -15,8 +16,7 @@ import ( "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" - - "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { @@ -97,6 +97,21 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return avail.Stop(ctx) }), )), + fx.Provide(fx.Annotate( + func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { + return shrexsub.NewPubSub( + ctx, + h, + string(network), + ) + }, + fx.OnStart(func(ctx context.Context, pubsub *shrexsub.PubSub) error { + return pubsub.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, pubsub *shrexsub.PubSub) error { + return pubsub.Stop(ctx) + }), + )), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability fx.Provide(cacheAvailability[*full.ShareAvailability]), diff --git a/share/p2p/shrexpush/pubsub.go b/share/p2p/shrexsub/pubsub.go similarity index 92% rename from share/p2p/shrexpush/pubsub.go rename to share/p2p/shrexsub/pubsub.go index cdeaf7ec70..93bd1530bd 100644 --- a/share/p2p/shrexpush/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -1,4 +1,4 @@ -package shrexpush +package shrexsub import ( "context" @@ -13,7 +13,7 @@ import ( "github.com/celestiaorg/celestia-node/share" ) -var log = logging.Logger("shrex-push") +var log = logging.Logger("shrex-sub") // pubSubTopic hardcodes the name of the EDS floodsub topic with the provided suffix. func pubSubTopic(suffix string) string { @@ -25,6 +25,9 @@ func pubSubTopic(suffix string) string { // Validator is allowed to be blocking for an indefinite time or until the context is canceled. type Validator func(context.Context, peer.ID, share.DataHash) pubsub.ValidationResult +// BroadcastFn aliases the function that broadcasts the DataHash. +type BroadcastFn func(context.Context, share.DataHash) error + // PubSub manages receiving and propagating the EDS from/to the network // over "eds-sub" subscription. type PubSub struct { @@ -64,9 +67,8 @@ func (s *PubSub) Start(context.Context) error { func (s *PubSub) Stop(context.Context) error { err := s.pubSub.UnregisterTopicValidator(s.pubSubTopic) if err != nil { - return err + log.Warnw("unregistering topic", "err", err) } - return s.topic.Close() } diff --git a/share/p2p/shrexpush/pubsub_test.go b/share/p2p/shrexsub/pubsub_test.go similarity index 98% rename from share/p2p/shrexpush/pubsub_test.go rename to share/p2p/shrexsub/pubsub_test.go index fde86864a9..e766c23837 100644 --- a/share/p2p/shrexpush/pubsub_test.go +++ b/share/p2p/shrexsub/pubsub_test.go @@ -1,4 +1,4 @@ -package shrexpush +package shrexsub import ( "context" diff --git a/share/p2p/shrexpush/subscription.go b/share/p2p/shrexsub/subscription.go similarity index 98% rename from share/p2p/shrexpush/subscription.go rename to share/p2p/shrexsub/subscription.go index 64126a436c..1e6794a33e 100644 --- a/share/p2p/shrexpush/subscription.go +++ b/share/p2p/shrexsub/subscription.go @@ -1,4 +1,4 @@ -package shrexpush +package shrexsub import ( "context" From c01d5dd635b45fce751648cb7f10c604ad9e0dab Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 23 Jan 2023 17:48:42 +0300 Subject: [PATCH 0316/1008] fix(daser) replace catchUpDoneCh channel gracefuly (#1631) DASer `waitCatchUp` method has a data race for catchUpDoneCh pointer. Pointer needs to be swapped gracefully. --- das/state.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/das/state.go b/das/state.go index 970433b8bc..b377c2e0f4 100644 --- a/das/state.go +++ b/das/state.go @@ -2,6 +2,7 @@ package das import ( "context" + "sync/atomic" ) // coordinatorState represents the current state of sampling @@ -18,7 +19,7 @@ type coordinatorState struct { next uint64 // all headers before next were sent to workers networkHead uint64 - catchUpDone bool // indicates if all headers are sampled + catchUpDone atomic.Bool // indicates if all headers are sampled catchUpDoneCh chan struct{} // blocks until all headers are sampled } @@ -34,7 +35,6 @@ func newCoordinatorState(params Parameters) coordinatorState { nextJobID: 0, next: params.SampleFrom, networkHead: params.SampleFrom, - catchUpDone: false, catchUpDoneCh: make(chan struct{}), } } @@ -201,28 +201,31 @@ func (s *coordinatorState) unsafeStats() SamplingStats { Failed: failed, Workers: workers, Concurrency: len(workers), - CatchUpDone: s.catchUpDone, - IsRunning: len(workers) > 0 || s.catchUpDone, + CatchUpDone: s.catchUpDone.Load(), + IsRunning: len(workers) > 0 || s.catchUpDone.Load(), } } func (s *coordinatorState) checkDone() { if len(s.inProgress) == 0 && len(s.priority) == 0 && s.next > s.networkHead { - if !s.catchUpDone { + if s.catchUpDone.CompareAndSwap(false, true) { close(s.catchUpDoneCh) - s.catchUpDone = true } return } - if s.catchUpDone { + if s.catchUpDone.Load() { + // overwrite channel before storing done flag s.catchUpDoneCh = make(chan struct{}) - s.catchUpDone = false + s.catchUpDone.Store(false) } } // waitCatchUp waits for sampling process to indicate catchup is done func (s *coordinatorState) waitCatchUp(ctx context.Context) error { + if s.catchUpDone.Load() { + return nil + } select { case <-s.catchUpDoneCh: case <-ctx.Done(): From 2a8ac09bed9ac66e73b8df04f04e43c181ccb0a3 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 24 Jan 2023 11:35:12 +0100 Subject: [PATCH 0317/1008] feat(share): implements sync CascadeGetter (#1628) Co-authored-by: Ryan --- share/getter.go | 2 + share/getters/cascade.go | 132 ++++++++++++++++++++++++++++++++++ share/getters/cascade_test.go | 106 +++++++++++++++++++++++++++ share/getters/testing.go | 62 ++++++++++++++++ share/getters/utils.go | 2 + share/mocks/getter.go | 84 ++++++++++++++++++++++ 6 files changed, 388 insertions(+) create mode 100644 share/getters/cascade.go create mode 100644 share/getters/cascade_test.go create mode 100644 share/getters/testing.go create mode 100644 share/mocks/getter.go diff --git a/share/getter.go b/share/getter.go index fdbbcb6ea0..f740bcaf24 100644 --- a/share/getter.go +++ b/share/getter.go @@ -15,6 +15,8 @@ import ( // Getter interface provides a set of accessors for shares by the Root. // Automatically verifies integrity of shares(exceptions possible depending on the implementation). +// +//go:generate mockgen -destination=mocks/getter.go -package=mocks . Getter type Getter interface { // GetShare gets a Share by coordinates in EDS. GetShare(ctx context.Context, root *Root, row, col int) (Share, error) diff --git a/share/getters/cascade.go b/share/getters/cascade.go new file mode 100644 index 0000000000..8981fc37e1 --- /dev/null +++ b/share/getters/cascade.go @@ -0,0 +1,132 @@ +package getters + +import ( + "context" + "errors" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.uber.org/multierr" + + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/share" +) + +var _ share.Getter = (*CascadeGetter)(nil) + +// CascadeGetter implements custom share.Getter that composes multiple Getter implementations in +// "cascading" order. +// +// See cascade func for details on cascading. +type CascadeGetter struct { + interval time.Duration + + getters []share.Getter +} + +// NewCascadeGetter instantiates a new CascadeGetter from given share.Getters with given interval. +func NewCascadeGetter(getters []share.Getter, interval time.Duration) *CascadeGetter { + return &CascadeGetter{ + interval: interval, + getters: getters, + } +} + +// GetShare gets a share from any of registered share.Getters in cascading order. +func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { + ctx, span := tracer.Start(ctx, "cascade/get-share", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.Int("row", row), + attribute.Int("col", col), + )) + defer span.End() + + get := func(ctx context.Context, get share.Getter) (share.Share, error) { + return get.GetShare(ctx, root, row, col) + } + + return cascadeGetters(ctx, cg.getters, get, cg.interval) +} + +// GetEDS gets a full EDS from any of registered share.Getters in cascading order. +func (cg *CascadeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + ctx, span := tracer.Start(ctx, "cascade/get-eds", trace.WithAttributes( + attribute.String("root", root.String()), + )) + defer span.End() + + get := func(ctx context.Context, get share.Getter) (*rsmt2d.ExtendedDataSquare, error) { + return get.GetEDS(ctx, root) + } + + return cascadeGetters(ctx, cg.getters, get, cg.interval) +} + +// GetSharesByNamespace gets NamespacedShares from any of registered share.Getters in cascading +// order. +func (cg *CascadeGetter) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + id namespace.ID, +) (share.NamespacedShares, error) { + ctx, span := tracer.Start(ctx, "cascade/get-shares-by-namespace", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.String("nid", id.String()), + )) + defer span.End() + + get := func(ctx context.Context, get share.Getter) (share.NamespacedShares, error) { + return get.GetSharesByNamespace(ctx, root, id) + } + + return cascadeGetters(ctx, cg.getters, get, cg.interval) +} + +// cascade implements a cascading retry algorithm for getting a value from multiple sources. +// Cascading implies trying the sources one-by-one in the given order with the +// given interval until either: +// - One of the sources returns the value +// - All of the sources errors +// - Context is canceled +// +// NOTE: New source attempts after interval do suspend running sources in progress. +func cascadeGetters[V any]( + ctx context.Context, + getters []share.Getter, + get func(context.Context, share.Getter) (V, error), + interval time.Duration, +) (V, error) { + var ( + zero V + err error + ) + ctx, span := tracer.Start(ctx, "cascade", trace.WithAttributes( + attribute.String("interval", interval.String()), + attribute.Int("total-getters", len(getters)), + )) + defer func() { + if err != nil { + utils.SetStatusAndEnd(span, errors.New("all getters failed")) + } + }() + + for i, getter := range getters { + log.Debugf("cascade: launching getter #%d", i) + span.AddEvent("getter launched", trace.WithAttributes(attribute.Int("getter_idx", i))) + ctx, cancel := context.WithTimeout(ctx, interval) + val, getErr := get(ctx, getter) + cancel() + if getErr == nil { + return val, nil + } + + // TODO(@Wondertan): migrate to errors.Join once Go1.20 is out! + err = multierr.Append(err, getErr) + span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) + } + return zero, err +} diff --git a/share/getters/cascade_test.go b/share/getters/cascade_test.go new file mode 100644 index 0000000000..f124e6ffe8 --- /dev/null +++ b/share/getters/cascade_test.go @@ -0,0 +1,106 @@ +package getters + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "go.uber.org/multierr" + + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/mocks" +) + +func TestCascadeGetter(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + const gettersN = 3 + roots := make([]*share.Root, gettersN) + getters := make([]share.Getter, gettersN) + for i := range roots { + getters[i], roots[i] = TestGetter(t) + } + + getter := NewCascadeGetter(getters, time.Millisecond) + t.Run("GetShare", func(t *testing.T) { + for _, r := range roots { + sh, err := getter.GetShare(ctx, r, 0, 0) + assert.NoError(t, err) + assert.NotEmpty(t, sh) + } + }) + + t.Run("GetEDS", func(t *testing.T) { + for _, r := range roots { + sh, err := getter.GetEDS(ctx, r) + assert.NoError(t, err) + assert.NotEmpty(t, sh) + } + }) +} + +func TestCascade(t *testing.T) { + ctrl := gomock.NewController(t) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + timeoutGetter := mocks.NewMockGetter(ctrl) + immediateFailGetter := mocks.NewMockGetter(ctrl) + successGetter := mocks.NewMockGetter(ctrl) + timeoutGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, _ *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + <-ctx.Done() + return nil, ctx.Err() + }).AnyTimes() + immediateFailGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). + Return(nil, errors.New("second getter fails immediately")).AnyTimes() + successGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). + Return(nil, nil).AnyTimes() + + get := func(ctx context.Context, get share.Getter) (*rsmt2d.ExtendedDataSquare, error) { + return get.GetEDS(ctx, nil) + } + + t.Run("SuccessFirst", func(t *testing.T) { + getters := []share.Getter{successGetter, timeoutGetter, immediateFailGetter} + _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + assert.NoError(t, err) + }) + + t.Run("SuccessSecond", func(t *testing.T) { + getters := []share.Getter{immediateFailGetter, successGetter} + _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + assert.NoError(t, err) + }) + + t.Run("SuccessSecondAfterFirst", func(t *testing.T) { + getters := []share.Getter{timeoutGetter, successGetter} + _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + assert.NoError(t, err) + }) + + t.Run("SuccessAfterMultipleTimeouts", func(t *testing.T) { + getters := []share.Getter{timeoutGetter, immediateFailGetter, timeoutGetter, timeoutGetter, successGetter} + _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + assert.NoError(t, err) + }) + + t.Run("Error", func(t *testing.T) { + getters := []share.Getter{immediateFailGetter, timeoutGetter, immediateFailGetter} + _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + assert.Error(t, err) + assert.Len(t, multierr.Errors(err), 3) + }) + + t.Run("Single", func(t *testing.T) { + getters := []share.Getter{successGetter} + _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + assert.NoError(t, err) + }) +} diff --git a/share/getters/testing.go b/share/getters/testing.go new file mode 100644 index 0000000000..eca65d9722 --- /dev/null +++ b/share/getters/testing.go @@ -0,0 +1,62 @@ +package getters + +import ( + "context" + "fmt" + "testing" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +// TestGetter provides a testing SingleEDSGetter and the root of the EDS it holds. +func TestGetter(t *testing.T) (share.Getter, *share.Root) { + eds := share.RandEDS(t, 8) + dah := da.NewDataAvailabilityHeader(eds) + return &SingleEDSGetter{ + EDS: eds, + }, &dah +} + +// SingleEDSGetter contains a single EDS where data is retrieved from. +// Its primary use is testing, and GetSharesByNamespace is not supported. +type SingleEDSGetter struct { + EDS *rsmt2d.ExtendedDataSquare +} + +// GetShare gets a share from a kept EDS if exist and if the correct root is given. +func (seg *SingleEDSGetter) GetShare(_ context.Context, root *share.Root, row, col int) (share.Share, error) { + err := seg.checkRoot(root) + if err != nil { + return nil, err + } + return seg.EDS.GetCell(uint(row), uint(col)), nil +} + +// GetEDS returns a kept EDS if the correct root is given. +func (seg *SingleEDSGetter) GetEDS(_ context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + err := seg.checkRoot(root) + if err != nil { + return nil, err + } + return seg.EDS, nil +} + +// GetSharesByNamespace returns NamespacedShares from a kept EDS if the correct root is given. +func (seg *SingleEDSGetter) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + id namespace.ID, +) (share.NamespacedShares, error) { + panic("SingleEDSGetter: GetSharesByNamespace is not implemented") +} + +func (seg *SingleEDSGetter) checkRoot(root *share.Root) error { + dah := da.NewDataAvailabilityHeader(seg.EDS) + if !root.Equals(&dah) { + return fmt.Errorf("unknown EDS: have %s, asked %s", dah.String(), root.String()) + } + return nil +} diff --git a/share/getters/utils.go b/share/getters/utils.go index 5cc17e5289..7771f68017 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -6,6 +6,7 @@ import ( "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -21,6 +22,7 @@ import ( var ( tracer = otel.Tracer("share/getters") + log = logging.Logger("share/getters") ) // filterRootsByNamespace returns the row roots from the given share.Root that contain the passed diff --git a/share/mocks/getter.go b/share/mocks/getter.go new file mode 100644 index 0000000000..1c73c9170d --- /dev/null +++ b/share/mocks/getter.go @@ -0,0 +1,84 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/share (interfaces: Getter) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + da "github.com/celestiaorg/celestia-app/pkg/da" + share "github.com/celestiaorg/celestia-node/share" + namespace "github.com/celestiaorg/nmt/namespace" + rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" +) + +// MockGetter is a mock of Getter interface. +type MockGetter struct { + ctrl *gomock.Controller + recorder *MockGetterMockRecorder +} + +// MockGetterMockRecorder is the mock recorder for MockGetter. +type MockGetterMockRecorder struct { + mock *MockGetter +} + +// NewMockGetter creates a new mock instance. +func NewMockGetter(ctrl *gomock.Controller) *MockGetter { + mock := &MockGetter{ctrl: ctrl} + mock.recorder = &MockGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGetter) EXPECT() *MockGetterMockRecorder { + return m.recorder +} + +// GetEDS mocks base method. +func (m *MockGetter) GetEDS(arg0 context.Context, arg1 *da.DataAvailabilityHeader) (*rsmt2d.ExtendedDataSquare, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEDS", arg0, arg1) + ret0, _ := ret[0].(*rsmt2d.ExtendedDataSquare) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEDS indicates an expected call of GetEDS. +func (mr *MockGetterMockRecorder) GetEDS(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEDS", reflect.TypeOf((*MockGetter)(nil).GetEDS), arg0, arg1) +} + +// GetShare mocks base method. +func (m *MockGetter) GetShare(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2, arg3 int) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetShare", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetShare indicates an expected call of GetShare. +func (mr *MockGetterMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetShare", reflect.TypeOf((*MockGetter)(nil).GetShare), arg0, arg1, arg2, arg3) +} + +// GetSharesByNamespace mocks base method. +func (m *MockGetter) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 namespace.ID) (share.NamespacedShares, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) + ret0, _ := ret[0].(share.NamespacedShares) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSharesByNamespace indicates an expected call of GetSharesByNamespace. +func (mr *MockGetterMockRecorder) GetSharesByNamespace(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSharesByNamespace", reflect.TypeOf((*MockGetter)(nil).GetSharesByNamespace), arg0, arg1, arg2) +} From 0eacfc03dd9626dd11fa4073cb712c44e726ff52 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 24 Jan 2023 11:57:59 +0100 Subject: [PATCH 0318/1008] refactor(header): Extract `CoreExchange` and `CoreListener` out of the header root package (#1627) This PR is a blocker to #1423 It extracts `CoreExchange` and `CoreListener` from the header package into the core package and moves header testing utilities into a separate package at the root level called `headertest`. --- {header/core => core}/exchange.go | 17 ++++--- {header/core => core}/exchange_test.go | 9 ++-- {header/core => core}/listener.go | 5 +- {header/core => core}/listener_test.go | 3 +- das/daser_test.go | 17 ++++--- fraud/testing.go | 3 +- {header => headertest}/header_test.go | 7 +-- {header => headertest}/serde_test.go | 10 ++-- {header => headertest}/testing.go | 69 ++++++++++++++------------ {header => headertest}/verify_test.go | 2 +- nodebuilder/core/module.go | 12 ++--- nodebuilder/testing.go | 3 +- nodebuilder/tests/fraud_test.go | 6 +-- nodebuilder/tests/swamp/swamp.go | 3 +- share/eds/retriever_test.go | 8 +-- 15 files changed, 93 insertions(+), 81 deletions(-) rename {header/core => core}/exchange.go (86%) rename {header/core => core}/exchange_test.go (80%) rename {header/core => core}/listener.go (96%) rename {header/core => core}/listener_test.go (98%) rename {header => headertest}/header_test.go (83%) rename {header => headertest}/serde_test.go (84%) rename {header => headertest}/testing.go (79%) rename {header => headertest}/verify_test.go (98%) diff --git a/header/core/exchange.go b/core/exchange.go similarity index 86% rename from header/core/exchange.go rename to core/exchange.go index de895cbffe..bd9d0de645 100644 --- a/header/core/exchange.go +++ b/core/exchange.go @@ -6,22 +6,22 @@ import ( "fmt" "github.com/ipfs/go-blockservice" - logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" ) -var log = logging.Logger("header/core") - type Exchange struct { - fetcher *core.BlockFetcher + fetcher *BlockFetcher shareStore blockservice.BlockService construct header.ConstructFn } -func NewExchange(fetcher *core.BlockFetcher, bServ blockservice.BlockService, construct header.ConstructFn) *Exchange { +func NewExchange( + fetcher *BlockFetcher, + bServ blockservice.BlockService, + construct header.ConstructFn, +) *Exchange { return &Exchange{ fetcher: fetcher, shareStore: bServ, @@ -54,7 +54,10 @@ func (ce *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( return headers, nil } -func (ce *Exchange) GetVerifiedRange(ctx context.Context, from *header.ExtendedHeader, amount uint64, +func (ce *Exchange) GetVerifiedRange( + ctx context.Context, + from *header.ExtendedHeader, + amount uint64, ) ([]*header.ExtendedHeader, error) { headers, err := ce.GetRangeByHeight(ctx, uint64(from.Height())+1, amount) if err != nil { diff --git a/header/core/exchange_test.go b/core/exchange_test.go similarity index 80% rename from header/core/exchange_test.go rename to core/exchange_test.go index c667dc2b2a..0a7b58ccd7 100644 --- a/header/core/exchange_test.go +++ b/core/exchange_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" ) @@ -34,12 +33,12 @@ func Test_hashMatch(t *testing.T) { assert.False(t, bytes.Equal(expected, mismatch)) } -func createCoreFetcher(t *testing.T) *core.BlockFetcher { - client := core.StartTestNode(t).Client - return core.NewBlockFetcher(client) +func createCoreFetcher(t *testing.T) *BlockFetcher { + client := StartTestNode(t).Client + return NewBlockFetcher(client) } -func generateBlocks(t *testing.T, fetcher *core.BlockFetcher) { +func generateBlocks(t *testing.T, fetcher *BlockFetcher) { sub, err := fetcher.SubscribeNewBlockEvent(context.Background()) require.NoError(t, err) diff --git a/header/core/listener.go b/core/listener.go similarity index 96% rename from header/core/listener.go rename to core/listener.go index c1e4d6f0df..76746adbf8 100644 --- a/header/core/listener.go +++ b/core/listener.go @@ -8,7 +8,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" @@ -22,7 +21,7 @@ import ( // broadcasts the new `ExtendedHeader` to the header-sub gossipsub // network. type Listener struct { - fetcher *core.BlockFetcher + fetcher *BlockFetcher bServ blockservice.BlockService construct header.ConstructFn @@ -35,7 +34,7 @@ type Listener struct { func NewListener( bcast libhead.Broadcaster[*header.ExtendedHeader], - fetcher *core.BlockFetcher, + fetcher *BlockFetcher, extHeaderBroadcaster shrexsub.BroadcastFn, bServ blockservice.BlockService, construct header.ConstructFn, diff --git a/header/core/listener_test.go b/core/listener_test.go similarity index 98% rename from header/core/listener_test.go rename to core/listener_test.go index 9a8e07b01d..050d2d0e9e 100644 --- a/header/core/listener_test.go +++ b/core/listener_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" @@ -101,7 +100,7 @@ func createMocknetWithTwoPubsubEndpoints(ctx context.Context, t *testing.T) (*pu func createListener( ctx context.Context, t *testing.T, - fetcher *core.BlockFetcher, + fetcher *BlockFetcher, ps *pubsub.PubSub, edsSub *shrexsub.PubSub, ) *Listener { diff --git a/das/daser_test.go b/das/daser_test.go index 7d0863839b..05b7d48001 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -19,6 +19,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/headertest" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" @@ -157,7 +158,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { // create fraud service and break one header f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, "private") require.NoError(t, f.Start(ctx)) - mockGet.headers[1] = header.CreateFraudExtHeader(t, mockGet.headers[1], bServ) + mockGet.headers[1] = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() // create and start DASer @@ -200,7 +201,7 @@ func TestDASerSampleTimeout(t *testing.T) { }) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - sub := new(header.DummySubscriber) + sub := new(headertest.DummySubscriber) f := new(fraud.DummyService) // create and start DASer @@ -229,7 +230,7 @@ func createDASerSubcomponents( bServ blockservice.BlockService, numGetter, numSub int, -) (*mockGetter, *header.DummySubscriber, *fraud.DummyService) { +) (*mockGetter, *headertest.DummySubscriber, *fraud.DummyService) { mockGet, sub := createMockGetterAndSub(t, bServ, numGetter, numSub) fraud := new(fraud.DummyService) return mockGet, sub, fraud @@ -240,7 +241,7 @@ func createMockGetterAndSub( bServ blockservice.BlockService, numGetter, numSub int, -) (*mockGetter, *header.DummySubscriber) { +) (*mockGetter, *headertest.DummySubscriber) { mockGet := &mockGetter{ headers: make(map[int64]*header.ExtendedHeader), doneCh: make(chan struct{}), @@ -249,7 +250,7 @@ func createMockGetterAndSub( mockGet.generateHeaders(t, bServ, 0, numGetter) - sub := new(header.DummySubscriber) + sub := new(headertest.DummySubscriber) mockGet.fillSubWithHeaders(t, sub, bServ, numGetter, numGetter+numSub) return mockGet, sub @@ -258,7 +259,7 @@ func createMockGetterAndSub( // fillSubWithHeaders generates `num` headers from the future for p2pSub to pipe through to DASer. func (m *mockGetter) fillSubWithHeaders( t *testing.T, - sub *header.DummySubscriber, + sub *headertest.DummySubscriber, bServ blockservice.BlockService, startHeight, endHeight int, @@ -269,7 +270,7 @@ func (m *mockGetter) fillSubWithHeaders( for i := startHeight; i < endHeight; i++ { dah := availability_test.RandFillBS(t, 16, bServ) - randHeader := header.RandExtendedHeader(t) + randHeader := headertest.RandExtendedHeader(t) randHeader.DataHash = dah.Hash() randHeader.DAH = dah randHeader.RawHeader.Height = int64(i + 1) @@ -297,7 +298,7 @@ func (m *mockGetter) generateHeaders(t *testing.T, bServ blockservice.BlockServi for i := startHeight; i < endHeight; i++ { dah := availability_test.RandFillBS(t, 16, bServ) - randHeader := header.RandExtendedHeader(t) + randHeader := headertest.RandExtendedHeader(t) randHeader.DataHash = dah.Hash() randHeader.DAH = dah randHeader.RawHeader.Height = int64(i + 1) diff --git a/fraud/testing.go b/fraud/testing.go index d891cf45d3..355d0440e4 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/headertest" ) type DummyService struct { @@ -45,7 +46,7 @@ func createStore(t *testing.T, numHeaders int) *mockStore { headHeight: 0, } - suite := header.NewTestSuite(t, numHeaders) + suite := headertest.NewTestSuite(t, numHeaders) for i := 0; i < numHeaders; i++ { header := suite.GenExtendedHeader() diff --git a/header/header_test.go b/headertest/header_test.go similarity index 83% rename from header/header_test.go rename to headertest/header_test.go index 43f0204a2a..3e360504a2 100644 --- a/header/header_test.go +++ b/headertest/header_test.go @@ -1,4 +1,4 @@ -package header +package headertest import ( "context" @@ -10,6 +10,7 @@ import ( "github.com/tendermint/tendermint/libs/rand" "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header" ) func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { @@ -32,10 +33,10 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { comm, val, err := fetcher.GetBlockInfo(ctx, &height) require.NoError(t, err) - headerExt, err := MakeExtendedHeader(ctx, b, comm, val, store) + headerExt, err := header.MakeExtendedHeader(ctx, b, comm, val, store) require.NoError(t, err) - assert.Equal(t, EmptyDAH(), *headerExt.DAH) + assert.Equal(t, header.EmptyDAH(), *headerExt.DAH) } func TestMismatchedDataHash_ComputedRoot(t *testing.T) { diff --git a/header/serde_test.go b/headertest/serde_test.go similarity index 84% rename from header/serde_test.go rename to headertest/serde_test.go index 2972b8c706..d06f7dfd41 100644 --- a/header/serde_test.go +++ b/headertest/serde_test.go @@ -1,10 +1,12 @@ -package header +package headertest import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/header" ) func TestMarshalUnmarshalExtendedHeader(t *testing.T) { @@ -12,7 +14,7 @@ func TestMarshalUnmarshalExtendedHeader(t *testing.T) { binaryData, err := in.MarshalBinary() require.NoError(t, err) - out := &ExtendedHeader{} + out := &header.ExtendedHeader{} err = out.UnmarshalBinary(binaryData) require.NoError(t, err) equalExtendedHeader(t, in, out) @@ -22,13 +24,13 @@ func TestMarshalUnmarshalExtendedHeader(t *testing.T) { jsonData, err := in.MarshalJSON() require.NoError(t, err) - out = &ExtendedHeader{} + out = &header.ExtendedHeader{} err = out.UnmarshalJSON(jsonData) require.NoError(t, err) equalExtendedHeader(t, in, out) } -func equalExtendedHeader(t *testing.T, in, out *ExtendedHeader) { +func equalExtendedHeader(t *testing.T, in, out *header.ExtendedHeader) { // ValidatorSet.totalVotingPower is not set (is a cached value that can be recomputed client side) assert.Equal(t, in.ValidatorSet.Validators, out.ValidatorSet.Validators) assert.Equal(t, in.ValidatorSet.Proposer, out.ValidatorSet.Proposer) diff --git a/header/testing.go b/headertest/testing.go similarity index 79% rename from header/testing.go rename to headertest/testing.go index abf2db387b..4028042e75 100644 --- a/header/testing.go +++ b/headertest/testing.go @@ -1,7 +1,4 @@ -// TODO(@Wondertan): Ideally, we should move that into subpackage, so this does not get included -// into binary of production code, but that does not matter at the moment. - -package header +package headertest import ( "context" @@ -11,6 +8,7 @@ import ( "time" "github.com/ipfs/go-blockservice" + logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/tmhash" @@ -24,11 +22,14 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/header/test" "github.com/celestiaorg/celestia-node/share" ) +var log = logging.Logger("headertest") + // TestSuite provides everything you need to test chain of Headers. // If not, please don't hesitate to extend it for your case. type TestSuite struct { @@ -38,7 +39,7 @@ type TestSuite struct { valSet *types.ValidatorSet valPntr int - head *ExtendedHeader + head *header.ExtendedHeader } // NewTestSuite setups a new test suite with a given number of validators. @@ -51,8 +52,8 @@ func NewTestSuite(t *testing.T, num int) *TestSuite { } } -func (s *TestSuite) genesis() *ExtendedHeader { - dah := EmptyDAH() +func (s *TestSuite) genesis() *header.ExtendedHeader { + dah := header.EmptyDAH() gen := RandRawHeader(s.t) @@ -64,7 +65,7 @@ func (s *TestSuite) genesis() *ExtendedHeader { commit, err := core.MakeCommit(RandBlockID(s.t), gen.Height, 0, voteSet, s.vals, time.Now()) require.NoError(s.t, err) - eh := &ExtendedHeader{ + eh := &header.ExtendedHeader{ RawHeader: *gen, Commit: commit, ValidatorSet: s.valSet, @@ -74,28 +75,28 @@ func (s *TestSuite) genesis() *ExtendedHeader { return eh } -func (s *TestSuite) Head() *ExtendedHeader { +func (s *TestSuite) Head() *header.ExtendedHeader { if s.head == nil { s.head = s.genesis() } return s.head } -func (s *TestSuite) GenExtendedHeaders(num int) []*ExtendedHeader { - headers := make([]*ExtendedHeader, num) +func (s *TestSuite) GenExtendedHeaders(num int) []*header.ExtendedHeader { + headers := make([]*header.ExtendedHeader, num) for i := range headers { headers[i] = s.GenExtendedHeader() } return headers } -func (s *TestSuite) GetRandomHeader() *ExtendedHeader { +func (s *TestSuite) GetRandomHeader() *header.ExtendedHeader { return s.GenExtendedHeader() } -var _ test.Generator[*ExtendedHeader] = &TestSuite{} +var _ test.Generator[*header.ExtendedHeader] = &TestSuite{} -func (s *TestSuite) GenExtendedHeader() *ExtendedHeader { +func (s *TestSuite) GenExtendedHeader() *header.ExtendedHeader { if s.head == nil { s.head = s.genesis() return s.head @@ -104,7 +105,7 @@ func (s *TestSuite) GenExtendedHeader() *ExtendedHeader { dah := da.MinDataAvailabilityHeader() height := s.Head().Height() + 1 rh := s.GenRawHeader(height, s.Head().Hash(), libhead.Hash(s.Head().Commit.Hash()), dah.Hash()) - s.head = &ExtendedHeader{ + s.head = &header.ExtendedHeader{ RawHeader: *rh, Commit: s.Commit(rh), ValidatorSet: s.valSet, @@ -115,7 +116,7 @@ func (s *TestSuite) GenExtendedHeader() *ExtendedHeader { } func (s *TestSuite) GenRawHeader( - height int64, lastHeader, lastCommit, dataHash libhead.Hash) *RawHeader { + height int64, lastHeader, lastCommit, dataHash libhead.Hash) *header.RawHeader { rh := RandRawHeader(s.t) rh.Height = height rh.Time = time.Now() @@ -128,7 +129,7 @@ func (s *TestSuite) GenRawHeader( return rh } -func (s *TestSuite) Commit(h *RawHeader) *types.Commit { +func (s *TestSuite) Commit(h *header.RawHeader) *types.Commit { bid := types.BlockID{ Hash: h.Hash(), // Unfortunately, we still have to commit PartSetHeader even we don't need it in Celestia @@ -166,8 +167,8 @@ func (s *TestSuite) nextProposer() *types.Validator { } // RandExtendedHeader provides an ExtendedHeader fixture. -func RandExtendedHeader(t *testing.T) *ExtendedHeader { - dah := EmptyDAH() +func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { + dah := header.EmptyDAH() rh := RandRawHeader(t) rh.DataHash = dah.Hash() @@ -178,7 +179,7 @@ func RandExtendedHeader(t *testing.T) *ExtendedHeader { commit, err := core.MakeCommit(RandBlockID(t), rh.Height, 0, voteSet, vals, time.Now()) require.NoError(t, err) - return &ExtendedHeader{ + return &header.ExtendedHeader{ RawHeader: *rh, Commit: commit, ValidatorSet: valSet, @@ -187,8 +188,8 @@ func RandExtendedHeader(t *testing.T) *ExtendedHeader { } // RandRawHeader provides a RawHeader fixture. -func RandRawHeader(t *testing.T) *RawHeader { - return &RawHeader{ +func RandRawHeader(t *testing.T) *header.RawHeader { + return &header.RawHeader{ Version: version.Consensus{Block: 11, App: 1}, ChainID: "test", Height: mrand.Int63(), @@ -221,15 +222,15 @@ func RandBlockID(t *testing.T) types.BlockID { } // FraudMaker creates a custom ConstructFn that breaks the block at the given height. -func FraudMaker(t *testing.T, faultHeight int64) ConstructFn { +func FraudMaker(t *testing.T, faultHeight int64) header.ConstructFn { log.Warn("Corrupting block...", "height", faultHeight) return func(ctx context.Context, b *types.Block, comm *types.Commit, vals *types.ValidatorSet, - bServ blockservice.BlockService) (*ExtendedHeader, error) { + bServ blockservice.BlockService) (*header.ExtendedHeader, error) { if b.Height == faultHeight { - eh := &ExtendedHeader{ + eh := &header.ExtendedHeader{ RawHeader: b.Header, Commit: comm, ValidatorSet: vals, @@ -238,11 +239,15 @@ func FraudMaker(t *testing.T, faultHeight int64) ConstructFn { eh = CreateFraudExtHeader(t, eh, bServ) return eh, nil } - return MakeExtendedHeader(ctx, b, comm, vals, bServ) + return header.MakeExtendedHeader(ctx, b, comm, vals, bServ) } } -func CreateFraudExtHeader(t *testing.T, eh *ExtendedHeader, dag blockservice.BlockService) *ExtendedHeader { +func CreateFraudExtHeader( + t *testing.T, + eh *header.ExtendedHeader, + dag blockservice.BlockService, +) *header.ExtendedHeader { extended := share.RandEDS(t, 2) shares := share.ExtractEDS(extended) copy(shares[0][share.NamespaceSize:], shares[1][share.NamespaceSize:]) @@ -255,25 +260,25 @@ func CreateFraudExtHeader(t *testing.T, eh *ExtendedHeader, dag blockservice.Blo } type DummySubscriber struct { - Headers []*ExtendedHeader + Headers []*header.ExtendedHeader } -func (mhs *DummySubscriber) AddValidator(func(context.Context, *ExtendedHeader) pubsub.ValidationResult) error { +func (mhs *DummySubscriber) AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult) error { return nil } -func (mhs *DummySubscriber) Subscribe() (libhead.Subscription[*ExtendedHeader], error) { +func (mhs *DummySubscriber) Subscribe() (libhead.Subscription[*header.ExtendedHeader], error) { return mhs, nil } -func (mhs *DummySubscriber) NextHeader(ctx context.Context) (*ExtendedHeader, error) { +func (mhs *DummySubscriber) NextHeader(ctx context.Context) (*header.ExtendedHeader, error) { defer func() { if len(mhs.Headers) > 1 { // pop the already-returned header cp := mhs.Headers mhs.Headers = cp[1:] } else { - mhs.Headers = make([]*ExtendedHeader, 0) + mhs.Headers = make([]*header.ExtendedHeader, 0) } }() if len(mhs.Headers) == 0 { diff --git a/header/verify_test.go b/headertest/verify_test.go similarity index 98% rename from header/verify_test.go rename to headertest/verify_test.go index b735076628..03aab15b89 100644 --- a/header/verify_test.go +++ b/headertest/verify_test.go @@ -1,4 +1,4 @@ -package header +package headertest import ( "strconv" diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index 165e0150fb..44982f0243 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -8,7 +8,6 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" - headercore "github.com/celestiaorg/celestia-node/header/core" "github.com/celestiaorg/celestia-node/libs/fxutil" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -34,19 +33,20 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module("core", baseComponents, fx.Provide(core.NewBlockFetcher), - fxutil.ProvideAs(headercore.NewExchange, new(libhead.Exchange[*header.ExtendedHeader])), + fxutil.ProvideAs(core.NewExchange, new(libhead.Exchange[*header.ExtendedHeader])), fx.Invoke(fx.Annotate( func(bcast libhead.Broadcaster[*header.ExtendedHeader], fetcher *core.BlockFetcher, pubsub *shrexsub.PubSub, bServ blockservice.BlockService, - construct header.ConstructFn) *headercore.Listener { - return headercore.NewListener(bcast, fetcher, pubsub.Broadcast, bServ, construct) + construct header.ConstructFn, + ) *core.Listener { + return core.NewListener(bcast, fetcher, pubsub.Broadcast, bServ, construct) }, - fx.OnStart(func(ctx context.Context, listener *headercore.Listener) error { + fx.OnStart(func(ctx context.Context, listener *core.Listener) error { return listener.Start(ctx) }), - fx.OnStop(func(ctx context.Context, listener *headercore.Listener) error { + fx.OnStop(func(ctx context.Context, listener *core.Listener) error { return listener.Stop(ctx) }), )), diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 8803037afb..c478191f48 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -14,6 +14,7 @@ import ( "github.com/celestiaorg/celestia-node/core" coreheader "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/headertest" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/libs/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -45,7 +46,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti fx.Replace(node.StorePath(t.TempDir())), // avoid requesting trustedPeer during initialization fxutil.ReplaceAs(mocks.NewStore[*coreheader.ExtendedHeader]( - t, coreheader.NewTestSuite(t, 5), 20), new(header.InitStore), + t, headertest.NewTestSuite(t, 5), 20), new(header.InitStore), ), ) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 12ff92c696..a19c042e91 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/headertest" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -35,7 +35,7 @@ func TestFraudProofBroadcasting(t *testing.T) { testTimeout := time.Millisecond * 200 sw := swamp.NewSwamp(t, swamp.WithBlockTime(testTimeout)) - bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(header.FraudMaker(t, 20))) + bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(headertest.FraudMaker(t, 20))) ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) @@ -99,7 +99,7 @@ func TestFraudProofSyncing(t *testing.T) { cfg := nodebuilder.DefaultConfig(node.Bridge) store := nodebuilder.MockStore(t, cfg) - bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(header.FraudMaker(t, 10))) + bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(headertest.FraudMaker(t, 10))) ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 4dbf6e3958..492c2ff93f 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -20,7 +20,6 @@ import ( "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" - headercore "github.com/celestiaorg/celestia-node/header/core" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" @@ -164,7 +163,7 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { func (s *Swamp) setupGenesis(ctx context.Context) { s.WaitTillHeight(ctx, 1) - ex := headercore.NewExchange( + ex := core.NewExchange( core.NewBlockFetcher(s.ClientContext.Client), mdutils.Bserv(), header.MakeExtendedHeader, diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 725f0f161f..a7bb8353ec 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -14,13 +14,15 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/rsmt2d" ) func init() { @@ -155,7 +157,7 @@ func generateByzantineError( h, err := store.GetByHeight(ctx, 1) require.NoError(t, err) - faultHeader := header.CreateFraudExtHeader(t, h, bServ) + faultHeader := headertest.CreateFraudExtHeader(t, h, bServ) _, err = NewRetriever(bServ).Retrieve(ctx, faultHeader.DAH) return faultHeader, err } From 311d9dc757f716289978da23913a72fe01c99ede Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 24 Jan 2023 12:19:11 +0100 Subject: [PATCH 0319/1008] fix(p2p/pubsub): extend pubsub options (#1634) --- nodebuilder/p2p/pubsub.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 10932fd4d2..8ebc1afd2c 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -6,6 +6,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/protocol" "go.uber.org/fx" "golang.org/x/crypto/blake2b" ) @@ -29,6 +30,9 @@ func PubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { pubsub.WithPeerExchange(cfg.PeerExchange || cfg.Bootstrapper), pubsub.WithDirectPeers(fpeers), pubsub.WithMessageIdFn(hashMsgID), + // specifying sub protocol helps to avoid conflicts with + // floodsub(because gossipsub supports floodsub protocol by default). + pubsub.WithGossipSubProtocols([]protocol.ID{pubsub.GossipSubID_v11}, pubsub.GossipSubDefaultFeatures), } return pubsub.NewGossipSub( From ce06da04debce3a753c6954d94e500c02d5f9204 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 24 Jan 2023 15:43:53 +0100 Subject: [PATCH 0320/1008] chore!: bump celestia app v0.12.0-rc2 (#1617) Co-authored-by: Viacheslav Gonkivskyi --- api/gateway/share.go | 8 +- core/testing.go | 6 +- go.mod | 57 +- go.sum | 138 ++-- nodebuilder/state/core.go | 2 +- nodebuilder/state/keyring.go | 2 +- nodebuilder/state/opts.go | 2 +- nodebuilder/testing.go | 2 +- nodebuilder/tests/swamp/config.go | 3 +- share/availability/light/availability_test.go | 38 +- .../light/testdata/block-825320.json | 659 ------------------ .../light/testdata/sample-block.json | 60 ++ share/empty.go | 2 +- share/ipld/nmt.go | 2 +- share/share.go | 2 +- state/core_access.go | 19 +- state/integration_test.go | 39 +- 17 files changed, 253 insertions(+), 788 deletions(-) delete mode 100644 share/availability/light/testdata/block-825320.json create mode 100755 share/availability/light/testdata/sample-block.json diff --git a/api/gateway/share.go b/api/gateway/share.go index 3046346e0f..4028e9927c 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -112,13 +112,13 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID } func dataFromShares(shares []share.Share) ([][]byte, error) { - messages, err := appshares.ParseMsgs(shares) + blobs, err := appshares.ParseBlobs(shares) if err != nil { return nil, err } - data := make([][]byte, len(messages.MessagesList)) - for i := range messages.MessagesList { - data[i] = messages.MessagesList[i].Data + data := make([][]byte, len(blobs)) + for i := range blobs { + data[i] = blobs[i].Data } return data, nil } diff --git a/core/testing.go b/core/testing.go index 7b84621c9d..726dfea493 100644 --- a/core/testing.go +++ b/core/testing.go @@ -72,12 +72,16 @@ func StartTestNode(t *testing.T) testnode.Context { // StartTestNodeWithConfig starts Tendermint and Celestia App tandem with custom configuration. func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { + state, kr, err := testnode.DefaultGenesisState(cfg.Accounts...) + require.NoError(t, err) + tmNode, app, cctx, err := testnode.New( t, cfg.ConsensusParams, cfg.Tendermint, cfg.SuppressLogs, - cfg.Accounts..., + state, + kr, ) require.NoError(t, err) diff --git a/go.mod b/go.mod index 0ced26a5c8..d5e2402212 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,11 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.11.0 + github.com/celestiaorg/celestia-app v0.12.0-rc2 github.com/celestiaorg/go-libp2p-messenger v0.1.0 - github.com/celestiaorg/nmt v0.11.0 + github.com/celestiaorg/nmt v0.12.0 github.com/celestiaorg/rsmt2d v0.8.0 - github.com/cosmos/cosmos-sdk v0.46.0 + github.com/cosmos/cosmos-sdk v0.46.7 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 github.com/dgraph-io/badger/v2 v2.2007.4 @@ -51,7 +51,7 @@ require ( github.com/multiformats/go-multiaddr v0.8.0 github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 - github.com/spf13/cobra v1.6.0 + github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 @@ -85,32 +85,33 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect - github.com/armon/go-metrics v0.4.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.40.45 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd v0.22.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect - github.com/celestiaorg/quantum-gravity-bridge v1.2.0 // indirect + github.com/celestiaorg/quantum-gravity-bridge v1.3.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect - github.com/confio/ics23/go v0.7.0 // indirect + github.com/confio/ics23/go v0.9.0 // indirect github.com/containerd/cgroups v1.0.4 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cosmos/btcutil v1.0.4 // indirect - github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-alpha8 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogoproto v1.4.2 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.19.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect - github.com/cosmos/ledger-go v0.9.2 // indirect + github.com/cosmos/iavl v0.19.4 // indirect + github.com/cosmos/ibc-go/v6 v6.1.0 // indirect + github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -126,11 +127,11 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect - github.com/ethereum/go-ethereum v1.10.17 // indirect + github.com/ethereum/go-ethereum v1.10.26 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gammazero/deque v0.2.0 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -219,10 +220,11 @@ require ( github.com/lucas-clemente/quic-go v0.31.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-pointer v0.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -247,7 +249,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -259,21 +261,21 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/rjeczalik/notify v0.9.1 // indirect github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.27.0 // indirect - github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect + github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil v3.21.6+incompatible // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.12.0 // indirect - github.com/subosito/gotenv v1.4.0 // indirect + github.com/spf13/viper v1.14.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect @@ -287,7 +289,8 @@ require ( github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect - github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect + github.com/zondax/hid v0.9.1 // indirect + github.com/zondax/ledger-go v0.14.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect @@ -308,8 +311,8 @@ require ( google.golang.org/api v0.102.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect - google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/ini.v1 v1.66.6 // indirect + google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -319,8 +322,8 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 366be3b6f0..31628a00ab 100644 --- a/go.sum +++ b/go.sum @@ -93,7 +93,7 @@ github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETF github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -114,7 +114,7 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/ github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= -github.com/adlio/schema v1.1.13 h1:LeNMVg5Z1FX+Qgz8tJUijBLRdcpbFUElz+d1489On98= +github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -135,8 +135,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= @@ -168,7 +168,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= @@ -180,13 +179,12 @@ github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= @@ -203,12 +201,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.11.0 h1:F76mBdLJZ1LLjWEPVEjoFPJA4skaY7Wrg+5o1LVlHGY= -github.com/celestiaorg/celestia-app v0.11.0/go.mod h1:6k/zcNDEgOyJRGnAgWw1VsrwTKcVjOyYG5LPTHcZR+w= -github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch h1:b0swFbc0JqivwVz1UwnzFPlXYsVFkoHAmJ3LIfP4/2o= -github.com/celestiaorg/celestia-core v1.5.0-tm-v0.34.20-verify-key-patch/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= -github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0 h1:65gnQ92mfz+9XNVTHeVwMp+SZuBqmToEnz8+WdDRmQ8= -github.com/celestiaorg/cosmos-sdk v1.4.0-sdk-v0.46.0/go.mod h1:ByQ2rOrZs7s2OnPfeaiTMC8IOlcrT195xIRPgevydCI= +github.com/celestiaorg/celestia-app v0.12.0-rc2 h1:eRusc/XPPTq0NC5Nes186XbidilSuT7Sv506IMdHVmM= +github.com/celestiaorg/celestia-app v0.12.0-rc2/go.mod h1:1VLYgvVX7GQ4OD+NoymwzCEqjNdm9woG06hDx2tKA2E= +github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23 h1:nMg928RIanyA2ICN3PHpIE6jgg+008BtD5XmxeObEhM= +github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23/go.mod h1:Iu2XuSzF+QG3rzd60/sGhhk3n4wOAm8YyadU80KlcMs= +github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6 h1:G7c1qcM2WTVdiLsyHASyP4pOK7D4QsOrbXl0qP3GKpk= +github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6/go.mod h1:2Oq+pYLgUOdcv++AXR77Nv/Yiw4dckswSQN1iyScUqk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= @@ -217,10 +215,10 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.11.0 h1:iqTaNwnVzM3njBmPklpHzb3A4Xy/JKahoclRPbAzxNc= -github.com/celestiaorg/nmt v0.11.0/go.mod h1:NN3W8EEoospv8EHCw50DDNWwPLpJkFHoEFiqCEcNCH4= -github.com/celestiaorg/quantum-gravity-bridge v1.2.0 h1:l/LEEUP+x8MhhXB8rrWkyUVFZgQj1Ur/TAwUpnyLK38= -github.com/celestiaorg/quantum-gravity-bridge v1.2.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= +github.com/celestiaorg/nmt v0.12.0 h1:6CmaMPri9FdSiytZP7yCrEq3ewebFiIEjlJhasrS6oQ= +github.com/celestiaorg/nmt v0.12.0/go.mod h1:NN3W8EEoospv8EHCw50DDNWwPLpJkFHoEFiqCEcNCH4= +github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= +github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.8.0 h1:ZUxTCELZCM9zMGKNF3cT+rUqMddXMeiuyleSJPZ3Wn4= github.com/celestiaorg/rsmt2d v0.8.0/go.mod h1:hhlsTi6G3+X5jOP/8Lb/d7i5y2XNFmnyMddYbFSmrgo= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -240,8 +238,14 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= @@ -264,8 +268,8 @@ github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkb github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= -github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= -github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= +github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= @@ -273,7 +277,7 @@ github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaO github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= -github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -287,10 +291,10 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= -github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= -github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-proto v1.0.0-alpha7 h1:yqYUOHF2jopwZh4dVQp3xgqwftE5/2hkrwIV6vkUbO0= -github.com/cosmos/cosmos-proto v1.0.0-alpha7/go.mod h1:dosO4pSAbJF8zWCzCoTWP7nNsjcvSUBQmniFxDg5daw= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/cosmos-proto v1.0.0-alpha8 h1:d3pCRuMYYvGA5bM0ZbbjKn+AoQD4A7dyNG2wzwWalUw= +github.com/cosmos/cosmos-proto v1.0.0-alpha8/go.mod h1:6/p+Bc4O8JKeZqe0VqUGTX31eoYqemTT4C1hLCWsO7I= github.com/cosmos/cosmos-sdk/api v0.1.0 h1:xfSKM0e9p+EJTMQnf5PbWE6VT8ruxTABIJ64Rd064dE= github.com/cosmos/cosmos-sdk/api v0.1.0/go.mod h1:CupqQBskAOiTXO1XDZ/wrtWzN/wTxUvbQmOqdUhR8wI= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= @@ -300,12 +304,12 @@ github.com/cosmos/gogoproto v1.4.2 h1:UeGRcmFW41l0G0MiefWhkPEVEwvu78SZsHBvI78dAY github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.19.0 h1:sgyrjqOkycXiN7Tuupuo4QAldKFg7Sipyfeg/IL7cps= -github.com/cosmos/iavl v0.19.0/go.mod h1:l5h9pAB3m5fihB3pXVgwYqdY8aBsMagqz7T0MUjxZeA= -github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= -github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= -github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= -github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= +github.com/cosmos/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= +github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= +github.com/cosmos/ibc-go/v6 v6.1.0 h1:o7oXws2vKkKfOFzJI+oNylRn44PCNt5wzHd/zKQKbvQ= +github.com/cosmos/ibc-go/v6 v6.1.0/go.mod h1:CY3zh2HLfetRiW8LY6kVHMATe90Wj/UOoY8T6cuB0is= +github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= +github.com/cosmos/ledger-cosmos-go v0.12.1/go.mod h1:dhO6kj+Y+AHIOgAe4L9HL/6NDdyyth4q238I9yFpD2g= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -399,8 +403,9 @@ github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakR github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= github.com/etclabscore/go-openrpc-reflect v0.0.37 h1:IH0e7JqIvR9OhbbFWi/BHIkXrqbR3Zyia3RJ733eT6c= github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= -github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= @@ -427,8 +432,8 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= @@ -495,8 +500,8 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -897,7 +902,7 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.12.1-0.20220417024638-438db461d753 h1:uFlcJKZPLQd7rmOY/RrvBuUaYmAFnlFHKLivhO6cOy8= +github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -1202,6 +1207,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= @@ -1224,8 +1231,9 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -1424,8 +1432,8 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWEr github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -1453,8 +1461,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= -github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= @@ -1535,8 +1543,8 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= @@ -1564,8 +1572,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4= -github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= +github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= @@ -1601,8 +1609,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= @@ -1620,15 +1628,15 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1637,8 +1645,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1658,12 +1666,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= -github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -1750,9 +1757,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 h1:O9XLFXGkVswDFmH9LaYpqu+r/AAFWqr0DL6V00KEVFg= -github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= +github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.0 h1:dlMC7aO8Wss1CxBq2I96kZ69Nh1ligzbs8UWOtq/AsA= +github.com/zondax/ledger-go v0.14.0/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc= gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 h1:dizWJqTWjwyD8KGcMOwgrkqu1JIkofYgKkmDeNE7oAs= @@ -2127,10 +2135,11 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -2366,8 +2375,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= +google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2381,8 +2390,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= -gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= @@ -2423,8 +2432,7 @@ lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= -pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= +pgregory.net/rapid v0.5.3 h1:163N50IHFqr1phZens4FQOdPgfJscR7a562mjQqeo4M= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index 069cb07673..d617f6211a 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -1,7 +1,7 @@ package state import ( - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/core" diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go index 1c4ff9b1b4..e5347acae9 100644 --- a/nodebuilder/state/keyring.go +++ b/nodebuilder/state/keyring.go @@ -9,7 +9,7 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" diff --git a/nodebuilder/state/opts.go b/nodebuilder/state/opts.go index b159f95ded..0b357b8396 100644 --- a/nodebuilder/state/opts.go +++ b/nodebuilder/state/opts.go @@ -3,7 +3,7 @@ package state import ( "go.uber.org/fx" - "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/celestia-app/x/blob/types" ) // WithKeyringSigner overrides the default keyring signer constructed diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index c478191f48..f0dac2c9a7 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -10,7 +10,7 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/core" coreheader "github.com/celestiaorg/celestia-node/header" diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index 11cfed0607..2c998b73cf 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -15,7 +15,8 @@ type Components struct { // 100ms func DefaultComponents() *Components { cfg := core.DefaultTestConfig() - cfg.Tendermint.Consensus.TimeoutCommit = 100 * time.Millisecond + // timeout commits faster than this tend to be flakier + cfg.Tendermint.Consensus.TimeoutCommit = 200 * time.Millisecond return &Components{ cfg, } diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 1fe77bf021..7918464a04 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/rand" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" @@ -171,8 +172,11 @@ func TestSharesRoundTrip(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var b core.Block - err := json.Unmarshal([]byte(sampleBlock), &b) + var pb tmproto.Block + err := json.Unmarshal([]byte(sampleBlock), &pb) + require.NoError(t, err) + + b, err := core.BlockFromProto(&pb) require.NoError(t, err) namespace, err := hex.DecodeString("00001337BEEF0000") @@ -191,7 +195,7 @@ func TestSharesRoundTrip(t *testing.T) { cases := []testCase{ { "original test case", - [][]byte{b.Data.Messages.MessagesList[0].Data}, + [][]byte{b.Data.Blobs[0].Data}, [][]byte{namespace}}, { "one short message", @@ -266,12 +270,12 @@ func TestSharesRoundTrip(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { // prepare data - b.Data.Messages.MessagesList = make([]core.Message, len(tc.messages)) - b.OriginalSquareSize = 16 + b.Data.Blobs = make([]core.Blob, len(tc.messages)) + b.SquareSize = 16 var msgsInNamespace [][]byte require.Equal(t, len(tc.namespaces), len(tc.messages)) for i := range tc.messages { - b.Data.Messages.MessagesList[i] = core.Message{NamespaceID: tc.namespaces[i], Data: tc.messages[i]} + b.Data.Blobs[i] = core.Blob{NamespaceID: tc.namespaces[i], Data: tc.messages[i]} if bytes.Equal(tc.namespaces[i], namespace) { msgsInNamespace = append(msgsInNamespace, tc.messages[i]) } @@ -292,11 +296,11 @@ func TestSharesRoundTrip(t *testing.T) { myShares = append(myShares, sh) } } - msgs, err := appshares.ParseMsgs(myShares) + blobs, err := appshares.ParseBlobs(myShares) require.NoError(t, err) - assert.Len(t, msgs.MessagesList, len(msgsInNamespace)) - for i := range msgs.MessagesList { - assert.Equal(t, msgsInNamespace[i], msgs.MessagesList[i].Data) + assert.Len(t, blobs, len(msgsInNamespace)) + for i := range blobs { + assert.Equal(t, msgsInNamespace[i], blobs[i].Data) } } @@ -311,19 +315,19 @@ func TestSharesRoundTrip(t *testing.T) { require.NoError(t, shares.Verify(&dah, namespace)) require.NotEmpty(t, shares) - msgs, err := appshares.ParseMsgs(shares.Flatten()) + blobs, err := appshares.ParseBlobs(shares.Flatten()) require.NoError(t, err) - assert.Len(t, msgs.MessagesList, len(msgsInNamespace)) - for i := range msgs.MessagesList { - assert.Equal(t, namespace, []byte(msgs.MessagesList[i].NamespaceID)) - assert.Equal(t, msgsInNamespace[i], msgs.MessagesList[i].Data) + assert.Len(t, blobs, len(msgsInNamespace)) + for i := range blobs { + assert.Equal(t, namespace, []byte(blobs[i].NamespaceID)) + assert.Equal(t, msgsInNamespace[i], blobs[i].Data) } } }) } } -// this is a sample block from devnet-2 which originally showed the issue with share ordering +// this is a sample block // -//go:embed "testdata/block-825320.json" +//go:embed "testdata/sample-block.json" var sampleBlock string diff --git a/share/availability/light/testdata/block-825320.json b/share/availability/light/testdata/block-825320.json deleted file mode 100644 index f159fdcc96..0000000000 --- a/share/availability/light/testdata/block-825320.json +++ /dev/null @@ -1,659 +0,0 @@ -{ - "header": { - "version": { - "block": 11 - }, - "chain_id": "devnet-2", - "height": 825320, - "time": "2022-04-14T07:52:33.890556391Z", - "last_block_id": { - "hash": "7FA631D8E8DA005132615B5BB9DD715E66ABE18DB3BF4A891242CCF090D629D1", - "parts": { - "total": 1, - "hash": "9C2705130B4D8A9A9FAD64D462944EABC281B5AC36ADBFA6E3A48A7F347DD8C1" - } - }, - "last_commit_hash": "00AAFEB5461158D198967E750E108367EE6D0739E69C0AC2F4D102E0F87D2595", - "data_hash": "2AE354BAA0A6C75EDAAB7AA9D6FAAF873FD6FF3E0884DD479306EC6C1AAFD796", - "validators_hash": "A76865C38C998C200C6056F8B1D08CB4009683B1110ACE85D95B9889A47650ED", - "next_validators_hash": "A76865C38C998C200C6056F8B1D08CB4009683B1110ACE85D95B9889A47650ED", - "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", - "app_hash": "E2DD3E32D66B330B16BCAB389DA17C2B6BC1B082B2B58ED0A90E8197E36A59A6", - "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", - "evidence_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", - "proposer_address": "2CB2349B8516D69839FE49892B1AF6909D7361A7" - }, - "data": { - "txs": [ - "CpoBCpcBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJwCixjZWxlczEycGd2eWRsOHQ0c3o4NjdzYWNwOWRnOHJhNngydmUwa2d5NjZuMhIzY2VsZXN2YWxvcGVyMTJwZ3Z5ZGw4dDRzejg2N3NhY3A5ZGc4cmE2eDJ2ZTBrZGY0Y3l6GgsKBWNlbGVzEgIxNRJaClIKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECEyDrMs7ZgCs9VLJY95YHFhUQHNNGMVnEvLnA+fgKuY0SBAoCCAEYr5oPEgQQjPwHGkBRV6kkcTFf+tZVGiSYnm/cy18RQ4Hq/k7NU5JQpJjstln/lCCx2mvjzZENdzoLB6e/ojG1tXPGLApLOtsDG071", - "CiDfWZrO6VHoRRehPruO7n8G95bt9EZXM1sPzskKRBlPXxKnAgp8CnoKGS9wYXltZW50Lk1zZ1BheUZvck1lc3NhZ2USXQosY2VsZXMxZ2hodmUyaG1hOW03Y3F1YWRtNTdqZzg3emV4Znhla3poc3B4OGcSCAAAEze+7wAAGIAEIiCB+JF+iC6YVjh3tYKgtF2dIkzNlxVCsRaFZWMmBedAqRJlClEKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEChGDFtCCOMQBEoVo9SwdQtb7tK9RcHFI/S6mrajefVW0SBAoCCAEYjwwSEAoKCgVjZWxlcxIBMRCAiXoaQGaO5xm+sB5eQN69UO01B0dbkHTJvNdyCMfk2VG3O012aK6eRB4/70zgZ00DLQSFpxZYNQwWZC/VuqRLj1a1e+g=" - ], - "intermediate_roots": { - "intermediate_roots": null - }, - "evidence": { - "evidence": [] - }, - "msgs": { - "msgs": [ - { - "NamespaceID": "AAATN77vAAA=", - "Data": "CvgBCgIICxIIAAATN77vAAAYAyDDpd+SBiogbVQ+9SML4CjY2Mg0LGwm5W0ZoQOumJUIkb0S4PjiNqQyIMKzG0e9Tl6QJUiIGK1WPPZ8blyB6AGq34E9XcvWe/A/OiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKILUe8T6ZAQKnvyvNP0iFQbQ5uNwqNTmfueick6JOJ0tyUiDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VVoUX9WPtt7dVrsuZpPR/svrM19RW8kSABpmCAISIG1UPvUjC+Ao2NjINCxsJuVtGaEDrpiVCJG9EuD44jakGkB78QszY4BV8F393pSze98ORvKUbNAdNaXwSK8HhfFUM30CwXlo9/BrbJVawCExfj7w2S6QmuhUv0S3KQ2Yh5YIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - } - ] - } - }, - "last_commit": { - "height": 825319, - "round": 0, - "block_id": { - "hash": "7FA631D8E8DA005132615B5BB9DD715E66ABE18DB3BF4A891242CCF090D629D1", - "parts": { - "total": 1, - "hash": "9C2705130B4D8A9A9FAD64D462944EABC281B5AC36ADBFA6E3A48A7F347DD8C1" - } - }, - "signatures": [ - { - "block_id_flag": 2, - "validator_address": "87265CC17922E01497F40B701EC9F05373B83467", - "timestamp": "2022-04-14T07:52:33.880851041Z", - "signature": "iYUZXYv5FuxC/XZIZ2dA9scTIZXz5dCsy7oSDow7r45KqxytygzleZ3u96X66MV48+G7pNAgyEAdSVku8undAQ==" - }, - { - "block_id_flag": 2, - "validator_address": "D345D62BBD18C301B843DF7C65F10E57AB17BD98", - "timestamp": "2022-04-14T07:52:33.882738752Z", - "signature": "Fk70wx+AsLxFSJVKH2cUwGgplVgkHmMIY0yoy/WZd+Mmde8mqOrQBNozR2/yvyQvmfcSFl5cEDwgsmoEZxNkAw==" - }, - { - "block_id_flag": 2, - "validator_address": "604377BC74F4F27274825A564761D9875B31AF05", - "timestamp": "2022-04-14T07:52:33.906538296Z", - "signature": "C/9OpYWM0cILxwpV8j4rWpeq5T95+Wj3Pv9ntbv+zmE5lHFbuTL9IEPXsnNFC3lTO/6AWZDhiiiBFBdXx5RZDw==" - }, - { - "block_id_flag": 2, - "validator_address": "D6084BAB9BCECCA7A82F6D1F019B315A056A990F", - "timestamp": "2022-04-14T07:52:33.934987498Z", - "signature": "4Wqvf8KEMTd/Gv/xIDTOBvMxFrE3GANF/C3VO+gMM3lKTQPjN4z2bZvO3r3jYpwUm13CjusDPEFferNa2FcDDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "B80E80B249B9AC1F192DF445A68A1B69D89458C0", - "timestamp": "2022-04-14T07:52:33.905001033Z", - "signature": "SxM4iUI9+iUnmCzQBepP2uHrka5Qhos7myhcC7MeiHSorWf0gXFGpxV0KGMlShiAIjEqwIr6wMRQHqV6Fkf/BA==" - }, - { - "block_id_flag": 2, - "validator_address": "89B63255AE88218533F9727D7CFDA0376A8C9A67", - "timestamp": "2022-04-14T07:52:33.954724715Z", - "signature": "rZkx1meoaYHHrEa97sNJKibxktE7OSvTxyGC3ojOhDsG/mMPJXvR9QnHTIn9myuEL9+cUuXjnjrEeL6Xl16tDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "C25C9BA4A8C2D71E6853A1227EBE6B1D7A07B5A7", - "timestamp": "2022-04-14T07:52:33.890556391Z", - "signature": "b4vt1uR5a9oTsiRsxxIahjc0lWyJ+hzCxVpJQaAU9yah3OFRH7nfLsPOgZVSky+odnArrP/rM6yqPllr2tMzAg==" - }, - { - "block_id_flag": 2, - "validator_address": "7635E9087D9875E784581BE236E4DFCBA71CE556", - "timestamp": "2022-04-14T07:52:33.92779807Z", - "signature": "I4Hfeufh+xNMwiOhjxds+tJ5E+s1UCnnvTq/rNC4oko3qbGci4xQsLNer/bpf3P1Fd2uHHYq9kE+M/9yv0TgCA==" - }, - { - "block_id_flag": 2, - "validator_address": "2CB2349B8516D69839FE49892B1AF6909D7361A7", - "timestamp": "2022-04-14T07:52:33.871894353Z", - "signature": "yppE7QcDrRB2ejnUURnS5I1GZJlO9GaChSeKIkLiAz8X1Rmmd3HTtRGJyc1a0/V2xMOu/ChYjQRBSVXB8ThKBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "DEC2642E786A941511A401090D21621E7F08A36D", - "timestamp": "2022-04-14T07:52:33.895698256Z", - "signature": "f+6qaGhh8qLW+DjEfIc8w0yoFjgxQkWL/BVU1z/YF42ByaVpYL8GKg+myb5wGznwsLG46nwGgvhaB9cV0SwXCg==" - }, - { - "block_id_flag": 2, - "validator_address": "03F1044A6DF782189C7061FF89146B3D33608F17", - "timestamp": "2022-04-14T07:52:33.875135897Z", - "signature": "5I0xK60/DwwVzRC1oA8FqZo7XrArA9R5jTYg08TXMKL3Dd/UCacyT9TVxaG/ekyMB0lCGajkccjWdI2OyqTnDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "BAD6693EFA5333479136B5B082C3C5A28FA97491", - "timestamp": "2022-04-14T07:52:33.916594881Z", - "signature": "7Ih2MIVMCUL78lPWZ22PGeb+TE1YKbrszO3Z/0awoEEmMBeWbRQBZgM6D5uWUbqc5SQ5fj2e2kYz8u04lFhfCA==" - }, - { - "block_id_flag": 2, - "validator_address": "EAB5D30E6F271470A37635E9889D613D41DED830", - "timestamp": "2022-04-14T07:52:33.822011898Z", - "signature": "ZcPUxJD52JOwXotNM+GTkivFcifihb9wq8xDQaBVKaJd7drQjHSlUMvCnQuDXcMlE0D2x909q+P2WNc0NkM1Dw==" - }, - { - "block_id_flag": 2, - "validator_address": "A85A278B256FF30E21057079F675644518010CDA", - "timestamp": "2022-04-14T07:52:33.948758137Z", - "signature": "ohveTMYCrItd9sGDPMPtALuigGVDi+uJANV/CUJLzqwBhIxNsABG5WhBDdrfZRI5vHzPfSUNtMbRAt+CpzUJBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "4342666C1595411A1C58CD949FB607F1DC473CFE", - "timestamp": "2022-04-14T07:52:33.913213874Z", - "signature": "CxX/ysvqpm8nsHLptr/6Q9PFCKgIxvswTcv6KJqiS5ymk8Usi9u5pGZNspu/mUgVAgJOT1yq/wHB3NL7FsklDA==" - }, - { - "block_id_flag": 2, - "validator_address": "09F4D1088EAB7E6EA2A87EE087A1467D23F22367", - "timestamp": "2022-04-14T07:52:33.977741332Z", - "signature": "IPV62zvcVPGN2BqTf0l9OuLSbOf+fixNQgG5TcA8D39wlz1Midwx1LcfVxXS2sQYpUDxoHro5QTG0ha95mnBAQ==" - }, - { - "block_id_flag": 2, - "validator_address": "A7B9EC7F97BD9F35E92F9CF873CE2CC618488BA2", - "timestamp": "2022-04-14T07:52:33.955291444Z", - "signature": "qY2HRpwAdA7JG3qqXzUQn1ox/hmo+BnnSID5ctslSsvwMZujxBCsx+7dMrDoIqnwP82IjI9wu8wvdIpIzXUCCQ==" - }, - { - "block_id_flag": 3, - "validator_address": "46387FF5D4E466F61891D04837E4C85294DB1FA5", - "timestamp": "2022-04-14T07:52:37.945040122Z", - "signature": "u7YUoivwzs5uN+TQTvHRB7eyIhGOzOzpPUyuMWq5p07GtdQJ4Z8j8NjmFyAvQKSl/++2F7h7IehmEMv9zZXODA==" - }, - { - "block_id_flag": 2, - "validator_address": "6B8A4D59A6C9B32622BE6E13CA3C9B89890DF317", - "timestamp": "2022-04-14T07:52:33.896204322Z", - "signature": "2Le62jKu6sdh83iiW+fWJG5m0UPcLcUId5w/JnVqYs628sbmcZmSf/xPuvniENWVQCRhYQrE59u8TlxDUp0zDA==" - }, - { - "block_id_flag": 2, - "validator_address": "B85CC62F660DC5416C2A19733E7FB4147D5A1BDC", - "timestamp": "2022-04-14T07:52:33.965734652Z", - "signature": "li1owrnyoTGd0gW82udA6Dj/sHu6aPQ475RkhMttH+SCRF+zjxJv3eXQ+jQ/QftaNJr/f5I+iswqYhjisgU2AA==" - }, - { - "block_id_flag": 2, - "validator_address": "2EC4B0E7E14949855ACD6976D1E11C2080DF0AE3", - "timestamp": "2022-04-14T07:52:33.883919347Z", - "signature": "/+XyGFufBUhCJB5CItF7QULJuQ6JuoHwY5CR86fE29ahEtjCXqvnbHA/hL7hVDZ0yZ3OGng2/M8mZ0tiuXzgBg==" - }, - { - "block_id_flag": 2, - "validator_address": "3E5203383282356A08F17ADF86BEE43482D7821C", - "timestamp": "2022-04-14T07:52:33.948533772Z", - "signature": "ZmsLZdA7DcCBuZDCrCLXyotdHMj3BTJxMuXjWbqmhYTwFdxw6IeUapAgFI9HmgQYIe470W5T/XQXLM8pKp0zCA==" - }, - { - "block_id_flag": 2, - "validator_address": "DA1662059918F943B478CC432345E9CABD0B84D0", - "timestamp": "2022-04-14T07:52:33.966697203Z", - "signature": "w33lYcxlgZCz9c+yMJrR4fHnx6JXdA3Y9C1xq5XnBr61unx5mbD3PwdXJoxekaequdtuwGKOwlm3Ilf7y/JgBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "0EACA93ECFF404F8C6675A05C6CEEBDB60E94E67", - "timestamp": "2022-04-14T07:52:33.903813219Z", - "signature": "DSCFvK8rOyvYqMFmgHz4ifuXHB425TysBIEN1a15j1NBKJTe47AOeyimRxD0QcTwDtMqlVqDP0RQmY/FqbxIAg==" - }, - { - "block_id_flag": 2, - "validator_address": "3050EC11FD87A5650113CCC2D88B4CEB4A33F4E9", - "timestamp": "2022-04-14T07:52:33.902721724Z", - "signature": "51WrpCpPyktY5t7lsbsPzWgfKzJjOucs26IeNtymOWi0HRBdG03s6UCTKGxJwpagTw/+fXjsW9Tpa1UTRM5/CQ==" - }, - { - "block_id_flag": 2, - "validator_address": "F6204971AA2A655087E16146A39D730336783EC7", - "timestamp": "2022-04-14T07:52:33.894105731Z", - "signature": "XO68atiTR0wuTLo0hw+6kdUuyBUrDhJtWpvGwYDr3K1hPjcM9R44k8OXbyTcvLsv7GTK85FY2XOTi+6LrMzRDA==" - }, - { - "block_id_flag": 2, - "validator_address": "D83F63EDC86900F07BF03F76777E748F637D30B6", - "timestamp": "2022-04-14T07:52:33.891952948Z", - "signature": "ylRqDHzF0vDjr1L3QZCxydB2bKgYL7W4BO17ETYLPqlNSpu3dbcjVK4+PPaDjB4pWYYl8o5yoVw7n22u+1hlBw==" - }, - { - "block_id_flag": 2, - "validator_address": "F617C06738A76B99B51F1B2F23F7F606CFFAEC7E", - "timestamp": "2022-04-14T07:52:33.895358159Z", - "signature": "AUGmFAecT+sqlPOjY0f7Y/BLw2TUB3UCsomw2XM0xinY37f3FNflxQWIkodgI98Rt5abQ6gsOkpnha83mQ5NAw==" - }, - { - "block_id_flag": 2, - "validator_address": "8ED38173BF8EAE85955C1CE8CC471C87A3950309", - "timestamp": "2022-04-14T07:52:33.927364939Z", - "signature": "4Qr/xA1s2zRnQIWuhlaWjxmSvPP4PLVUrOlepIJOkOD5IedSGQuwtAL2MsJ4l8cXrfx3Dz85JqiEejBOdaHzAA==" - }, - { - "block_id_flag": 2, - "validator_address": "352E90351E5605C7C991BB4B4C076196851795CC", - "timestamp": "2022-04-14T07:52:33.943534076Z", - "signature": "RWD2HzMukwL6bDB8DwRX8yspMNDQL61+aHtFBzB+fttpMZA7in+iyvW8UKbx5H3ZZh1BZSQ+pgKJIuyzkldrCg==" - }, - { - "block_id_flag": 2, - "validator_address": "628C6B13D44802869464D0763F7B946B105C1C4F", - "timestamp": "2022-04-14T07:52:34.014354318Z", - "signature": "3OwaoPDGYCYqUXuKU+FX6bCfpyFbI85NNDWZ3PPeAJ9n3de0GCR8QGBfkUc0g0h8nnXqrNidkt2r0HLH4llKDg==" - }, - { - "block_id_flag": 2, - "validator_address": "6518C991A8C3F9C94899423D32623E8E1069649B", - "timestamp": "2022-04-14T07:52:33.947923197Z", - "signature": "lDsqlaxL4Vpn4bL/U5tjsDg5qjScIo1hFRrm5FihX/TAAhL812TM/OTjAITbEccNBCcjJhFrA0KnXPGDjFadCg==" - }, - { - "block_id_flag": 2, - "validator_address": "7F8E54ECF4E33A0A9B07394C4DE15F57AB86A648", - "timestamp": "2022-04-14T07:52:33.893345159Z", - "signature": "gOg13rDOjEg24JTJCug5I8CfUbutDnX2/70YOUydV/4uGiMyHe9UU4hcNSRXMsX1ZYye77DxGRq4SOG6nHLnDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "B14790FAFAB929B56513EBE7BC5ABB25D1883CE3", - "timestamp": "2022-04-14T07:52:33.93583778Z", - "signature": "KuOyvyTYRLuq0emzAXQRHnyaWLe8ozXZR+109pOVKRk+UFut0IAUxPjynCz8PKStv/XkyDbS0w3StsNygfO+Cw==" - }, - { - "block_id_flag": 2, - "validator_address": "C23DD7A2BDB836A97C09046FB8247871BD5741CC", - "timestamp": "2022-04-14T07:52:33.984675309Z", - "signature": "IJaB0A+79QXvy3fgZc/8HbPVSymDuSIRf0oQJq8dZjBlsRLrr4GGs6eBauqID52+kAKHPNO/SH+OuRHNCk6FCA==" - }, - { - "block_id_flag": 2, - "validator_address": "33479A3C40E3827C2CB1775FF69B1F8C01F1C97F", - "timestamp": "2022-04-14T07:52:33.932691066Z", - "signature": "B0sot4lgyxaivEbymHFzUh9tUfQoHNu7B82vavwnGAVs+BsPVC5yqsUFL3UviDuBUUdz9A4LdzsAHkcCn/TEBA==" - }, - { - "block_id_flag": 2, - "validator_address": "2B5CA1444614CD5693259A2836F237E695C20374", - "timestamp": "2022-04-14T07:52:33.931461341Z", - "signature": "wBkQGvp7HnBl0GcLqL/Yf9+aFJX4cL2c02yuJbiSJbxprPLb2fGVezwkrQxZevotJ5gnrIJkYVytW80Nb71nCw==" - }, - { - "block_id_flag": 2, - "validator_address": "B315C0E5926653484495AB4D38055A15C0BF43FE", - "timestamp": "2022-04-14T07:52:34.00036607Z", - "signature": "jjDrFCjUsXra3qe7YQSgyROPIPLKyNUa6F/WRTTQTyuip1fU0R4WRK6gxVKTsgcyWJF3KQfVg+TxNPsfPs06Cg==" - }, - { - "block_id_flag": 2, - "validator_address": "8700D7FEC0879EFA716909F686176D87B346B40E", - "timestamp": "2022-04-14T07:52:33.874461806Z", - "signature": "piUqWW89xUYRjsEnxBEwROgFek8kCW4faObb7VOmm0ub7Nq60hbDhpxges1tjLhByGXc6rh6o3j21sFIc2ZhDw==" - }, - { - "block_id_flag": 2, - "validator_address": "81875DA3D4465AFC9769B351F2A038304DD646FC", - "timestamp": "2022-04-14T07:52:34.008911756Z", - "signature": "GA/wwpF5IZQj2ZCdF9CbqgP58uwxYkz/6adcpvDMsx2G1Z9f/1Es128GIkc6irxfIc8HMqRCrg9kBniLilCPBw==" - }, - { - "block_id_flag": 2, - "validator_address": "0E0402827BF366AEB78362B097BC394C4AB44E81", - "timestamp": "2022-04-14T07:52:33.8677605Z", - "signature": "ae/UG3XI0zPgzp6AhjjiLqlpJakdKIOkyFduRtu3BDnGh8MuiWbuwYDNLpZPxqor8CuBtfFQQN+4Wjgls4lnBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "AFB99D32657A2034F2C5400D78C66E8E507AF941", - "timestamp": "2022-04-14T07:52:33.933281067Z", - "signature": "U6su6S7ViquugX/6BNnTeuYh3G7p9hS65rVoq1pBAzgUN65/ZD6CUMi+Rv8ZSr9jaILSiIieJy1JlGb3wecMAQ==" - }, - { - "block_id_flag": 2, - "validator_address": "C5FE3F4F3091BFE32A61E50EAC660E9D7A2EF7A6", - "timestamp": "2022-04-14T07:52:33.847676476Z", - "signature": "VfiTA1FogVv5IO5AOlhMMQU8e1VOnsmSgB5gLxsGlGoeXVj/Pp+3mrQIE4thUPGRXyZvDI+QaFB8l3iDEQMjAg==" - }, - { - "block_id_flag": 2, - "validator_address": "C4456081E1163BF2323D3D354EDA3AB614F24F83", - "timestamp": "2022-04-14T07:52:33.888619411Z", - "signature": "c/CPqU0JG0Xy5PrfP8sMVjqIUxk4GijE6C80zROH0VOvRQaQ0E288MM8N4ZISQK7AwiEMF+Tcx6wzo/NQDXcBg==" - }, - { - "block_id_flag": 2, - "validator_address": "7EED60460B269EFA5D8B08A26C0E1D814AFF64DC", - "timestamp": "2022-04-14T07:52:33.937532147Z", - "signature": "WJ/UbF1dXQ1NY2bZzw/WcZMTWL4Qjc8+hn7VA3V1/ar2NWu09OigcKSMVNnM7OwRZyX0WIgjTnKysD9rTzuQBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "23133C4595E8DF70B816B5FE370FC2A2236D289E", - "timestamp": "2022-04-14T07:52:33.902898336Z", - "signature": "czCfr3VcSaFEbPhI3DzXpD+t/QYhhvwvzeU0OdbcGK9uTmCp9K7jcLiaUVc6Dx2b0C5BOa+YYlVeT6slIQCLAw==" - }, - { - "block_id_flag": 2, - "validator_address": "3CD42258ED58B0251D3721E9C7794FEF02DEEB65", - "timestamp": "2022-04-14T07:52:33.943636445Z", - "signature": "nJvJKkuMLnSRmF9czfGooEeAMxTWBstIIaLDU6aiWgknhAQrZN3/1VUmxc9o2fJG2HJpFRrTh06aKSft/Uy+Cg==" - }, - { - "block_id_flag": 2, - "validator_address": "A76175C0D7D55FDC57E7533A081F8A9B186D718B", - "timestamp": "2022-04-14T07:52:33.899216378Z", - "signature": "S6rnEmrurT0JsYvvrairdn5VOOp9mLD6jgBPYZAbC/x8errZMQdm08c5ooyEGYHSRHuuc2g7eqjVH50i9wBnAw==" - }, - { - "block_id_flag": 2, - "validator_address": "BE756A959F2CBE58542FF246D6D6BB8E728191B3", - "timestamp": "2022-04-14T07:52:33.934581952Z", - "signature": "dXnBT/5A+8CBWH36/GKvhFdiZktHNmNkjolRckFzXiNxcL+wuU3qjZMqVmohxh/kcKBxIuV7RigsegIg1RmJAw==" - }, - { - "block_id_flag": 2, - "validator_address": "7FA1FB4D6C52A814F112B963AA88EF427FE7CD72", - "timestamp": "2022-04-14T07:52:33.886367881Z", - "signature": "2BIkeQczV+3zZjmP14CusZHwC0Dvn296CokyGs2PJeiGe4VwjSesKzl8ptxI1i02tWwqYoTeBzxUReh1aGGGCQ==" - }, - { - "block_id_flag": 2, - "validator_address": "8911065B7BA2C2235A4CB996B1A2C433E69A617F", - "timestamp": "2022-04-14T07:52:33.867223901Z", - "signature": "fDoCxLgaiMFrySOJxOq5qnThJNHYQMCS/OQ1mfAy2eCOsoPsfzs0eGr94D9K4RZeK3PvwOXAN9JWdX9SVXqLDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "8A033BD3830BF187779409E1651F4E82B6B8E5D8", - "timestamp": "2022-04-14T07:52:33.882866438Z", - "signature": "hGbNdDol/Ts5mM4ePoB27EiWMDLUKJzcdEGatXxP7wrP+i5sA6xif7EFaXzmDz0n4M8RSRpds6BHPOHog8ovAg==" - }, - { - "block_id_flag": 2, - "validator_address": "F214F894751C8B7BAC62FB1DE98D19ED1F98604A", - "timestamp": "2022-04-14T07:52:33.946890864Z", - "signature": "ZLPYgqm5SHjgxwyc1CWNBxqqWvVZ9OcrLdnUVciNFYsRKC3NOyNpRbYtyLgVNSZPQwSs5UV/sOJELuD4Z8Z6BQ==" - }, - { - "block_id_flag": 2, - "validator_address": "25A30FE88ADE7817AA86D94D056A7BBB97540D18", - "timestamp": "2022-04-14T07:52:33.833070775Z", - "signature": "aH+tpm0v4ODvsq9lNFd9D7lp3Sg0v4dXFepSZ+0DoE3a798lFRhauzAnzMGHg3bC5hiTv2AW/No5T/ERl33nBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "4A28AAB7BB3117858A129DED53D812558F6C8A02", - "timestamp": "2022-04-14T07:52:33.957379945Z", - "signature": "w5klvmWoKZuzf94ebjwDa5qVmrcgX/SOAnWM6kIzzUrkI/MjXZjMn5WePtI6dhF+0c3ttAiEfQJN8UAPusRvCw==" - }, - { - "block_id_flag": 2, - "validator_address": "E7DC8AD584DAECB46C42F972D6D3E4D4B65DD6B6", - "timestamp": "2022-04-14T07:52:34.219257162Z", - "signature": "e7quVi6UzZ2Cw/PQtSawQ7CAT7BzvW4YwuS5Wczzyu0sWvg5szZdFUmXTe5q6sdl+4qjq6sPAhuajdUajeBCAQ==" - }, - { - "block_id_flag": 2, - "validator_address": "33F2583A28E7227356AD6B423AEE8325CEB9DA52", - "timestamp": "2022-04-14T07:52:33.921088338Z", - "signature": "4uIzXiWswKT5JU1FyzA6rjT2Lv5ni3joGHF0qEKuavRKFTXMin8CPm/GRiU0UTZmhYzAh4d96vGa3Es2/bQjDA==" - }, - { - "block_id_flag": 2, - "validator_address": "09EE417F173A1F340E5C53A42C41B3E878890340", - "timestamp": "2022-04-14T07:52:33.881423271Z", - "signature": "ao2Gc4fAqM+3LnwzN6RgkhTI8wuvorCx2kqrc2BaAV4+ibG4qUUiYEOgfhMObDf32F6I6j/vPnOGRDlqDKu/AQ==" - }, - { - "block_id_flag": 2, - "validator_address": "2AAB6CA116BC52DCF7A32569114291FF1D7A32CA", - "timestamp": "2022-04-14T07:52:33.886147184Z", - "signature": "2vJIdQXYpdrElEsep2hFrq/DsJrgZqXE+NsQCNZazvbRnLl9vr4HrnsLnLSIBkbl9xel9T2/KL4Aa8/NMSesCw==" - }, - { - "block_id_flag": 2, - "validator_address": "6060A27F639300584BD095F881C2A8714538F0CB", - "timestamp": "2022-04-14T07:52:33.942592602Z", - "signature": "p4rVbiLaaclEn0CMeoHNRaRwDyaRWQWztTfrR7xhLeZ7O9nDuWtftPTTA1MuwzsUeGnL6qMIPlIIjKF9irRiAg==" - }, - { - "block_id_flag": 2, - "validator_address": "C3671ED4ED22EB622312E76615CE286ABF0AC53D", - "timestamp": "2022-04-14T07:52:33.879020462Z", - "signature": "XTLdspKUnlw1SHiw2NYb4MZQZFgZq+OOFOHG53Aq/86o3oGyN57mY82yS+RLmSEet6n+KKW4WuR8+2KJCJuoAA==" - }, - { - "block_id_flag": 2, - "validator_address": "F5F246DEA8E27C59F6D0884CCAB31ABDB589F4CE", - "timestamp": "2022-04-14T07:52:33.89889915Z", - "signature": "HXLuWVGBRO6B/olGgAl4W6HGNmbyp5cmSLVO4ZPde9miRhvVzFU/NurytMNhGBUBu5+z8Sa+Zu+bG39OlPdBCQ==" - }, - { - "block_id_flag": 2, - "validator_address": "0C3A78D0F3B2ED2E9ED3F3DB6289185D40742870", - "timestamp": "2022-04-14T07:52:33.91923213Z", - "signature": "2baYTKRWAatJmWL/mKxrUaPa1tUpzrQ7JuLClFWP8snhOozHN66IwrK7a8DEYpIprJ2k/WQEY40abTQ4el9nBw==" - }, - { - "block_id_flag": 2, - "validator_address": "B423FF79F90608BC76DA0AB850DEED5A7231E678", - "timestamp": "2022-04-14T07:52:33.924263801Z", - "signature": "XU8g6Eao+jqcMld8v97z/iEvDbDRSB4MjOOdqz4CAHf7c2TNY1+I+9iqAG8YU7tKoDWckgSA4en7mUu5SvasBg==" - }, - { - "block_id_flag": 2, - "validator_address": "B4B28030F3503F0650D0F12A70FCDDA6A6A844E0", - "timestamp": "2022-04-14T07:52:33.940053644Z", - "signature": "v2xN5tRKt7x9krj9DiI/ZJWRHQrHjnpEQZJ3SU8j6resu7y7Q0JsVEnCjWmuIcKhJgc6AxK5olf1MhmOS5gDDg==" - }, - { - "block_id_flag": 2, - "validator_address": "C2E2B0C72E6A427EC87E23675CE1A3EAEC30618C", - "timestamp": "2022-04-14T07:52:33.984048042Z", - "signature": "v4K2aXKdcwdUmZWmYIfXoTkb7eJL0lQ+Y9LZ5st/GX65o9fmoWMQSS9N5usqza3evt+XPJfapiezW+rMJqBHCg==" - }, - { - "block_id_flag": 2, - "validator_address": "E3461442CDD237B39BC3F40063FF4712E2E066F2", - "timestamp": "2022-04-14T07:52:33.942541279Z", - "signature": "ScPbwMwLL5e7hiRKO/IyllXhnT8Ifu2T64EUxqPjM/LDh4XwEQhsXCJHWbZEkX5JHVEp7kWyaEfc6B8163mWBg==" - }, - { - "block_id_flag": 2, - "validator_address": "1A302C41C4E175F2D61E9C3FD2195C3DB7545407", - "timestamp": "2022-04-14T07:52:33.93829292Z", - "signature": "BjgWxTQVajqmzZQZRZJi3V39VgDwazOsfqJFM/acaiCv9Oo3wlCH358pUGXHtTujLr3p8M2PEreFcp/7V9LBDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "DE9CB7EB31538B3846C2EB36A9E3C4341EDF6A95", - "timestamp": "2022-04-14T07:52:33.871429849Z", - "signature": "pe9FGfJjngntnzuFzS5kPAojjkmk6+LD/2M1KsVp74FveU8mpAiEiYvYh8n0v92Sb5djoOD6F3s8F06C9suoAg==" - }, - { - "block_id_flag": 2, - "validator_address": "1C8BCA4A78B616E2ACEE8D0E218087614D85823B", - "timestamp": "2022-04-14T07:52:33.909438503Z", - "signature": "0HmvyxC3WWRmttUHZ/01mwp2lyvSRaM0pO1dRx50M+BxyZ3jgINGNauNEjJo54csq8H6ow7dwoT/UY+XE5cBBQ==" - }, - { - "block_id_flag": 2, - "validator_address": "48666481633E7C3A717BE6180449F2143E95A53F", - "timestamp": "2022-04-14T07:52:33.903349178Z", - "signature": "F9bnteb/6c/T3Mi/4yUyXWUVRKTneTJ/gQmFu2fbQ3qVvvi9Nm0CN4XOMxv8gSpxc5FVhfgbWGBZleOX6V2MDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "DEAFFD4536C979DB03184C422FE5B3D020490372", - "timestamp": "2022-04-14T07:52:33.882483659Z", - "signature": "vqi7SJigSj6PDzt0VBLdZfb6xEL4v7Ih6930unpDWrgpBctEfQVIKBW/BEP2UZZvAxfdSqb5l9XG4DjFbFR+AQ==" - }, - { - "block_id_flag": 2, - "validator_address": "107524E392B718F087153DF0405BC58853F4DCEA", - "timestamp": "2022-04-14T07:52:33.886372524Z", - "signature": "ce/cp+MZIBHQA22uqOhu85sA0BqJmfXcBhG6TvcCutfmILkAJtcpUF8obgp7hWYH3uUKJ6VnbbDzgpIWMAyNAQ==" - }, - { - "block_id_flag": 2, - "validator_address": "23012F4B162F9805888DCBB3A04A475AE2867184", - "timestamp": "2022-04-14T07:52:33.961357434Z", - "signature": "KMe3UCtOkqgZvYJ3aXcuBtbWEAslZ6pjx7scD09jhMcbQHmBls1va8vW3d09lTzMr1erHTt8revAMHPHLcVpAg==" - }, - { - "block_id_flag": 2, - "validator_address": "EF7E1D03677202D2A845452B42E47C6A5F047649", - "timestamp": "2022-04-14T07:52:33.880907414Z", - "signature": "DSxqFunRRibkWkEJRq4zwUdCaO8SS3oHp1xeQAjyYebEt4UajdY9WrSboyquF0dzK+ll7RH9WjPolS1rQJZdBA==" - }, - { - "block_id_flag": 2, - "validator_address": "43F8A1A85AEB63533830AF2476306F0641E42BCA", - "timestamp": "2022-04-14T07:52:33.924804598Z", - "signature": "Wb1qEyJ63qIZIQelyCRmQ2EdYgXTmfEkWS5wVCDr0BieD+QMlycX9HKPV29Gf4d5o6FcnEHSCT/mv8LNdtAuAg==" - }, - { - "block_id_flag": 2, - "validator_address": "E94F109D064F48DF1147A1487338FE785A0CA285", - "timestamp": "2022-04-14T07:52:33.842587485Z", - "signature": "vIAWj7j2tloOCW1Y6S+AAajx5uCABMDFNAb6Y3bC0Hq1SRGSCJC0A5jAGYqcxn/7PAHD7mdqAgWbmeFOF4r1BA==" - }, - { - "block_id_flag": 2, - "validator_address": "4609FD57FBDDC077A2C68ACD45782D9B25CB894A", - "timestamp": "2022-04-14T07:52:34.11149712Z", - "signature": "OxTf6b9V3PzrNmCnS6zft3pbl3Tqrcr0AYBrl/4cOZZ+/HupRmc0Cckdbuti4cY1UbjlvoDr2mWe+LhNi/hcCw==" - }, - { - "block_id_flag": 2, - "validator_address": "4FB44F29BE1F9941FB8A0E32CA463B67065AADB4", - "timestamp": "2022-04-14T07:52:33.976831752Z", - "signature": "9Lc/1XxND2a0nXUDQ5Tm++g4j9noxLI6m8dbO0YEMVefE3LaFSEdNVEQtIG1llhN0tLjp869xx5fOtb8N/olAg==" - }, - { - "block_id_flag": 2, - "validator_address": "AE4A96CEC1423B86D6FA62F34ACEBFB281FD3112", - "timestamp": "2022-04-14T07:52:33.856149693Z", - "signature": "wBvnmo+4DzzhmIUT3izZCdTsW+L3wEt2bzNlKzmhFkJWiRMnDWOBHBDpG+Q3Cgsb5KDfxMW+kynGEJFRbuo6CQ==" - }, - { - "block_id_flag": 2, - "validator_address": "6A6964D3274DA12A0575BDEC7DB58C059D53CE10", - "timestamp": "2022-04-14T07:52:33.875321135Z", - "signature": "4ejz/jY9Hsgc3aQ2ZKO8Q39AXhoxZWSZ4tRAfUBZn77fRPr37sI8fVbu/HQO2bcv0fNdUUzX2M5wgKCyCVSCAA==" - }, - { - "block_id_flag": 2, - "validator_address": "830DEB89DB946F2B8514123E0A0CEA8D2F6DA726", - "timestamp": "2022-04-14T07:52:34.099862263Z", - "signature": "n3pgQzrNpuHckrpvN2C4HTzWpgPuhk+yvpZWDwusSw+jQQr/4hskjkv8hoR4TsDNFO2FXHzbHHX4mD3IA3uaAA==" - }, - { - "block_id_flag": 2, - "validator_address": "F91AC9B31A2ABFDA33F38816D02D082DEFBD2ED3", - "timestamp": "2022-04-14T07:52:33.902887385Z", - "signature": "ayaGI0JQWmoGDJLblDR7GJpYqjbqT3Ccb51IzTC/ZwLXmkjatxlN+3+4+CLnYk6CfJxRuiWsBYtSLFqhMSRPCw==" - }, - { - "block_id_flag": 2, - "validator_address": "9163DEBE8B3F6C37A204DE2678F6D5F1009B4EC6", - "timestamp": "2022-04-14T07:52:33.959494792Z", - "signature": "ITfE3vhZyOmgi1vVmc0VLsrlsneVRz9+phMTYSXxPu/1iG1XfgbC1Pbev08Xd2mdVksNpSQFPARouk/SFRriAA==" - }, - { - "block_id_flag": 2, - "validator_address": "DCC2C2EC7FD1A8339BC34A1529DAC1711412FD96", - "timestamp": "2022-04-14T07:52:33.905452048Z", - "signature": "2PqctIN7lxQPYHghKoWSe5W9oy28BK1SlEdB+6hl5X3qlqmVU2NGYa0PBiCkhkq+k8UXmhIzKyOwz/6bvOlnBg==" - }, - { - "block_id_flag": 2, - "validator_address": "B2B9701A3D2929285E9F1F5035DF65F2086223B4", - "timestamp": "2022-04-14T07:52:34.215622183Z", - "signature": "8gZDKFQYtXIXvb1E+ThX5qDfkXSzuA53hqcmbM6qFelPRIq+ta6anF20KrQY8YjWopQX2RuHA2ZZcl6FT1h1AA==" - }, - { - "block_id_flag": 2, - "validator_address": "5F2D57512F251AE4E06C2D117273F33391750F43", - "timestamp": "2022-04-14T07:52:33.935519822Z", - "signature": "L2pPgkBz2akDvcstcANEeOvp4Qwf1g3aY1kpR2IcZuOsPtZ7wER9kQCjK9kTM10RJIWB4kRrgvFiXn0L6JOmAQ==" - }, - { - "block_id_flag": 2, - "validator_address": "582A84896F496809F359EDC222FEDC416F373D8C", - "timestamp": "2022-04-14T07:52:33.94923149Z", - "signature": "Ccyodn11h932ctLI1MxV/lYap9gxnVUDSsdjhna/kuNx2RSvuEeD/u9SUicBUXPUpPbqzxLG1SkA2hUi4Zv5Dw==" - }, - { - "block_id_flag": 2, - "validator_address": "DEBCE7328D1B8B0ECEA210141908F733D6B5FEEB", - "timestamp": "2022-04-14T07:52:34.383657541Z", - "signature": "kf3kkoE7QAI0vPUvvdRi/KWrMT50RDLMBqqAvlK3XBOYZIyHFod9dGfxAhFG5dMloD7jIqiWDRVVdYao27IgBA==" - }, - { - "block_id_flag": 2, - "validator_address": "EA1FBB2E1EA6766ED1A51D3330C6E1E38934C543", - "timestamp": "2022-04-14T07:52:34.187181731Z", - "signature": "zkAPDsiKReUh2PaWV3PX6dKOwkZmgPWyll/V8XmfgGFk3ymZ1PGBuHIZ2KlpC637AnB2Y1t0cGekX6s236cDBA==" - }, - { - "block_id_flag": 2, - "validator_address": "4C27A85A8055B381BEEF72658878AB442DD67106", - "timestamp": "2022-04-14T07:52:33.908674714Z", - "signature": "qLXPns/FCZSO0rQwck6ifL7sDgnhM/Yy9oHrsHFr531QpKIDrKoIZrqm2whCBs1RuNfPL0II7/QWGA5lFlCbDA==" - }, - { - "block_id_flag": 2, - "validator_address": "AF3691263C43369774C461A4B2F168BB09A5DDD1", - "timestamp": "2022-04-14T07:52:33.960449971Z", - "signature": "RWmVGGq6QXP9ti1NQCB7EUf3QipB1FeiVty66lQvJifAgTmG9BWDaQOpLqe/p6gvEx0ESZawUWQvp5L3Nq+7DA==" - }, - { - "block_id_flag": 2, - "validator_address": "B0AE11B935A1B3B7C2AB9FBBEC6EC7DF7E243F55", - "timestamp": "2022-04-14T07:52:33.899219543Z", - "signature": "R23ccKlU8N0nVSlZqhjHQaRQZQ/PXIQF1BgorJLuDp6QavQClYUBRvFW8Oees827rDQmCvmsi1FUsxYsZfrmCQ==" - }, - { - "block_id_flag": 2, - "validator_address": "C6AD188E37799621EFA778139823F1771C7539E0", - "timestamp": "2022-04-14T07:52:34.003983026Z", - "signature": "dKDJInOyi/4YH3Pr7VytlwxX2ATbHn4vak4NsgjlLZIZNj3eptv9RFzO/Yxkli7uOQIhN0c80Yz+kuPVBeyJAA==" - }, - { - "block_id_flag": 2, - "validator_address": "E9F7AE72FF98F98FE6F109CEB28B002D5C3AE234", - "timestamp": "2022-04-14T07:52:33.858520773Z", - "signature": "uQUB6z3mAk3PY9AJKXvg7/YPKVxrjJmVPfskVJuT/qugU8PS7E59Sw8H3Eca6ExwNle5LLCxuy+oUjvjlq1gBw==" - }, - { - "block_id_flag": 2, - "validator_address": "F305CFA31F9A8060E34EB5FD1FCD2E6FC6E0DC4A", - "timestamp": "2022-04-14T07:52:33.854407457Z", - "signature": "G+NY2T5TF3FWA/aDRysP5F/W3woEsZYSp7Y8+khaB9+t8P+NF9M+cEw5Dkru2w+LUpzL3H5qNmLMSYauSdZ0Ag==" - }, - { - "block_id_flag": 2, - "validator_address": "8312473768107B9D21048CEF310BCF89E00CC22D", - "timestamp": "2022-04-14T07:52:33.903177736Z", - "signature": "idHiXEpQBJ3icGYzbsbSC8KeU53tLJPcCVgXXNa01tgNtLL/ovWYNkClZwc923ztPv4NNuXyUC0ImpGmTZbGDQ==" - }, - { - "block_id_flag": 2, - "validator_address": "AE201075C1D2EF9970AC8EB0A8A4DC0BC121B37D", - "timestamp": "2022-04-14T07:52:33.9829821Z", - "signature": "O+JVxO2mc5xrBKoRlSD+FzguQCQf/qTZhH8muo3Vq8E20KbmyQUV1bgElmg9cGxuu9M2DyH4EcsSb8NRNppDCQ==" - }, - { - "block_id_flag": 2, - "validator_address": "C487F83AE5903E388231E5AA8D345D8977D56F04", - "timestamp": "2022-04-14T07:52:33.901238972Z", - "signature": "jYDqHlWuUMXvCZJ4BauCrM4Pzj/lRxOPtUmcm/tf+M1LA69balDuRGADkGx7TLlD0TjqGbXkRD55PF2M5NVqBg==" - }, - { - "block_id_flag": 2, - "validator_address": "DB968E2D6C5D1CA87A67B79BCA7B9E5424C0DC72", - "timestamp": "2022-04-14T07:52:33.915773636Z", - "signature": "phweHuFyD6i2wYaFr5ka4l55H3rykWYY9ZO+yeCokSF6bIgSBzomLOftKtuY31VQAZxWg6CuR0g5IvCPgBNWDA==" - } - ] - } -} diff --git a/share/availability/light/testdata/sample-block.json b/share/availability/light/testdata/sample-block.json new file mode 100755 index 0000000000..2bb8d35bfb --- /dev/null +++ b/share/availability/light/testdata/sample-block.json @@ -0,0 +1,60 @@ +{ + "header": { + "version": { + "block": 11 + }, + "chain_id": "7sUWGE", + "height": 12, + "time": "2023-01-19T01:41:20.109585974Z", + "last_block_id": { + "hash": "MfZsPUTgVOBRak+3xnKdwWEr2NQ1v095T4BYaWcdYTs=", + "part_set_header": { + "total": 1, + "hash": "/qyxxHNcuOyYBcaGw90PqAa2GdFErMHkkbH66BDI1hI=" + } + }, + "last_commit_hash": "sQXqALyCQ+tHFKOjfQhB3+lQuud0d3C2ohuOgrO6YX4=", + "data_hash": "Pu8CIJqgqCFYkaSvfeS5jvb6+ThXtJkXeRDZPnb1auM=", + "validators_hash": "X7yBLGyFWnZvyakBElaaaqyV0iCSNTINfm+puFxcX/g=", + "next_validators_hash": "X7yBLGyFWnZvyakBElaaaqyV0iCSNTINfm+puFxcX/g=", + "consensus_hash": "BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=", + "app_hash": "pqd8GP5/icLOzlysLtq9IupqfLmoxNVySLWNarJssbE=", + "last_results_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", + "evidence_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", + "proposer_address": "oFwwfI48D9wGpICbXtGZNlx6xOI=" + }, + "data": { + "txs": [ + "CpsCCnsKeQoTL2Jsb2IuTXNnUGF5Rm9yQmxvYhJiCi1jb3Ntb3Mxemdwamp4bWZhNm00ZjU5cTJsNjgzOGp1c2RjcmZ4eHc1ajI4ZG0SCF62zWQ6Pw9TGgLBGyIgo5vwRkbYLo3+Xm1IpV8weKHdFYfqkfDyt8a1vfthQQJCAQASWgpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA64OVjUldXAMb1uSZqrBNuYaatsk+CMGyQKNzY2vM5T+EgQKAggBEggQgIDpg7HeFhpAM88quyAFPvk6/4fuiolAJf3SmOLNv471av8uir6yHaJPdsusvOUQ6gK+/gwoksZ2Lytk48k3Gf4F+tcJO06ZBxIBBBoESU5EWA==" + ], + "blobs": [ + { + "namespace_id": "XrbNZDo/D1M=", + "data": "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" + } + ], + "square_size": 4, + "hash": "Pu8CIJqgqCFYkaSvfeS5jvb6+ThXtJkXeRDZPnb1auM=" + }, + "evidence": { + "evidence": [] + }, + "last_commit": { + "height": 11, + "block_id": { + "hash": "MfZsPUTgVOBRak+3xnKdwWEr2NQ1v095T4BYaWcdYTs=", + "part_set_header": { + "total": 1, + "hash": "/qyxxHNcuOyYBcaGw90PqAa2GdFErMHkkbH66BDI1hI=" + } + }, + "signatures": [ + { + "block_id_flag": 2, + "validator_address": "oFwwfI48D9wGpICbXtGZNlx6xOI=", + "timestamp": "2023-01-19T01:41:20.109585974Z", + "signature": "mPRPdLbp8cQcZ2w6CmWlmE+4lVzX9YgxzxOBp7lYTT8jYmehJC62sC/Nt/X/ezVW7u9OCXZjGG2oIDYmabKsAA==" + } + ] + } +} \ No newline at end of file diff --git a/share/empty.go b/share/empty.go index c6707b3225..5fee787d6a 100644 --- a/share/empty.go +++ b/share/empty.go @@ -24,7 +24,7 @@ func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockServic // EmptyExtendedDataSquare returns the EDS of the empty block data square. func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { shares := emptyDataSquare() - squareSize := uint64(math.Sqrt(float64(appconsts.MinSquareSize))) + squareSize := uint64(math.Sqrt(float64(appconsts.DefaultMinSquareSize))) eds, err := da.ExtendShares(squareSize, shares) if err != nil { panic(fmt.Errorf("failed to create empty EDS: %w", err)) diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 6124e5b9fe..3f6384ac48 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -37,7 +37,7 @@ const ( // MaxSquareSize is currently the maximum size supported for unerasured data in // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.MaxSquareSize + MaxSquareSize = appconsts.DefaultMaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize diff --git a/share/share.go b/share/share.go index 8fbf427a95..66d2c0df29 100644 --- a/share/share.go +++ b/share/share.go @@ -19,7 +19,7 @@ var ( const ( // MaxSquareSize is currently the maximum size supported for unerasured data in // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.MaxSquareSize + MaxSquareSize = appconsts.DefaultMaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize // Size is a system-wide size of a share, including both data and namespace ID diff --git a/state/core_access.go b/state/core_access.go index 73c75d2fd5..596c3ab7eb 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -20,8 +20,9 @@ import ( "google.golang.org/grpc/credentials/insecure" "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/x/payment" - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/x/blob" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/header" @@ -162,8 +163,16 @@ func (ca *CoreAccessor) SubmitPayForBlob( fee Int, gasLim uint64, ) (*TxResponse, error) { - response, err := payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim, withFee(fee)) - // metrics should only be counted on a successful PFB tx + b := &apptypes.Blob{NamespaceId: nID, Data: data, ShareVersion: uint32(appconsts.DefaultShareVersion)} + response, err := blob.SubmitPayForBlob( + ctx, + ca.signer, + ca.coreConn, + []*apptypes.Blob{b}, + apptypes.SetGasLimit(gasLim), + withFee(fee), + ) + // metrics should only be counted on a successful PFD tx if err == nil && response.Code == 0 { ca.lastPayForBlob = time.Now().UnixMilli() ca.payForBlobCount++ @@ -231,7 +240,7 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B return nil, fmt.Errorf("cannot convert %s into sdktypes.Int", string(value)) } // verify balance - err = ca.prt.VerifyValueKeys( + err = ca.prt.VerifyValueFromKeys( result.Response.GetProofOps(), head.AppHash, [][]byte{[]byte(banktypes.StoreKey), diff --git a/state/integration_test.go b/state/integration_test.go index ffb84b92a3..07bf044da6 100644 --- a/state/integration_test.go +++ b/state/integration_test.go @@ -2,21 +2,26 @@ package state import ( "context" + "encoding/json" + "os" "testing" + "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" rpcclient "github.com/tendermint/tendermint/rpc/client" "google.golang.org/grpc" "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/testutil/testfactory" "github.com/celestiaorg/celestia-app/testutil/testnode" - blobtypes "github.com/celestiaorg/celestia-app/x/payment/types" + blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/header" ) @@ -51,7 +56,8 @@ func (s *IntegrationTestSuite) SetupSuite() { s.accessor = accessor // required to ensure the Head request is non-nil - require.NoError(s.T(), s.cctx.WaitForNextBlock()) + _, err := s.cctx.WaitForHeight(3) + require.NoError(s.T(), err) } func setClients(ca *CoreAccessor, conn *grpc.ClientConn, abciCli rpcclient.ABCIClient) { @@ -110,3 +116,32 @@ func (s *IntegrationTestSuite) TestGetBalance() { require.Equal(&expectedBal, bal) } } + +// This test can be used to generate a json encoded block for other test data, +// such as that in share/availability/light/testdata +func (s *IntegrationTestSuite) TestGenerateJSONBlock() { + t := s.T() + t.Skip("skipping testdata generation test") + resp, err := s.cctx.FillBlock(4, s.accounts, flags.BroadcastSync) + require := s.Require() + require.NoError(err) + require.Equal(abci.CodeTypeOK, resp.Code) + require.NoError(s.cctx.WaitForNextBlock()) + + // download the block that the tx was in + res, err := testfactory.QueryWithoutProof(s.cctx.Context, resp.TxHash) + require.NoError(err) + + block, err := s.cctx.Client.Block(s.cctx.GoContext(), &res.Height) + require.NoError(err) + + pBlock, err := block.Block.ToProto() + require.NoError(err) + + file, err := os.OpenFile("sample-block.json", os.O_CREATE|os.O_RDWR, os.ModePerm) + defer file.Close() //nolint: staticcheck + require.NoError(err) + + err = json.NewEncoder(file).Encode(pBlock) + require.NoError(err) +} From 676b047d571b16f77d698d6e7d765f2632d79cb1 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Tue, 24 Jan 2023 22:47:15 +0100 Subject: [PATCH 0321/1008] docs: fix minor typos (#1636) --- docs/adr/adr-001-predevnet-celestia-node.md | 2 +- docs/adr/adr-005-plugins.md | 2 +- docs/adr/adr-template.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/adr-001-predevnet-celestia-node.md b/docs/adr/adr-001-predevnet-celestia-node.md index 0a3cff0402..b5ac40f8bc 100644 --- a/docs/adr/adr-001-predevnet-celestia-node.md +++ b/docs/adr/adr-001-predevnet-celestia-node.md @@ -100,7 +100,7 @@ For devnet, it should be possible for Celestia `full` Nodes to receive informati For the Celestia Node to be able to propagate `StateFraudProof`s, we must modify Celestia Core to store blocks with invalid state and serve them to both the Celestia Node and the Celestia App, **and** the Celestia App must be able to generate and serve `StateFraudProof`s via RPC to Celestia nodes. -This feature is not necessarily required for devnet (so state exection functionality for Celestia Full Nodes can be stubbed out), but it would be nice to have for devnet as we will likely allow Celestia Full Nodes to speak with other Celestia Full Nodes instead of running a trusted Celestia Core node simultaenously and relying on it for information. +This feature is not necessarily required for devnet (so state execution functionality for Celestia Full Nodes can be stubbed out), but it would be nice to have for devnet as we will likely allow Celestia Full Nodes to speak with other Celestia Full Nodes instead of running a trusted Celestia Core node simultaneously and relying on it for information. A roadmap to implementation could look like the following: diff --git a/docs/adr/adr-005-plugins.md b/docs/adr/adr-005-plugins.md index b01c772b54..7d98b63718 100644 --- a/docs/adr/adr-005-plugins.md +++ b/docs/adr/adr-005-plugins.md @@ -259,7 +259,7 @@ Proposed - easier to create custom applications that run on top of celestia - allows for developers to create a better UX for their custom celestia-nodes - isolates the added functionality to its own service(s), which could potentially be combined with other plugins -- helps move us towards our goal of reducing any any duplicate functionality coded in optimint's dalc +- helps move us towards our goal of reducing any duplicate functionality coded in optimint's dalc ### Negative diff --git a/docs/adr/adr-template.md b/docs/adr/adr-template.md index 5f8e09e49c..32cf5b3b41 100644 --- a/docs/adr/adr-template.md +++ b/docs/adr/adr-template.md @@ -10,7 +10,7 @@ ## Alternative Approaches -> This section contains information around alternative options that are considered before making a decision. It should contain a explanation on why the alternative approach(es) were not chosen. +> This section contains information around alternative options that are considered before making a decision. It should contain an explanation of why the alternative approach(es) were not chosen. ## Decision From f3b334ffd491024aacadc196f8ae078995855894 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 25 Jan 2023 09:28:28 +0100 Subject: [PATCH 0322/1008] chore(swamp): replace cosmos prefix with celestia in swamp tests (#1583) --- cmd/celestia/main.go | 10 ---------- core/config.go | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 core/config.go diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index 81baf23b34..660e6e6c31 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -6,20 +6,10 @@ import ( "os" "time" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" - - "github.com/celestiaorg/celestia-app/app" ) func init() { - // This is necessary to ensure that the account addresses are correctly prefixed - // as in the celestia application. - cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) - cfg.Seal() - rootCmd.AddCommand( bridgeCmd, lightCmd, diff --git a/core/config.go b/core/config.go new file mode 100644 index 0000000000..9d9b82630c --- /dev/null +++ b/core/config.go @@ -0,0 +1,16 @@ +package core + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/celestiaorg/celestia-app/app" +) + +func init() { + // This is necessary to ensure that the account addresses are correctly prefixed + // as in the celestia application. + cfg := sdk.GetConfig() + cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) + cfg.Seal() +} From b727ff62cca78c8f3b5ffc973a5a7afac6145f5c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 26 Jan 2023 17:04:57 +0300 Subject: [PATCH 0323/1008] feat(share/discovery): Ensure only one instance of EnsurePeers is running (#1642) ensure only one instance of EnsurePeers is running --- share/availability/discovery/discovery.go | 10 ++++++++++ share/availability/full/availability.go | 2 +- share/availability/light/availability.go | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 95e8e3b7a8..b27123a6f7 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -2,6 +2,7 @@ package discovery import ( "context" + "sync" "time" logging "github.com/ipfs/go-log/v2" @@ -41,6 +42,8 @@ type Discovery struct { advertiseInterval time.Duration // onUpdatedPeers will be called on peer set changes onUpdatedPeers OnUpdatedPeers + // ensureIsRunning allows only one ensurePeers process to be running + ensurePeersOnce sync.Once } type OnUpdatedPeers func(peerID peer.ID, isAdded bool) @@ -62,6 +65,7 @@ func NewDiscovery( discInterval, advertiseInterval, func(peer.ID, bool) {}, + sync.Once{}, } } @@ -102,6 +106,12 @@ func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer // It starts peer discovery every 30 seconds until peer cache reaches peersLimit. // Discovery is restarted if any previously connected peers disconnect. func (d *Discovery) EnsurePeers(ctx context.Context) { + d.ensurePeersOnce.Do(func() { + go d.ensurePeers(ctx) + }) +} + +func (d *Discovery) ensurePeers(ctx context.Context) { if d.peersLimit == 0 { log.Warn("peers limit is set to 0. Skipping discovery...") return diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index f6ef1ff05f..9a0ce67f70 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -37,7 +37,7 @@ func (fa *ShareAvailability) Start(context.Context) error { fa.cancel = cancel go fa.disc.Advertise(ctx) - go fa.disc.EnsurePeers(ctx) + fa.disc.EnsurePeers(ctx) return nil } diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index f804a91e32..3519ae1c8a 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -44,7 +44,7 @@ func (la *ShareAvailability) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) la.cancel = cancel - go la.disc.EnsurePeers(ctx) + la.disc.EnsurePeers(ctx) return nil } From 2b4f219df2331ebd7753b741d51ff2d23a390b8a Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Thu, 26 Jan 2023 23:42:22 +0100 Subject: [PATCH 0324/1008] feat: bump arabica chain-id and genesis hash (#1645) ## Overview ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index fd85d66da0..b258cc649a 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "04EE55B212745B88F29943D7B9528C415473A211F12EEF6E9333EF32E4DEAF3C", + Arabica: "6D2D85AC2817EC2CCA8C75893EFA398B7EEB988528AC403CD0102AE4230D415A", Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 11427f5ae7..a528629a44 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-2" + Arabica Network = "arabica-3" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha" // Private can be used to set up any private network, including local testing setups. From 8c56affb75fc70d933ee8a6269f2c3c387064279 Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Fri, 27 Jan 2023 11:55:32 +0100 Subject: [PATCH 0325/1008] chore(nodebuilder/p2p): Update Arabica kaarina peerID (#1650) --- nodebuilder/p2p/bootstrap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 6b4f5fc877..96f46bcaa7 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -32,7 +32,7 @@ var bootstrapList = map[Network][]string{ "/dns4/limani.celestia-devops.dev/tcp/2121/p2p/12D3KooWDgG69kXfmSiHjUErN2ahpUC1SXpSfB2urrqMZ6aWC8NS", "/dns4/marsellesa.celestia-devops.dev/tcp/2121/p2p/12D3KooWHr2wqFAsMXnPzpFsgxmePgXb8BqpkePebwUgLyZc95bd", "/dns4/parainem.celestia-devops.dev/tcp/2121/p2p/12D3KooWHX8xpwg8qkP7kLKmKGtgZvmsopvgxc6Fwtu665QC7G8q", - "/dns4/kaarina.celestia-devops.dev/tcp/2121/p2p/12D3KooWRSaqC5H77PGMC7rLx5JBXiutJu7ouUyCToF8d6J1Scfh", + "/dns4/kaarina.celestia-devops.dev/tcp/2121/p2p/12D3KooWN6fzdt4sG5QfWRPn4kwCQBdkt7TDNQkWsUymAwKrmvUs", }, Mocha: { "/dns4/andromeda.celestia-devops.dev/tcp/2121/p2p/12D3KooWKvPXtV1yaQ6e3BRNUHa5Phh8daBwBi3KkGaSSkUPys6D", From 6d3773e09d669918781dfd13c360d564c9b87015 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 27 Jan 2023 13:46:30 +0100 Subject: [PATCH 0326/1008] fix(core/listener): Only broadcast datahash if core node is not syncing (#1652) Fixes a bug found in implementation of datahash broadcasting in bridge nodes -- now bridge nodes will only broadcast a hash if its connected core node is synced up. --- core/listener.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/listener.go b/core/listener.go index 76746adbf8..7a8a3c8055 100644 --- a/core/listener.go +++ b/core/listener.go @@ -109,11 +109,13 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { "err", err) } - // notify network of new EDS hash - err = cl.hashBroadcaster(ctx, eh.DataHash.Bytes()) - if err != nil { - log.Errorw("listener: broadcasting data hash", "height", eh.Height(), - "hash", eh.Hash(), "err", err) + // notify network of new EDS hash only if core is already synced + if !syncing { + err = cl.hashBroadcaster(ctx, eh.DataHash.Bytes()) + if err != nil { + log.Errorw("listener: broadcasting data hash", "height", eh.Height(), + "hash", eh.Hash(), "err", err) + } } case <-ctx.Done(): return From dc17ccae65989d493c73de53429009e1e6bd4407 Mon Sep 17 00:00:00 2001 From: jserrat <35823283+Jpserrat@users.noreply.github.com> Date: Mon, 30 Jan 2023 07:00:47 -0300 Subject: [PATCH 0327/1008] nodebuilder: make proxy constructors private in all modules (#1643) --- nodebuilder/core/constructors.go | 2 +- nodebuilder/core/module.go | 2 +- nodebuilder/das/constructors.go | 2 +- nodebuilder/das/module.go | 2 +- nodebuilder/gateway/constructors.go | 2 +- nodebuilder/gateway/module.go | 2 +- nodebuilder/header/module.go | 2 +- nodebuilder/header/service.go | 4 ++-- nodebuilder/p2p/addrs.go | 12 ++++++------ nodebuilder/p2p/bitswap.go | 8 ++++---- nodebuilder/p2p/config.go | 4 ++-- nodebuilder/p2p/host.go | 12 ++++++------ nodebuilder/p2p/identity.go | 2 +- nodebuilder/p2p/ipld.go | 4 ++-- nodebuilder/p2p/misc.go | 22 +++++++++++----------- nodebuilder/p2p/module.go | 24 ++++++++++++------------ nodebuilder/p2p/opts.go | 5 +++-- nodebuilder/p2p/p2p_test.go | 2 +- nodebuilder/p2p/pubsub.go | 8 ++++---- nodebuilder/p2p/routing.go | 8 ++++---- nodebuilder/rpc/constructors.go | 6 +++--- nodebuilder/rpc/module.go | 4 ++-- nodebuilder/state/core.go | 4 ++-- nodebuilder/state/keyring.go | 12 ++++++------ nodebuilder/state/module.go | 4 ++-- 25 files changed, 80 insertions(+), 79 deletions(-) diff --git a/nodebuilder/core/constructors.go b/nodebuilder/core/constructors.go index 07a9c69225..53c914a041 100644 --- a/nodebuilder/core/constructors.go +++ b/nodebuilder/core/constructors.go @@ -4,6 +4,6 @@ import ( "github.com/celestiaorg/celestia-node/core" ) -func Remote(cfg Config) (core.Client, error) { +func remote(cfg Config) (core.Client, error) { return core.NewRemote(cfg.IP, cfg.RPCPort) } diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index 44982f0243..efaa3dd8e8 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -51,7 +51,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option }), )), fx.Provide(fx.Annotate( - Remote, + remote, fx.OnStart(func(ctx context.Context, client core.Client) error { return client.Start() }), diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index c643eef6ef..f73c9bbf53 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -33,7 +33,7 @@ func newDaserStub() Module { return &daserStub{} } -func NewDASer( +func newDASer( da share.Availability, hsub libhead.Subscriber[*header.ExtendedHeader], store libhead.Store[*header.ExtendedHeader], diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 6bba4423a9..d060a6a0b2 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -42,7 +42,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "daser", baseComponents, fx.Provide(fx.Annotate( - NewDASer, + newDASer, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, das *das.DASer) error { return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, das.Start, das.Stop) diff --git a/nodebuilder/gateway/constructors.go b/nodebuilder/gateway/constructors.go index ae8ce73804..c28153b0a5 100644 --- a/nodebuilder/gateway/constructors.go +++ b/nodebuilder/gateway/constructors.go @@ -21,6 +21,6 @@ func Handler( handler.RegisterMiddleware(serv) } -func Server(cfg *Config) *gateway.Server { +func server(cfg *Config) *gateway.Server { return gateway.NewServer(cfg.Address, cfg.Port) } diff --git a/nodebuilder/gateway/module.go b/nodebuilder/gateway/module.go index ab4503c13e..4cdf325dc0 100644 --- a/nodebuilder/gateway/module.go +++ b/nodebuilder/gateway/module.go @@ -26,7 +26,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Supply(cfg), fx.Error(cfgErr), fx.Provide(fx.Annotate( - Server, + server, fx.OnStart(func(ctx context.Context, server *gateway.Server) error { return server.Start(ctx) }), diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 791d5536e6..24c71736cf 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -47,7 +47,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithRequestTimeout[p2p.ServerParameters](cfg.Server.RequestTimeout), } }), - fx.Provide(NewHeaderService), + fx.Provide(newHeaderService), fx.Provide(fx.Annotate( func(ds datastore.Batching, opts []store.Option) (libhead.Store[*header.ExtendedHeader], error) { return store.NewStore[*header.ExtendedHeader](ds, opts...) diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 1948c09315..90c78bec46 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -21,8 +21,8 @@ type Service struct { store libhead.Store[*header.ExtendedHeader] } -// NewHeaderService creates a new instance of header Service. -func NewHeaderService( +// newHeaderService creates a new instance of header Service. +func newHeaderService( syncer *sync.Syncer[*header.ExtendedHeader], sub libhead.Subscriber[*header.ExtendedHeader], p2pServer *p2p.ExchangeServer[*header.ExtendedHeader], diff --git a/nodebuilder/p2p/addrs.go b/nodebuilder/p2p/addrs.go index 3d15179c90..d8f50c8144 100644 --- a/nodebuilder/p2p/addrs.go +++ b/nodebuilder/p2p/addrs.go @@ -4,13 +4,13 @@ import ( "fmt" p2pconfig "github.com/libp2p/go-libp2p/config" - "github.com/libp2p/go-libp2p/core/host" + hst "github.com/libp2p/go-libp2p/core/host" ma "github.com/multiformats/go-multiaddr" ) // Listen returns invoke function that starts listening for inbound connections with libp2p.Host. -func Listen(listen []string) func(host host.Host) (err error) { - return func(host host.Host) (err error) { +func Listen(listen []string) func(h hst.Host) (err error) { + return func(h hst.Host) (err error) { maListen := make([]ma.Multiaddr, len(listen)) for i, addr := range listen { maListen[i], err = ma.NewMultiaddr(addr) @@ -18,12 +18,12 @@ func Listen(listen []string) func(host host.Host) (err error) { return fmt.Errorf("failure to parse config.P2P.ListenAddresses: %s", err) } } - return host.Network().Listen(maListen...) + return h.Network().Listen(maListen...) } } -// AddrsFactory returns a constructor for AddrsFactory. -func AddrsFactory(announce []string, noAnnounce []string) func() (_ p2pconfig.AddrsFactory, err error) { +// addrsFactory returns a constructor for AddrsFactory. +func addrsFactory(announce []string, noAnnounce []string) func() (_ p2pconfig.AddrsFactory, err error) { return func() (_ p2pconfig.AddrsFactory, err error) { // Convert maAnnounce strings to Multiaddresses maAnnounce := make([]ma.Multiaddr, len(announce)) diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index a81580e8cd..36be9ab953 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -10,7 +10,7 @@ import ( blockstore "github.com/ipfs/go-ipfs-blockstore" exchange "github.com/ipfs/go-ipfs-exchange-interface" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" - "github.com/libp2p/go-libp2p/core/host" + hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" "go.uber.org/fx" ) @@ -24,8 +24,8 @@ const ( defaultARCCacheSize = 64 << 10 ) -// DataExchange provides a constructor for IPFS block's DataExchange over BitSwap. -func DataExchange(params bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { +// dataExchange provides a constructor for IPFS block's DataExchange over BitSwap. +func dataExchange(params bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { bs, err := blockstore.CachedBlockstore( params.Ctx, blockstore.NewBlockstore(params.Ds), @@ -56,6 +56,6 @@ type bitSwapParams struct { Ctx context.Context Net Network - Host host.Host + Host hst.Host Ds datastore.Batching } diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 070049a91e..0388a883c9 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -31,7 +31,7 @@ type Config struct { // This is enabled by default for Bootstrappers. PeerExchange bool // ConnManager is a configuration tuple for ConnectionManager. - ConnManager ConnManagerConfig + ConnManager connManagerConfig RoutingTableRefreshPeriod time.Duration } @@ -56,7 +56,7 @@ func DefaultConfig() Config { MutualPeers: []string{}, Bootstrapper: false, PeerExchange: false, - ConnManager: DefaultConnManagerConfig(), + ConnManager: defaultConnManagerConfig(), RoutingTableRefreshPeriod: defaultRoutingRefreshPeriod, } } diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index c8bf52153a..727ac81c9b 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -8,7 +8,7 @@ import ( p2pconfig "github.com/libp2p/go-libp2p/config" "github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/crypto" - "github.com/libp2p/go-libp2p/core/host" + hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/metrics" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" @@ -21,14 +21,14 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -// RoutedHost constructs a wrapped Host that may fallback to address discovery, +// routedHost constructs a wrapped Host that may fallback to address discovery, // if any top-level operation on the Host is provided with PeerID(Hash(PbK)) only. -func RoutedHost(base HostBase, r routing.PeerRouting) host.Host { +func routedHost(base HostBase, r routing.PeerRouting) hst.Host { return routedhost.Wrap(base, r) } -// Host returns constructor for Host. -func Host(params hostParams) (HostBase, error) { +// host returns constructor for Host. +func host(params hostParams) (HostBase, error) { opts := []libp2p.Option{ libp2p.NoListenAddrs, // do not listen automatically libp2p.AddrsFactory(params.AddrF), @@ -64,7 +64,7 @@ func Host(params hostParams) (HostBase, error) { return h, nil } -type HostBase host.Host +type HostBase hst.Host type hostParams struct { fx.In diff --git a/nodebuilder/p2p/identity.go b/nodebuilder/p2p/identity.go index 24f9f6a94a..a6f139834c 100644 --- a/nodebuilder/p2p/identity.go +++ b/nodebuilder/p2p/identity.go @@ -42,7 +42,7 @@ func Key(kstore keystore.Keystore) (crypto.PrivKey, error) { return crypto.UnmarshalPrivateKey(ksPriv.Body) } -func ID(key crypto.PrivKey, pstore peerstore.Peerstore) (peer.ID, error) { +func id(key crypto.PrivKey, pstore peerstore.Peerstore) (peer.ID, error) { id, err := peer.IDFromPrivateKey(key) if err != nil { return "", err diff --git a/nodebuilder/p2p/ipld.go b/nodebuilder/p2p/ipld.go index 5835618a43..6278538825 100644 --- a/nodebuilder/p2p/ipld.go +++ b/nodebuilder/p2p/ipld.go @@ -6,7 +6,7 @@ import ( exchange "github.com/ipfs/go-ipfs-exchange-interface" ) -// BlockService constructs IPFS's BlockService for fetching arbitrary Merkle structures. -func BlockService(bs blockstore.Blockstore, ex exchange.Interface) blockservice.BlockService { +// blockService constructs IPFS's BlockService for fetching arbitrary Merkle structures. +func blockService(bs blockstore.Blockstore, ex exchange.Interface) blockservice.BlockService { return blockservice.New(bs, ex) } diff --git a/nodebuilder/p2p/misc.go b/nodebuilder/p2p/misc.go index bd1c169a72..4812d85cf1 100644 --- a/nodebuilder/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -11,8 +11,8 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/connmgr" ) -// ConnManagerConfig configures connection manager. -type ConnManagerConfig struct { +// connManagerConfig configures connection manager. +type connManagerConfig struct { // Low and High are watermarks governing the number of connections that'll be maintained. Low, High int // GracePeriod is the amount of time a newly opened connection is given before it becomes subject @@ -20,17 +20,17 @@ type ConnManagerConfig struct { GracePeriod time.Duration } -// DefaultConnManagerConfig returns defaults for ConnManagerConfig. -func DefaultConnManagerConfig() ConnManagerConfig { - return ConnManagerConfig{ +// defaultConnManagerConfig returns defaults for ConnManagerConfig. +func defaultConnManagerConfig() connManagerConfig { + return connManagerConfig{ Low: 50, High: 100, GracePeriod: time.Minute, } } -// ConnectionManager provides a constructor for ConnectionManager. -func ConnectionManager(cfg Config, bpeers Bootstrappers) (connmgri.ConnManager, error) { +// connectionManager provides a constructor for ConnectionManager. +func connectionManager(cfg Config, bpeers Bootstrappers) (connmgri.ConnManager, error) { fpeers, err := cfg.mutualPeers() if err != nil { return nil, err @@ -53,12 +53,12 @@ func ConnectionManager(cfg Config, bpeers Bootstrappers) (connmgri.ConnManager, return cm, nil } -// ConnectionGater constructs a BasicConnectionGater. -func ConnectionGater(ds datastore.Batching) (*conngater.BasicConnectionGater, error) { +// connectionGater constructs a BasicConnectionGater. +func connectionGater(ds datastore.Batching) (*conngater.BasicConnectionGater, error) { return conngater.NewBasicConnectionGater(ds) } -// PeerStore constructs a PeerStore. -func PeerStore() (peerstore.Peerstore, error) { +// peerStore constructs a PeerStore. +func peerStore() (peerstore.Peerstore, error) { return pstoremem.NewPeerstore() } diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 270e760f21..c00f8ab60c 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -22,18 +22,18 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Supply(*cfg), fx.Error(cfgErr), fx.Provide(Key), - fx.Provide(ID), - fx.Provide(PeerStore), - fx.Provide(ConnectionManager), - fx.Provide(ConnectionGater), - fx.Provide(Host), - fx.Provide(RoutedHost), - fx.Provide(PubSub), - fx.Provide(DataExchange), - fx.Provide(BlockService), - fx.Provide(PeerRouting), - fx.Provide(ContentRouting), - fx.Provide(AddrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), + fx.Provide(id), + fx.Provide(peerStore), + fx.Provide(connectionManager), + fx.Provide(connectionGater), + fx.Provide(host), + fx.Provide(routedHost), + fx.Provide(pubSub), + fx.Provide(dataExchange), + fx.Provide(blockService), + fx.Provide(peerRouting), + fx.Provide(contentRouting), + fx.Provide(addrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), fx.Provide(metrics.NewBandwidthCounter), fx.Provide(newModule), fx.Invoke(Listen(cfg.ListenAddresses)), diff --git a/nodebuilder/p2p/opts.go b/nodebuilder/p2p/opts.go index b96cbef5da..6e9bf64eef 100644 --- a/nodebuilder/p2p/opts.go +++ b/nodebuilder/p2p/opts.go @@ -3,8 +3,9 @@ package p2p import ( "encoding/hex" + hst "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/crypto" - "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/libs/fxutil" @@ -31,6 +32,6 @@ func WithP2PKeyStr(key string) fx.Option { } // WithHost sets custom Host's data for p2p networking. -func WithHost(hst host.Host) fx.Option { +func WithHost(hst hst.Host) fx.Option { return fxutil.ReplaceAs(hst, new(HostBase)) } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index 1bde19e78f..48941886ee 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -219,7 +219,7 @@ func TestP2PModule_Pubsub(t *testing.T) { // TestP2PModule_ConnGater tests P2P Module methods on // the instance of ConnectionGater. func TestP2PModule_ConnGater(t *testing.T) { - gater, err := ConnectionGater(datastore.NewMapDatastore()) + gater, err := connectionGater(datastore.NewMapDatastore()) require.NoError(t, err) mgr := newModule(nil, nil, gater, nil, nil) diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 8ebc1afd2c..256848ebcf 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -5,14 +5,14 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" - "github.com/libp2p/go-libp2p/core/host" + hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" "go.uber.org/fx" "golang.org/x/crypto/blake2b" ) -// PubSub provides a constructor for PubSub protocol with GossipSub routing. -func PubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { +// pubSub provides a constructor for PubSub protocol with GossipSub routing. +func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { fpeers, err := cfg.mutualPeers() if err != nil { return nil, err @@ -51,5 +51,5 @@ type pubSubParams struct { fx.In Ctx context.Context - Host host.Host + Host hst.Host } diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index e2a9aeb01e..c3797d8f3e 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -11,15 +11,15 @@ import ( "go.uber.org/fx" ) -// ContentRouting constructs nil content routing, +// contentRouting constructs nil content routing, // as for our use-case existing ContentRouting mechanisms, e.g DHT, are unsuitable -func ContentRouting(r routing.PeerRouting) routing.ContentRouting { +func contentRouting(r routing.PeerRouting) routing.ContentRouting { return r.(*dht.IpfsDHT) } -// PeerRouting provides constructor for PeerRouting over DHT. +// peerRouting provides constructor for PeerRouting over DHT. // Basically, this provides a way to discover peer addresses by respecting public keys. -func PeerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) { +func peerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) { opts := []dht.Option{ dht.Mode(dht.ModeAuto), dht.BootstrapPeers(params.Peers...), diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index 97518b64f3..3d5c368042 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -13,8 +13,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -// RegisterEndpoints registers the given services on the rpc. -func RegisterEndpoints( +// registerEndpoints registers the given services on the rpc. +func registerEndpoints( stateMod state.Module, shareMod share.Module, fraudMod fraud.Module, @@ -33,6 +33,6 @@ func RegisterEndpoints( serv.RegisterAuthedService("node", nodeMod, &node.API{}) } -func Server(cfg *Config, auth jwt.Signer) *rpc.Server { +func server(cfg *Config, auth jwt.Signer) *rpc.Server { return rpc.NewServer(cfg.Address, cfg.Port, auth) } diff --git a/nodebuilder/rpc/module.go b/nodebuilder/rpc/module.go index d83e09750e..141018288c 100644 --- a/nodebuilder/rpc/module.go +++ b/nodebuilder/rpc/module.go @@ -17,7 +17,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Supply(cfg), fx.Error(cfgErr), fx.Provide(fx.Annotate( - Server, + server, fx.OnStart(func(ctx context.Context, server *rpc.Server) error { return server.Start(ctx) }), @@ -32,7 +32,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "rpc", baseComponents, - fx.Invoke(RegisterEndpoints), + fx.Invoke(registerEndpoints), ) default: panic("invalid node type") diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index d617f6211a..001bdd420f 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -8,9 +8,9 @@ import ( "github.com/celestiaorg/celestia-node/state" ) -// CoreAccessor constructs a new instance of state.Module over +// coreAccessor constructs a new instance of state.Module over // a celestia-core connection. -func CoreAccessor( +func coreAccessor( corecfg core.Config, signer *apptypes.KeyringSigner, sync *sync.Syncer[*header.ExtendedHeader], diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go index e5347acae9..42e76625d9 100644 --- a/nodebuilder/state/keyring.go +++ b/nodebuilder/state/keyring.go @@ -5,7 +5,7 @@ import ( "os" "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" + kr "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" @@ -15,19 +15,19 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -func Keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.KeyringSigner, error) { +func keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.KeyringSigner, error) { // TODO @renaynay: Include option for setting custom `userInput` parameter with // implementation of https://github.com/celestiaorg/celestia-node/issues/415. // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed // here instead of hardcoded `BackendTest`: // https://github.com/celestiaorg/celestia-node/issues/603. encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - ring, err := keyring.New(app.Name, keyring.BackendTest, ks.Path(), os.Stdin, encConf.Codec) + ring, err := kr.New(app.Name, kr.BackendTest, ks.Path(), os.Stdin, encConf.Codec) if err != nil { return nil, err } - var info *keyring.Record + var info *kr.Record // if custom keyringAccName provided, find key for that name if cfg.KeyringAccName != "" { keyInfo, err := ring.Key(cfg.KeyringAccName) @@ -44,7 +44,7 @@ func Keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.Keyri // if no key was found in keystore path, generate new key for node if len(keys) == 0 { log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ks.Path()) - keyInfo, mn, err := ring.NewMnemonic("my_celes_key", keyring.English, "", + keyInfo, mn, err := ring.NewMnemonic("my_celes_key", kr.English, "", "", hd.Secp256k1) if err != nil { return nil, err @@ -66,7 +66,7 @@ func Keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.Keyri // construct signer using the default key found / generated above signer := apptypes.NewKeyringSigner(ring, info.Name, string(net)) signerInfo := signer.GetSignerInfo() - log.Infow("constructed keyring signer", "backend", keyring.BackendTest, "path", ks.Path(), + log.Infow("constructed keyring signer", "backend", kr.BackendTest, "path", ks.Path(), "key name", signerInfo.Name, "chain-id", string(net)) return signer, nil diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index 11b78fa162..3c50f88f05 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -23,9 +23,9 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents := fx.Options( fx.Supply(*cfg), fx.Error(cfgErr), - fx.Provide(Keyring), + fx.Provide(keyring), fx.Provide(fx.Annotate( - CoreAccessor, + coreAccessor, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, ca *state.CoreAccessor) error { return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, ca.Start, ca.Stop) }), From 3a1353c0d122e64b991983cf889530e2c7747d10 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 30 Jan 2023 15:59:19 +0100 Subject: [PATCH 0328/1008] fix(share/discovery): decouple peer discovery from event processing (#1639) ## Overview Starting to listen to subscriptions async will help us to avoid any blocking in the case when we will not have the needed amount of FNs and will be blocked in `FindPeers`. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/availability/discovery/discovery.go | 56 +++++++++++++++++------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index b27123a6f7..540e1c9efb 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -11,6 +11,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/host/eventbus" ) var log = logging.Logger("share/discovery") @@ -21,6 +22,10 @@ const ( // so ConnManager will not break a connection with them. peerWeight = 1000 topic = "full" + + // eventbusBufSize is the size of the buffered channel to handle + // events in libp2p + eventbusBufSize = 32 ) // waitF calculates time to restart announcing. @@ -116,8 +121,11 @@ func (d *Discovery) ensurePeers(ctx context.Context) { log.Warn("peers limit is set to 0. Skipping discovery...") return } - // subscribe on Event Bus in order to catch disconnected peers and restart the discovery - sub, err := d.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) + // subscribe on EventBus in order to catch disconnected peers and restart + // the discovery. We specify a larger buffer size for the channel where + // EvtPeerConnectednessChanged events are sent (by default it is 16, we + // specify 32) to avoid any blocks on writing to the full channel. + sub, err := d.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}, eventbus.BufSize(eventbusBufSize)) if err != nil { log.Error(err) return @@ -131,9 +139,40 @@ func (d *Discovery) ensurePeers(ctx context.Context) { log.Error(err) } }() + + // starting to listen to subscriptions async will help us to avoid any blocking + // in the case when we will not have the needed amount of FNs and will be blocked in `FindPeers`. + go func() { + select { + case <-ctx.Done(): + log.Debug("Context canceled. Finish listening for connectedness events.") + return + case e, ok := <-sub.Out(): + if !ok { + log.Debug("Subscription for connectedness events is closed.") + return + } + // listen to disconnect event to remove peer from set and reset backoff time + // reset timer in order to restart the discovery, once stored peer is disconnected + connStatus := e.(event.EvtPeerConnectednessChanged) + if connStatus.Connectedness == network.NotConnected { + if d.set.Contains(connStatus.Peer) { + log.Debugw("removing the peer from the peer set", + "peer", connStatus.Peer, "status", connStatus.Connectedness.String()) + d.connector.RestartBackoff(connStatus.Peer) + d.set.Remove(connStatus.Peer) + d.onUpdatedPeers(connStatus.Peer, false) + d.host.ConnManager().UntagPeer(connStatus.Peer, topic) + t.Reset(d.discoveryInterval) + } + } + } + }() + for { select { case <-ctx.Done(): + log.Info("Context canceled. Finishing peer discovery") return case <-t.C: if uint(d.set.Size()) == d.peersLimit { @@ -149,19 +188,6 @@ func (d *Discovery) ensurePeers(ctx context.Context) { for p := range peers { go d.handlePeerFound(ctx, topic, p) } - case e := <-sub.Out(): - // listen to disconnect event to remove peer from set and reset backoff time - // reset timer in order to restart the discovery, once stored peer is disconnected - connStatus := e.(event.EvtPeerConnectednessChanged) - if connStatus.Connectedness == network.NotConnected { - if d.set.Contains(connStatus.Peer) { - d.connector.RestartBackoff(connStatus.Peer) - d.set.Remove(connStatus.Peer) - d.onUpdatedPeers(connStatus.Peer, false) - d.host.ConnManager().UntagPeer(connStatus.Peer, topic) - t.Reset(d.discoveryInterval) - } - } } } } From 6f734419b38c05ba4fc590c26cc0eb1709198b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Tue, 31 Jan 2023 11:04:17 +0100 Subject: [PATCH 0329/1008] fix(libs/header/sync): Make ranges.Add thread-safe (#1649) --- libs/header/sync/ranges.go | 12 ++++++++---- libs/header/sync/ranges_test.go | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 libs/header/sync/ranges_test.go diff --git a/libs/header/sync/ranges.go b/libs/header/sync/ranges.go index 99047c4093..8a05b728c3 100644 --- a/libs/header/sync/ranges.go +++ b/libs/header/sync/ranges.go @@ -19,6 +19,10 @@ func (rs *ranges[H]) Head() H { rs.lk.RLock() defer rs.lk.RUnlock() + return rs.head() +} + +func (rs *ranges[H]) head() H { ln := len(rs.ranges) if ln == 0 { var zero H @@ -32,7 +36,10 @@ func (rs *ranges[H]) Head() H { // Add appends the new Header to existing range or starts a new one. // It starts a new one if the new Header is not adjacent to any of existing ranges. func (rs *ranges[H]) Add(h H) { - head := rs.Head() + rs.lk.Lock() + defer rs.lk.Unlock() + + head := rs.head() // short-circuit if header is from the past if !head.IsZero() && head.Height() >= h.Height() { @@ -47,9 +54,6 @@ func (rs *ranges[H]) Add(h H) { return } - rs.lk.Lock() - defer rs.lk.Unlock() - // if the new header is adjacent to head if !head.IsZero() && h.Height() == head.Height()+1 { // append it to the last known range diff --git a/libs/header/sync/ranges_test.go b/libs/header/sync/ranges_test.go new file mode 100644 index 0000000000..fea81378ac --- /dev/null +++ b/libs/header/sync/ranges_test.go @@ -0,0 +1,34 @@ +package sync + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/celestia-node/libs/header/test" +) + +func TestAddParallel(t *testing.T) { + var pending ranges[*test.DummyHeader] + + n := 500 + suite := test.NewTestSuite(t) + headers := suite.GenDummyHeaders(n) + + wg := &sync.WaitGroup{} + wg.Add(n) + for i := 0; i < n; i++ { + go func(i int) { + pending.Add(headers[i]) + wg.Done() + }(i) + } + wg.Wait() + + last := uint64(0) + for _, r := range pending.ranges { + assert.Greater(t, r.start, last) + last = r.start + } +} From 9982af0834d8b993392df12766cd629dba19425a Mon Sep 17 00:00:00 2001 From: Rootul P Date: Tue, 31 Jan 2023 07:53:35 -0700 Subject: [PATCH 0330/1008] chore: upgrade to celestia-app 0.12.0-rc4 (#1648) Upgrade to celestia-app 0.12.0-rc4 to unblock #1526 Co-authored-by: Hlib Kanunnikov --- go.mod | 2 +- go.sum | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d5e2402212..fed04de866 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.12.0-rc2 + github.com/celestiaorg/celestia-app v0.12.0-rc4 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.12.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index 31628a00ab..3efac940e5 100644 --- a/go.sum +++ b/go.sum @@ -201,8 +201,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.0-rc2 h1:eRusc/XPPTq0NC5Nes186XbidilSuT7Sv506IMdHVmM= -github.com/celestiaorg/celestia-app v0.12.0-rc2/go.mod h1:1VLYgvVX7GQ4OD+NoymwzCEqjNdm9woG06hDx2tKA2E= +github.com/celestiaorg/celestia-app v0.12.0-rc4 h1:sy7zq44EpRHi1Max5Cjw4fCQY7/rPphdoGwRgQu1Poo= +github.com/celestiaorg/celestia-app v0.12.0-rc4/go.mod h1:1VLYgvVX7GQ4OD+NoymwzCEqjNdm9woG06hDx2tKA2E= github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23 h1:nMg928RIanyA2ICN3PHpIE6jgg+008BtD5XmxeObEhM= github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23/go.mod h1:Iu2XuSzF+QG3rzd60/sGhhk3n4wOAm8YyadU80KlcMs= github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6 h1:G7c1qcM2WTVdiLsyHASyP4pOK7D4QsOrbXl0qP3GKpk= @@ -310,9 +310,11 @@ github.com/cosmos/ibc-go/v6 v6.1.0 h1:o7oXws2vKkKfOFzJI+oNylRn44PCNt5wzHd/zKQKbv github.com/cosmos/ibc-go/v6 v6.1.0/go.mod h1:CY3zh2HLfetRiW8LY6kVHMATe90Wj/UOoY8T6cuB0is= github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= github.com/cosmos/ledger-cosmos-go v0.12.1/go.mod h1:dhO6kj+Y+AHIOgAe4L9HL/6NDdyyth4q238I9yFpD2g= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= @@ -416,6 +418,7 @@ github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8S github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/filecoin-project/go-jsonrpc v0.1.9 h1:HRWLxo7HAWzI3xZGeFG4LZJoYpms+Q+8kwmMTLnyS3A= github.com/filecoin-project/go-jsonrpc v0.1.9/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= @@ -438,6 +441,7 @@ github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -525,6 +529,7 @@ github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= @@ -679,6 +684,7 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -1298,6 +1304,7 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -1567,8 +1574,10 @@ github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -1648,6 +1657,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1706,6 +1716,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -1716,8 +1727,10 @@ github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -1751,6 +1764,7 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7V github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -2164,6 +2178,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From bd96ae33fe2a905c99d0b184c99ebe21a2bb8196 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Tue, 31 Jan 2023 11:33:56 -0700 Subject: [PATCH 0331/1008] fix: `namespaced_data` endpoint (#1526) Closes https://github.com/celestiaorg/celestia-node/issues/1448 --- api/gateway/share.go | 15 +++-- api/gateway/share_test.go | 123 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 api/gateway/share_test.go diff --git a/api/gateway/share.go b/api/gateway/share.go index 4028e9927c..4326168d86 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -9,7 +9,7 @@ import ( "github.com/gorilla/mux" - appshares "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/nmt/namespace" @@ -111,14 +111,17 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID return shares.Flatten(), header.Height(), err } -func dataFromShares(shares []share.Share) ([][]byte, error) { - blobs, err := appshares.ParseBlobs(shares) +func dataFromShares(input []share.Share) (data [][]byte, err error) { + sequences, err := shares.ParseShares(input) if err != nil { return nil, err } - data := make([][]byte, len(blobs)) - for i := range blobs { - data[i] = blobs[i].Data + for _, sequence := range sequences { + raw, err := sequence.RawData() + if err != nil { + return nil, err + } + data = append(data, raw) } return data, nil } diff --git a/api/gateway/share_test.go b/api/gateway/share_test.go new file mode 100644 index 0000000000..58bf458515 --- /dev/null +++ b/api/gateway/share_test.go @@ -0,0 +1,123 @@ +package gateway + +import ( + "bytes" + "testing" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + + "github.com/stretchr/testify/assert" +) + +func Test_dataFromShares(t *testing.T) { + type testCase struct { + name string + input [][]byte + want [][]byte + wantErr bool + } + + smallTxInput := padShare([]uint8{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // namespace id + 0x1, // info byte + 0x0, 0x0, 0x0, 0x2, // 1 byte (unit) + 1 byte (unit length) = 2 bytes sequence length + 0x0, 0x0, 0x0, 17, // reserved bytes + 0x1, // unit length of first transaction + 0xa, // data of first transaction + }) + smallTxData := []byte{0x1, 0xa} + + largeTxInput := [][]byte{ + fillShare([]uint8{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // namespace id + 0x1, // info byte + 0x0, 0x0, 0x2, 0x2, // 512 (unit) + 2 (unit length) = 514 sequence length + 0x0, 0x0, 0x0, 17, // reserved bytes + 128, 4, // unit length of transaction is 512 + }, 0xc), // data of transaction + padShare(append([]uint8{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // namespace id + 0x0, // info byte + 0x0, 0x0, 0x0, 0x0, // reserved bytes + }, bytes.Repeat([]byte{0xc}, 19)..., // continuation data of transaction + )), + } + largeTxData := []byte{128, 4} + largeTxData = append(largeTxData, bytes.Repeat([]byte{0xc}, 512)...) + + largePfbTxInput := [][]byte{ + fillShare([]uint8{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, // namespace id + 0x1, // info byte + 0x0, 0x0, 0x2, 0x2, // 512 (unit) + 2 (unit length) = 514 sequence length + 0x0, 0x0, 0x0, 17, // reserved bytes + 128, 4, // unit length of transaction is 512 + }, 0xc), // data of transaction + padShare(append([]uint8{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, // namespace id + 0x0, // info byte + 0x0, 0x0, 0x0, 0x0, // reserved bytes + }, bytes.Repeat([]byte{0xc}, 19)..., // continuation data of transaction + )), + } + largePfbTxData := []byte{128, 4} + largePfbTxData = append(largePfbTxData, bytes.Repeat([]byte{0xc}, 512)...) + + testCases := []testCase{ + { + name: "empty", + input: [][]byte{}, + want: nil, + wantErr: false, + }, + { + name: "returns an error when shares contain two different namespaces", + input: [][]byte{ + {0, 0, 0, 0, 0, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 2}, + }, + want: nil, + wantErr: true, + }, + { + name: "returns raw data of a single tx share", + input: [][]byte{smallTxInput}, + want: [][]byte{smallTxData}, + wantErr: false, + }, + { + name: "returns raw data of a large tx that spans two shares", + input: largeTxInput, + want: [][]byte{largeTxData}, + wantErr: false, + }, + { + name: "returns raw data of a large PFB tx that spans two shares", + input: largePfbTxInput, + want: [][]byte{largePfbTxData}, + wantErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := dataFromShares(tc.input) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.Equal(t, tc.want, got) + }) + } +} + +// padShare returns a share padded with trailing zeros. +func padShare(share []byte) (paddedShare []byte) { + return fillShare(share, 0) +} + +// fillShare returns a share filled with filler so that the share length +// is equal to appconsts.ShareSize. +func fillShare(share []byte, filler byte) (paddedShare []byte) { + return append(share, bytes.Repeat([]byte{filler}, appconsts.ShareSize-len(share))...) +} From 64f83cc31d7e30d698b992f1d2fe9c45141bde1d Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 31 Jan 2023 21:20:24 +0100 Subject: [PATCH 0332/1008] fix(header/p2p): add trivial retrying for trustedPeer requested (#1647) Anyway, we should have a retry mechanism for requests here(but long term, it should be better than this implementation), and also, this is helping with #1623 during the first initialization of a node. --- libs/header/p2p/exchange.go | 31 +++++++++++++++++++++++++------ libs/header/p2p/options.go | 28 ++++++++++++++++++---------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index 5ea9b6379d..b7e06721fb 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -3,6 +3,7 @@ package p2p import ( "bytes" "context" + "errors" "fmt" "math/rand" "sort" @@ -40,7 +41,7 @@ type Exchange[H header.Header] struct { func NewExchange[H header.Header]( host host.Host, - peers peer.IDSlice, + trustedPeers peer.IDSlice, protocolSuffix string, connGater *conngater.BasicConnectionGater, opts ...Option[ClientParameters], @@ -58,7 +59,7 @@ func NewExchange[H header.Header]( return &Exchange[H]{ host: host, protocolID: protocolID(protocolSuffix), - trustedPeers: peers, + trustedPeers: trustedPeers, peerTracker: newPeerTracker( host, connGater, @@ -77,7 +78,12 @@ func (ex *Exchange[H]) Start(context.Context) error { // Try to pre-connect to trusted peers. // We don't really care if we succeed at this point // and just need any peers in the peerTracker asap - go ex.host.Connect(ex.ctx, peer.AddrInfo{ID: p}) //nolint:errcheck + go func(p peer.ID) { + err := ex.host.Connect(ex.ctx, peer.AddrInfo{ID: p}) + if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { + log.Debugw("err connecting to a bootstrap peer", "err", err, "peer", p) + } + }(p) } go ex.peerTracker.gc() go ex.peerTracker.track() @@ -220,13 +226,26 @@ func (ex *Exchange[H]) performRequest( return make([]H, 0), nil } + // TODO: Move this check to constructor(#1671) if len(ex.trustedPeers) == 0 { return nil, fmt.Errorf("no trusted peers") } - //nolint:gosec // G404: Use of weak random number generator - index := rand.Intn(len(ex.trustedPeers)) - return ex.request(ctx, ex.trustedPeers[index], req) + for { + //nolint:gosec // G404: Use of weak random number generator + idx := rand.Intn(len(ex.trustedPeers)) + ctx, cancel := context.WithTimeout(ctx, ex.Params.TrustedPeersRequestTimeout) + + h, err := ex.request(ctx, ex.trustedPeers[idx], req) + cancel() + switch err { + default: + log.Debug(err) + continue + case context.Canceled, context.DeadlineExceeded, nil: + return h, err + } + } } // request sends the HeaderRequest to a remote peer. diff --git a/libs/header/p2p/options.go b/libs/header/p2p/options.go index a644bc547e..8fa20ee56f 100644 --- a/libs/header/p2p/options.go +++ b/libs/header/p2p/options.go @@ -104,13 +104,14 @@ func WithRequestTimeout[T parameters](duration time.Duration) Option[T] { } // ClientParameters is the set of parameters that must be configured for the exchange. +// TODO: #1667 type ClientParameters struct { // the target minimum amount of responses with the same chain head MinResponses int // MaxRequestSize defines the max amount of headers that can be handled at once. - MaxRequestSize uint64 + MaxRequestSize uint64 // TODO: Rename to MaxRangeRequestSize // MaxHeadersPerRequest defines the max amount of headers that can be requested per 1 request. - MaxHeadersPerRequest uint64 + MaxHeadersPerRequest uint64 // TODO: Rename to MaxHeadersPerRangeRequest // MaxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, // otherwise it will be removed on the next GC cycle. MaxAwaitingTime time.Duration @@ -118,7 +119,9 @@ type ClientParameters struct { DefaultScore float32 // RequestTimeout defines a timeout after which the session will try to re-request headers // from another peer. - RequestTimeout time.Duration + RequestTimeout time.Duration // TODO: Rename to RangeRequestTimeout + // TrustedPeersRequestTimeout a timeout for any request to a trusted peer. + TrustedPeersRequestTimeout time.Duration // MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker. MaxPeerTrackerSize int } @@ -126,13 +129,14 @@ type ClientParameters struct { // DefaultClientParameters returns the default params to configure the store. func DefaultClientParameters() ClientParameters { return ClientParameters{ - MinResponses: 2, - MaxRequestSize: 512, - MaxHeadersPerRequest: 64, - MaxAwaitingTime: time.Hour, - DefaultScore: 1, - RequestTimeout: time.Second * 3, - MaxPeerTrackerSize: 100, + MinResponses: 2, + MaxRequestSize: 512, + MaxHeadersPerRequest: 64, + MaxAwaitingTime: time.Hour, + DefaultScore: 1, + RequestTimeout: time.Second * 3, + TrustedPeersRequestTimeout: time.Millisecond * 300, + MaxPeerTrackerSize: 100, } } @@ -167,6 +171,10 @@ func (p *ClientParameters) Validate() error { return fmt.Errorf("invalid request timeout for session: "+ "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) } + if p.TrustedPeersRequestTimeout == 0 { + return fmt.Errorf("invalid TrustedPeersRequestTimeout: "+ + "%s. %s: %v", greaterThenZero, providedSuffix, p.TrustedPeersRequestTimeout) + } if p.MaxPeerTrackerSize <= 0 { return fmt.Errorf("invalid MaxTrackerSize: %s. %s: %d", greaterThenZero, providedSuffix, p.MaxPeerTrackerSize) } From 897ce1fde73a06fa5ef6829e549bd7e671dedd35 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 1 Feb 2023 17:01:17 +0100 Subject: [PATCH 0333/1008] deps: temporary replace go-libp2p with the custom fork containing a hotfx (#1646) --- go.mod | 1 + go.sum | 355 +++++++++++++-------------------------------------------- 2 files changed, 81 insertions(+), 275 deletions(-) diff --git a/go.mod b/go.mod index fed04de866..8055999b8d 100644 --- a/go.mod +++ b/go.mod @@ -325,5 +325,6 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + github.com/libp2p/go-libp2p => github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 3efac940e5..a72136552b 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,6 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= -github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= @@ -89,7 +87,6 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= @@ -111,6 +108,8 @@ github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe h1:P7b6c5e/79/AzPiVfU5x+87gJgAYXWuN26EhlSYDkx0= +github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe/go.mod h1:WuxtL2V8yGjam03D93ZBC19tvOUiPpewYv1xdFGWu1k= github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= @@ -171,11 +170,8 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= -github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= @@ -235,7 +231,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= @@ -247,6 +242,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -286,6 +282,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -332,7 +329,6 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= @@ -347,10 +343,6 @@ github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= -github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= -github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.3/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= @@ -362,7 +354,6 @@ github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/Lu github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= -github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -435,6 +426,7 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= @@ -538,7 +530,6 @@ github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -595,6 +586,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -623,6 +615,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -737,7 +730,6 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= @@ -747,6 +739,7 @@ github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= @@ -782,34 +775,21 @@ github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUP github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= -github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= +github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= -github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= -github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= -github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= -github.com/ipfs/go-datastore v0.4.6/go.mod h1:XSipLSc64rFKSFRFGo1ecQl+WhYce3K7frtpHkyPFUc= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= -github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= -github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= -github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= -github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= +github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= -github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= -github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= -github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= @@ -852,16 +832,10 @@ github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQ github.com/ipfs/go-ipns v0.2.0 h1:BgmNtQhqOw5XEZ8RAfWEpK4DhqaYiuP6h71MhIp7xXU= github.com/ipfs/go-ipns v0.2.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= -github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= -github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= -github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= -github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= -github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= -github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= @@ -891,13 +865,9 @@ github.com/ipld/go-ipld-prime v0.16.0 h1:RS5hhjB/mcpeEPJvfyj0qbOj/QL+/j05heZ0qa9 github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= -github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= -github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= -github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= @@ -940,7 +910,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -956,6 +925,7 @@ github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM52 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI= github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -965,7 +935,6 @@ github.com/klauspost/reedsolomon v1.11.1/go.mod h1:FXLZzlJIdfqEnQLdUKWNRuMZg747h github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= -github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -989,215 +958,61 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= -github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= -github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= -github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= -github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= -github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= -github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= -github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= -github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= -github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= -github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= -github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= -github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= -github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= -github.com/libp2p/go-libp2p v0.24.1 h1:+lS4fqj7RF9egcPq9Yo3iqdRTcDMApzoBbQMhxtwOVw= -github.com/libp2p/go-libp2p v0.24.1/go.mod h1:5LJqbrqFsUzWrq70JHCYqjATlX4ey8Klpct3OEe8hSI= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= -github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= -github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= -github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= -github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= -github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= -github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= -github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= -github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= -github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= -github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= -github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= -github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= -github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= -github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= -github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= -github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= -github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= -github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= -github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= -github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= -github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= -github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= -github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= -github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= -github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= -github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= -github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.20.0 h1:1bcMa74JFwExCHZMFEmjtHzxX5DovhJ07EtR6UOTEpc= github.com/libp2p/go-libp2p-kad-dht v0.20.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= -github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= -github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= -github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= -github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= -github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= -github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= -github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= -github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= -github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= -github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= -github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= -github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= -github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= -github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= -github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= -github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= -github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-pubsub v0.8.2 h1:QLGUmkgKmwEVxVDYGsqc5t9CykOMY2Y21cXQHjR462I= github.com/libp2p/go-libp2p-pubsub v0.8.2/go.mod h1:e4kT+DYjzPUYGZeWk4I+oxCSYTXizzXii5LDRRhjKSw= -github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= -github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-routing-helpers v0.6.0 h1:Rfyd+wp/cU0PjNjCphGzLYzd7Q51fjOMs5Sjj6zWGT0= github.com/libp2p/go-libp2p-routing-helpers v0.6.0/go.mod h1:wwK/XSLt6njjO7sRbjhf8w7PGBOfdntMQ2mOQPZ5s/Q= -github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= -github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= -github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= -github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= -github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= -github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= -github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= -github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= -github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= -github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= -github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= -github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= -github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= -github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= -github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= -github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= -github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= -github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= -github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= -github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= -github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= -github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= -github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= -github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= -github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= -github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= -github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= -github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= -github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= -github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= -github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= -github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= -github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= -github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= -github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= -github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-msgio v0.2.0 h1:W6shmB+FeynDrUVl2dgFQvzfBZcXiyqY4VmpQLu9FqU= github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= -github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= -github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= -github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= -github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= -github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo= github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= -github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= -github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= -github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= -github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= -github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= -github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= -github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= -github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= -github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= -github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= -github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= -github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= -github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= -github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= -github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= -github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= -github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= -github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= -github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= +github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= -github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= +github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= @@ -1206,7 +1021,6 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1215,21 +1029,16 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= -github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= -github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= -github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= -github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= -github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= +github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g= github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE= github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/marten-seemann/webtransport-go v0.4.2 h1:8ZRr9AsPuDiLQwnX2PxGs2t35GPvUaqPJnvk+c2SFSs= +github.com/marten-seemann/webtransport-go v0.4.3 h1:vkt5o/Ci+luknRteWdYGYH1KcB7ziup+J+1PzZJIvmg= +github.com/marten-seemann/webtransport-go v0.4.3/go.mod h1:4xcfySgZMLP4aG5GBGj1egP7NlpfwgYJ1WJMvPPiVMU= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1262,11 +1071,8 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -1285,7 +1091,6 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= -github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= @@ -1324,40 +1129,28 @@ github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.0.4/go.mod h1:jNLFzjPZtp3aIARHbJRZIaPuspdH0J6q39uUM5pnABM= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= -github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= +github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= -github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= -github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= -github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= -github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= -github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= -github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= -github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= -github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= -github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= -github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= @@ -1365,6 +1158,7 @@ github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyD github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= +github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= github.com/multiformats/go-multicodec v0.7.0 h1:rTUjGOwjlhGHbEMbPoSUJowG1spZTVsITRANCjKTUAQ= github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= @@ -1375,16 +1169,13 @@ github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= +github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 h1:qLF997Rz0X1WvdcZ2r5CUkLZ2rvdiXwG1JRSrJZEAuE= github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6/go.mod h1:kaHxr8TfO1cxIR/tYxgZ7e59HraJq8arEQQR8E/YNvI= -github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= -github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= -github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o= github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= @@ -1418,23 +1209,30 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1502,8 +1300,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= -github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= @@ -1525,9 +1321,7 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= @@ -1537,7 +1331,6 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= @@ -1625,7 +1418,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= @@ -1656,7 +1448,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= -github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1753,14 +1544,9 @@ github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= -github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= -github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= -github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= -github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -1771,6 +1557,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.0 h1:dlMC7aO8Wss1CxBq2I96kZ69Nh1ligzbs8UWOtq/AsA= @@ -1788,12 +1576,10 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= @@ -1829,22 +1615,22 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= -go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= @@ -1852,10 +1638,9 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -1874,7 +1659,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1894,8 +1678,11 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1915,6 +1702,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1945,6 +1733,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1974,7 +1765,6 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1992,7 +1782,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -2003,7 +1792,6 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= @@ -2012,10 +1800,16 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2046,6 +1840,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2071,7 +1867,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2080,13 +1875,11 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2118,7 +1911,6 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2126,9 +1918,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2136,7 +1926,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2144,21 +1933,36 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2170,6 +1974,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2183,7 +1989,6 @@ golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2202,13 +2007,11 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2246,6 +2049,9 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2362,7 +2168,6 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -2390,6 +2195,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -2411,8 +2218,6 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= -gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= From a60c24fea4e0f855209b3fddad881cdb6d87274e Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 2 Feb 2023 12:27:05 +0100 Subject: [PATCH 0334/1008] refactor(ipld): removing nmt hasher (#1651) --- go.mod | 2 +- go.sum | 4 +- share/ipld/namespace_hasher.go | 95 ----------------------------- share/ipld/namespace_hasher_test.go | 89 --------------------------- share/ipld/nmt.go | 12 ++++ 5 files changed, 15 insertions(+), 187 deletions(-) delete mode 100644 share/ipld/namespace_hasher.go delete mode 100644 share/ipld/namespace_hasher_test.go diff --git a/go.mod b/go.mod index 8055999b8d..2e41495bcd 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/celestiaorg/celestia-app v0.12.0-rc4 github.com/celestiaorg/go-libp2p-messenger v0.1.0 - github.com/celestiaorg/nmt v0.12.0 + github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 github.com/cosmos/cosmos-sdk v0.46.7 github.com/cosmos/cosmos-sdk/api v0.1.0 diff --git a/go.sum b/go.sum index a72136552b..6a452758db 100644 --- a/go.sum +++ b/go.sum @@ -211,8 +211,8 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.12.0 h1:6CmaMPri9FdSiytZP7yCrEq3ewebFiIEjlJhasrS6oQ= -github.com/celestiaorg/nmt v0.12.0/go.mod h1:NN3W8EEoospv8EHCw50DDNWwPLpJkFHoEFiqCEcNCH4= +github.com/celestiaorg/nmt v0.14.0 h1:ET1PXBm8f3KHCAB7+sRVMTmvejkpQp6HAQsGLjIRtcY= +github.com/celestiaorg/nmt v0.14.0/go.mod h1:b+pwd9cGTSSYLZnUIQSJl07pusJdFeEvCVsVfSRH9gA= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.8.0 h1:ZUxTCELZCM9zMGKNF3cT+rUqMddXMeiuyleSJPZ3Wn4= diff --git a/share/ipld/namespace_hasher.go b/share/ipld/namespace_hasher.go deleted file mode 100644 index bf7eb8ddf8..0000000000 --- a/share/ipld/namespace_hasher.go +++ /dev/null @@ -1,95 +0,0 @@ -package ipld - -import ( - "fmt" - "hash" - - "github.com/minio/sha256-simd" - mhcore "github.com/multiformats/go-multihash/core" - - "github.com/celestiaorg/nmt" -) - -func init() { - // Register custom hasher in the multihash register. - // Required for the Bitswap to hash and verify inbound data correctly - mhcore.Register(sha256Namespace8Flagged, func() hash.Hash { - return defaultHasher() - }) -} - -// namespaceHasher implements hash.Hash over NMT Hasher. -// TODO: Move to NMT repo! -// -// As NMT already defines Hasher that implements hash.Hash by embedding, -// it should define full and *correct* implementation of hash.Hash, -// which is the namespaceHasher. This hash.Hash, is not IPLD specific and -// can be used elsewhere. -type namespaceHasher struct { - hasher *nmt.Hasher - - tp byte // keeps type of NMT node to be hashed - data []byte // written data of the NMT node -} - -// defaultHasher constructs the namespaceHasher with default configuration -func defaultHasher() *namespaceHasher { - nh := &namespaceHasher{hasher: nmt.NewNmtHasher(sha256.New(), NamespaceSize, true)} - nh.Reset() - return nh -} - -// Size returns the number of bytes Sum will return. -func (n *namespaceHasher) Size() int { - return n.hasher.Size() + int(n.hasher.NamespaceLen)*2 -} - -// Write writes the namespaced data to be hashed. -// -// Requires data of fixed size to match leaf or inner NMT nodes. -// Only one write is allowed. -func (n *namespaceHasher) Write(data []byte) (int, error) { - if n.data != nil { - panic("namespaceHasher: only single Write is allowed") - } - - ln := len(data) - switch ln { - default: - log.Warnf("unexpected data size: %d", ln) - return 0, fmt.Errorf("wrong sized data written to the hasher, len: %v", ln) - case innerNodeSize: - n.tp = nmt.NodePrefix - case leafNodeSize: - n.tp = nmt.LeafPrefix - } - - n.data = data - return ln, nil -} - -// Sum computes the hash. -// Does not append the given suffix and violating the interface. -func (n *namespaceHasher) Sum([]byte) []byte { - switch n.tp { - case nmt.LeafPrefix: - return n.hasher.HashLeaf(n.data) - case nmt.NodePrefix: - flagLen := int(n.hasher.NamespaceLen * 2) - sha256Len := n.hasher.Size() - return n.hasher.HashNode(n.data[:flagLen+sha256Len], n.data[flagLen+sha256Len:]) - default: - panic("namespaceHasher: node type wasn't set") - } -} - -// Reset resets the Hash to its initial state. -func (n *namespaceHasher) Reset() { - n.tp, n.data = 255, nil // reset with an invalid node type, as zero value is a valid Leaf - n.hasher.Reset() -} - -// BlockSize returns the hash's underlying block size. -func (n *namespaceHasher) BlockSize() int { - return n.hasher.BlockSize() -} diff --git a/share/ipld/namespace_hasher_test.go b/share/ipld/namespace_hasher_test.go deleted file mode 100644 index 4bc0c9f9c0..0000000000 --- a/share/ipld/namespace_hasher_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package ipld - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-app/pkg/appconsts" -) - -func TestNamespaceHasherWrite(t *testing.T) { - leafSize := appconsts.ShareSize + appconsts.NamespaceSize - innerSize := NmtHashSize * 2 - tt := []struct { - name string - expectedSize int - writtenSize int - }{ - { - "Leaf", - leafSize, - leafSize, - }, - { - "Inner", - innerSize, - innerSize, - }, - } - - for _, ts := range tt { - t.Run("Success"+ts.name, func(t *testing.T) { - h := defaultHasher() - n, err := h.Write(make([]byte, ts.writtenSize)) - assert.NoError(t, err) - assert.Equal(t, ts.expectedSize, n) - assert.Equal(t, ts.expectedSize, len(h.data)) - }) - } - - t.Run("ErrorSecondWrite", func(t *testing.T) { - h := defaultHasher() - n, err := h.Write(make([]byte, leafSize)) - assert.NoError(t, err) - assert.Equal(t, leafSize, n) - - require.Panics(t, func() { - _, _ = h.Write(make([]byte, leafSize)) - }) - }) - - t.Run("ErrorIncorrectSize", func(t *testing.T) { - h := defaultHasher() - n, err := h.Write(make([]byte, 13)) - assert.Error(t, err) - assert.Equal(t, 0, n) - }) -} - -func TestNamespaceHasherSum(t *testing.T) { - leafSize := appconsts.ShareSize + appconsts.NamespaceSize - innerSize := NmtHashSize * 2 - tt := []struct { - name string - expectedSize int - writtenSize int - }{ - { - "Leaf", - NmtHashSize, - leafSize, - }, - { - "Inner", - NmtHashSize, - innerSize, - }, - } - - for _, ts := range tt { - t.Run("Success"+ts.name, func(t *testing.T) { - h := defaultHasher() - _, _ = h.Write(make([]byte, ts.writtenSize)) - sum := h.Sum(nil) - assert.Equal(t, len(sum), ts.expectedSize) - }) - } -} diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 3f6384ac48..9122faee3f 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "errors" "fmt" + "hash" "math/rand" blocks "github.com/ipfs/go-block-format" @@ -13,10 +14,12 @@ import ( ipld "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" + mhcore "github.com/multiformats/go-multihash/core" "go.opentelemetry.io/otel" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/nmt" ) var ( @@ -62,6 +65,15 @@ const ( NMTIgnoreMaxNamespace = true ) +func init() { + // required for Bitswap to hash and verify inbound data correctly + mhcore.Register(sha256Namespace8Flagged, func() hash.Hash { + nh := nmt.NewNmtHasher(sha256.New(), NamespaceSize, true) + nh.Reset() + return nh + }) +} + func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid) (ipld.Node, error) { block, err := bGetter.GetBlock(ctx, root) if err != nil { From 484933e246d714caf45181fc0e9dfa56fabac58d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 2 Feb 2023 12:42:44 +0100 Subject: [PATCH 0335/1008] fix(share/discovery): add loop to read events from the channel multiple times (#1684) --- nodebuilder/tests/p2p_test.go | 1 + share/availability/discovery/discovery.go | 42 ++++++++++++----------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 4ca74b04f3..8557725423 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -226,6 +226,7 @@ func TestRestartNodeDiscovery(t *testing.T) { select { case <-ctx.Done(): require.True(t, nodes[0].Host.Network().Connectedness(node.Host.ID()) == network.Connected) + return case conn := <-connectSub.Out(): status := conn.(event.EvtPeerConnectednessChanged) if status.Peer != node.Host.ID() { diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 540e1c9efb..fc59c7bf41 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -143,27 +143,29 @@ func (d *Discovery) ensurePeers(ctx context.Context) { // starting to listen to subscriptions async will help us to avoid any blocking // in the case when we will not have the needed amount of FNs and will be blocked in `FindPeers`. go func() { - select { - case <-ctx.Done(): - log.Debug("Context canceled. Finish listening for connectedness events.") - return - case e, ok := <-sub.Out(): - if !ok { - log.Debug("Subscription for connectedness events is closed.") + for { + select { + case <-ctx.Done(): + log.Debug("Context canceled. Finish listening for connectedness events.") return - } - // listen to disconnect event to remove peer from set and reset backoff time - // reset timer in order to restart the discovery, once stored peer is disconnected - connStatus := e.(event.EvtPeerConnectednessChanged) - if connStatus.Connectedness == network.NotConnected { - if d.set.Contains(connStatus.Peer) { - log.Debugw("removing the peer from the peer set", - "peer", connStatus.Peer, "status", connStatus.Connectedness.String()) - d.connector.RestartBackoff(connStatus.Peer) - d.set.Remove(connStatus.Peer) - d.onUpdatedPeers(connStatus.Peer, false) - d.host.ConnManager().UntagPeer(connStatus.Peer, topic) - t.Reset(d.discoveryInterval) + case e, ok := <-sub.Out(): + if !ok { + log.Debug("Subscription for connectedness events is closed.") + return + } + // listen to disconnect event to remove peer from set and reset backoff time + // reset timer in order to restart the discovery, once stored peer is disconnected + connStatus := e.(event.EvtPeerConnectednessChanged) + if connStatus.Connectedness == network.NotConnected { + if d.set.Contains(connStatus.Peer) { + log.Debugw("removing the peer from the peer set", + "peer", connStatus.Peer, "status", connStatus.Connectedness.String()) + d.connector.RestartBackoff(connStatus.Peer) + d.set.Remove(connStatus.Peer) + d.onUpdatedPeers(connStatus.Peer, false) + d.host.ConnManager().UntagPeer(connStatus.Peer, topic) + t.Reset(d.discoveryInterval) + } } } } From 80226a57a4fb0b6239652f41e953d01583a86f58 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:39:35 +0100 Subject: [PATCH 0336/1008] chore: bump celestia-app to v0.12.0-rc5 (#1679) Co-authored-by: Wondertan --- go.mod | 6 +-- go.sum | 12 +++--- header/pb/extended_header.pb.go | 30 +++++++------- header/pb/extended_header.proto | 4 +- libs/header/p2p/pb/header_request.pb.go | 53 ++++++++++++------------- 5 files changed, 52 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 2e41495bcd..3438792715 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.12.0-rc4 + github.com/celestiaorg/celestia-app v0.12.0-rc5 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 @@ -322,9 +322,9 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p => github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 6a452758db..0168668082 100644 --- a/go.sum +++ b/go.sum @@ -197,12 +197,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.0-rc4 h1:sy7zq44EpRHi1Max5Cjw4fCQY7/rPphdoGwRgQu1Poo= -github.com/celestiaorg/celestia-app v0.12.0-rc4/go.mod h1:1VLYgvVX7GQ4OD+NoymwzCEqjNdm9woG06hDx2tKA2E= -github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23 h1:nMg928RIanyA2ICN3PHpIE6jgg+008BtD5XmxeObEhM= -github.com/celestiaorg/celestia-core v1.13.0-tm-v0.34.23/go.mod h1:Iu2XuSzF+QG3rzd60/sGhhk3n4wOAm8YyadU80KlcMs= -github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6 h1:G7c1qcM2WTVdiLsyHASyP4pOK7D4QsOrbXl0qP3GKpk= -github.com/celestiaorg/cosmos-sdk v1.5.1-sdk-v0.46.6/go.mod h1:2Oq+pYLgUOdcv++AXR77Nv/Yiw4dckswSQN1iyScUqk= +github.com/celestiaorg/celestia-app v0.12.0-rc5 h1:IyhV+wh+iznFV7K78qhNm4DCYIHxagAVolRIGV64Fgs= +github.com/celestiaorg/celestia-app v0.12.0-rc5/go.mod h1:+N3b0GfR+twY/TajKicIeZGjFU9YnHWWzbsztxcARFU= +github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 h1:8zE523TUe5W33/nheJ9umHF2d1q6iHQlqJfMXMTPe3k= +github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23/go.mod h1:fGDSg7aw2OH/Uze1zymop0x0y1kAPEO9OII2A2cb99Q= +github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7 h1:jy24W2wTCnDY843A/5hy79QP/4GNhGPtADN55zs9o6g= +github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7/go.mod h1:qH1Q0s96i4Op0WSh6qC72yWkoWdZNs0CY4HPUrWaK44= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= diff --git a/header/pb/extended_header.pb.go b/header/pb/extended_header.pb.go index b61b9e3514..59fdee0d72 100644 --- a/header/pb/extended_header.pb.go +++ b/header/pb/extended_header.pb.go @@ -5,7 +5,7 @@ package header_pb import ( fmt "fmt" - da "github.com/celestiaorg/celestia-app/proto/da" + da "github.com/celestiaorg/celestia-app/proto/celestia/da" proto "github.com/gogo/protobuf/proto" types "github.com/tendermint/tendermint/proto/tendermint/types" io "io" @@ -99,24 +99,24 @@ func init() { func init() { proto.RegisterFile("header/pb/extended_header.proto", fileDescriptor_370294a9fc09133f) } var fileDescriptor_370294a9fc09133f = []byte{ - // 258 bytes of a gzipped FileDescriptorProto + // 268 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcf, 0x48, 0x4d, 0x4c, 0x49, 0x2d, 0xd2, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x87, 0x08, 0xe9, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0x71, 0xc2, 0x78, 0x49, 0x52, 0x32, 0x60, 0xf9, 0xa2, 0xdc, 0xcc, 0xbc, 0x12, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0x08, 0x09, 0x51, 0x28, 0xa5, - 0x80, 0x21, 0x5b, 0x96, 0x98, 0x93, 0x99, 0x92, 0x58, 0x92, 0x0f, 0x35, 0x4a, 0x4a, 0x31, 0x25, - 0x51, 0x3f, 0x25, 0xb1, 0x24, 0x31, 0x3e, 0xb1, 0x2c, 0x31, 0x33, 0x27, 0x31, 0x29, 0x33, 0x27, - 0xb3, 0xa4, 0x12, 0xc5, 0x36, 0xa5, 0xe7, 0x8c, 0x5c, 0x7c, 0xae, 0x50, 0x77, 0x78, 0x80, 0x25, - 0x84, 0x0c, 0xb8, 0xd8, 0x20, 0x4a, 0x24, 0x18, 0x15, 0x18, 0x35, 0xb8, 0x8d, 0x24, 0xf4, 0x10, - 0x16, 0xe9, 0x41, 0x1c, 0x00, 0x51, 0x19, 0x04, 0x55, 0x07, 0xd2, 0x91, 0x9c, 0x9f, 0x9b, 0x9b, - 0x59, 0x22, 0xc1, 0x84, 0x4b, 0x87, 0x33, 0x58, 0x3e, 0x08, 0xaa, 0x4e, 0xc8, 0x99, 0x8b, 0x17, - 0xee, 0xd8, 0xf8, 0xe2, 0xd4, 0x12, 0x09, 0x66, 0xb0, 0x46, 0x39, 0x4c, 0x8d, 0x61, 0x30, 0x65, - 0xc1, 0xa9, 0x25, 0x41, 0x3c, 0x65, 0x48, 0x3c, 0x21, 0x1d, 0x2e, 0xe6, 0x94, 0xc4, 0x0c, 0x09, - 0x16, 0xb0, 0x56, 0x29, 0xbd, 0x94, 0x44, 0x3d, 0x97, 0xc4, 0x92, 0x44, 0x47, 0x24, 0xbf, 0x42, - 0xdd, 0x09, 0x52, 0xe6, 0x24, 0x71, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, - 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x49, - 0x6c, 0xe0, 0xa0, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x55, 0x41, 0xd1, 0x9b, 0x01, - 0x00, 0x00, + 0x80, 0x21, 0x5b, 0x96, 0x98, 0x93, 0x99, 0x92, 0x58, 0x92, 0x0f, 0x35, 0x4a, 0x4a, 0x2b, 0x39, + 0x35, 0x27, 0xb5, 0xb8, 0x24, 0x33, 0x51, 0x3f, 0x05, 0x84, 0x4a, 0x12, 0xe3, 0x13, 0xcb, 0x12, + 0x33, 0x73, 0x12, 0x93, 0x32, 0x73, 0x32, 0x4b, 0x2a, 0x51, 0xac, 0x55, 0xfa, 0xc0, 0xc8, 0xc5, + 0xe7, 0x0a, 0x75, 0x90, 0x07, 0x58, 0x42, 0xc8, 0x80, 0x8b, 0x0d, 0xa2, 0x44, 0x82, 0x51, 0x81, + 0x51, 0x83, 0xdb, 0x48, 0x42, 0x0f, 0x61, 0xa3, 0x1e, 0xc4, 0x25, 0x10, 0x95, 0x41, 0x50, 0x75, + 0x20, 0x1d, 0xc9, 0xf9, 0xb9, 0xb9, 0x99, 0x25, 0x12, 0x4c, 0xb8, 0x74, 0x38, 0x83, 0xe5, 0x83, + 0xa0, 0xea, 0x84, 0x9c, 0xb9, 0x78, 0xe1, 0xae, 0x8e, 0x2f, 0x4e, 0x2d, 0x91, 0x60, 0x06, 0x6b, + 0x94, 0xc3, 0xd4, 0x18, 0x06, 0x53, 0x16, 0x9c, 0x5a, 0x12, 0xc4, 0x53, 0x86, 0xc4, 0x13, 0x32, + 0xe5, 0x62, 0x4e, 0x49, 0xcc, 0x90, 0x60, 0x01, 0x6b, 0x55, 0xd6, 0x83, 0xf9, 0x5a, 0x2f, 0x25, + 0x51, 0xcf, 0x25, 0xb1, 0x24, 0xd1, 0x11, 0xc9, 0xd3, 0x50, 0x07, 0x83, 0xd4, 0x3b, 0x49, 0x9c, + 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, + 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x12, 0x1b, 0x38, 0x4c, 0x8c, 0x01, 0x01, + 0x00, 0x00, 0xff, 0xff, 0x33, 0x74, 0x5f, 0xa8, 0xad, 0x01, 0x00, 0x00, } func (m *ExtendedHeader) Marshal() (dAtA []byte, err error) { diff --git a/header/pb/extended_header.proto b/header/pb/extended_header.proto index af9825365d..aafb575327 100644 --- a/header/pb/extended_header.proto +++ b/header/pb/extended_header.proto @@ -4,13 +4,13 @@ package header.pb; import "tendermint/types/types.proto"; import "tendermint/types/validator.proto"; -import "da/data_availability_header.proto"; +import "celestia/da/data_availability_header.proto"; message ExtendedHeader { tendermint.types.Header header = 1; tendermint.types.Commit commit = 2; tendermint.types.ValidatorSet validator_set = 3; - da.DataAvailabilityHeader dah = 4; + celestia.da.DataAvailabilityHeader dah = 4; } // Generated with: diff --git a/libs/header/p2p/pb/header_request.pb.go b/libs/header/p2p/pb/header_request.pb.go index ee90374d6e..e0b7e4f016 100644 --- a/libs/header/p2p/pb/header_request.pb.go +++ b/libs/header/p2p/pb/header_request.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: pkg/header/p2p/pb/header_request.proto +// source: libs/header/p2p/pb/header_request.proto package p2p_pb @@ -47,12 +47,11 @@ func (x StatusCode) String() string { } func (StatusCode) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_bd9664a357f22804, []int{0} + return fileDescriptor_43554822dc0b0806, []int{0} } type HeaderRequest struct { // Types that are valid to be assigned to Data: - // // *HeaderRequest_Origin // *HeaderRequest_Hash Data isHeaderRequest_Data `protobuf_oneof:"data"` @@ -63,7 +62,7 @@ func (m *HeaderRequest) Reset() { *m = HeaderRequest{} } func (m *HeaderRequest) String() string { return proto.CompactTextString(m) } func (*HeaderRequest) ProtoMessage() {} func (*HeaderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_bd9664a357f22804, []int{0} + return fileDescriptor_43554822dc0b0806, []int{0} } func (m *HeaderRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -153,7 +152,7 @@ func (m *HeaderResponse) Reset() { *m = HeaderResponse{} } func (m *HeaderResponse) String() string { return proto.CompactTextString(m) } func (*HeaderResponse) ProtoMessage() {} func (*HeaderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_bd9664a357f22804, []int{1} + return fileDescriptor_43554822dc0b0806, []int{1} } func (m *HeaderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -203,28 +202,28 @@ func init() { } func init() { - proto.RegisterFile("pkg/header/p2p/pb/header_request.proto", fileDescriptor_bd9664a357f22804) -} - -var fileDescriptor_bd9664a357f22804 = []byte{ - // 270 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xcb, 0x4a, 0xc3, 0x40, - 0x14, 0x86, 0x67, 0x6a, 0x18, 0xf1, 0xd8, 0x96, 0x32, 0x88, 0x64, 0x35, 0x94, 0x2e, 0xa4, 0xb8, - 0x48, 0x24, 0x3e, 0x81, 0xb5, 0x48, 0x8a, 0x92, 0xc0, 0x78, 0xc1, 0x5d, 0x98, 0x90, 0xa1, 0x29, - 0x62, 0x66, 0xcc, 0x4c, 0x16, 0xbe, 0x85, 0x8f, 0xe5, 0xb2, 0x4b, 0x97, 0x92, 0xbc, 0x88, 0x38, - 0x8d, 0x97, 0xdd, 0xf9, 0xf8, 0x7e, 0xce, 0x7f, 0x38, 0x70, 0xa2, 0x9f, 0xd6, 0x61, 0x29, 0x45, - 0x21, 0xeb, 0x50, 0x47, 0x3a, 0xd4, 0x79, 0x4f, 0x59, 0x2d, 0x5f, 0x1a, 0x69, 0x6c, 0xa0, 0x6b, - 0x65, 0x15, 0x25, 0x3a, 0xd2, 0x81, 0xce, 0x67, 0x19, 0x8c, 0x62, 0xe7, 0xf9, 0x4e, 0x53, 0x1f, - 0x88, 0xaa, 0x37, 0xeb, 0x4d, 0xe5, 0xe3, 0x29, 0x9e, 0x7b, 0x31, 0xe2, 0x3d, 0xd3, 0x23, 0xf0, - 0x4a, 0x61, 0x4a, 0x7f, 0x30, 0xc5, 0xf3, 0x61, 0x8c, 0xb8, 0x23, 0x7a, 0x0c, 0x44, 0x3c, 0xab, - 0xa6, 0xb2, 0xfe, 0xde, 0x77, 0x9e, 0xf7, 0xb4, 0x20, 0xe0, 0x15, 0xc2, 0x8a, 0xd9, 0x23, 0x8c, - 0x7f, 0x0a, 0x8c, 0x56, 0x95, 0x91, 0x94, 0x82, 0x97, 0xab, 0xe2, 0xd5, 0xed, 0x1f, 0x72, 0x37, - 0xd3, 0x08, 0xc0, 0x58, 0x61, 0x1b, 0x73, 0xa9, 0x0a, 0xe9, 0x1a, 0xc6, 0x11, 0x0d, 0x76, 0x37, - 0x06, 0xb7, 0xbf, 0x86, 0xff, 0x4b, 0x9d, 0x9e, 0x01, 0xfc, 0x19, 0x7a, 0x08, 0xfb, 0xab, 0xe4, - 0xe1, 0xe2, 0x66, 0xb5, 0x9c, 0x20, 0x4a, 0x60, 0x90, 0x5e, 0x4f, 0x30, 0x1d, 0xc1, 0x41, 0x92, - 0xde, 0x65, 0x57, 0xe9, 0x7d, 0xb2, 0x9c, 0x0c, 0x16, 0xfe, 0x7b, 0xcb, 0xf0, 0xb6, 0x65, 0xf8, - 0xb3, 0x65, 0xf8, 0xad, 0x63, 0x68, 0xdb, 0x31, 0xf4, 0xd1, 0x31, 0x94, 0x13, 0xf7, 0x95, 0xf3, - 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x41, 0x37, 0xb7, 0xe6, 0x3f, 0x01, 0x00, 0x00, + proto.RegisterFile("libs/header/p2p/pb/header_request.proto", fileDescriptor_43554822dc0b0806) +} + +var fileDescriptor_43554822dc0b0806 = []byte{ + // 271 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xcd, 0x4a, 0xf3, 0x40, + 0x14, 0x86, 0x67, 0xfa, 0x85, 0xf9, 0xf0, 0xd8, 0x96, 0x32, 0x88, 0x64, 0x35, 0x94, 0x6e, 0x2c, + 0x2e, 0x12, 0x89, 0x57, 0x60, 0x2d, 0x92, 0xa2, 0x24, 0x30, 0xfe, 0xe0, 0x2e, 0x4c, 0xc8, 0x60, + 0x02, 0x9a, 0x19, 0x33, 0x93, 0x85, 0x77, 0xe1, 0x65, 0xb9, 0xec, 0xd2, 0xa5, 0x24, 0x37, 0x22, + 0x4e, 0xe3, 0xcf, 0xee, 0x3c, 0x3c, 0x2f, 0xe7, 0x3d, 0x1c, 0x38, 0x7a, 0xac, 0x72, 0x13, 0x96, + 0x52, 0x14, 0xb2, 0x09, 0x75, 0xa4, 0x43, 0x9d, 0x0f, 0x94, 0x35, 0xf2, 0xb9, 0x95, 0xc6, 0x06, + 0xba, 0x51, 0x56, 0x51, 0xa2, 0x23, 0x1d, 0xe8, 0x7c, 0x91, 0xc1, 0x24, 0x76, 0x9e, 0xef, 0x34, + 0xf5, 0x81, 0xa8, 0xa6, 0x7a, 0xa8, 0x6a, 0x1f, 0xcf, 0xf1, 0xd2, 0x8b, 0x11, 0x1f, 0x98, 0x1e, + 0x80, 0x57, 0x0a, 0x53, 0xfa, 0xa3, 0x39, 0x5e, 0x8e, 0x63, 0xc4, 0x1d, 0xd1, 0x43, 0x20, 0xe2, + 0x49, 0xb5, 0xb5, 0xf5, 0xff, 0x7d, 0xe5, 0xf9, 0x40, 0x2b, 0x02, 0x5e, 0x21, 0xac, 0x58, 0xdc, + 0xc3, 0xf4, 0xbb, 0xc0, 0x68, 0x55, 0x1b, 0x49, 0x29, 0x78, 0xb9, 0x2a, 0x5e, 0xdc, 0xfe, 0x31, + 0x77, 0x33, 0x8d, 0x00, 0x8c, 0x15, 0xb6, 0x35, 0xe7, 0xaa, 0x90, 0xae, 0x61, 0x1a, 0xd1, 0x60, + 0x77, 0x63, 0x70, 0xfd, 0x63, 0xf8, 0x9f, 0xd4, 0xf1, 0x09, 0xc0, 0xaf, 0xa1, 0xfb, 0xf0, 0x7f, + 0x93, 0xdc, 0x9d, 0x5d, 0x6d, 0xd6, 0x33, 0x44, 0x09, 0x8c, 0xd2, 0xcb, 0x19, 0xa6, 0x13, 0xd8, + 0x4b, 0xd2, 0x9b, 0xec, 0x22, 0xbd, 0x4d, 0xd6, 0xb3, 0xd1, 0xca, 0x7f, 0xeb, 0x18, 0xde, 0x76, + 0x0c, 0x7f, 0x74, 0x0c, 0xbf, 0xf6, 0x0c, 0x6d, 0x7b, 0x86, 0xde, 0x7b, 0x86, 0x72, 0xe2, 0xbe, + 0x72, 0xfa, 0x19, 0x00, 0x00, 0xff, 0xff, 0x04, 0xdd, 0x70, 0x54, 0x40, 0x01, 0x00, 0x00, } func (m *HeaderRequest) Marshal() (dAtA []byte, err error) { From ffc8fdf594ca549a9ee63ff3588edd6569884af4 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 2 Feb 2023 14:42:30 +0100 Subject: [PATCH 0337/1008] fix(Makefile): make install should not invoke build (#1673) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e18e9f6325..974f156c8a 100644 --- a/Makefile +++ b/Makefile @@ -41,8 +41,8 @@ deps: @go mod download .PHONY: deps -## install: Build and install the celestia-node binary into the $PREFIX (/usr/local/ by default) directory. -install: build +## install: Install all build binaries into the $PREFIX (/usr/local/ by default) directory. +install: @echo "--> Installing Celestia" @install -v ./build/* -t ${PREFIX}/bin/ .PHONY: install From 1402f81d72660a141349677063dd302c40831ee5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 13:58:39 +0000 Subject: [PATCH 0338/1008] chore(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#1655) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 1c7512a257..c92f820447 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -23,7 +23,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: golangci-lint - uses: golangci/golangci-lint-action@v3.3.1 + uses: golangci/golangci-lint-action@v3.4.0 with: version: v1.49.0 From ba337b1ae679f0018f3baff75f6d2c204fd9dea6 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 2 Feb 2023 15:25:16 +0100 Subject: [PATCH 0339/1008] bump(metrics): upgrade otel to the latest version (#1537) Co-authored-by: derrandz Co-authored-by: Hlib Kanunnikov --- cmd/flags_misc.go | 2 +- go.mod | 22 ++++++++++----------- go.sum | 44 ++++++++++++++++++++--------------------- nodebuilder/settings.go | 29 ++++++++------------------- 4 files changed, 42 insertions(+), 55 deletions(-) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index cf0a40e835..2fdf66988b 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -15,7 +15,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.10.0" + semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" diff --git a/go.mod b/go.mod index 3438792715..da65d85763 100644 --- a/go.mod +++ b/go.mod @@ -55,13 +55,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 - go.opentelemetry.io/otel v1.11.1 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 - go.opentelemetry.io/otel/metric v0.33.0 - go.opentelemetry.io/otel/sdk v1.10.0 - go.opentelemetry.io/otel/sdk/metric v0.31.0 - go.opentelemetry.io/otel/trace v1.11.1 + go.opentelemetry.io/otel v1.11.2 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 + go.opentelemetry.io/otel/metric v0.34.0 + go.opentelemetry.io/otel/sdk v1.11.2 + go.opentelemetry.io/otel/sdk/metric v0.34.0 + go.opentelemetry.io/otel/trace v1.11.2 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.5.0 @@ -95,7 +95,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect github.com/celestiaorg/quantum-gravity-bridge v1.3.0 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.0 // indirect @@ -293,9 +293,9 @@ require ( github.com/zondax/ledger-go v0.14.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect diff --git a/go.sum b/go.sum index 0168668082..c96a96f541 100644 --- a/go.sum +++ b/go.sum @@ -220,8 +220,8 @@ github.com/celestiaorg/rsmt2d v0.8.0/go.mod h1:hhlsTi6G3+X5jOP/8Lb/d7i5y2XNFmnyM github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -1583,30 +1583,30 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= -go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0/go.mod h1:nkenGD8vcvs0uN6WhR90ZVHQlgDsRmXicnNadMnk+XQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 h1:MuEG0gG27QZQrqhNl0f7vQ5Nl03OQfFeDAqWkGt+1zM= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0/go.mod h1:52qtPFDDaa0FaSyyzPnxWMehx2SZv0xuobTlNEZA2JA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 h1:S8DedULB3gp93Rh+9Z+7NTEv+6Id/KYS7LDyipZ9iCE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0/go.mod h1:5WV40MLWwvWlGP7Xm8g3pMcg0pKOUY609qxJn8y7LmM= +go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= +go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 h1:kpskzLZ60cJ48SJ4uxWa6waBL+4kSV6nVK8rP+QM8Wg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0/go.mod h1:4+x3i62TEegDHuzNva0bMcAN8oUi5w4liGb1d/VgPYo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 h1:t4Ajxj8JGjxkqoBtbkCOY2cDUl9RwiNE9LPQavooi9U= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0/go.mod h1:WO7omosl4P7JoanH9NgInxDxEn2F2M5YinIh8EyeT8w= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 h1:fqR1kli93643au1RKo0Uma3d2aPQKT+WBKfTSBaKbOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2/go.mod h1:5Qn6qvgkMsLDX+sYK64rHb1FPhpn0UtxF+ouX1uhyJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 h1:Us8tbCmuN16zAnK5TC69AtODLycKbwnskQzaB6DfFhc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2/go.mod h1:GZWSQQky8AgdJj50r1KJm8oiQiIPaAX7uZCFQX9GzC8= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= -go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= +go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= +go.opentelemetry.io/otel/metric v0.34.0/go.mod h1:ZFuI4yQGNCupurTXCwkeD/zHBt+C2bR7bw5JqUm/AP8= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= -go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= -go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= -go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= +go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= +go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= +go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= +go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= -go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= +go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= +go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index e5af35538d..18001c4bb1 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -8,11 +8,9 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/metric/global" - controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" - processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" - selector "go.opentelemetry.io/otel/sdk/metric/selector/simple" + "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.10.0" + semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/fraud" @@ -77,32 +75,21 @@ func initializeMetrics( return err } - pusher := controller.New( - processor.NewFactory( - selector.NewWithHistogramDistribution(), - exp, - ), - controller.WithExporter(exp), - controller.WithCollectPeriod(2*time.Second), - controller.WithResource(resource.NewWithAttributes( + provider := metric.NewMeterProvider( + metric.WithReader(metric.NewPeriodicReader(exp, metric.WithTimeout(2*time.Second))), + metric.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", nodeType.String())), // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey semconv.ServiceInstanceIDKey.String(peerID.String()), - )), - ) + ))) lc.Append(fx.Hook{ - // here we take the context from fx.Invoke because pusher uses it for its entire lifetime, - // instead of only for the Start operation - OnStart: func(context.Context) error { - return pusher.Start(ctx) - }, OnStop: func(ctx context.Context) error { - return pusher.Stop(ctx) + return provider.Shutdown(ctx) }, }) + global.SetMeterProvider(provider) - global.SetMeterProvider(pusher) return nil } From e98e8f3884bdf52a219dcb951a0e41b45c59f34b Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 2 Feb 2023 16:27:19 +0100 Subject: [PATCH 0340/1008] fix: flake in TestNamespaceHasher_CorruptedData (#1686) --- share/availability/test/corrupt_data.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index 1d7716cbb4..c2cc19e529 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -87,9 +87,9 @@ func (fb FraudulentBlockstore) Put(ctx context.Context, block blocks.Block) erro return err } - // create data that doesn't match the CID with arbitrary lengths between 0 and + // create data that doesn't match the CID with arbitrary lengths between 1 and // len(block.RawData())*2 - corrupted := make([]byte, mrand.Int()%(len(block.RawData())*2)) + corrupted := make([]byte, 1+mrand.Int()%(len(block.RawData())*2-1)) mrand.Read(corrupted) return fb.Datastore.Put(ctx, ds.NewKey("corrupt"+block.Cid().String()), corrupted) } From da832b92f324648300d34b2e9ef9f311b2353333 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 2 Feb 2023 17:11:00 +0100 Subject: [PATCH 0341/1008] bugfix(libs/header): fix data race in syncer (#1682) --- libs/header/sync/sync.go | 32 ++++++++++++++++++++------------ libs/header/sync/sync_test.go | 7 ++++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index ecd9475c3e..63be056591 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -41,6 +41,8 @@ type Syncer[H header.Header] struct { state State // signals to start syncing triggerSync chan struct{} + // syncedHead is the latest synced header. + syncedHead H // pending keeps ranges of valid new network headers awaiting to be appended to store pending ranges[H] // netReqLk ensures only one network head is requested at any moment @@ -173,15 +175,17 @@ func (s *Syncer[H]) sync(ctx context.Context) { return } - head, err := s.store.Head(ctx) - if err != nil { - log.Errorw("getting head during sync", "err", err) - return + if s.syncedHead.IsZero() { + head, err := s.store.Head(ctx) + if err != nil { + log.Errorw("getting head during sync", "err", err) + return + } + s.syncedHead = head } - - if head.Height() >= newHead.Height() { + if s.syncedHead.Height() >= newHead.Height() { log.Warnw("sync attempt to an already synced header", - "synced_height", head.Height(), + "synced_height", s.syncedHead.Height(), "attempted_height", newHead.Height(), ) log.Warn("PLEASE REPORT THIS AS A BUG") @@ -189,9 +193,9 @@ func (s *Syncer[H]) sync(ctx context.Context) { } log.Infow("syncing headers", - "from", head.Height(), + "from", s.syncedHead.Height(), "to", newHead.Height()) - err = s.doSync(ctx, head, newHead) + err := s.doSync(ctx, s.syncedHead, newHead) if err != nil { if errors.Is(err, context.Canceled) { // don't log this error as it is normal case of Syncer being stopped @@ -199,14 +203,14 @@ func (s *Syncer[H]) sync(ctx context.Context) { } log.Errorw("syncing headers", - "from", head.Height(), + "from", s.syncedHead.Height(), "to", newHead.Height(), "err", err) return } log.Infow("finished syncing", - "from", head.Height(), + "from", s.syncedHead.Height(), "to", newHead.Height(), "elapsed time", s.state.End.Sub(s.state.Start)) } @@ -246,7 +250,11 @@ func (s *Syncer[H]) processHeaders(ctx context.Context, from, to uint64) (int, e return 0, err } - return s.store.Append(ctx, headers...) + amount, err := s.store.Append(ctx, headers...) + if err == nil && amount > 0 { + s.syncedHead = headers[amount-1] + } + return amount, err } // findHeaders gets headers from either remote peers or from local cache of headers received by diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index 3f937ee00c..724b432659 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -85,20 +85,21 @@ func TestSyncCatchUp(t *testing.T) { _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) + incomingHead := suite.GenDummyHeaders(1)[0] // 3. syncer rcvs header from the future and starts catching-up - res := syncer.incomingNetHead(ctx, suite.GenDummyHeaders(1)[0]) + res := syncer.incomingNetHead(ctx, incomingHead) assert.Equal(t, pubsub.ValidationAccept, res) time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing err = syncer.WaitSync(ctx) require.NoError(t, err) - exp, err := remoteStore.Head(ctx) require.NoError(t, err) // 4. assert syncer caught-up have, err := localStore.Head(ctx) require.NoError(t, err) + assert.Equal(t, syncer.syncedHead.Height(), incomingHead.Height()) assert.Equal(t, exp.Height()+1, have.Height()) // plus one as we didn't add last header to remoteStore assert.Empty(t, syncer.pending.Head()) @@ -156,7 +157,7 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { require.NoError(t, err) _, err = localStore.GetByHeight(ctx, 43) require.NoError(t, err) - + require.Equal(t, syncer.syncedHead.Height(), int64(43)) exp, err := remoteStore.Head(ctx) require.NoError(t, err) From 2a51f868c4b56d3b75e3b7b8cefa154af11c7da4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 3 Feb 2023 11:47:04 +0100 Subject: [PATCH 0342/1008] feat: using tmjson encoding on `core.Header` (#1641) Closes https://github.com/celestiaorg/celestia-node/issues/1476 --- header/header.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/header/header.go b/header/header.go index ba4600eb90..e233852aa5 100644 --- a/header/header.go +++ b/header/header.go @@ -10,13 +10,12 @@ import ( "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" - amino "github.com/tendermint/tendermint/libs/json" + tmjson "github.com/tendermint/tendermint/libs/json" core "github.com/tendermint/tendermint/types" appshares "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-app/pkg/da" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" ) @@ -188,15 +187,21 @@ func (eh *ExtendedHeader) UnmarshalBinary(data []byte) error { // to be able to unmarshal the crypto.PubKey type back from JSON. func (eh *ExtendedHeader) MarshalJSON() ([]byte, error) { type Alias ExtendedHeader - validatorSet, err := amino.Marshal(eh.ValidatorSet) + validatorSet, err := tmjson.Marshal(eh.ValidatorSet) + if err != nil { + return nil, err + } + rawHeader, err := tmjson.Marshal(eh.RawHeader) if err != nil { return nil, err } return json.Marshal(&struct { + RawHeader json.RawMessage `json:"header"` ValidatorSet json.RawMessage `json:"validator_set"` *Alias }{ ValidatorSet: validatorSet, + RawHeader: rawHeader, Alias: (*Alias)(eh), }) } @@ -206,6 +211,7 @@ func (eh *ExtendedHeader) MarshalJSON() ([]byte, error) { func (eh *ExtendedHeader) UnmarshalJSON(data []byte) error { type Alias ExtendedHeader aux := &struct { + RawHeader json.RawMessage `json:"header"` ValidatorSet json.RawMessage `json:"validator_set"` *Alias }{ @@ -216,10 +222,15 @@ func (eh *ExtendedHeader) UnmarshalJSON(data []byte) error { } valSet := new(core.ValidatorSet) - if err := amino.Unmarshal(aux.ValidatorSet, valSet); err != nil { + if err := tmjson.Unmarshal(aux.ValidatorSet, valSet); err != nil { + return err + } + rawHeader := new(RawHeader) + if err := tmjson.Unmarshal(aux.RawHeader, rawHeader); err != nil { return err } eh.ValidatorSet = valSet + eh.RawHeader = *rawHeader return nil } From d873e026194c353e48fe26fd9406237725e93c6c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 3 Feb 2023 14:52:09 +0200 Subject: [PATCH 0343/1008] dep(pubsub): upgrade pubsub version (#1688) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index da65d85763..aec6a7bdf5 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/ipld/go-car v0.5.0 github.com/libp2p/go-libp2p v0.24.1 github.com/libp2p/go-libp2p-kad-dht v0.20.0 - github.com/libp2p/go-libp2p-pubsub v0.8.2 + github.com/libp2p/go-libp2p-pubsub v0.8.3 github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.6.0 github.com/minio/sha256-simd v1.0.0 @@ -126,6 +126,7 @@ require ( github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect github.com/ethereum/go-ethereum v1.10.26 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect @@ -288,7 +289,6 @@ require ( github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect - github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect diff --git a/go.sum b/go.sum index c96a96f541..e559b665ac 100644 --- a/go.sum +++ b/go.sum @@ -383,6 +383,8 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -980,8 +982,8 @@ github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9b github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= -github.com/libp2p/go-libp2p-pubsub v0.8.2 h1:QLGUmkgKmwEVxVDYGsqc5t9CykOMY2Y21cXQHjR462I= -github.com/libp2p/go-libp2p-pubsub v0.8.2/go.mod h1:e4kT+DYjzPUYGZeWk4I+oxCSYTXizzXii5LDRRhjKSw= +github.com/libp2p/go-libp2p-pubsub v0.8.3 h1:T4+pcfcFm1K2v5oFyk68peSjVroaoM8zFygf6Y5WOww= +github.com/libp2p/go-libp2p-pubsub v0.8.3/go.mod h1:eje970FXxjhtFbVEoiae+VUw24ZoSlk67BsiZPLRzlw= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= @@ -1544,8 +1546,6 @@ github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= -github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow= -github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= From ddd0b9df7d7a4649fa102145d373a3e5822690bc Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 7 Feb 2023 11:44:22 +0200 Subject: [PATCH 0344/1008] feat!: add versioning for pubsub topics in fraud, header packages (#1620) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1455, https://github.com/celestiaorg/celestia-node/issues/1454, https://github.com/celestiaorg/celestia-node/issues/1453 PLEASE NOTE: this PR is a protocol breaking since it introduces changes in protocol namings. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- core/listener_test.go | 20 +++++++-------- das/daser_test.go | 3 ++- fraud/helpers.go | 18 +++++++++++--- fraud/requester.go | 4 ++- fraud/service.go | 30 +++++++++++----------- fraud/service_test.go | 5 ++-- fraud/sync.go | 5 ++-- fraud/testing.go | 3 ++- libs/header/p2p/exchange.go | 11 +++------ libs/header/p2p/exchange_test.go | 37 ++++++++++++++++++++-------- libs/header/p2p/helpers.go | 4 +++ libs/header/p2p/options.go | 18 ++++++++++++++ libs/header/p2p/server.go | 5 ++-- libs/header/p2p/server_test.go | 7 +++++- libs/header/p2p/subscriber.go | 20 ++++++++++----- libs/header/p2p/subscription_test.go | 5 ++-- nodebuilder/header/constructors.go | 5 ++-- nodebuilder/header/module.go | 10 +++++--- nodebuilder/header/module_test.go | 1 + share/p2p/shrexsub/pubsub.go | 16 ++++++------ 20 files changed, 148 insertions(+), 79 deletions(-) diff --git a/core/listener_test.go b/core/listener_test.go index 050d2d0e9e..a1940b3975 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -14,6 +14,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" + network "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -24,10 +25,13 @@ func TestListener(t *testing.T) { // create mocknet with two pubsub endpoints ps0, ps1 := createMocknetWithTwoPubsubEndpoints(ctx, t) - // create second subscription endpoint to listen for Listener's pubsub messages - topic, err := ps1.Join(p2p.PubSubTopic) + subscriber := p2p.NewSubscriber[*header.ExtendedHeader](ps1, header.MsgID, string(network.Private)) + err := subscriber.AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult { + return pubsub.ValidationAccept + }) require.NoError(t, err) - sub, err := topic.Subscribe() + require.NoError(t, subscriber.Start(ctx)) + subs, err := subscriber.Subscribe() require.NoError(t, err) // create one block to store as Head in local store and then unsubscribe from block events @@ -44,17 +48,13 @@ func TestListener(t *testing.T) { // ensure headers and dataHash are getting broadcasted to the relevant topics for i := 1; i < 6; i++ { - msg, err := sub.Next(ctx) - require.NoError(t, err) - - var resp header.ExtendedHeader - err = resp.UnmarshalBinary(msg.Data) + h, err := subs.NextHeader(ctx) require.NoError(t, err) dataHash, err := edsSubs.Next(ctx) require.NoError(t, err) - require.Equal(t, resp.DataHash.Bytes(), []byte(dataHash)) + require.Equal(t, h.DataHash.Bytes(), []byte(dataHash)) } err = cl.Stop(ctx) @@ -104,7 +104,7 @@ func createListener( ps *pubsub.PubSub, edsSub *shrexsub.PubSub, ) *Listener { - p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID) + p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, string(network.Private)) err := p2pSub.Start(ctx) require.NoError(t, err) t.Cleanup(func() { diff --git a/das/daser_test.go b/das/daser_test.go index 05b7d48001..ab89e76b02 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -21,6 +21,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/headertest" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" @@ -156,7 +157,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) // create fraud service and break one header - f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, "private") + f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, string(p2p.Private)) require.NoError(t, f.Start(ctx)) mockGet.headers[1] = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() diff --git a/fraud/helpers.go b/fraud/helpers.go index 260232bd95..c9c1c66735 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -2,19 +2,31 @@ package fraud import ( "context" + "fmt" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" ) -func join(p *pubsub.PubSub, proofType ProofType, +func pubsubTopicID(fraudType, protocolSuffix string) string { + return fmt.Sprintf("/fraud-sub/%s/v0.0.1/%s", fraudType, protocolSuffix) +} + +func protocolID(protocolSuffix string) protocol.ID { + return protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", protocolSuffix)) +} + +func join(p *pubsub.PubSub, proofType ProofType, protocolSuffix string, validate func(context.Context, ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult) (*pubsub.Topic, error) { - t, err := p.Join(string(proofType)) + topic := pubsubTopicID(string(proofType), protocolSuffix) + log.Infow("joining topic", "id", topic) + t, err := p.Join(topic) if err != nil { return nil, err } err = p.RegisterTopicValidator( - string(proofType), + topic, func(ctx context.Context, from peer.ID, msg *pubsub.Message) pubsub.ValidationResult { return validate(ctx, proofType, from, msg) }, diff --git a/fraud/requester.go b/fraud/requester.go index 06d960a1e4..18b68a864f 100644 --- a/fraud/requester.go +++ b/fraud/requester.go @@ -5,6 +5,7 @@ import ( "time" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -20,11 +21,12 @@ const ( func (f *ProofService) requestProofs( ctx context.Context, + id protocol.ID, pid peer.ID, proofTypes []string, ) ([]*pb.ProofResponse, error) { msg := &pb.FraudMessageRequest{RequestedProofType: proofTypes} - stream, err := f.host.NewStream(ctx, pid, f.protocolID) + stream, err := f.host.NewStream(ctx, pid, id) if err != nil { return nil, err } diff --git a/fraud/service.go b/fraud/service.go index f9c260f4ab..6194edf693 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -12,7 +12,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" @@ -25,7 +24,7 @@ const fraudRequests = 5 // ProofService is responsible for validating and propagating Fraud Proofs. // It implements the Service interface. type ProofService struct { - protocolID protocol.ID + protocolSuffix string ctx context.Context cancel context.CancelFunc @@ -53,21 +52,21 @@ func NewProofService( protocolSuffix string, ) *ProofService { return &ProofService{ - pubsub: p, - host: host, - getter: getter, - topics: make(map[ProofType]*pubsub.Topic), - stores: make(map[ProofType]datastore.Datastore), - ds: ds, - protocolID: protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", protocolSuffix)), - syncerEnabled: syncerEnabled, + pubsub: p, + host: host, + getter: getter, + topics: make(map[ProofType]*pubsub.Topic), + stores: make(map[ProofType]datastore.Datastore), + ds: ds, + protocolSuffix: protocolSuffix, + syncerEnabled: syncerEnabled, } } // registerProofTopics registers proofTypes as pubsub topics to be joined. func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { for _, proofType := range proofTypes { - t, err := join(f.pubsub, proofType, f.processIncoming) + t, err := join(f.pubsub, proofType, f.protocolSuffix, f.processIncoming) if err != nil { return err } @@ -85,16 +84,19 @@ func (f *ProofService) Start(context.Context) error { if err := f.registerProofTopics(registeredProofTypes()...); err != nil { return err } - f.host.SetStreamHandler(f.protocolID, f.handleFraudMessageRequest) + id := protocolID(f.protocolSuffix) + log.Infow("starting fraud proof service", "protocol ID", id) + + f.host.SetStreamHandler(id, f.handleFraudMessageRequest) if f.syncerEnabled { - go f.syncFraudProofs(f.ctx) + go f.syncFraudProofs(f.ctx, id) } return nil } // Stop removes the stream handler and cancels the underlying ProofService func (f *ProofService) Stop(context.Context) error { - f.host.RemoveStreamHandler(f.protocolID) + f.host.RemoveStreamHandler(protocolID(f.protocolSuffix)) f.cancel() return nil } diff --git a/fraud/service_test.go b/fraud/service_test.go index a1f405e3d1..faafd1efff 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) func TestService_Subscribe(t *testing.T) { @@ -132,7 +133,7 @@ func TestService_ReGossiping(t *testing.T) { }, sync.MutexWrap(datastore.NewMapDatastore()), false, - "private", + string(p2p.Private), ) addrB := host.InfoFromHost(net.Hosts()[1]) // -> B @@ -148,7 +149,7 @@ func TestService_ReGossiping(t *testing.T) { }, sync.MutexWrap(datastore.NewMapDatastore()), false, - "private", + string(p2p.Private), ) // establish connections // connect peers: A -> B -> C, so A and C are not connected to each other diff --git a/fraud/sync.go b/fraud/sync.go index ce7b7b88d3..d4ba4ba268 100644 --- a/fraud/sync.go +++ b/fraud/sync.go @@ -9,6 +9,7 @@ import ( "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -22,7 +23,7 @@ import ( // to request fraud proofs from and request fraud proofs from them. // After fraud proofs are received, they are published to all local subscriptions for verification // order to be verified. -func (f *ProofService) syncFraudProofs(ctx context.Context) { +func (f *ProofService) syncFraudProofs(ctx context.Context, id protocol.ID) { log.Debug("start fetching fraud proofs") // subscribe to new peer connections that we can request fraud proofs from sub, err := f.host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) @@ -67,7 +68,7 @@ func (f *ProofService) syncFraudProofs(ctx context.Context) { attribute.StringSlice("proof_types", proofTypes), ) log.Debugw("requesting proofs from peer", "pid", pid) - respProofs, err := f.requestProofs(ctx, pid, proofTypes) + respProofs, err := f.requestProofs(ctx, id, pid, proofTypes) if err != nil { log.Errorw("error while requesting fraud proofs", "err", err, "peer", pid) span.RecordError(err) diff --git a/fraud/testing.go b/fraud/testing.go index 355d0440e4..368f6a5b75 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -16,6 +16,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/headertest" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) type DummyService struct { @@ -143,6 +144,6 @@ func createTestServiceWithHost( store.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore()), enabledSyncer, - "private", + string(p2p.Private), ), store } diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index b7e06721fb..ca464da7eb 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -20,10 +20,6 @@ import ( var log = logging.Logger("header/p2p") -// PubSubTopic hardcodes the name of the Header -// gossipsub topic. -const PubSubTopic = "header-sub" - // Exchange enables sending outbound HeaderRequests to the network as well as // handling inbound HeaderRequests from the network. type Exchange[H header.Header] struct { @@ -41,8 +37,7 @@ type Exchange[H header.Header] struct { func NewExchange[H header.Header]( host host.Host, - trustedPeers peer.IDSlice, - protocolSuffix string, + peers peer.IDSlice, connGater *conngater.BasicConnectionGater, opts ...Option[ClientParameters], ) (*Exchange[H], error) { @@ -58,8 +53,8 @@ func NewExchange[H header.Header]( return &Exchange[H]{ host: host, - protocolID: protocolID(protocolSuffix), - trustedPeers: trustedPeers, + protocolID: protocolID(params.protocolSuffix), + trustedPeers: peers, peerTracker: newPeerTracker( host, connGater, diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index 430cbd2a0f..b27c760949 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -24,9 +24,10 @@ import ( headerMock "github.com/celestiaorg/celestia-node/libs/header/mocks" p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" "github.com/celestiaorg/celestia-node/libs/header/test" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -var privateProtocolID = protocolID("private") +var privateProtocolID = protocolID(string(p2p.Private)) func TestExchange_RequestHead(t *testing.T) { hosts := createMocknet(t, 2) @@ -94,11 +95,12 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { hosts := createMocknet(t, 5) totalAmount := 80 store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), totalAmount) - protocolSuffix := "private" connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) // create new exchange - exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{}, protocolSuffix, connGater) + exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{}, connGater, + WithProtocolSuffix[ClientParameters](string(p2p.Private)), + ) require.NoError(t, err) exchange.Params.MaxHeadersPerRequest = 10 exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) @@ -106,7 +108,11 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { // amount of servers is len(hosts)-1 because one peer acts as a client servers := make([]*ExchangeServer[*test.DummyHeader], len(hosts)-1) for index := range servers { - servers[index], err = NewExchangeServer[*test.DummyHeader](hosts[index], store, protocolSuffix) + servers[index], err = NewExchangeServer[*test.DummyHeader]( + hosts[index], + store, + WithProtocolSuffix[ServerParameters](string(p2p.Private)), + ) require.NoError(t, err) servers[index].Start(context.Background()) //nolint:errcheck exchange.peerTracker.peerLk.Lock() @@ -140,7 +146,8 @@ func TestExchange_RequestHeadersFromAnotherPeer(t *testing.T) { exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) // create one more server(with more headers in the store) serverSideEx, err := NewExchangeServer[*test.DummyHeader]( - hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), "private", + hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), + WithProtocolSuffix[ServerParameters](string(p2p.Private)), ) require.NoError(t, err) require.NoError(t, serverSideEx.Start(context.Background())) @@ -169,7 +176,11 @@ func TestExchange_RequestByHash(t *testing.T) { host, peer := net.Hosts()[0], net.Hosts()[1] // create and start the ExchangeServer store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) - serv, err := NewExchangeServer[*test.DummyHeader](host, store, "private") + serv, err := NewExchangeServer[*test.DummyHeader]( + host, + store, + WithProtocolSuffix[ServerParameters](string(p2p.Private)), + ) require.NoError(t, err) err = serv.Start(ctx) require.NoError(t, err) @@ -279,7 +290,8 @@ func TestExchange_RequestByHashFails(t *testing.T) { // get host and peer host, peer := net.Hosts()[0], net.Hosts()[1] serv, err := NewExchangeServer[*test.DummyHeader]( - host, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 0), "private", + host, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 0), + WithProtocolSuffix[ServerParameters](string(p2p.Private)), ) require.NoError(t, err) err = serv.Start(ctx) @@ -330,7 +342,8 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { exchg.Params.RequestTimeout = time.Millisecond * 100 // create one more server(with more headers in the store) serverSideEx, err := NewExchangeServer[*test.DummyHeader]( - host2, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), "private", + host2, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), + WithProtocolSuffix[ServerParameters](string(p2p.Private)), ) require.NoError(t, err) // change store implementation @@ -362,13 +375,17 @@ func createP2PExAndServer( host, tpeer libhost.Host, ) (*Exchange[*test.DummyHeader], *headerMock.MockStore[*test.DummyHeader]) { store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) - serverSideEx, err := NewExchangeServer[*test.DummyHeader](tpeer, store, "private") + serverSideEx, err := NewExchangeServer[*test.DummyHeader](tpeer, store, + WithProtocolSuffix[ServerParameters](string(p2p.Private)), + ) require.NoError(t, err) err = serverSideEx.Start(context.Background()) require.NoError(t, err) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - ex, err := NewExchange[*test.DummyHeader](host, []peer.ID{tpeer.ID()}, "private", connGater) + ex, err := NewExchange[*test.DummyHeader](host, []peer.ID{tpeer.ID()}, connGater, + WithProtocolSuffix[ClientParameters](string(p2p.Private)), + ) require.NoError(t, err) require.NoError(t, ex.Start(context.Background())) time.Sleep(time.Millisecond * 100) // give peerTracker time to add a trusted peer diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go index 3b48362479..da75c28f87 100644 --- a/libs/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -20,6 +20,10 @@ func protocolID(protocolSuffix string) protocol.ID { return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) } +func pubsubTopicID(protocolSuffix string) string { + return fmt.Sprintf("/header-sub/v0.0.1/%s", protocolSuffix) +} + // sendMessage opens the stream to the given peers and sends HeaderRequest to fetch // Headers. As a result sendMessage returns HeaderResponse, the size of fetched // data, the duration of the request and an error. diff --git a/libs/header/p2p/options.go b/libs/header/p2p/options.go index 8fa20ee56f..66bcebee34 100644 --- a/libs/header/p2p/options.go +++ b/libs/header/p2p/options.go @@ -26,6 +26,9 @@ type ServerParameters struct { // RequestTimeout defines a timeout after which the session will try to re-request headers // from another peer. RequestTimeout time.Duration + // protocolSuffix is a network suffix that will be used to create a protocol.ID + // Is empty by default + protocolSuffix string } // DefaultServerParameters returns the default params to configure the store. @@ -103,6 +106,19 @@ func WithRequestTimeout[T parameters](duration time.Duration) Option[T] { } } +// WithProtocolSuffix is a functional option that configures the +// `protocolSuffix` parameter. +func WithProtocolSuffix[T parameters](protocolSuffix string) Option[T] { + return func(p *T) { + switch t := any(p).(type) { + case *ClientParameters: + t.protocolSuffix = protocolSuffix + case *ServerParameters: + t.protocolSuffix = protocolSuffix + } + } +} + // ClientParameters is the set of parameters that must be configured for the exchange. // TODO: #1667 type ClientParameters struct { @@ -124,6 +140,8 @@ type ClientParameters struct { TrustedPeersRequestTimeout time.Duration // MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker. MaxPeerTrackerSize int + // protocolSuffix is a network suffix that will be used to create a protocol.ID + protocolSuffix string } // DefaultClientParameters returns the default params to configure the store. diff --git a/libs/header/p2p/server.go b/libs/header/p2p/server.go index 673dcd1185..d7d6b81d9e 100644 --- a/libs/header/p2p/server.go +++ b/libs/header/p2p/server.go @@ -42,7 +42,6 @@ type ExchangeServer[H header.Header] struct { func NewExchangeServer[H header.Header]( host host.Host, getter header.Getter[H], - protocolSuffix string, opts ...Option[ServerParameters], ) (*ExchangeServer[H], error) { params := DefaultServerParameters() @@ -54,7 +53,7 @@ func NewExchangeServer[H header.Header]( } return &ExchangeServer[H]{ - protocolID: protocolID(protocolSuffix), + protocolID: protocolID(params.protocolSuffix), host: host, getter: getter, Params: params, @@ -64,7 +63,7 @@ func NewExchangeServer[H header.Header]( // Start sets the stream handler for inbound header-related requests. func (serv *ExchangeServer[H]) Start(context.Context) error { serv.ctx, serv.cancel = context.WithCancel(context.Background()) - log.Info("server: listening for inbound header requests") + log.Infow("server: listening for inbound header requests", "protocol ID", serv.protocolID) serv.host.SetStreamHandler(serv.protocolID, serv.requestHandler) diff --git a/libs/header/p2p/server_test.go b/libs/header/p2p/server_test.go index ff7d33ddc9..9b073a2dea 100644 --- a/libs/header/p2p/server_test.go +++ b/libs/header/p2p/server_test.go @@ -9,13 +9,18 @@ import ( "github.com/celestiaorg/celestia-node/libs/header/store" "github.com/celestiaorg/celestia-node/libs/header/test" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) func TestExchangeServer_handleRequestTimeout(t *testing.T) { peer := createMocknet(t, 1) s, err := store.NewStore[*test.DummyHeader](datastore.NewMapDatastore()) require.NoError(t, err) - server, err := NewExchangeServer[*test.DummyHeader](peer[0], s, "private") + server, err := NewExchangeServer[*test.DummyHeader]( + peer[0], + s, + WithProtocolSuffix[ServerParameters](string(p2p.Private)), + ) require.NoError(t, err) err = server.Start(context.Background()) require.NoError(t, err) diff --git a/libs/header/p2p/subscriber.go b/libs/header/p2p/subscriber.go index 7abd0d1864..464d55828e 100644 --- a/libs/header/p2p/subscriber.go +++ b/libs/header/p2p/subscriber.go @@ -13,6 +13,8 @@ import ( // Subscriber manages the lifecycle and relationship of header Module // with the "header-sub" gossipsub topic. type Subscriber[H header.Header] struct { + pubsubTopicID string + pubsub *pubsub.PubSub topic *pubsub.Topic msgID pubsub.MsgIdFunction @@ -20,23 +22,29 @@ type Subscriber[H header.Header] struct { // NewSubscriber returns a Subscriber that manages the header Module's // relationship with the "header-sub" gossipsub topic. -func NewSubscriber[H header.Header](ps *pubsub.PubSub, msgID pubsub.MsgIdFunction) *Subscriber[H] { +func NewSubscriber[H header.Header]( + ps *pubsub.PubSub, + msgID pubsub.MsgIdFunction, + protocolSuffix string, +) *Subscriber[H] { return &Subscriber[H]{ - pubsub: ps, - msgID: msgID, + pubsubTopicID: pubsubTopicID(protocolSuffix), + pubsub: ps, + msgID: msgID, } } // Start starts the Subscriber, registering a topic validator for the "header-sub" // topic and joining it. func (p *Subscriber[H]) Start(context.Context) (err error) { - p.topic, err = p.pubsub.Join(PubSubTopic, pubsub.WithTopicMessageIdFn(p.msgID)) + log.Infow("joining topic", "topic ID", p.pubsubTopicID) + p.topic, err = p.pubsub.Join(p.pubsubTopicID, pubsub.WithTopicMessageIdFn(p.msgID)) return err } // Stop closes the topic and unregisters its validator. func (p *Subscriber[H]) Stop(context.Context) error { - err := p.pubsub.UnregisterTopicValidator(PubSubTopic) + err := p.pubsub.UnregisterTopicValidator(p.pubsubTopicID) if err != nil { log.Warnf("unregistering validator: %s", err) } @@ -59,7 +67,7 @@ func (p *Subscriber[H]) AddValidator(val func(context.Context, H) pubsub.Validat msg.ValidatorData = maybeHead return val(ctx, maybeHead.(H)) } - return p.pubsub.RegisterTopicValidator(PubSubTopic, pval) + return p.pubsub.RegisterTopicValidator(p.pubsubTopicID, pval) } // Subscribe returns a new subscription to the Subscriber's diff --git a/libs/header/p2p/subscription_test.go b/libs/header/p2p/subscription_test.go index 06b0622ad3..331988a2a8 100644 --- a/libs/header/p2p/subscription_test.go +++ b/libs/header/p2p/subscription_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/libs/header/test" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // TestSubscriber tests the header Module's implementation of Subscriber. @@ -30,7 +31,7 @@ func TestSubscriber(t *testing.T) { require.NoError(t, err) // create sub-service lifecycles for header service 1 - p2pSub1 := NewSubscriber[*test.DummyHeader](pubsub1, pubsub.DefaultMsgIdFn) + p2pSub1 := NewSubscriber[*test.DummyHeader](pubsub1, pubsub.DefaultMsgIdFn, string(p2p.Private)) err = p2pSub1.Start(context.Background()) require.NoError(t, err) @@ -40,7 +41,7 @@ func TestSubscriber(t *testing.T) { require.NoError(t, err) // create sub-service lifecycles for header service 2 - p2pSub2 := NewSubscriber[*test.DummyHeader](pubsub2, pubsub.DefaultMsgIdFn) + p2pSub2 := NewSubscriber[*test.DummyHeader](pubsub2, pubsub.DefaultMsgIdFn, string(p2p.Private)) err = p2pSub2.Start(context.Background()) require.NoError(t, err) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 480885afa9..fb3c0f5223 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -21,10 +21,9 @@ import ( func newP2PServer( host host.Host, store libhead.Store[*header.ExtendedHeader], - network modp2p.Network, opts []p2p.Option[p2p.ServerParameters], ) (*p2p.ExchangeServer[*header.ExtendedHeader], error) { - return p2p.NewExchangeServer[*header.ExtendedHeader](host, store, string(network), opts...) + return p2p.NewExchangeServer[*header.ExtendedHeader](host, store, opts...) } // newP2PExchange constructs a new Exchange for headers. @@ -53,7 +52,7 @@ func newP2PExchange(cfg Config) func( ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - exchange, err := p2p.NewExchange[*header.ExtendedHeader](host, ids, string(network), conngater, opts...) + exchange, err := p2p.NewExchange[*header.ExtendedHeader](host, ids, conngater, opts...) if err != nil { return nil, err } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 24c71736cf..743f601f4d 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -39,12 +39,13 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }, ), fx.Provide( - func(cfg Config) []p2p.Option[p2p.ServerParameters] { + func(cfg Config, network modp2p.Network) []p2p.Option[p2p.ServerParameters] { return []p2p.Option[p2p.ServerParameters]{ p2p.WithWriteDeadline(cfg.Server.WriteDeadline), p2p.WithReadDeadline(cfg.Server.ReadDeadline), p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), p2p.WithRequestTimeout[p2p.ServerParameters](cfg.Server.RequestTimeout), + p2p.WithProtocolSuffix[p2p.ServerParameters](string(network)), } }), fx.Provide(newHeaderService), @@ -85,8 +86,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), )), fx.Provide(fx.Annotate( - func(ps *pubsub.PubSub) *p2p.Subscriber[*header.ExtendedHeader] { - return p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID) + func(ps *pubsub.PubSub, network modp2p.Network) *p2p.Subscriber[*header.ExtendedHeader] { + return p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, string(network)) }, fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber[*header.ExtendedHeader]) error { return sub.Start(ctx) @@ -112,7 +113,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "header", baseComponents, fx.Provide( - func(cfg Config) []p2p.Option[p2p.ClientParameters] { + func(cfg Config, network modp2p.Network) []p2p.Option[p2p.ClientParameters] { return []p2p.Option[p2p.ClientParameters]{ p2p.WithMinResponses(cfg.Client.MinResponses), p2p.WithMaxRequestSize[p2p.ClientParameters](cfg.Client.MaxRequestSize), @@ -121,6 +122,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithDefaultScore(cfg.Client.DefaultScore), p2p.WithRequestTimeout[p2p.ClientParameters](cfg.Client.RequestTimeout), p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), + p2p.WithProtocolSuffix[p2p.ClientParameters](string(network)), } }, ), diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index d64bf46266..15e0604e14 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -33,6 +33,7 @@ func TestConstructModule_StoreParams(t *testing.T) { var headerStore *store.Store[*header.ExtendedHeader] app := fxtest.New(t, + fx.Supply(modp2p.Private), fx.Provide(func() datastore.Batching { return datastore.NewMapDatastore() }), diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 93bd1530bd..51797a045f 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -15,9 +15,9 @@ import ( var log = logging.Logger("shrex-sub") -// pubSubTopic hardcodes the name of the EDS floodsub topic with the provided suffix. -func pubSubTopic(suffix string) string { - return "eds-sub/v0.0.1/" + suffix +// pubsubTopic hardcodes the name of the EDS floodsub topic with the provided suffix. +func pubsubTopic(suffix string) string { + return fmt.Sprintf("/eds-sub/v0.0.1/%s", suffix) } // Validator is an injectable func and governs EDS notification or DataHash validity. @@ -34,7 +34,7 @@ type PubSub struct { pubSub *pubsub.PubSub topic *pubsub.Topic - pubSubTopic string + pubsubTopic string } // NewPubSub creates a libp2p.PubSub wrapper. @@ -46,13 +46,13 @@ func NewPubSub(ctx context.Context, h host.Host, suffix string) (*PubSub, error) } return &PubSub{ pubSub: pubsub, - pubSubTopic: pubSubTopic(suffix), + pubsubTopic: pubsubTopic(suffix), }, nil } // Start creates an instances of FloodSub and joins specified topic. func (s *PubSub) Start(context.Context) error { - topic, err := s.pubSub.Join(s.pubSubTopic) + topic, err := s.pubSub.Join(s.pubsubTopic) if err != nil { return err } @@ -65,7 +65,7 @@ func (s *PubSub) Start(context.Context) error { // * Unregisters all the added Validators // * Closes the `ShrEx/Sub` topic func (s *PubSub) Stop(context.Context) error { - err := s.pubSub.UnregisterTopicValidator(s.pubSubTopic) + err := s.pubSub.UnregisterTopicValidator(s.pubsubTopic) if err != nil { log.Warnw("unregistering topic", "err", err) } @@ -75,7 +75,7 @@ func (s *PubSub) Stop(context.Context) error { // AddValidator registers given Validator for EDS notifications (DataHash). // Any amount of Validators can be registered. func (s *PubSub) AddValidator(validate Validator) error { - return s.pubSub.RegisterTopicValidator(s.pubSubTopic, + return s.pubSub.RegisterTopicValidator(s.pubsubTopic, func(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { return validate(ctx, p, msg.Data) }) From 2cf6813611708f275efe389306605ba57ad1c9d4 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:46:28 +0100 Subject: [PATCH 0345/1008] chore(nodebuilder/p2p)!: Upgrade arabica chain ID to `arabica-4` (#1696) Blocked on #1620 --- nodebuilder/p2p/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index a528629a44..24a5153ec4 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-3" + Arabica Network = "arabica-4" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha" // Private can be used to set up any private network, including local testing setups. From 35cc9d1d18e8de9c80299d7b099758d8a0d89dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:38:53 +0100 Subject: [PATCH 0346/1008] feat!: Move Dockerfile to the root of the project (#1693) --- .github/workflows/ci_release.yml | 2 +- .github/workflows/docker-build.yml | 4 ++-- docker/Dockerfile => Dockerfile | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename docker/Dockerfile => Dockerfile (100%) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index b18f9bc4aa..3f8ea9de24 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -26,7 +26,7 @@ jobs: hadolint: uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@main # yamllint disable-line rule:line-length with: - dockerfile: docker/Dockerfile + dockerfile: Dockerfile yamllint: runs-on: ubuntu-latest diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index fbf85b0a8e..d250833bab 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -45,7 +45,7 @@ jobs: platforms: linux/amd64 push: true tags: ${{ steps.meta.outputs.tags }} - file: docker/Dockerfile + file: Dockerfile - name: Build and Push amd64+arm64 Images if: | github.ref == 'refs/heads/main' || @@ -55,4 +55,4 @@ jobs: platforms: linux/arm64, linux/amd64 push: true tags: ${{ steps.meta.outputs.tags }} - file: docker/Dockerfile + file: Dockerfile diff --git a/docker/Dockerfile b/Dockerfile similarity index 100% rename from docker/Dockerfile rename to Dockerfile From de89e12d11f34e90376368686847fb8c08fc0e08 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 7 Feb 2023 13:00:01 +0100 Subject: [PATCH 0347/1008] chore: bump celestia-app v0.12.0-rc6 (#1689) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index aec6a7bdf5..43371b9f1f 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.12.0-rc5 + github.com/celestiaorg/celestia-app v0.12.0-rc6 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 @@ -322,7 +322,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p => github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe diff --git a/go.sum b/go.sum index e559b665ac..48166e6e4e 100644 --- a/go.sum +++ b/go.sum @@ -197,12 +197,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.0-rc5 h1:IyhV+wh+iznFV7K78qhNm4DCYIHxagAVolRIGV64Fgs= -github.com/celestiaorg/celestia-app v0.12.0-rc5/go.mod h1:+N3b0GfR+twY/TajKicIeZGjFU9YnHWWzbsztxcARFU= +github.com/celestiaorg/celestia-app v0.12.0-rc6 h1:MQ1Nb+awcnqK+EHpsH+NlBLT6F5Mph6Z3VnzgXUlxAU= +github.com/celestiaorg/celestia-app v0.12.0-rc6/go.mod h1:4qMJfFq0Yr9At8dpP271seswj1wav20vqNfrnBlFxmo= github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 h1:8zE523TUe5W33/nheJ9umHF2d1q6iHQlqJfMXMTPe3k= github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23/go.mod h1:fGDSg7aw2OH/Uze1zymop0x0y1kAPEO9OII2A2cb99Q= -github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7 h1:jy24W2wTCnDY843A/5hy79QP/4GNhGPtADN55zs9o6g= -github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7/go.mod h1:qH1Q0s96i4Op0WSh6qC72yWkoWdZNs0CY4HPUrWaK44= +github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= +github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= From b51509dd7c5253c2eb21d1abfc819fdcdf9296cb Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Tue, 7 Feb 2023 15:52:40 +0100 Subject: [PATCH 0348/1008] chore!: bump arabica-4 genesis hash (#1701) --- nodebuilder/p2p/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index b258cc649a..15719acfaa 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "6D2D85AC2817EC2CCA8C75893EFA398B7EEB988528AC403CD0102AE4230D415A", + Arabica: "C89DDAB34DB47895DB79F67BF28CE72570D6C586436FC3449284CA411DEFCC53", Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", Private: "", } From abb9fa1cb4439e501db4966814e1512868f11c4a Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 8 Feb 2023 12:54:16 +0100 Subject: [PATCH 0349/1008] chore(go.mod): Depend on upstream libp2p (AKA remove hlibp2p) (#1700) As #1684 resolves instability with our bootstrappers, we no longer need to depend on @Wondertan's fork of libp2p Related to #1623 --- go.mod | 1 - go.sum | 355 ++++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 275 insertions(+), 81 deletions(-) diff --git a/go.mod b/go.mod index 43371b9f1f..e76a9626c3 100644 --- a/go.mod +++ b/go.mod @@ -325,6 +325,5 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/libp2p/go-libp2p => github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 48166e6e4e..ed3354bc9b 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= @@ -87,6 +89,7 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= @@ -108,8 +111,6 @@ github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe h1:P7b6c5e/79/AzPiVfU5x+87gJgAYXWuN26EhlSYDkx0= -github.com/Wondertan/go-libp2p v0.5.3-0.20230126212817-d54022334dbe/go.mod h1:WuxtL2V8yGjam03D93ZBC19tvOUiPpewYv1xdFGWu1k= github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= @@ -170,8 +171,11 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= @@ -231,6 +235,7 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= @@ -242,7 +247,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -282,7 +286,6 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -329,6 +332,7 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= @@ -343,6 +347,10 @@ github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.3/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= @@ -354,6 +362,7 @@ github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/Lu github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -428,7 +437,6 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= @@ -532,6 +540,7 @@ github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -588,7 +597,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -617,7 +625,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -732,6 +739,7 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= @@ -741,7 +749,6 @@ github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= @@ -777,21 +784,34 @@ github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUP github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= +github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= +github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= +github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= +github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= +github.com/ipfs/go-datastore v0.4.6/go.mod h1:XSipLSc64rFKSFRFGo1ecQl+WhYce3K7frtpHkyPFUc= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= -github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= +github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= +github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= +github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= +github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= +github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= +github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= +github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= @@ -834,10 +854,16 @@ github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQ github.com/ipfs/go-ipns v0.2.0 h1:BgmNtQhqOw5XEZ8RAfWEpK4DhqaYiuP6h71MhIp7xXU= github.com/ipfs/go-ipns v0.2.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= +github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= +github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= +github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= +github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= +github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= @@ -867,9 +893,13 @@ github.com/ipld/go-ipld-prime v0.16.0 h1:RS5hhjB/mcpeEPJvfyj0qbOj/QL+/j05heZ0qa9 github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= +github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= @@ -912,6 +942,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -927,7 +958,6 @@ github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM52 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI= github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -937,6 +967,7 @@ github.com/klauspost/reedsolomon v1.11.1/go.mod h1:FXLZzlJIdfqEnQLdUKWNRuMZg747h github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -960,61 +991,215 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= +github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= +github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= +github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= +github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= +github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= +github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= +github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= +github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= +github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= +github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= +github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= +github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= +github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= +github.com/libp2p/go-libp2p v0.24.1 h1:+lS4fqj7RF9egcPq9Yo3iqdRTcDMApzoBbQMhxtwOVw= +github.com/libp2p/go-libp2p v0.24.1/go.mod h1:5LJqbrqFsUzWrq70JHCYqjATlX4ey8Klpct3OEe8hSI= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= +github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= +github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= +github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= +github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= +github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= +github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= +github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= +github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= +github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= +github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= +github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= +github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= +github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= +github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= +github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= +github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= +github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= +github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= +github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= +github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= +github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= +github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= +github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= +github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= +github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= +github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= +github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.20.0 h1:1bcMa74JFwExCHZMFEmjtHzxX5DovhJ07EtR6UOTEpc= github.com/libp2p/go-libp2p-kad-dht v0.20.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= +github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= +github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= +github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= +github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= +github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= +github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= +github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= +github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= +github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= +github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= +github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= +github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= +github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= +github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= +github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= +github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= +github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-pubsub v0.8.3 h1:T4+pcfcFm1K2v5oFyk68peSjVroaoM8zFygf6Y5WOww= github.com/libp2p/go-libp2p-pubsub v0.8.3/go.mod h1:eje970FXxjhtFbVEoiae+VUw24ZoSlk67BsiZPLRzlw= +github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= +github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-routing-helpers v0.6.0 h1:Rfyd+wp/cU0PjNjCphGzLYzd7Q51fjOMs5Sjj6zWGT0= github.com/libp2p/go-libp2p-routing-helpers v0.6.0/go.mod h1:wwK/XSLt6njjO7sRbjhf8w7PGBOfdntMQ2mOQPZ5s/Q= +github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= +github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= +github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= +github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= +github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= +github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= +github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= +github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= +github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= +github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= +github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= +github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= +github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= +github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= -github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= +github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= +github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= +github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= +github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= +github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= +github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= +github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= +github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= +github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= +github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= +github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= +github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= +github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= +github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= +github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= -github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= +github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= +github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= +github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= +github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-msgio v0.2.0 h1:W6shmB+FeynDrUVl2dgFQvzfBZcXiyqY4VmpQLu9FqU= github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= +github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= +github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= +github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= +github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= +github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo= github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= +github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= +github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= +github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= +github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= +github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= +github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= +github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= +github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= +github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= +github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= +github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= +github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= +github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= +github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= +github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= +github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= +github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= +github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= +github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= +github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= +github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= +github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= +github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= @@ -1023,6 +1208,7 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1031,16 +1217,21 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= -github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g= +github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= +github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= +github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE= github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/marten-seemann/webtransport-go v0.4.3 h1:vkt5o/Ci+luknRteWdYGYH1KcB7ziup+J+1PzZJIvmg= -github.com/marten-seemann/webtransport-go v0.4.3/go.mod h1:4xcfySgZMLP4aG5GBGj1egP7NlpfwgYJ1WJMvPPiVMU= +github.com/marten-seemann/webtransport-go v0.4.2 h1:8ZRr9AsPuDiLQwnX2PxGs2t35GPvUaqPJnvk+c2SFSs= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1073,8 +1264,11 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -1093,6 +1287,7 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= +github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= @@ -1131,28 +1326,40 @@ github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= -github.com/multiformats/go-base32 v0.0.4/go.mod h1:jNLFzjPZtp3aIARHbJRZIaPuspdH0J6q39uUM5pnABM= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= +github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= +github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= -github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= +github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= +github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= +github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= +github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= +github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= +github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= +github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= @@ -1160,7 +1367,6 @@ github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyD github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= github.com/multiformats/go-multicodec v0.7.0 h1:rTUjGOwjlhGHbEMbPoSUJowG1spZTVsITRANCjKTUAQ= github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= @@ -1171,13 +1377,16 @@ github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= -github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 h1:qLF997Rz0X1WvdcZ2r5CUkLZ2rvdiXwG1JRSrJZEAuE= github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6/go.mod h1:kaHxr8TfO1cxIR/tYxgZ7e59HraJq8arEQQR8E/YNvI= +github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= +github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= +github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o= github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= @@ -1211,30 +1420,23 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1302,6 +1504,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= @@ -1323,7 +1527,9 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= @@ -1333,6 +1539,7 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= @@ -1420,6 +1627,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= @@ -1450,6 +1658,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1546,7 +1755,12 @@ github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= +github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= +github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= +github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= +github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -1557,8 +1771,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.0 h1:dlMC7aO8Wss1CxBq2I96kZ69Nh1ligzbs8UWOtq/AsA= @@ -1576,10 +1788,12 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= @@ -1615,22 +1829,22 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= @@ -1638,9 +1852,10 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -1659,6 +1874,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1678,11 +1894,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1702,7 +1915,6 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1733,9 +1945,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1765,6 +1974,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1782,6 +1992,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1792,6 +2003,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= @@ -1800,16 +2012,10 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1840,8 +2046,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1867,6 +2071,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1875,11 +2080,13 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1911,6 +2118,7 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1918,7 +2126,9 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1926,6 +2136,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1933,36 +2144,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1974,8 +2170,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1989,6 +2183,7 @@ golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2007,11 +2202,13 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2049,9 +2246,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2168,6 +2362,7 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -2195,8 +2390,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -2218,6 +2411,8 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= +gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= From 6933f03feb4a35ecd018e1a3cd70d7ae1644eb51 Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Wed, 8 Feb 2023 17:30:05 +0100 Subject: [PATCH 0350/1008] feat(node | das | libs/header/sync): add total uptime node metrics + totalSampled das metrics + totalSynced sync metrics (#1638) ## Overview This PR introduces node uptime metrics + das total sampled headers metrics to support calculating the uptime index proposed by mustafa on the monitoring side. This PR introduces a new module named `Telemetry` to support node related telemetry. This module can also host all general telemetry and observability that does not interest specific modules. ## Changes - [x] Introduced uptime metrics for node under `nodebuilder/node/uptime.go` - [x] Introduced persistent uptime metrics using datastore to persist node start time - [x] Testing for uptime metrics persistence using the store - [x] Unit testing for uptime metrics - [x] Integration testing for uptime metrics - [ ] e2e testing for uptime metrics ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords ## Blocked By PR: #1537 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- das/checkpoint.go | 9 +++ das/coordinator.go | 4 ++ das/metrics.go | 60 +++++++++++++--- das/options.go | 3 +- das/worker.go | 40 ++++++++--- go.mod | 4 +- header/metrics.go | 9 ++- libs/header/sync/metrics.go | 43 ++++++++++++ libs/header/sync/sync.go | 5 ++ nodebuilder/node/metrics.go | 53 ++++++++++++++ nodebuilder/node_test.go | 88 ++++++++++++++++++++++++ nodebuilder/settings.go | 10 +-- share/availability/mocks/availability.go | 3 +- share/mocks/getter.go | 3 +- 14 files changed, 306 insertions(+), 28 deletions(-) create mode 100644 libs/header/sync/metrics.go create mode 100644 nodebuilder/node/metrics.go diff --git a/das/checkpoint.go b/das/checkpoint.go index 860c72cb35..04e6cafaf5 100644 --- a/das/checkpoint.go +++ b/das/checkpoint.go @@ -48,3 +48,12 @@ func (c checkpoint) String() string { return str } + +// totalSampled returns the total amount of sampled headers +func (c checkpoint) totalSampled() uint64 { + var totalInProgress uint64 + for _, w := range c.Workers { + totalInProgress += (w.To - w.From) + 1 + } + return c.SampleFrom - totalInProgress - uint64(len(c.Failed)) +} diff --git a/das/coordinator.go b/das/coordinator.go index d8e149ebd3..cf356349d9 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -55,6 +55,10 @@ func newSamplingCoordinator( func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { sc.state.resumeFromCheckpoint(cp) + + // the amount of sampled headers from the last checkpoint + sc.metrics.recordTotalSampled(cp.totalSampled()) + // resume workers for _, wk := range cp.Workers { sc.runWorker(ctx, sc.state.newJob(wk.From, wk.To)) diff --git a/das/metrics.go b/das/metrics.go index b417cb05c1..f498e02ae7 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -24,7 +24,9 @@ type metrics struct { sampleTime syncfloat64.Histogram getHeaderTime syncfloat64.Histogram newHead syncint64.Counter - lastSampledTS int64 + + lastSampledTS uint64 + totalSampledInt uint64 } func (d *DASer) InitMetrics() error { @@ -76,6 +78,16 @@ func (d *DASer) InitMetrics() error { return err } + totalSampled, err := meter. + AsyncInt64(). + Gauge( + "das_total_sampled_headers", + instrument.WithDescription("total sampled headers gauge"), + ) + if err != nil { + return err + } + d.sampler.metrics = &metrics{ sampled: sampled, sampleTime: sampleTime, @@ -85,7 +97,11 @@ func (d *DASer) InitMetrics() error { err = meter.RegisterCallback( []instrument.Asynchronous{ - lastSampledTS, busyWorkers, networkHead, sampledChainHead, + lastSampledTS, + busyWorkers, + networkHead, + sampledChainHead, + totalSampled, }, func(ctx context.Context) { stats, err := d.sampler.stats(ctx) @@ -97,9 +113,12 @@ func (d *DASer) InitMetrics() error { networkHead.Observe(ctx, int64(stats.NetworkHead)) sampledChainHead.Observe(ctx, int64(stats.SampledChainHead)) - if ts := atomic.LoadInt64(&d.sampler.metrics.lastSampledTS); ts != 0 { - lastSampledTS.Observe(ctx, ts) + if ts := atomic.LoadUint64(&d.sampler.metrics.lastSampledTS); ts != 0 { + lastSampledTS.Observe(ctx, int64(ts)) } + + totalSampledInt := atomic.LoadUint64(&d.sampler.metrics.totalSampledInt) + totalSampled.Observe(ctx, int64(totalSampledInt)) }, ) @@ -110,19 +129,35 @@ func (d *DASer) InitMetrics() error { return nil } -func (m *metrics) observeSample(ctx context.Context, h *header.ExtendedHeader, sampleTime time.Duration, err error) { +// observeSample records the time it took to sample a header + +// the amount of sampled contiguous headers +func (m *metrics) observeSample( + ctx context.Context, + h *header.ExtendedHeader, + sampleTime time.Duration, + err error, +) { if m == nil { return } m.sampleTime.Record(ctx, sampleTime.Seconds(), attribute.Bool("failed", err != nil), - attribute.Int("header_width", len(h.DAH.RowsRoots))) + attribute.Int("header_width", len(h.DAH.RowsRoots)), + ) + m.sampled.Add(ctx, 1, attribute.Bool("failed", err != nil), - attribute.Int("header_width", len(h.DAH.RowsRoots))) - atomic.StoreInt64(&m.lastSampledTS, time.Now().UTC().Unix()) + attribute.Int("header_width", len(h.DAH.RowsRoots)), + ) + + atomic.StoreUint64(&m.lastSampledTS, uint64(time.Now().UTC().Unix())) + + if err == nil { + atomic.AddUint64(&m.totalSampledInt, 1) + } } +// observeGetHeader records the time it took to get a header from the header store. func (m *metrics) observeGetHeader(ctx context.Context, d time.Duration) { if m == nil { return @@ -130,9 +165,18 @@ func (m *metrics) observeGetHeader(ctx context.Context, d time.Duration) { m.getHeaderTime.Record(ctx, d.Seconds()) } +// observeNewHead records the network head. func (m *metrics) observeNewHead(ctx context.Context) { if m == nil { return } m.newHead.Add(ctx, 1) } + +// recordTotalSampled records the total sampled headers. +func (m *metrics) recordTotalSampled(totalSampled uint64) { + if m == nil { + return + } + atomic.StoreUint64(&m.totalSampledInt, totalSampled) +} diff --git a/das/options.go b/das/options.go index 55be4c9e1f..eca8596de4 100644 --- a/das/options.go +++ b/das/options.go @@ -38,7 +38,8 @@ type Parameters struct { // SampleFrom is the height sampling will start from if no previous checkpoint was saved SampleFrom uint64 - // SampleTimeout is a maximum amount time sampling of single block may take until it will be canceled + // SampleTimeout is a maximum amount time sampling of single block may take until it will be + // canceled SampleTimeout time.Duration } diff --git a/das/worker.go b/das/worker.go index 7eb73ac703..13ab67a05e 100644 --- a/das/worker.go +++ b/das/worker.go @@ -60,8 +60,15 @@ func (w *worker) run( } metrics.observeGetHeader(ctx, time.Since(startGet)) - log.Debugw("got header from header store", "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startGet)) + + log.Debugw( + "got header from header store", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "finished (s)", time.Since(startGet), + ) startSample := time.Now() err = sample(ctx, h) @@ -72,18 +79,35 @@ func (w *worker) run( w.setResult(curr, err) metrics.observeSample(ctx, h, time.Since(startSample), err) if err != nil { - log.Debugw("failed to sampled header", "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err) + log.Debugw( + "failed to sampled header", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "err", err, + ) } else { - log.Debugw("sampled header", "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startSample)) + log.Debugw( + "sampled header", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "finished (s)", time.Since(startSample), + ) } } if w.state.Curr > w.state.From { jobTime := time.Since(jobStart) - log.Infow("sampled headers", "from", w.state.From, "to", w.state.Curr, - "finished (s)", jobTime.Seconds()) + log.Infow( + "sampled headers", + "from", w.state.From, + "to", w.state.Curr, + "finished (s)", + jobTime.Seconds(), + ) } select { diff --git a/go.mod b/go.mod index e76a9626c3..8e03eeadb8 100644 --- a/go.mod +++ b/go.mod @@ -62,12 +62,14 @@ require ( go.opentelemetry.io/otel/sdk v1.11.2 go.opentelemetry.io/otel/sdk/metric v0.34.0 go.opentelemetry.io/otel/trace v1.11.2 + go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.5.0 golang.org/x/sync v0.1.0 golang.org/x/text v0.6.0 google.golang.org/grpc v1.52.0 + google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) require ( @@ -296,7 +298,6 @@ require ( go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect go.uber.org/zap v1.24.0 // indirect @@ -311,7 +312,6 @@ require ( google.golang.org/api v0.102.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect - google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/header/metrics.go b/header/metrics.go index 64ff448c7f..dbf78d1e57 100644 --- a/header/metrics.go +++ b/header/metrics.go @@ -9,12 +9,13 @@ import ( "go.opentelemetry.io/otel/metric/unit" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/sync" ) var meter = global.MeterProvider().Meter("header") -// WithMetrics enables Otel metrics to monitor head. -func WithMetrics(store libhead.Store[*ExtendedHeader]) { +// WithMetrics enables Otel metrics to monitor head and total amount of synced headers. +func WithMetrics(store libhead.Store[*ExtendedHeader], syncer *sync.Syncer[*ExtendedHeader]) error { headC, _ := meter.AsyncInt64().Counter( "head", instrument.WithUnit(unit.Dimensionless), @@ -40,6 +41,8 @@ func WithMetrics(store libhead.Store[*ExtendedHeader]) { }, ) if err != nil { - panic(err) + return err } + + return syncer.InitMetrics() } diff --git a/libs/header/sync/metrics.go b/libs/header/sync/metrics.go new file mode 100644 index 0000000000..99851e9d18 --- /dev/null +++ b/libs/header/sync/metrics.go @@ -0,0 +1,43 @@ +package sync + +import ( + "context" + "sync/atomic" + + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" +) + +var meter = global.MeterProvider().Meter("header/sync") + +type metrics struct { + totalSynced int64 +} + +func (s *Syncer[H]) InitMetrics() error { + s.metrics = &metrics{} + + totalSynced, err := meter. + AsyncFloat64(). + Gauge( + "total_synced_headers", + instrument.WithDescription("total synced headers"), + ) + if err != nil { + return err + } + + return meter.RegisterCallback( + []instrument.Asynchronous{ + totalSynced, + }, + func(ctx context.Context) { + totalSynced.Observe(ctx, float64(atomic.LoadInt64(&s.metrics.totalSynced))) + }, + ) +} + +// recordTotalSynced records the total amount of synced headers. +func (m *metrics) recordTotalSynced(totalSynced int) { + atomic.AddInt64(&m.totalSynced, int64(totalSynced)) +} diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 63be056591..983a841742 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -53,6 +53,8 @@ type Syncer[H header.Header] struct { cancel context.CancelFunc Params *Parameters + + metrics *metrics } // NewSyncer creates a new instance of Syncer. @@ -233,6 +235,9 @@ func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) if err != nil && processed == 0 { break } + if s.metrics != nil { + s.metrics.recordTotalSynced(processed) + } } s.stateLk.Lock() diff --git a/nodebuilder/node/metrics.go b/nodebuilder/node/metrics.go new file mode 100644 index 0000000000..625e8425e8 --- /dev/null +++ b/nodebuilder/node/metrics.go @@ -0,0 +1,53 @@ +package node + +import ( + "context" + "time" + + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" +) + +var meter = global.MeterProvider().Meter("node") + +var ( + timeStarted time.Time + nodeStarted bool +) + +// WithMetrics registers node metrics. +func WithMetrics() error { + nodeStartTS, err := meter. + AsyncFloat64(). + Gauge( + "node_start_ts", + instrument.WithDescription("timestamp when the node was started"), + ) + if err != nil { + return err + } + + totalNodeRunTime, err := meter. + AsyncFloat64(). + Counter( + "node_runtime_counter_in_seconds", + instrument.WithDescription("total time the node has been running"), + ) + if err != nil { + return err + } + + return meter.RegisterCallback( + []instrument.Asynchronous{nodeStartTS, totalNodeRunTime}, + func(ctx context.Context) { + if !nodeStarted { + // Observe node start timestamp + timeStarted = time.Now() + nodeStartTS.Observe(ctx, float64(timeStarted.Unix())) + nodeStarted = true + } + + totalNodeRunTime.Observe(ctx, time.Since(timeStarted).Seconds()) + }, + ) +} diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 51e0becc4d..3ed16d51ee 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -2,10 +2,18 @@ package nodebuilder import ( "context" + "net/http" + "net/http/httptest" "strconv" "testing" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + + collectormetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" + "google.golang.org/protobuf/proto" + + "strings" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -47,3 +55,83 @@ func TestLifecycle(t *testing.T) { }) } } + +func TestLifecycle_WithMetrics(t *testing.T) { + url, close := StartMockOtelCollectorHTTPServer(t) + defer close() + + otelCollectorURL := strings.ReplaceAll(url, "http://", "") + + var test = []struct { + tp node.Type + coreExpected bool + }{ + {tp: node.Bridge}, + {tp: node.Full}, + {tp: node.Light}, + } + + for i, tt := range test { + t.Run(strconv.Itoa(i), func(t *testing.T) { + node := TestNode( + t, + tt.tp, + WithMetrics( + []otlpmetrichttp.Option{ + otlpmetrichttp.WithEndpoint(otelCollectorURL), + otlpmetrichttp.WithInsecure(), + }, + tt.tp, + ), + ) + require.NotNil(t, node) + require.NotNil(t, node.Config) + require.NotNil(t, node.Host) + require.NotNil(t, node.HeaderServ) + require.NotNil(t, node.StateServ) + require.Equal(t, tt.tp, node.Type) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := node.Start(ctx) + require.NoError(t, err) + + // ensure the state service is running + require.False(t, node.StateServ.IsStopped(ctx)) + + err = node.Stop(ctx) + require.NoError(t, err) + + // ensure the state service is stopped + require.True(t, node.StateServ.IsStopped(ctx)) + }) + } +} + +func StartMockOtelCollectorHTTPServer(t *testing.T) (string, func()) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/v1/metrics" && r.Method != http.MethodPost { + t.Errorf("Expected to request [POST] '/fixedvalue', got: [%s] %s", r.Method, r.URL.Path) + } + + if r.Header.Get("Content-Type") != "application/x-protobuf" { + t.Errorf("Expected Content-Type: application/x-protobuf header, got: %s", r.Header.Get("Content-Type")) + } + + response := collectormetricpb.ExportMetricsServiceResponse{} + rawResponse, _ := proto.Marshal(&response) + contentType := "application/x-protobuf" + status := http.StatusOK + + log.Debug("Responding to otlp POST request") + w.Header().Set("Content-Type", contentType) + w.WriteHeader(status) + _, _ = w.Write(rawResponse) + + log.Debug("Responded to otlp POST request") + })) + + server.EnableHTTP2 = true + return server.URL, server.Close +} diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 18001c4bb1..bfa9f99667 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -13,8 +13,9 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/header" + fraudPkg "github.com/celestiaorg/celestia-node/fraud" + headerPkg "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -38,9 +39,10 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti baseComponents := fx.Options( fx.Supply(metricOpts), fx.Invoke(initializeMetrics), - fx.Invoke(header.WithMetrics), + fx.Invoke(headerPkg.WithMetrics), fx.Invoke(state.WithMetrics), - fx.Invoke(fraud.WithMetrics), + fx.Invoke(fraudPkg.WithMetrics), + fx.Invoke(node.WithMetrics), ) var opts fx.Option diff --git a/share/availability/mocks/availability.go b/share/availability/mocks/availability.go index 51c2acc391..030348f4e4 100644 --- a/share/availability/mocks/availability.go +++ b/share/availability/mocks/availability.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - da "github.com/celestiaorg/celestia-app/pkg/da" gomock "github.com/golang/mock/gomock" peer "github.com/libp2p/go-libp2p/core/peer" + + da "github.com/celestiaorg/celestia-app/pkg/da" ) // MockAvailability is a mock of Availability interface. diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 1c73c9170d..12c36cb015 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -8,11 +8,12 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" - gomock "github.com/golang/mock/gomock" ) // MockGetter is a mock of Getter interface. From 43c10adc3b1f56f612ca46ad06b0690dd68a1cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?joshcs=2Eeth=20=E1=B5=8D=E1=B5=90?= <46639943+jcstein@users.noreply.github.com> Date: Thu, 9 Feb 2023 10:55:59 -0500 Subject: [PATCH 0351/1008] Add cel-key utility to Docker images (#1672) ## Overview Closes https://github.com/celestiaorg/celestia-node/issues/1621 and makes https://github.com/celestiaorg/docs/issues/402 possible ## Checklist - [x] New and updated code has appropriate documentation https://github.com/celestiaorg/docs/pull/453 - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords https://github.com/celestiaorg/celestia-node/issues/1621 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b51ee7fa72..53ed4e96f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY . . ARG TARGETOS TARGETARCH ENV GOOS=$TARGETOS ENV GOARCH=$TARGETARCH -RUN make build +RUN make build && make cel-key FROM ubuntu:20.04 # Default node type can be overwritten in deployment manifest @@ -18,6 +18,7 @@ COPY docker/entrypoint.sh / # Copy in the binary COPY --from=builder /src/build/celestia / +COPY --from=builder /src/./cel-key / EXPOSE 2121 From b53a4ca249db7ef3bb3a8d93482d8fdfaba2a16d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 9 Feb 2023 20:04:35 +0100 Subject: [PATCH 0352/1008] chore(nodebuidler/p2p)!: Upgrade arabica to `arabica-5` (#1725) Upgrades chain ID to arabica 5 and updates genesis hash. TODO: - [x] genesis hash --- nodebuilder/p2p/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 24a5153ec4..07a9a8b0ac 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-4" + Arabica Network = "arabica-5" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha" // Private can be used to set up any private network, including local testing setups. From 849ef45cc1803123a28843231357b940914472ad Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Fri, 10 Feb 2023 15:08:35 +0100 Subject: [PATCH 0353/1008] chore!: bump arabica-5 genesis-hash (#1735) --- nodebuilder/p2p/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 15719acfaa..24464950a5 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "C89DDAB34DB47895DB79F67BF28CE72570D6C586436FC3449284CA411DEFCC53", + Arabica: "896C2935D8688C288870F99300D8A580D07DDA5DAA0D197D33C6557E57E4AF87", Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", Private: "", } From ef59637c0cf3f9f01ff587b37fa842123e641dc7 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 10 Feb 2023 16:35:29 +0200 Subject: [PATCH 0354/1008] bugfix(libs/header): store incoming head in syncer (#1724) ## Overview Closes https://github.com/celestiaorg/celestia-node/issues/1728 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- libs/header/sync/sync.go | 27 +++++++++++++++++---------- libs/header/sync/sync_head.go | 1 + libs/header/sync/sync_test.go | 11 +++++++++-- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 983a841742..8e2bc3444a 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -4,6 +4,7 @@ import ( "context" "errors" "sync" + "sync/atomic" "time" logging "github.com/ipfs/go-log/v2" @@ -42,7 +43,7 @@ type Syncer[H header.Header] struct { // signals to start syncing triggerSync chan struct{} // syncedHead is the latest synced header. - syncedHead H + syncedHead atomic.Pointer[H] // pending keeps ranges of valid new network headers awaiting to be appended to store pending ranges[H] // netReqLk ensures only one network head is requested at any moment @@ -177,17 +178,23 @@ func (s *Syncer[H]) sync(ctx context.Context) { return } - if s.syncedHead.IsZero() { + headPtr := s.syncedHead.Load() + + var header H + if headPtr == nil { head, err := s.store.Head(ctx) if err != nil { log.Errorw("getting head during sync", "err", err) return } - s.syncedHead = head + header = head + } else { + header = *headPtr } - if s.syncedHead.Height() >= newHead.Height() { + + if header.Height() >= newHead.Height() { log.Warnw("sync attempt to an already synced header", - "synced_height", s.syncedHead.Height(), + "synced_height", header.Height(), "attempted_height", newHead.Height(), ) log.Warn("PLEASE REPORT THIS AS A BUG") @@ -195,9 +202,9 @@ func (s *Syncer[H]) sync(ctx context.Context) { } log.Infow("syncing headers", - "from", s.syncedHead.Height(), + "from", header.Height(), "to", newHead.Height()) - err := s.doSync(ctx, s.syncedHead, newHead) + err := s.doSync(ctx, header, newHead) if err != nil { if errors.Is(err, context.Canceled) { // don't log this error as it is normal case of Syncer being stopped @@ -205,14 +212,14 @@ func (s *Syncer[H]) sync(ctx context.Context) { } log.Errorw("syncing headers", - "from", s.syncedHead.Height(), + "from", header.Height(), "to", newHead.Height(), "err", err) return } log.Infow("finished syncing", - "from", s.syncedHead.Height(), + "from", header.Height(), "to", newHead.Height(), "elapsed time", s.state.End.Sub(s.state.Start)) } @@ -257,7 +264,7 @@ func (s *Syncer[H]) processHeaders(ctx context.Context, from, to uint64) (int, e amount, err := s.store.Append(ctx, headers...) if err == nil && amount > 0 { - s.syncedHead = headers[amount-1] + s.syncedHead.Store(&headers[amount-1]) } return amount, err } diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go index 1bdb818c4e..c6a007abd9 100644 --- a/libs/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -112,6 +112,7 @@ func (s *Syncer[H]) incomingNetHead(ctx context.Context, netHead H) pubsub.Valid _, err := s.store.Append(ctx, netHead) if err == nil { // a happy case where we appended maybe head directly, so accept + s.syncedHead.Store(&netHead) return pubsub.ValidationAccept } var nonAdj *header.ErrNonAdjacent diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index 724b432659..0f6526b577 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -99,7 +99,10 @@ func TestSyncCatchUp(t *testing.T) { // 4. assert syncer caught-up have, err := localStore.Head(ctx) require.NoError(t, err) - assert.Equal(t, syncer.syncedHead.Height(), incomingHead.Height()) + headerPtr := syncer.syncedHead.Load() + require.NotNil(t, headerPtr) + head = *headerPtr + assert.Equal(t, head.Height(), incomingHead.Height()) assert.Equal(t, exp.Height()+1, have.Height()) // plus one as we didn't add last header to remoteStore assert.Empty(t, syncer.pending.Head()) @@ -157,7 +160,11 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { require.NoError(t, err) _, err = localStore.GetByHeight(ctx, 43) require.NoError(t, err) - require.Equal(t, syncer.syncedHead.Height(), int64(43)) + + headerPtr := syncer.syncedHead.Load() + require.NotNil(t, headerPtr) + lastHead := *headerPtr + require.Equal(t, lastHead.Height(), int64(43)) exp, err := remoteStore.Head(ctx) require.NoError(t, err) From 4c98c20847446cf5635269ed7c9f8b2a83a5e7fc Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:20:43 +0100 Subject: [PATCH 0355/1008] chore: bump celestia-app to v0.12.0-rc7 (#1723) ## Overview very minor bump, with the only celestia-node relevant change being that we can remove the secondary `config.Seal` in this repo now that we're doing it in a single spot in the application. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- cmd/cel-key/main.go | 6 ------ core/config.go | 16 ---------------- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 core/config.go diff --git a/cmd/cel-key/main.go b/cmd/cel-key/main.go index 852ae4f4ed..fdfb8d0d93 100644 --- a/cmd/cel-key/main.go +++ b/cmd/cel-key/main.go @@ -8,7 +8,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/spf13/cobra" @@ -59,11 +58,6 @@ func main() { } func run() error { - cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) - cfg.Seal() - ctx := context.WithValue(context.Background(), client.ClientContextKey, &initClientCtx) return rootCmd.ExecuteContext(ctx) } diff --git a/core/config.go b/core/config.go deleted file mode 100644 index 9d9b82630c..0000000000 --- a/core/config.go +++ /dev/null @@ -1,16 +0,0 @@ -package core - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/celestiaorg/celestia-app/app" -) - -func init() { - // This is necessary to ensure that the account addresses are correctly prefixed - // as in the celestia application. - cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) - cfg.Seal() -} diff --git a/go.mod b/go.mod index 8e03eeadb8..64f09683f9 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/celestiaorg/celestia-app v0.12.0-rc6 + github.com/celestiaorg/celestia-app v0.12.0-rc7 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index ed3354bc9b..fde8f37ed5 100644 --- a/go.sum +++ b/go.sum @@ -201,8 +201,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.0-rc6 h1:MQ1Nb+awcnqK+EHpsH+NlBLT6F5Mph6Z3VnzgXUlxAU= -github.com/celestiaorg/celestia-app v0.12.0-rc6/go.mod h1:4qMJfFq0Yr9At8dpP271seswj1wav20vqNfrnBlFxmo= +github.com/celestiaorg/celestia-app v0.12.0-rc7 h1:83Ig3eTc0rAapUZyc4RbygJFLVfZ4z3TGgloN0fIXJw= +github.com/celestiaorg/celestia-app v0.12.0-rc7/go.mod h1:60QSrI+bbGzL/m80ocWrYpdg/ECLxmVvVets6vPuG8k= github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 h1:8zE523TUe5W33/nheJ9umHF2d1q6iHQlqJfMXMTPe3k= github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23/go.mod h1:fGDSg7aw2OH/Uze1zymop0x0y1kAPEO9OII2A2cb99Q= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= From 8b9937fa04b9702bfb4f4b5604f05aa254713644 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:10:23 +0100 Subject: [PATCH 0356/1008] chore(libs/header/p2p): Fixing some debug logs (#1741) self explanatory boi --- libs/header/p2p/exchange.go | 1 + libs/header/p2p/session.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index ca464da7eb..50459534c0 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -68,6 +68,7 @@ func NewExchange[H header.Header]( func (ex *Exchange[H]) Start(context.Context) error { ex.ctx, ex.cancel = context.WithCancel(context.Background()) + log.Infow("client: starting client", "protocol ID", ex.protocolID) for _, p := range ex.trustedPeers { // Try to pre-connect to trusted peers. diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index e00cc8a05f..d637acd9c1 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -139,12 +139,12 @@ func (s *session[H]) doRequest( r, size, duration, err := sendMessage(ctx, s.host, stat.peerID, s.protocolID, req) if err != nil { - log.Errorw("requesting headers from peer failed.", "failed peer", stat.peerID, "err", err) + log.Errorw("requesting headers from peer failed", "failed peer", stat.peerID, "err", err) select { case <-s.ctx.Done(): case s.reqCh <- req: stat.decreaseScore() - log.Debug("Retrying the request from different peer") + log.Debug("retrying the request from different peer") } return } @@ -162,7 +162,7 @@ func (s *session[H]) doRequest( } return } - log.Debugw("request headers from peer succeed ", "from", s.host.ID(), "pID", stat.peerID, "amount", req.Amount) + log.Debugw("request headers from peer succeeded", "peer", stat.peerID, "amount", req.Amount) // send headers to the channel, update peer stats and return peer to the queue, so it can be // re-used in case if there are other requests awaiting headers <- h From f66c36fbb26849095ebaed6c60a490d98e8c611b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Tue, 14 Feb 2023 10:34:37 +0100 Subject: [PATCH 0357/1008] feat: Use common Docker Build Pipeline (#1737) --- .github/workflows/docker-build-common.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/docker-build-common.yml diff --git a/.github/workflows/docker-build-common.yml b/.github/workflows/docker-build-common.yml new file mode 100644 index 0000000000..a0f10a9932 --- /dev/null +++ b/.github/workflows/docker-build-common.yml @@ -0,0 +1,22 @@ +name: Docker Build & Publish + +# Trigger on all push events, new semantic version tags, and all PRs +on: + push: + branches: + - "**" + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" + pull_request: + +jobs: + docker-security-build: + permissions: + contents: write + packages: write + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@main # yamllint disable-line rule:line-length + with: + dockerfile: Dockerfile From 381511dccf9aa54c35cc9d22c03a4226ccc5e41e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 15 Feb 2023 18:59:34 +0100 Subject: [PATCH 0358/1008] feat(nodebuilder/gateway): Add warn log if gateway is enabled so users know to use with caution (#1753) Since the gateway's endpoints are not authenticated and it is disabled by default, we should warn users to use the gateway with caution if gateway is enabled. Closes #1752 --- nodebuilder/gateway/module.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodebuilder/gateway/module.go b/nodebuilder/gateway/module.go index 4cdf325dc0..b3070e01a6 100644 --- a/nodebuilder/gateway/module.go +++ b/nodebuilder/gateway/module.go @@ -21,6 +21,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { if !cfg.Enabled { return fx.Options() } + // NOTE @distractedm1nd @renaynay: Remove whenever/if we decide to add auth to gateway + log.Warn("Gateway is enabled, however gateway endpoints are not authenticated. Use with caution!") baseComponents := fx.Options( fx.Supply(cfg), From bd96b3dab06e7d733cf0e62adb94802e2007a407 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 15 Feb 2023 21:45:07 +0200 Subject: [PATCH 0359/1008] improvement(libs/header): check for requested height before fetching it from the store (#1706) --- libs/header/interface.go | 3 +++ libs/header/mocks/store.go | 4 ++++ libs/header/p2p/exchange_test.go | 33 +++++--------------------------- libs/header/p2p/server.go | 20 ++++++++++++------- libs/header/store/store.go | 4 ++++ 5 files changed, 29 insertions(+), 35 deletions(-) diff --git a/libs/header/interface.go b/libs/header/interface.go index 387159d92c..96cdb39628 100644 --- a/libs/header/interface.go +++ b/libs/header/interface.go @@ -87,6 +87,9 @@ type Store[H Header] interface { // Has checks whether Header is already stored. Has(context.Context, Hash) (bool, error) + // HasAt checks whether Header at the given height is already stored. + HasAt(context.Context, uint64) bool + // Append stores and verifies the given Header(s). // It requires them to be adjacent and in ascending order, // as it applies them contiguously on top of the current head height. diff --git a/libs/header/mocks/store.go b/libs/header/mocks/store.go index a6227a60c9..8d3667d131 100644 --- a/libs/header/mocks/store.go +++ b/libs/header/mocks/store.go @@ -86,6 +86,10 @@ func (m *MockStore[H]) Has(context.Context, header.Hash) (bool, error) { return false, nil } +func (m *MockStore[H]) HasAt(_ context.Context, height uint64) bool { + return height != 0 && m.HeadHeight >= int64(height) +} + func (m *MockStore[H]) Append(ctx context.Context, headers ...H) (int, error) { for _, header := range headers { m.Headers[header.Height()] = header diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index b27c760949..b816d65f1e 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -347,19 +347,19 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { ) require.NoError(t, err) // change store implementation - serverSideEx.getter = &timedOutStore{exchg.Params.RequestTimeout} + serverSideEx.store = &timedOutStore{timeout: exchg.Params.RequestTimeout} require.NoError(t, serverSideEx.Start(context.Background())) t.Cleanup(func() { serverSideEx.Stop(context.Background()) //nolint:errcheck }) + prevScore := exchg.peerTracker.trackedPeers[host1.ID()].score() exchg.peerTracker.peerLk.Lock() exchg.peerTracker.trackedPeers[host2.ID()] = &peerStat{peerID: host2.ID(), peerScore: 200} exchg.peerTracker.peerLk.Unlock() _, err = exchg.GetRangeByHeight(context.Background(), 1, 3) require.NoError(t, err) - // ensure that peerScore for the first peer was decrease by 20% - newPeerScore := exchg.peerTracker.trackedPeers[host2.ID()].score() - assert.Less(t, newPeerScore, float32(200)) + newPeerScore := exchg.peerTracker.trackedPeers[host1.ID()].score() + assert.NotEqual(t, newPeerScore, prevScore) } func createMocknet(t *testing.T, amount int) []libhost.Host { @@ -400,34 +400,11 @@ func createP2PExAndServer( } type timedOutStore struct { + headerMock.MockStore[*test.DummyHeader] timeout time.Duration } -func (t *timedOutStore) Head(context.Context) (*test.DummyHeader, error) { - // TODO implement me - panic("implement me") -} - -func (t *timedOutStore) Get(context.Context, header.Hash) (*test.DummyHeader, error) { - // TODO implement me - panic("implement me") -} - -func (t *timedOutStore) GetByHeight(context.Context, uint64) (*test.DummyHeader, error) { - // TODO implement me - panic("implement me") -} - func (t *timedOutStore) GetRangeByHeight(_ context.Context, _, _ uint64) ([]*test.DummyHeader, error) { time.Sleep(t.timeout + 1) return []*test.DummyHeader{}, nil } - -func (t *timedOutStore) GetVerifiedRange( - context.Context, - *test.DummyHeader, - uint64, -) ([]*test.DummyHeader, error) { - // TODO implement me - panic("implement me") -} diff --git a/libs/header/p2p/server.go b/libs/header/p2p/server.go index d7d6b81d9e..e9d54aaa16 100644 --- a/libs/header/p2p/server.go +++ b/libs/header/p2p/server.go @@ -28,8 +28,8 @@ var ( type ExchangeServer[H header.Header] struct { protocolID protocol.ID - host host.Host - getter header.Getter[H] + host host.Host + store header.Store[H] ctx context.Context cancel context.CancelFunc @@ -41,7 +41,7 @@ type ExchangeServer[H header.Header] struct { // header-related requests. func NewExchangeServer[H header.Header]( host host.Host, - getter header.Getter[H], + store header.Store[H], opts ...Option[ServerParameters], ) (*ExchangeServer[H], error) { params := DefaultServerParameters() @@ -55,7 +55,7 @@ func NewExchangeServer[H header.Header]( return &ExchangeServer[H]{ protocolID: protocolID(params.protocolSuffix), host: host, - getter: getter, + store: store, Params: params, }, nil } @@ -164,7 +164,7 @@ func (serv *ExchangeServer[H]) handleRequestByHash(hash []byte) ([]H, error) { )) defer span.End() - h, err := serv.getter.Get(ctx, hash) + h, err := serv.store.Get(ctx, hash) if err != nil { log.Errorw("server: getting header by hash", "hash", header.Hash(hash).String(), "err", err) span.SetStatus(codes.Error, err.Error()) @@ -201,7 +201,13 @@ func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { } log.Debugw("server: handling headers request", "from", from, "to", to) - headersByRange, err := serv.getter.GetRangeByHeight(ctx, from, to) + if !serv.store.HasAt(ctx, to-1) { + span.SetStatus(codes.Error, header.ErrNotFound.Error()) + log.Debugw("server: requested headers not stored", "from", from, "to", to) + return nil, header.ErrNotFound + } + + headersByRange, err := serv.store.GetRangeByHeight(ctx, from, to) if err != nil { span.SetStatus(codes.Error, err.Error()) if errors.Is(err, context.DeadlineExceeded) { @@ -226,7 +232,7 @@ func (serv *ExchangeServer[H]) handleHeadRequest() ([]H, error) { ctx, span := tracer.Start(ctx, "request-head") defer span.End() - head, err := serv.getter.Head(ctx) + head, err := serv.store.Head(ctx) if err != nil { log.Errorw("server: getting head", "err", err) span.SetStatus(codes.Error, err.Error()) diff --git a/libs/header/store/store.go b/libs/header/store/store.go index 59c8897323..1f36fa839a 100644 --- a/libs/header/store/store.go +++ b/libs/header/store/store.go @@ -283,6 +283,10 @@ func (s *Store[H]) Has(ctx context.Context, hash header.Hash) (bool, error) { return s.ds.Has(ctx, datastore.NewKey(hash.String())) } +func (s *Store[H]) HasAt(_ context.Context, height uint64) bool { + return height != uint64(0) && s.Height() >= height +} + func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { lh := len(headers) if lh == 0 { From 736761db03d74d05731720ee4d12ffda01ed810a Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 8 Feb 2023 20:58:50 +0300 Subject: [PATCH 0360/1008] feat(share/p2p/peers): Implement peer manager (#1707) implement peer management for shrex.Getter Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- das/daser_test.go | 3 +- nodebuilder/share/module.go | 10 +- share/availability.go | 4 +- share/availability/cache/availability.go | 4 +- share/availability/cache/availability_test.go | 3 +- share/availability/discovery/discovery.go | 15 +- share/availability/full/availability.go | 4 +- share/availability/light/availability.go | 32 +- share/availability/light/testing.go | 7 +- share/availability/mocks/availability.go | 14 +- share/p2p/peers/manager.go | 328 ++++++++++++++ share/p2p/peers/manager_test.go | 402 ++++++++++++++++++ share/p2p/peers/pool.go | 156 +++++++ share/p2p/peers/pool_test.go | 145 +++++++ 14 files changed, 1048 insertions(+), 79 deletions(-) create mode 100644 share/p2p/peers/manager.go create mode 100644 share/p2p/peers/manager_test.go create mode 100644 share/p2p/peers/pool.go create mode 100644 share/p2p/peers/pool_test.go diff --git a/das/daser_test.go b/das/daser_test.go index ab89e76b02..6333f4a781 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -11,7 +11,6 @@ import ( ds_sync "github.com/ipfs/go-datastore/sync" mdutils "github.com/ipfs/go-merkledag/test" pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -191,7 +190,7 @@ func TestDASerSampleTimeout(t *testing.T) { getter := getterStub{} avail := mocks.NewMockAvailability(gomock.NewController(t)) avail.EXPECT().SharesAvailable(gomock.Any(), gomock.Any()).DoAndReturn( - func(sampleCtx context.Context, h *share.Root, peers ...peer.ID) error { + func(sampleCtx context.Context, h *share.Root) error { select { case <-sampleCtx.Done(): return sampleCtx.Err() diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 0db7fab2c3..e39e0c84d0 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -38,15 +38,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, - fx.Provide(fx.Annotate( - light.NewShareAvailability, - fx.OnStart(func(ctx context.Context, avail *light.ShareAvailability) error { - return avail.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, avail *light.ShareAvailability) error { - return avail.Stop(ctx) - }), - )), + fx.Provide(fx.Annotate(light.NewShareAvailability)), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability fx.Provide(cacheAvailability[*light.ShareAvailability]), diff --git a/share/availability.go b/share/availability.go index 190fb31dd1..045de350e8 100644 --- a/share/availability.go +++ b/share/availability.go @@ -5,8 +5,6 @@ import ( "errors" "time" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/celestiaorg/celestia-app/pkg/da" ) @@ -28,7 +26,7 @@ type Root = da.DataAvailabilityHeader type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on // the Network by requesting the EDS from the provided peers. - SharesAvailable(context.Context, *Root, ...peer.ID) error + SharesAvailable(context.Context, *Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. // TODO(@Wondertan): Merge with SharesAvailable method, eventually diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index 80ff4822df..e9ee88f867 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -9,10 +9,8 @@ import ( "github.com/ipfs/go-datastore/autobatch" "github.com/ipfs/go-datastore/namespace" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" ) @@ -54,7 +52,7 @@ func NewShareAvailability(avail share.Availability, ds datastore.Batching) *Shar } // SharesAvailable will store, upon success, the hash of the given Root to disk. -func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root, _ ...peer.ID) error { +func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { // short-circuit if the given root is minimum DAH of an empty data square if isMinRoot(root) { return nil diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index e578c0db6f..47df434c06 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -10,7 +10,6 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" mdutils "github.com/ipfs/go-merkledag/test" - "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -123,7 +122,7 @@ type dummyAvailability struct { // SharesAvailable should only be called once, if called more than once, return // error. -func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root, _ ...peer.ID) error { +func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root) error { if root == &invalidHeader { return fmt.Errorf("invalid header") } diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index fc59c7bf41..f8fc129724 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -2,7 +2,6 @@ package discovery import ( "context" - "sync" "time" logging "github.com/ipfs/go-log/v2" @@ -47,8 +46,6 @@ type Discovery struct { advertiseInterval time.Duration // onUpdatedPeers will be called on peer set changes onUpdatedPeers OnUpdatedPeers - // ensureIsRunning allows only one ensurePeers process to be running - ensurePeersOnce sync.Once } type OnUpdatedPeers func(peerID peer.ID, isAdded bool) @@ -70,14 +67,14 @@ func NewDiscovery( discInterval, advertiseInterval, func(peer.ID, bool) {}, - sync.Once{}, } } -// WithOnPeersUpdate adds OnPeersUpdate callback call on every update of discovered peers list. +// WithOnPeersUpdate chains OnPeersUpdate callbacks on every update of discovered peers list. func (d *Discovery) WithOnPeersUpdate(f OnUpdatedPeers) { + prev := d.onUpdatedPeers d.onUpdatedPeers = func(peerID peer.ID, isAdded bool) { - d.onUpdatedPeers(peerID, isAdded) + prev(peerID, isAdded) f(peerID, isAdded) } } @@ -111,12 +108,6 @@ func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer // It starts peer discovery every 30 seconds until peer cache reaches peersLimit. // Discovery is restarted if any previously connected peers disconnect. func (d *Discovery) EnsurePeers(ctx context.Context) { - d.ensurePeersOnce.Do(func() { - go d.ensurePeers(ctx) - }) -} - -func (d *Discovery) ensurePeers(ctx context.Context) { if d.peersLimit == 0 { log.Warn("peers limit is set to 0. Skipping discovery...") return diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 9a0ce67f70..31d72d0111 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -6,7 +6,6 @@ import ( ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" @@ -37,7 +36,6 @@ func (fa *ShareAvailability) Start(context.Context) error { fa.cancel = cancel go fa.disc.Advertise(ctx) - fa.disc.EnsurePeers(ctx) return nil } @@ -48,7 +46,7 @@ func (fa *ShareAvailability) Stop(context.Context) error { // SharesAvailable reconstructs the data committed to the given Root by requesting // enough Shares from the network. -func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root, _ ...peer.ID) error { +func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) defer cancel() // we assume the caller of this method has already performed basic validation on the diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 3519ae1c8a..7fd13adb4e 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -7,10 +7,8 @@ import ( ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -22,40 +20,16 @@ var log = logging.Logger("share/light") // on the network doing sampling over the same Root to collectively verify its availability. type ShareAvailability struct { getter share.Getter - // disc discovers new full nodes in the network. - // it is not allowed to call advertise for light nodes (Full nodes only). - disc *discovery.Discovery - cancel context.CancelFunc } // NewShareAvailability creates a new light Availability. -func NewShareAvailability( - getter share.Getter, - disc *discovery.Discovery, -) *ShareAvailability { - la := &ShareAvailability{ - getter: getter, - disc: disc, - } - return la -} - -func (la *ShareAvailability) Start(context.Context) error { - ctx, cancel := context.WithCancel(context.Background()) - la.cancel = cancel - - la.disc.EnsurePeers(ctx) - return nil -} - -func (la *ShareAvailability) Stop(context.Context) error { - la.cancel() - return nil +func NewShareAvailability(getter share.Getter) *ShareAvailability { + return &ShareAvailability{getter} } // SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given // Root. This way SharesAvailable subjectively verifies that Shares are available. -func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root, _ ...peer.ID) error { +func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { log.Debugw("Validate availability", "root", dah.Hash()) // We assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go index 0072f226c6..59163d6356 100644 --- a/share/availability/light/testing.go +++ b/share/availability/light/testing.go @@ -2,15 +2,11 @@ package light import ( "testing" - "time" "github.com/ipfs/go-blockservice" mdutils "github.com/ipfs/go-merkledag/test" - routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" - "github.com/libp2p/go-libp2p/p2p/discovery/routing" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -46,8 +42,7 @@ func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { } func TestAvailability(getter share.Getter) *ShareAvailability { - disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) - return NewShareAvailability(getter, disc) + return NewShareAvailability(getter) } func SubNetNode(sn *availability_test.SubNet) *availability_test.TestNode { diff --git a/share/availability/mocks/availability.go b/share/availability/mocks/availability.go index 030348f4e4..ff4b8e1328 100644 --- a/share/availability/mocks/availability.go +++ b/share/availability/mocks/availability.go @@ -9,7 +9,6 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - peer "github.com/libp2p/go-libp2p/core/peer" da "github.com/celestiaorg/celestia-app/pkg/da" ) @@ -52,20 +51,15 @@ func (mr *MockAvailabilityMockRecorder) ProbabilityOfAvailability(arg0 interface } // SharesAvailable mocks base method. -func (m *MockAvailability) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 ...peer.ID) error { +func (m *MockAvailability) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader) error { m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "SharesAvailable", varargs...) + ret := m.ctrl.Call(m, "SharesAvailable", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // SharesAvailable indicates an expected call of SharesAvailable. -func (mr *MockAvailabilityMockRecorder) SharesAvailable(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockAvailabilityMockRecorder) SharesAvailable(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SharesAvailable", reflect.TypeOf((*MockAvailability)(nil).SharesAvailable), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SharesAvailable", reflect.TypeOf((*MockAvailability)(nil).SharesAvailable), arg0, arg1) } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go new file mode 100644 index 0000000000..52e929c7a2 --- /dev/null +++ b/share/p2p/peers/manager.go @@ -0,0 +1,328 @@ +package peers + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/libp2p/go-libp2p/p2p/net/conngater" + + logging "github.com/ipfs/go-log/v2" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" +) + +const ( + ResultSuccess syncResult = iota + ResultFail + ResultPeerMisbehaved + + gcInterval = time.Second * 30 +) + +var log = logging.Logger("shrex/peer-manager") + +// Manager keeps track of peers coming from shrex.Sub and from discovery +type Manager struct { + lock sync.Mutex + + // header subscription is necessary in order to validate the inbound eds hash + headerSub libhead.Subscriber[*header.ExtendedHeader] + shrexSub *shrexsub.PubSub + disc *discovery.Discovery + host host.Host + connGater *conngater.BasicConnectionGater + + // pools collecting peers from shrexSub + pools map[string]*syncPool + poolSyncTimeout time.Duration + // fullNodes collects full nodes peer.ID found via discovery + fullNodes *pool + + // hashes that are not in the chain + blacklistedHashes map[string]bool + + cancel context.CancelFunc + done chan struct{} +} + +// DoneFunc is a function performs an action based on the given syncResult. +type DoneFunc func(result syncResult) + +type syncResult int + +type syncPool struct { + *pool + + // isValidatedDataHash indicates if datahash was validated by receiving corresponding extended + // header from headerSub + isValidatedDataHash atomic.Bool + createdAt time.Time +} + +func NewManager( + headerSub libhead.Subscriber[*header.ExtendedHeader], + shrexSub *shrexsub.PubSub, + discovery *discovery.Discovery, + host host.Host, + connGater *conngater.BasicConnectionGater, + syncTimeout time.Duration, +) *Manager { + s := &Manager{ + headerSub: headerSub, + shrexSub: shrexSub, + disc: discovery, + connGater: connGater, + host: host, + pools: make(map[string]*syncPool), + poolSyncTimeout: syncTimeout, + fullNodes: newPool(), + blacklistedHashes: make(map[string]bool), + done: make(chan struct{}), + } + + discovery.WithOnPeersUpdate( + func(peerID peer.ID, isAdded bool) { + if isAdded && !s.peerIsBlacklisted(peerID) { + s.fullNodes.add(peerID) + return + } + s.fullNodes.remove(peerID) + }) + + return s +} + +func (s *Manager) Start(startCtx context.Context) error { + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = cancel + + err := s.shrexSub.Start(startCtx) + if err != nil { + return fmt.Errorf("starting shrexsub: %w", err) + } + + err = s.shrexSub.AddValidator(s.validate) + if err != nil { + return fmt.Errorf("registering validator: %w", err) + } + + _, err = s.shrexSub.Subscribe() + if err != nil { + return fmt.Errorf("subscribing to shrexsub: %w", err) + } + + sub, err := s.headerSub.Subscribe() + if err != nil { + return fmt.Errorf("subscribing to headersub: %w", err) + } + + go s.disc.EnsurePeers(ctx) + go s.subscribeHeader(ctx, sub) + go s.GC(ctx) + + return nil +} + +func (s *Manager) Stop(ctx context.Context) error { + s.cancel() + select { + case <-s.done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// Peer returns peer collected from shrex.Sub for given datahash if any available. +// If there is none, it will look for fullnodes collected from discovery. If there is no discovered +// full nodes, it will wait until any peer appear in either source or timeout happen. +// After fetching data using given peer, caller is required to call returned DoneFunc +func (s *Manager) Peer( + ctx context.Context, datahash share.DataHash, +) (peer.ID, DoneFunc, error) { + p := s.getOrCreatePool(datahash.String()) + p.markValidated() + + // first, check if a peer is available for the given datahash + peerID, ok := p.tryGet() + if ok { + // some pools could still have blacklisted peers in storage + if s.peerIsBlacklisted(peerID) { + p.remove(peerID) + return s.Peer(ctx, datahash) + } + return peerID, s.doneFunc(datahash, peerID), nil + } + + // if no peer for datahash is currently available, try to use full node + // obtained from discovery + peerID, ok = s.fullNodes.tryGet() + if ok { + return peerID, s.doneFunc(datahash, peerID), nil + } + + // no peers are available right now, wait for the first one + select { + case peerID = <-p.next(ctx): + return peerID, s.doneFunc(datahash, peerID), nil + case peerID = <-s.fullNodes.next(ctx): + return peerID, s.doneFunc(datahash, peerID), nil + case <-ctx.Done(): + return "", nil, ctx.Err() + } +} + +func (s *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { + return func(result syncResult) { + switch result { + case ResultSuccess: + s.deletePool(datahash.String()) + case ResultFail: + case ResultPeerMisbehaved: + s.blacklistPeers(peerID) + } + } +} + +// subscribeHeader takes datahash from received header and validates corresponding peer pool. +func (s *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscription[*header.ExtendedHeader]) { + defer close(s.done) + defer headerSub.Cancel() + + for { + h, err := headerSub.NextHeader(ctx) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + log.Errorw("get next header from sub", "err", err) + continue + } + + s.getOrCreatePool(h.DataHash.String()).markValidated() + } +} + +// Validate will collect peer.ID into corresponding peer pool +func (s *Manager) validate(ctx context.Context, peerID peer.ID, hash share.DataHash) pubsub.ValidationResult { + // messages broadcasted from self should bypass the validation with Accept + if peerID == s.host.ID() { + return pubsub.ValidationAccept + } + + // punish peer for sending invalid hash if it has misbehaved in the past + if s.hashIsBlacklisted(hash) || s.peerIsBlacklisted(peerID) { + return pubsub.ValidationReject + } + + s.getOrCreatePool(hash.String()).add(peerID) + return pubsub.ValidationIgnore +} + +func (s *Manager) getOrCreatePool(datahash string) *syncPool { + s.lock.Lock() + defer s.lock.Unlock() + + p, ok := s.pools[datahash] + if !ok { + p = &syncPool{ + pool: newPool(), + createdAt: time.Now(), + } + s.pools[datahash] = p + } + + return p +} + +func (s *Manager) deletePool(datahash string) { + s.lock.Lock() + defer s.lock.Unlock() + delete(s.pools, datahash) +} + +func (s *Manager) blacklistPeers(peerIDs ...peer.ID) { + for _, peerID := range peerIDs { + s.fullNodes.remove(peerID) + // add peer to the blacklist, so we can't connect to it in the future. + err := s.connGater.BlockPeer(peerID) + if err != nil { + log.Debugw("blocking peer failed", "peer_id", peerID, "err", err) + } + // close connections to peer. + err = s.host.Network().ClosePeer(peerID) + if err != nil { + log.Debugw("closing connection with peer failed", "peer_id", peerID, "err", err) + } + } +} + +func (s *Manager) peerIsBlacklisted(peerID peer.ID) bool { + return !s.connGater.InterceptPeerDial(peerID) +} + +func (s *Manager) hashIsBlacklisted(hash share.DataHash) bool { + s.lock.Lock() + defer s.lock.Unlock() + return s.blacklistedHashes[hash.String()] +} + +func (p *syncPool) markValidated() { + p.isValidatedDataHash.Store(true) +} + +func (s *Manager) GC(ctx context.Context) { + ticker := time.NewTicker(gcInterval) + defer ticker.Stop() + + var blacklist []peer.ID + for { + blacklist = s.cleanUp() + s.blacklistPeers(blacklist...) + + select { + case <-ticker.C: + case <-ctx.Done(): + return + } + } +} + +func (s *Manager) cleanUp() []peer.ID { + s.lock.Lock() + defer s.lock.Unlock() + + addToBlackList := make(map[peer.ID]struct{}) + for h, p := range s.pools { + if time.Since(p.createdAt) > s.poolSyncTimeout && !p.isValidatedDataHash.Load() { + log.Debug("blacklisting datahash with all corresponding peers", + "datahash", h, + "peer_list", p.peersList) + // blacklist hash + delete(s.pools, h) + s.blacklistedHashes[h] = true + + // blacklist peers + for _, peer := range p.peersList { + addToBlackList[peer] = struct{}{} + } + } + } + + blacklist := make([]peer.ID, 0, len(addToBlackList)) + for peerID := range addToBlackList { + blacklist = append(blacklist, peerID) + } + return blacklist +} diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go new file mode 100644 index 0000000000..2d78e1436c --- /dev/null +++ b/share/p2p/peers/manager_test.go @@ -0,0 +1,402 @@ +package peers + +import ( + "context" + sync2 "sync" + "testing" + "time" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "github.com/libp2p/go-libp2p/p2p/net/conngater" + + dht "github.com/libp2p/go-libp2p-kad-dht" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" +) + +// TODO: add broadcast to tests +func TestManager(t *testing.T) { + t.Run("validate datahash by headerSub", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) + t.Cleanup(cancel) + + // create headerSub mock + h := testHeader() + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + // wait until header is requested from header sub + err = headerSub.wait(ctx, 1) + require.NoError(t, err) + + // check validation + require.True(t, manager.pools[h.DataHash.String()].isValidatedDataHash.Load()) + stopManager(t, manager) + }) + + t.Run("validate datahash by shrex.Getter", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + h := testHeader() + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + peerID := peer.ID("peer1") + result := manager.validate(ctx, peerID, h.DataHash.Bytes()) + require.Equal(t, pubsub.ValidationIgnore, result) + + pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + require.NoError(t, err) + require.Equal(t, peerID, pID) + + // check pool validation + require.True(t, manager.pools[h.DataHash.String()].isValidatedDataHash.Load()) + + done(ResultSuccess) + // pool should be removed after success + require.Len(t, manager.pools, 0) + }) + + t.Run("validator", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) + t.Cleanup(cancel) + + // create headerSub mock + h := testHeader() + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + result := manager.validate(ctx, manager.host.ID(), h.DataHash.Bytes()) + require.Equal(t, pubsub.ValidationAccept, result) + + peerID := peer.ID("peer1") + result = manager.validate(ctx, peerID, h.DataHash.Bytes()) + require.Equal(t, pubsub.ValidationIgnore, result) + + pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + require.NoError(t, err) + require.Equal(t, peerID, pID) + + // mark peer as misbehaved tp blacklist it + done(ResultPeerMisbehaved) + + // misbehaved should be Rejected + result = manager.validate(ctx, pID, h.DataHash.Bytes()) + require.Equal(t, pubsub.ValidationReject, result) + + stopManager(t, manager) + }) + + t.Run("cleanup", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + // create headerSub mock + h := testHeader() + headerSub := newSubLock(h) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + peerID := peer.ID("peer1") + manager.validate(ctx, peerID, h.DataHash.Bytes()) + // set syncTimeout to 0 to allow cleanup to find outdated datahash + manager.poolSyncTimeout = 0 + + blacklisted := manager.cleanUp() + require.Contains(t, blacklisted, peerID) + require.True(t, manager.hashIsBlacklisted(h.DataHash.Bytes())) + }) + + t.Run("no peers from shrex.Sub, get from discovery", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + // create headerSub mock + h := testHeader() + headerSub := newSubLock(h) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + // add peers to fullnodes, imitating discovery add + peers := []peer.ID{"peer1", "peer2", "peer3"} + manager.fullNodes.add(peers...) + + peerID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + done(ResultSuccess) + require.NoError(t, err) + require.Contains(t, peers, peerID) + + stopManager(t, manager) + }) + + t.Run("no peers from shrex.Sub and from discovery. Wait", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + // create headerSub mock + h := testHeader() + headerSub := newSubLock(h) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + // make sure peers are not returned before timeout + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + t.Cleanup(cancel) + _, _, err = manager.Peer(timeoutCtx, h.DataHash.Bytes()) + require.ErrorIs(t, err, context.DeadlineExceeded) + + peers := []peer.ID{"peer1", "peer2", "peer3"} + + // launch wait routine + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + peerID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + done(ResultSuccess) + require.NoError(t, err) + require.Contains(t, peers, peerID) + }() + + // send peers + manager.fullNodes.add(peers...) + + // wait for peer to be received + select { + case <-doneCh: + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + } + + stopManager(t, manager) + }) + +} + +func TestIntagration(t *testing.T) { + t.Run("get peer from shrexsub", func(t *testing.T) { + nw, err := mocknet.FullMeshConnected(3) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + bnPubSub, err := shrexsub.NewPubSub(ctx, nw.Hosts()[0], "test") + require.NoError(t, err) + + fnPubSub, err := shrexsub.NewPubSub(ctx, nw.Hosts()[1], "test") + require.NoError(t, err) + + require.NoError(t, bnPubSub.Start(ctx)) + require.NoError(t, fnPubSub.Start(ctx)) + + fnPeerManager, err := testManager(ctx, newSubLock()) + require.NoError(t, err) + fnPeerManager.host = nw.Hosts()[1] + + require.NoError(t, fnPubSub.AddValidator(fnPeerManager.validate)) + _, err = fnPubSub.Subscribe() + require.NoError(t, err) + + // broadcast from BN + peerHash := share.DataHash("peer1") + require.NoError(t, bnPubSub.Broadcast(ctx, peerHash)) + + // FN should get message + peerID, _, err := fnPeerManager.Peer(ctx, peerHash) + require.NoError(t, err) + + // check that peerID matched bridge node + require.Equal(t, nw.Hosts()[0].ID(), peerID) + }) + + t.Run("get peer from discovery", func(t *testing.T) { + nw, err := mocknet.FullMeshConnected(3) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + // set up bootstrapper + bsHost := nw.Hosts()[2] + bs := host.InfoFromHost(bsHost) + opts := []dht.Option{ + dht.Mode(dht.ModeAuto), + dht.BootstrapPeers(*bs), + dht.RoutingTableRefreshPeriod(time.Second), + } + + bsOpts := opts + bsOpts = append(bsOpts, + dht.Mode(dht.ModeServer), // it must accept incoming connections + dht.BootstrapPeers(), // no bootstrappers for a bootstrapper ¯\_(ツ)_/¯ + ) + bsRouter, err := dht.New(ctx, bsHost, bsOpts...) + require.NoError(t, err) + require.NoError(t, bsRouter.Bootstrap(ctx)) + + // set up broadcaster node + bnHost := nw.Hosts()[0] + router1, err := dht.New(ctx, bnHost, opts...) + require.NoError(t, err) + bnDisc := discovery.NewDiscovery( + nw.Hosts()[0], + routingdisc.NewRoutingDiscovery(router1), + 10, + time.Second, + time.Second) + + // set up full node / receiver node + fnHost := nw.Hosts()[0] + router2, err := dht.New(ctx, fnHost, opts...) + require.NoError(t, err) + fnDisc := discovery.NewDiscovery( + nw.Hosts()[1], + routingdisc.NewRoutingDiscovery(router2), + 10, + time.Second, + time.Second) + + // hook peer manager to discovery + fnPeerManager := NewManager(nil, nil, fnDisc, nil, nil, time.Minute) + + waitCh := make(chan struct{}) + fnDisc.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { + defer close(waitCh) + // check that obtained peer id is same as BN + require.Equal(t, nw.Hosts()[0].ID(), peerID) + }) + + require.NoError(t, router1.Bootstrap(ctx)) + require.NoError(t, router2.Bootstrap(ctx)) + + go fnDisc.EnsurePeers(ctx) + go bnDisc.Advertise(ctx) + + select { + case <-waitCh: + require.Contains(t, fnPeerManager.fullNodes.peersList, fnHost.ID()) + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + } + }) +} + +func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.ExtendedHeader]) (*Manager, error) { + host, err := mocknet.New().GenPeer() + if err != nil { + return nil, err + } + shrexSub, err := shrexsub.NewPubSub(ctx, host, "test") + if err != nil { + return nil, err + } + disc := discovery.NewDiscovery(nil, + routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + if err != nil { + return nil, err + } + manager := NewManager(headerSub, shrexSub, disc, host, connGater, time.Minute) + err = manager.Start(ctx) + return manager, err +} + +func stopManager(t *testing.T, m *Manager) { + closeCtx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + require.NoError(t, m.Stop(closeCtx)) +} + +func testHeader() *header.ExtendedHeader { + return &header.ExtendedHeader{ + RawHeader: header.RawHeader{}, + } +} + +type subLock struct { + next chan struct{} + wg *sync2.WaitGroup + expected []*header.ExtendedHeader +} + +func (s subLock) wait(ctx context.Context, count int) error { + s.wg.Add(count) + for i := 0; i < count; i++ { + err := s.release(ctx) + if err != nil { + return err + } + } + s.wg.Wait() + return nil +} + +func (s subLock) release(ctx context.Context) error { + select { + case s.next <- struct{}{}: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func newSubLock(expected ...*header.ExtendedHeader) *subLock { + wg := &sync2.WaitGroup{} + wg.Add(1) + return &subLock{ + next: make(chan struct{}), + expected: expected, + wg: wg, + } +} + +func (s *subLock) Subscribe() (libhead.Subscription[*header.ExtendedHeader], error) { + return s, nil +} + +func (s *subLock) AddValidator(f func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult) error { + panic("implement me") +} + +func (s *subLock) NextHeader(ctx context.Context) (*header.ExtendedHeader, error) { + s.wg.Done() + + // wait for call to be unlocked by release + select { + case <-s.next: + h := s.expected[0] + s.expected = s.expected[1:] + return h, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func (s *subLock) Cancel() { +} diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go new file mode 100644 index 0000000000..37664e0d8a --- /dev/null +++ b/share/p2p/peers/pool.go @@ -0,0 +1,156 @@ +package peers + +import ( + "context" + "sync" + + "github.com/libp2p/go-libp2p/core/peer" +) + +const defaultCleanupThreshold = 2 + +// pool stores peers and provides methods for simple round-robin access. +type pool struct { + m sync.Mutex + peersList []peer.ID + active map[peer.ID]bool + activeCount int + nextIdx int + + hasPeer bool + hasPeerCh chan struct{} + + cleanupThreshold int +} + +// newPool returns new empty pool. +func newPool() *pool { + return &pool{ + peersList: make([]peer.ID, 0), + active: make(map[peer.ID]bool), + hasPeerCh: make(chan struct{}), + cleanupThreshold: defaultCleanupThreshold, + } +} + +// tryGet returns peer along with bool flag indicating success of operation. +func (p *pool) tryGet() (peer.ID, bool) { + p.m.Lock() + defer p.m.Unlock() + + if p.activeCount == 0 { + return "", false + } + + start := p.nextIdx + for { + peerID := p.peersList[p.nextIdx] + + p.nextIdx++ + if p.nextIdx == len(p.peersList) { + p.nextIdx = 0 + } + + if alive := p.active[peerID]; alive { + return peerID, true + } + + // full circle passed + if p.nextIdx == start { + return "", false + } + } +} + +// next sends a peer to the returned channel when it becomes available. +func (p *pool) next(ctx context.Context) <-chan peer.ID { + peerCh := make(chan peer.ID, 1) + go func() { + for { + if peerID, ok := p.tryGet(); ok { + peerCh <- peerID + return + } + + select { + case <-p.hasPeerCh: + case <-ctx.Done(): + return + } + } + }() + return peerCh +} + +func (p *pool) add(peers ...peer.ID) { + p.m.Lock() + defer p.m.Unlock() + + for _, peerID := range peers { + alive, ok := p.active[peerID] + if !ok { + p.peersList = append(p.peersList, peerID) + } + + if !ok || !alive { + p.active[peerID] = true + p.activeCount++ + } + } + p.checkHasPeers() +} + +func (p *pool) remove(peers ...peer.ID) { + p.m.Lock() + defer p.m.Unlock() + + for _, peerID := range peers { + if alive, ok := p.active[peerID]; ok && alive { + p.active[peerID] = false + p.activeCount-- + } + } + + // do cleanup if too much garbage + if len(p.peersList) >= p.activeCount+p.cleanupThreshold { + p.cleanup() + } + p.checkHasPeers() +} + +// cleanup will reduce memory footprint of pool. +func (p *pool) cleanup() { + newList := make([]peer.ID, 0, p.activeCount) + for idx, peerID := range p.peersList { + alive := p.active[peerID] + if alive { + newList = append(newList, peerID) + } else { + delete(p.active, peerID) + } + + if idx == p.nextIdx { + // if peer is not active and no more active peers left in list point to first peer + if !alive && len(newList) >= p.activeCount { + p.nextIdx = 0 + continue + } + p.nextIdx = len(newList) + } + } + p.peersList = newList +} + +// checkHasPeers will check and indicate if there are peers in the pool. +func (p *pool) checkHasPeers() { + if p.activeCount > 0 && !p.hasPeer { + p.hasPeer = true + close(p.hasPeerCh) + return + } + + if p.activeCount == 0 && p.hasPeer { + p.hasPeerCh = make(chan struct{}) + p.hasPeer = false + } +} diff --git a/share/p2p/peers/pool_test.go b/share/p2p/peers/pool_test.go new file mode 100644 index 0000000000..0fcfc7e339 --- /dev/null +++ b/share/p2p/peers/pool_test.go @@ -0,0 +1,145 @@ +package peers + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +func TestPool(t *testing.T) { + t.Run("add / remove peers", func(t *testing.T) { + p := newPool() + + peers := []peer.ID{"peer1", "peer1", "peer2", "peer3"} + // adding same peer twice should not produce copies + p.add(peers...) + require.Equal(t, len(peers)-1, p.activeCount) + + p.remove("peer1", "peer2") + require.Equal(t, len(peers)-3, p.activeCount) + + peerID, ok := p.tryGet() + require.True(t, ok) + require.Equal(t, peers[3], peerID) + + p.remove("peer3") + p.remove("peer3") + require.Equal(t, 0, p.activeCount) + _, ok = p.tryGet() + require.False(t, ok) + fmt.Println(p) + }) + + t.Run("round robin", func(t *testing.T) { + p := newPool() + + peers := []peer.ID{"peer1", "peer1", "peer2", "peer3"} + // adding same peer twice should not produce copies + p.add(peers...) + require.Equal(t, 3, p.activeCount) + + peerID, ok := p.tryGet() + require.True(t, ok) + require.Equal(t, peer.ID("peer1"), peerID) + + peerID, ok = p.tryGet() + require.True(t, ok) + require.Equal(t, peer.ID("peer2"), peerID) + + peerID, ok = p.tryGet() + require.True(t, ok) + require.Equal(t, peer.ID("peer3"), peerID) + + peerID, ok = p.tryGet() + require.True(t, ok) + require.Equal(t, peer.ID("peer1"), peerID) + + p.remove("peer2", "peer3") + require.Equal(t, 1, p.activeCount) + + // pointer should skip removed items until found active one + peerID, ok = p.tryGet() + require.True(t, ok) + require.Equal(t, peer.ID("peer1"), peerID) + }) + + t.Run("wait for peer", func(t *testing.T) { + timeout := time.Second + shortCtx, cancel := context.WithTimeout(context.Background(), timeout/10) + t.Cleanup(cancel) + + longCtx, cancel := context.WithTimeout(context.Background(), timeout) + t.Cleanup(cancel) + + p := newPool() + done := make(chan struct{}) + + go func() { + select { + case <-p.next(shortCtx): + case <-shortCtx.Done(): + require.Error(t, shortCtx.Err()) + // unlock longCtx waiter by adding new peer + p.add("peer1") + } + }() + + go func() { + defer close(done) + select { + case peerID := <-p.next(longCtx): + require.Equal(t, peer.ID("peer1"), peerID) + case <-longCtx.Done(): + require.NoError(t, longCtx.Err()) + } + }() + + select { + case <-done: + case <-longCtx.Done(): + require.NoError(t, longCtx.Err()) + } + }) + + t.Run("nextIdx got removed", func(t *testing.T) { + p := newPool() + + peers := []peer.ID{"peer1", "peer2", "peer3"} + p.add(peers...) + p.nextIdx = 2 + p.remove(peers[p.nextIdx]) + + // if previous nextIdx was removed, tryGet should iterate until available peer found + peerID, ok := p.tryGet() + require.True(t, ok) + require.Equal(t, peers[0], peerID) + }) + + t.Run("cleanup", func(t *testing.T) { + p := newPool() + p.cleanupThreshold = 3 + + peers := []peer.ID{"peer1", "peer2", "peer3", "peer4", "peer5"} + p.add(peers...) + require.Equal(t, len(peers), p.activeCount) + + // point to last element that will be removed, to check how pointer will be updated + p.nextIdx = len(peers) - 1 + + // remove some, but not trigger cleanup yet + p.remove(peers[3:]...) + require.Equal(t, len(peers)-2, p.activeCount) + require.Equal(t, len(peers), len(p.active)) + + // trigger cleanup + p.remove(peers[2]) + require.Equal(t, len(peers)-3, p.activeCount) + require.Equal(t, len(peers)-3, len(p.active)) + // nextIdx pointer should be updated + require.Equal(t, 0, p.nextIdx) + }) +} From d91fba30a6930f0cb288e0ac655bec01c4dc946c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:54:48 +0300 Subject: [PATCH 0361/1008] fix(share/peer_manager): fix peer manager integration test (#1721) --- share/p2p/peers/manager_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 2d78e1436c..41518b1918 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -200,8 +200,9 @@ func TestManager(t *testing.T) { } -func TestIntagration(t *testing.T) { +func TestIntegration(t *testing.T) { t.Run("get peer from shrexsub", func(t *testing.T) { + t.SkipNow() nw, err := mocknet.FullMeshConnected(3) require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) @@ -283,7 +284,9 @@ func TestIntagration(t *testing.T) { time.Second) // hook peer manager to discovery - fnPeerManager := NewManager(nil, nil, fnDisc, nil, nil, time.Minute) + connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + require.NoError(t, err) + fnPeerManager := NewManager(nil, nil, fnDisc, nil, connGater, time.Minute) waitCh := make(chan struct{}) fnDisc.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { From 0b0a38a012ab1aafe43e36522e50235cb69457d8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:16:45 +0100 Subject: [PATCH 0362/1008] refactor(core): Store eds to `eds.Store` upon extension (#1632) Replaces #1562 This PR is blocked until eds store is fully integrated + in use in the node. This PR also breaks the `header.ConstructFn` flow -- instead of implicitly storing shares to block store during extension + extended header construction, `header.ConstructFn` now takes an eds and makes an ExtendedHeader from it, but does not perform any storage. It is up to the caller to both extend the square and store the eds. Also, as part of the effort to prevent cyclical dependencies, some test files have been shifted around. More notably, extracting header testing functionality into a package called `headertest`. --- core/eds.go | 26 ++++++++ core/exchange.go | 52 +++++++++++---- core/exchange_test.go | 34 ++++++---- {headertest => core}/header_test.go | 18 ++--- core/listener.go | 33 ++++++--- core/listener_test.go | 71 ++++++++++++++++++-- core/testing.go | 74 --------------------- go.mod | 2 +- header/header.go | 31 +++------ headertest/testing.go | 96 ++++++++++++++++++++++++--- libs/utils/square.go | 8 +++ nodebuilder/core/module.go | 9 +-- nodebuilder/tests/fraud_test.go | 9 ++- nodebuilder/tests/reconstruct_test.go | 4 +- nodebuilder/tests/swamp/swamp.go | 13 ++-- nodebuilder/tests/swamp/swamp_tx.go | 6 +- nodebuilder/tests/sync_test.go | 4 +- share/add.go | 11 +-- share/get_test.go | 7 +- share/ipld/nmt_test.go | 5 +- 20 files changed, 332 insertions(+), 181 deletions(-) create mode 100644 core/eds.go rename {headertest => core}/header_test.go (78%) create mode 100644 libs/utils/square.go diff --git a/core/eds.go b/core/eds.go new file mode 100644 index 0000000000..e996c0111e --- /dev/null +++ b/core/eds.go @@ -0,0 +1,26 @@ +package core + +import ( + "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/da" + appshares "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/libs/utils" +) + +// extendBlock extends the given block data, returning the resulting +// ExtendedDataSquare (EDS). If there are no transactions in the block, +// nil is returned in place of the eds. +func extendBlock(data types.Data) (*rsmt2d.ExtendedDataSquare, error) { + if len(data.Txs) == 0 { + return nil, nil + } + shares, err := appshares.Split(data, true) + if err != nil { + return nil, err + } + size := utils.SquareSize(len(shares)) + return da.ExtendShares(size, appshares.ToBytes(shares)) +} diff --git a/core/exchange.go b/core/exchange.go index bd9d0de645..71dd3b13d6 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -5,27 +5,26 @@ import ( "context" "fmt" - "github.com/ipfs/go-blockservice" - "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share/eds" ) type Exchange struct { - fetcher *BlockFetcher - shareStore blockservice.BlockService - construct header.ConstructFn + fetcher *BlockFetcher + store *eds.Store + construct header.ConstructFn } func NewExchange( fetcher *BlockFetcher, - bServ blockservice.BlockService, + store *eds.Store, construct header.ConstructFn, ) *Exchange { return &Exchange{ - fetcher: fetcher, - shareStore: bServ, - construct: construct, + fetcher: fetcher, + store: store, + construct: construct, } } @@ -86,15 +85,27 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende return nil, err } - eh, err := ce.construct(ctx, block, comm, vals, ce.shareStore) + // extend block data + eds, err := extendBlock(block.Data) + if err != nil { + return nil, err + } + // construct extended header + eh, err := ce.construct(ctx, block, comm, vals, eds) if err != nil { return nil, err } - // verify hashes match if !bytes.Equal(hash, eh.Hash()) { return nil, fmt.Errorf("incorrect hash in header: expected %x, got %x", hash, eh.Hash()) } + // store extended block if it is not empty + if eds != nil { + err = ce.store.Put(ctx, eh.DAH.Hash(), eds) + if err != nil { + return nil, err + } + } return eh, nil } @@ -115,5 +126,22 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 return nil, err } - return ce.construct(ctx, b, comm, vals, ce.shareStore) + // extend block data + eds, err := extendBlock(b.Data) + if err != nil { + return nil, err + } + // create extended header + eh, err := ce.construct(ctx, b, comm, vals, eds) + if err != nil { + return nil, err + } + // only store extended block if it's not empty + if eds != nil { + err = ce.store.Put(ctx, eh.DAH.Hash(), eds) + if err != nil { + return nil, err + } + } + return eh, nil } diff --git a/core/exchange_test.go b/core/exchange_test.go index 0a7b58ccd7..aae310d482 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -1,24 +1,29 @@ package core import ( - "bytes" "context" "testing" + "time" - mdutils "github.com/ipfs/go-merkledag/test" + ds "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share/eds" ) func TestCoreExchange_RequestHeaders(t *testing.T) { - fetcher := createCoreFetcher(t) - store := mdutils.Bserv() + fetcher, _ := createCoreFetcher(t, DefaultTestConfig()) // generate 10 blocks generateBlocks(t, fetcher) + store := createStore(t) + ce := NewExchange(fetcher, store, header.MakeExtendedHeader) headers, err := ce.GetRangeByHeight(context.Background(), 1, 10) require.NoError(t, err) @@ -26,16 +31,21 @@ func TestCoreExchange_RequestHeaders(t *testing.T) { assert.Equal(t, 10, len(headers)) } -func Test_hashMatch(t *testing.T) { - expected := []byte("AE0F153556A4FA5C0B7C3BFE0BAF0EC780C031933B281A8D759BB34C1DA31C56") - mismatch := []byte("57A0D7FE69FE88B3D277C824B3ACB9B60E5E65837A802485DE5CBB278C43576A") - - assert.False(t, bytes.Equal(expected, mismatch)) +func createCoreFetcher(t *testing.T, cfg *TestConfig) (*BlockFetcher, testnode.Context) { + cctx := StartTestNodeWithConfig(t, cfg) + // wait for height 2 in order to be able to start submitting txs (this prevents + // flakiness with accessing account state) + _, err := cctx.WaitForHeightWithTimeout(2, time.Second) // TODO @renaynay: configure? + require.NoError(t, err) + return NewBlockFetcher(cctx.Client), cctx } -func createCoreFetcher(t *testing.T) *BlockFetcher { - client := StartTestNode(t).Client - return NewBlockFetcher(client) +func createStore(t *testing.T) *eds.Store { + t.Helper() + + store, err := eds.NewStore(t.TempDir(), ds_sync.MutexWrap(ds.NewMapDatastore())) + require.NoError(t, err) + return store } func generateBlocks(t *testing.T, fetcher *BlockFetcher) { diff --git a/headertest/header_test.go b/core/header_test.go similarity index 78% rename from headertest/header_test.go rename to core/header_test.go index 3e360504a2..17337405c9 100644 --- a/headertest/header_test.go +++ b/core/header_test.go @@ -1,26 +1,23 @@ -package headertest +package core import ( "context" "testing" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/rand" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/headertest" ) func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - client := core.StartTestNode(t).Client - fetcher := core.NewBlockFetcher(client) - - store := mdutils.Bserv() + client := StartTestNode(t).Client + fetcher := NewBlockFetcher(client) sub, err := fetcher.SubscribeNewBlockEvent(ctx) require.NoError(t, err) @@ -33,14 +30,17 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { comm, val, err := fetcher.GetBlockInfo(ctx, &height) require.NoError(t, err) - headerExt, err := header.MakeExtendedHeader(ctx, b, comm, val, store) + eds, err := extendBlock(b.Data) + require.NoError(t, err) + + headerExt, err := header.MakeExtendedHeader(ctx, b, comm, val, eds) require.NoError(t, err) assert.Equal(t, header.EmptyDAH(), *headerExt.DAH) } func TestMismatchedDataHash_ComputedRoot(t *testing.T) { - header := RandExtendedHeader(t) + header := headertest.RandExtendedHeader(t) header.DataHash = rand.Bytes(32) diff --git a/core/listener.go b/core/listener.go index 7a8a3c8055..6ac3776076 100644 --- a/core/listener.go +++ b/core/listener.go @@ -4,12 +4,12 @@ import ( "context" "fmt" - "github.com/ipfs/go-blockservice" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -22,9 +22,9 @@ import ( // network. type Listener struct { fetcher *BlockFetcher - bServ blockservice.BlockService construct header.ConstructFn + store *eds.Store headerBroadcaster libhead.Broadcaster[*header.ExtendedHeader] hashBroadcaster shrexsub.BroadcastFn @@ -35,16 +35,16 @@ type Listener struct { func NewListener( bcast libhead.Broadcaster[*header.ExtendedHeader], fetcher *BlockFetcher, - extHeaderBroadcaster shrexsub.BroadcastFn, - bServ blockservice.BlockService, + hashBroadcaster shrexsub.BroadcastFn, construct header.ConstructFn, + store *eds.Store, ) *Listener { return &Listener{ - headerBroadcaster: bcast, fetcher: fetcher, - hashBroadcaster: extHeaderBroadcaster, - bServ: bServ, + headerBroadcaster: bcast, + hashBroadcaster: hashBroadcaster, construct: construct, + store: store, } } @@ -65,7 +65,7 @@ func (cl *Listener) Start(ctx context.Context) error { return nil } -// Stop stops the Listener listener loop. +// Stop stops the listener loop. func (cl *Listener) Stop(ctx context.Context) error { cl.cancel() cl.cancel = nil @@ -96,11 +96,26 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { return } - eh, err := cl.construct(ctx, b, comm, vals, cl.bServ) + // extend block data + eds, err := extendBlock(b.Data) + if err != nil { + log.Errorw("listener: extending block data", "err", err) + return + } + // generate extended header + eh, err := cl.construct(ctx, b, comm, vals, eds) if err != nil { log.Errorw("listener: making extended header", "err", err) return } + // store block data if not empty + if eds != nil { + err = cl.store.Put(ctx, eh.DAH.Hash(), eds) + if err != nil { + log.Errorw("listener: storing extended header", "err", err) + return + } + } // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) diff --git a/core/listener_test.go b/core/listener_test.go index a1940b3975..87b32ab995 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -1,11 +1,12 @@ package core import ( + "bytes" "context" "testing" "time" - mdutils "github.com/ipfs/go-merkledag/test" + "github.com/cosmos/cosmos-sdk/client/flags" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/event" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" @@ -15,6 +16,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" network "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -33,21 +35,22 @@ func TestListener(t *testing.T) { require.NoError(t, subscriber.Start(ctx)) subs, err := subscriber.Subscribe() require.NoError(t, err) + t.Cleanup(subs.Cancel) // create one block to store as Head in local store and then unsubscribe from block events - fetcher := createCoreFetcher(t) + fetcher, _ := createCoreFetcher(t, DefaultTestConfig()) eds := createEdsPubSub(ctx, t) // create Listener and start listening - cl := createListener(ctx, t, fetcher, ps0, eds) + cl := createListener(ctx, t, fetcher, ps0, eds, createStore(t)) err = cl.Start(ctx) require.NoError(t, err) edsSubs, err := eds.Subscribe() require.NoError(t, err) - defer edsSubs.Cancel() + t.Cleanup(edsSubs.Cancel) // ensure headers and dataHash are getting broadcasted to the relevant topics - for i := 1; i < 6; i++ { + for i := 0; i < 5; i++ { h, err := subs.NextHeader(ctx) require.NoError(t, err) @@ -62,6 +65,61 @@ func TestListener(t *testing.T) { require.Nil(t, cl.cancel) } +// TestListenerWithNonEmptyBlocks ensures that non-empty blocks are actually +// stored to eds.Store. +func TestListenerWithNonEmptyBlocks(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + + // create mocknet with two pubsub endpoints + ps0, _ := createMocknetWithTwoPubsubEndpoints(ctx, t) + + // create one block to store as Head in local store and then unsubscribe from block events + cfg := DefaultTestConfig() + fetcher, cctx := createCoreFetcher(t, cfg) + eds := createEdsPubSub(ctx, t) + + store := createStore(t) + err := store.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = store.Stop(ctx) + require.NoError(t, err) + }) + + // create Listener and start listening + cl := createListener(ctx, t, fetcher, ps0, eds, store) + err = cl.Start(ctx) + require.NoError(t, err) + + // listen for eds hashes broadcasted through eds-sub and ensure store has + // already stored them + sub, err := eds.Subscribe() + require.NoError(t, err) + t.Cleanup(sub.Cancel) + + empty := header.EmptyDAH() + // TODO extract 16 + for i := 0; i < 16; i++ { + _, err := cctx.FillBlock(16, cfg.Accounts, flags.BroadcastBlock) + require.NoError(t, err) + hash, err := sub.Next(ctx) + require.NoError(t, err) + + if bytes.Equal(empty.Hash(), hash) { + continue + } + + has, err := store.Has(ctx, hash) + require.NoError(t, err) + require.True(t, has) + } + + err = cl.Stop(ctx) + require.NoError(t, err) + require.Nil(t, cl.cancel) +} + func createMocknetWithTwoPubsubEndpoints(ctx context.Context, t *testing.T) (*pubsub.PubSub, *pubsub.PubSub) { net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) @@ -103,6 +161,7 @@ func createListener( fetcher *BlockFetcher, ps *pubsub.PubSub, edsSub *shrexsub.PubSub, + store *eds.Store, ) *Listener { p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, string(network.Private)) err := p2pSub.Start(ctx) @@ -111,7 +170,7 @@ func createListener( require.NoError(t, p2pSub.Stop(ctx)) }) - return NewListener(p2pSub, fetcher, edsSub.Broadcast, mdutils.Bserv(), header.MakeExtendedHeader) + return NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, store) } func createEdsPubSub(ctx context.Context, t *testing.T) *shrexsub.PubSub { diff --git a/core/testing.go b/core/testing.go index 726dfea493..202cf9f0a1 100644 --- a/core/testing.go +++ b/core/testing.go @@ -2,19 +2,15 @@ package core import ( "fmt" - "math/rand" "net" "net/url" - "sort" "testing" - "time" appconfig "github.com/cosmos/cosmos-sdk/server/config" "github.com/stretchr/testify/require" tmconfig "github.com/tendermint/tendermint/config" tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmtypes "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/testutil/testnode" ) @@ -119,76 +115,6 @@ func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { return cctx } -func RandValidator(randPower bool, minPower int64) (*tmtypes.Validator, tmtypes.PrivValidator) { - privVal := tmtypes.NewMockPV() - votePower := minPower - if randPower { - //nolint:gosec // G404: Use of weak random number generator - votePower += int64(rand.Uint32()) - } - pubKey, err := privVal.GetPubKey() - if err != nil { - panic(fmt.Errorf("could not retrieve pubkey %w", err)) - } - val := tmtypes.NewValidator(pubKey, votePower) - return val, privVal -} - -func RandValidatorSet(numValidators int, votingPower int64) (*tmtypes.ValidatorSet, []tmtypes.PrivValidator) { - var ( - valz = make([]*tmtypes.Validator, numValidators) - privValidators = make([]tmtypes.PrivValidator, numValidators) - ) - - for i := 0; i < numValidators; i++ { - val, privValidator := RandValidator(false, votingPower) - valz[i] = val - privValidators[i] = privValidator - } - - sort.Sort(tmtypes.PrivValidatorsByAddress(privValidators)) - - return tmtypes.NewValidatorSet(valz), privValidators -} - -func MakeCommit(blockID tmtypes.BlockID, height int64, round int32, - voteSet *tmtypes.VoteSet, validators []tmtypes.PrivValidator, now time.Time) (*tmtypes.Commit, error) { - - // all sign - for i := 0; i < len(validators); i++ { - pubKey, err := validators[i].GetPubKey() - if err != nil { - return nil, fmt.Errorf("can't get pubkey: %w", err) - } - vote := &tmtypes.Vote{ - ValidatorAddress: pubKey.Address(), - ValidatorIndex: int32(i), - Height: height, - Round: round, - Type: tmproto.PrecommitType, - BlockID: blockID, - Timestamp: now, - } - - _, err = signAddVote(validators[i], vote, voteSet) - if err != nil { - return nil, err - } - } - - return voteSet.MakeCommit(), nil -} - -func signAddVote(privVal tmtypes.PrivValidator, vote *tmtypes.Vote, voteSet *tmtypes.VoteSet) (signed bool, err error) { - v := vote.ToProto() - err = privVal.SignVote(voteSet.ChainID(), v) - if err != nil { - return false, err - } - vote.Signature = v.Signature - return voteSet.AddVote(vote) -} - func getFreePort() int { a, err := net.ResolveTCPAddr("tcp", "localhost:0") if err == nil { diff --git a/go.mod b/go.mod index 64f09683f9..3ce6c9e4d9 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,7 @@ require ( go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.5.0 + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db golang.org/x/sync v0.1.0 golang.org/x/text v0.6.0 google.golang.org/grpc v1.52.0 @@ -301,7 +302,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.5.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect diff --git a/header/header.go b/header/header.go index e233852aa5..3e371e1c70 100644 --- a/header/header.go +++ b/header/header.go @@ -7,28 +7,22 @@ import ( "fmt" "time" - "github.com/ipfs/go-blockservice" - logging "github.com/ipfs/go-log/v2" - tmjson "github.com/tendermint/tendermint/libs/json" core "github.com/tendermint/tendermint/types" - appshares "github.com/celestiaorg/celestia-app/pkg/shares" - "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" + libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/share" ) -var log = logging.Logger("header") - // ConstructFn aliases a function that creates an ExtendedHeader. type ConstructFn = func( context.Context, *core.Block, *core.Commit, *core.ValidatorSet, - blockservice.BlockService, + *rsmt2d.ExtendedDataSquare, ) (*ExtendedHeader, error) type DataAvailabilityHeader = da.DataAvailabilityHeader @@ -79,23 +73,14 @@ func MakeExtendedHeader( b *core.Block, comm *core.Commit, vals *core.ValidatorSet, - bServ blockservice.BlockService, + eds *rsmt2d.ExtendedDataSquare, ) (*ExtendedHeader, error) { var dah DataAvailabilityHeader - if len(b.Txs) > 0 { - shares, err := appshares.Split(b.Data, true) - if err != nil { - return nil, err - } - extended, err := share.AddShares(ctx, appshares.ToBytes(shares), bServ) - if err != nil { - return nil, err - } - dah = da.NewDataAvailabilityHeader(extended) - } else { - // use MinDataAvailabilityHeader for empty block + switch eds { + case nil: dah = EmptyDAH() - log.Debugw("empty block received", "height", "blockID", "time", b.Height, b.Time.String(), comm.BlockID) + default: + dah = da.NewDataAvailabilityHeader(eds) } eh := &ExtendedHeader{ diff --git a/headertest/testing.go b/headertest/testing.go index 4028042e75..68c7ef3a8f 100644 --- a/headertest/testing.go +++ b/headertest/testing.go @@ -2,8 +2,9 @@ package headertest import ( "context" - + "fmt" mrand "math/rand" + "sort" "testing" "time" @@ -18,10 +19,11 @@ import ( "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "golang.org/x/exp/rand" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/header/test" @@ -44,7 +46,7 @@ type TestSuite struct { // NewTestSuite setups a new test suite with a given number of validators. func NewTestSuite(t *testing.T, num int) *TestSuite { - valSet, vals := core.RandValidatorSet(num, 10) + valSet, vals := RandValidatorSet(num, 10) return &TestSuite{ t: t, vals: vals, @@ -62,7 +64,7 @@ func (s *TestSuite) genesis() *header.ExtendedHeader { gen.NextValidatorsHash = s.valSet.Hash() gen.Height = 1 voteSet := types.NewVoteSet(gen.ChainID, gen.Height, 0, tmproto.PrecommitType, s.valSet) - commit, err := core.MakeCommit(RandBlockID(s.t), gen.Height, 0, voteSet, s.vals, time.Now()) + commit, err := MakeCommit(RandBlockID(s.t), gen.Height, 0, voteSet, s.vals, time.Now()) require.NoError(s.t, err) eh := &header.ExtendedHeader{ @@ -75,6 +77,44 @@ func (s *TestSuite) genesis() *header.ExtendedHeader { return eh } +func MakeCommit(blockID types.BlockID, height int64, round int32, + voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) { + + // all sign + for i := 0; i < len(validators); i++ { + pubKey, err := validators[i].GetPubKey() + if err != nil { + return nil, fmt.Errorf("can't get pubkey: %w", err) + } + vote := &types.Vote{ + ValidatorAddress: pubKey.Address(), + ValidatorIndex: int32(i), + Height: height, + Round: round, + Type: tmproto.PrecommitType, + BlockID: blockID, + Timestamp: now, + } + + _, err = signAddVote(validators[i], vote, voteSet) + if err != nil { + return nil, err + } + } + + return voteSet.MakeCommit(), nil +} + +func signAddVote(privVal types.PrivValidator, vote *types.Vote, voteSet *types.VoteSet) (signed bool, err error) { + v := vote.ToProto() + err = privVal.SignVote(voteSet.ChainID(), v) + if err != nil { + return false, err + } + vote.Signature = v.Signature + return voteSet.AddVote(vote) +} + func (s *TestSuite) Head() *header.ExtendedHeader { if s.head == nil { s.head = s.genesis() @@ -173,10 +213,10 @@ func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { rh := RandRawHeader(t) rh.DataHash = dah.Hash() - valSet, vals := core.RandValidatorSet(3, 1) + valSet, vals := RandValidatorSet(3, 1) rh.ValidatorsHash = valSet.Hash() voteSet := types.NewVoteSet(rh.ChainID, rh.Height, 0, tmproto.PrecommitType, valSet) - commit, err := core.MakeCommit(RandBlockID(t), rh.Height, 0, voteSet, vals, time.Now()) + commit, err := MakeCommit(RandBlockID(t), rh.Height, 0, voteSet, vals, time.Now()) require.NoError(t, err) return &header.ExtendedHeader{ @@ -187,6 +227,38 @@ func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { } } +func RandValidatorSet(numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { + var ( + valz = make([]*types.Validator, numValidators) + privValidators = make([]types.PrivValidator, numValidators) + ) + + for i := 0; i < numValidators; i++ { + val, privValidator := RandValidator(false, votingPower) + valz[i] = val + privValidators[i] = privValidator + } + + sort.Sort(types.PrivValidatorsByAddress(privValidators)) + + return types.NewValidatorSet(valz), privValidators +} + +func RandValidator(randPower bool, minPower int64) (*types.Validator, types.PrivValidator) { + privVal := types.NewMockPV() + votePower := minPower + if randPower { + //nolint:gosec // G404: Use of weak random number generator + votePower += int64(rand.Uint32()) + } + pubKey, err := privVal.GetPubKey() + if err != nil { + panic(fmt.Errorf("could not retrieve pubkey %w", err)) + } + val := types.NewValidator(pubKey, votePower) + return val, privVal +} + // RandRawHeader provides a RawHeader fixture. func RandRawHeader(t *testing.T) *header.RawHeader { return &header.RawHeader{ @@ -222,13 +294,14 @@ func RandBlockID(t *testing.T) types.BlockID { } // FraudMaker creates a custom ConstructFn that breaks the block at the given height. -func FraudMaker(t *testing.T, faultHeight int64) header.ConstructFn { +func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService) header.ConstructFn { log.Warn("Corrupting block...", "height", faultHeight) return func(ctx context.Context, b *types.Block, comm *types.Commit, vals *types.ValidatorSet, - bServ blockservice.BlockService) (*header.ExtendedHeader, error) { + eds *rsmt2d.ExtendedDataSquare, + ) (*header.ExtendedHeader, error) { if b.Height == faultHeight { eh := &header.ExtendedHeader{ RawHeader: b.Header, @@ -239,7 +312,12 @@ func FraudMaker(t *testing.T, faultHeight int64) header.ConstructFn { eh = CreateFraudExtHeader(t, eh, bServ) return eh, nil } - return header.MakeExtendedHeader(ctx, b, comm, vals, bServ) + flattened := eds.Flattened() + _, err := share.ImportShares(ctx, flattened, bServ) + if err != nil { + return nil, err + } + return header.MakeExtendedHeader(ctx, b, comm, vals, eds) } } diff --git a/libs/utils/square.go b/libs/utils/square.go new file mode 100644 index 0000000000..ce2663fd81 --- /dev/null +++ b/libs/utils/square.go @@ -0,0 +1,8 @@ +package utils + +import "math" + +// SquareSize returns the size of the square based on the given amount of shares. +func SquareSize(lenShares int) uint64 { + return uint64(math.Sqrt(float64(lenShares))) +} diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index efaa3dd8e8..ecb002890a 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -3,7 +3,6 @@ package core import ( "context" - "github.com/ipfs/go-blockservice" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/core" @@ -11,6 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/fxutil" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -35,13 +35,14 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(core.NewBlockFetcher), fxutil.ProvideAs(core.NewExchange, new(libhead.Exchange[*header.ExtendedHeader])), fx.Invoke(fx.Annotate( - func(bcast libhead.Broadcaster[*header.ExtendedHeader], + func( + bcast libhead.Broadcaster[*header.ExtendedHeader], fetcher *core.BlockFetcher, pubsub *shrexsub.PubSub, - bServ blockservice.BlockService, construct header.ConstructFn, + store *eds.Store, ) *core.Listener { - return core.NewListener(bcast, fetcher, pubsub.Broadcast, bServ, construct) + return core.NewListener(bcast, fetcher, pubsub.Broadcast, construct, store) }, fx.OnStart(func(ctx context.Context, listener *core.Listener) error { return listener.Start(ctx) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index a19c042e91..b2d32de1c0 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + mdutils "github.com/ipfs/go-merkledag/test" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" @@ -35,7 +36,7 @@ func TestFraudProofBroadcasting(t *testing.T) { testTimeout := time.Millisecond * 200 sw := swamp.NewSwamp(t, swamp.WithBlockTime(testTimeout)) - bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(headertest.FraudMaker(t, 20))) + bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(headertest.FraudMaker(t, 20, mdutils.Bserv()))) ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) @@ -99,7 +100,11 @@ func TestFraudProofSyncing(t *testing.T) { cfg := nodebuilder.DefaultConfig(node.Bridge) store := nodebuilder.MockStore(t, cfg) - bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(headertest.FraudMaker(t, 10))) + bridge := sw.NewNodeWithStore( + node.Bridge, + store, + core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv())), + ) ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index dbbed77c2f..2f62464e28 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -46,7 +46,7 @@ func TestFullReconstructFromBridge(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - fillDn := sw.FillBlocks(ctx, bsize, blocks) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) bridge := sw.NewBridgeNode() err := bridge.Start(ctx) @@ -104,7 +104,7 @@ func TestFullReconstructFromLights(t *testing.T) { t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - fillDn := sw.FillBlocks(ctx, bsize, blocks) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) const defaultTimeInterval = time.Second * 5 cfg := nodebuilder.DefaultConfig(node.Full) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 492c2ff93f..32434788a2 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -8,7 +8,8 @@ import ( "testing" "time" - mdutils "github.com/ipfs/go-merkledag/test" + ds "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" @@ -28,6 +29,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/state" + "github.com/celestiaorg/celestia-node/share/eds" ) var blackholeIP6 = net.ParseIP("100::") @@ -50,7 +52,7 @@ type Swamp struct { comps *Components ClientContext testnode.Context - accounts []string + Accounts []string genesis *header.ExtendedHeader } @@ -78,7 +80,7 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { Network: mocknet.New(), ClientContext: cctx, comps: ic, - accounts: ic.Accounts, + Accounts: ic.Accounts, } swp.t.Cleanup(func() { @@ -163,9 +165,12 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { func (s *Swamp) setupGenesis(ctx context.Context) { s.WaitTillHeight(ctx, 1) + store, err := eds.NewStore(s.t.TempDir(), ds_sync.MutexWrap(ds.NewMapDatastore())) + require.NoError(s.t, err) + ex := core.NewExchange( core.NewBlockFetcher(s.ClientContext.Client), - mdutils.Bserv(), + store, header.MakeExtendedHeader, ) diff --git a/nodebuilder/tests/swamp/swamp_tx.go b/nodebuilder/tests/swamp/swamp_tx.go index df885429da..d8e7e29554 100644 --- a/nodebuilder/tests/swamp/swamp_tx.go +++ b/nodebuilder/tests/swamp/swamp_tx.go @@ -4,17 +4,19 @@ import ( "context" "github.com/cosmos/cosmos-sdk/client/flags" + + "github.com/celestiaorg/celestia-app/testutil/testnode" ) // FillBlocks produces the given amount of contiguous blocks with customizable size. // The returned channel reports when the process is finished. -func (s *Swamp) FillBlocks(ctx context.Context, bsize, blocks int) chan error { +func FillBlocks(ctx context.Context, cctx testnode.Context, accounts []string, bsize, blocks int) chan error { errCh := make(chan error) go func() { // TODO: FillBlock must respect the context var err error for i := 0; i < blocks; i++ { - _, err = s.ClientContext.FillBlock(bsize, s.accounts, flags.BroadcastBlock) + _, err = cctx.FillBlock(bsize, accounts, flags.BroadcastBlock) if err != nil { break } diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index d121c44c79..6d35072bbf 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -37,7 +37,7 @@ func TestSyncLightWithBridge(t *testing.T) { t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - fillDn := sw.FillBlocks(ctx, bsize, blocks) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) bridge := sw.NewBridgeNode() @@ -150,7 +150,7 @@ func TestSyncFullWithBridge(t *testing.T) { t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - fillDn := sw.FillBlocks(ctx, bsize, blocks) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) bridge := sw.NewBridgeNode() diff --git a/share/add.go b/share/add.go index 4e60eabb2c..02016cadf6 100644 --- a/share/add.go +++ b/share/add.go @@ -3,14 +3,15 @@ package share import ( "context" "fmt" - "math" "github.com/ipfs/go-blockservice" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/share/ipld" ) // AddShares erasures and extends shares to blockservice.BlockService using the provided @@ -23,7 +24,7 @@ func AddShares( if len(shares) == 0 { return nil, fmt.Errorf("empty data") // empty block is not an empty Data } - squareSize := int(math.Sqrt(float64(len(shares)))) + squareSize := int(utils.SquareSize(len(shares))) // create nmt adder wrapping batch adder with calculated size batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) // create the nmt wrapper to generate row and col commitments @@ -43,7 +44,7 @@ func AddShares( return eds, batchAdder.Commit() } -// ImportShares imports flattend chunks of data into Extended Data square and saves it in +// ImportShares imports flattened chunks of data into Extended Data square and saves it in // blockservice.BlockService func ImportShares( ctx context.Context, @@ -52,7 +53,7 @@ func ImportShares( if len(shares) == 0 { return nil, fmt.Errorf("ipld: importing empty data") } - squareSize := int(math.Sqrt(float64(len(shares)))) + squareSize := int(utils.SquareSize(len(shares))) // create nmt adder wrapping batch adder with calculated size batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) // recompute the eds diff --git a/share/get_test.go b/share/get_test.go index fdf882c569..3466a726f1 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -2,7 +2,6 @@ package share import ( "context" - "math" "math/rand" "strconv" "testing" @@ -20,10 +19,12 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/share/ipld" ) func TestGetShare(t *testing.T) { @@ -73,7 +74,7 @@ func TestBlockRecovery(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - squareSize := uint64(math.Sqrt(float64(len(tc.shares)))) + squareSize := utils.SquareSize(len(tc.shares)) eds, err := rsmt2d.ComputeExtendedDataSquare(tc.shares, rsmt2d.NewRSGF8Codec(), wrapper.NewConstructor(squareSize)) require.NoError(t, err) diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index edc8824d04..ff0d38ea72 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -2,7 +2,6 @@ package ipld import ( "bytes" - "math" "math/rand" "sort" "strconv" @@ -13,6 +12,8 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" + + "github.com/celestiaorg/celestia-node/libs/utils" ) // TestNamespaceFromCID checks that deriving the Namespaced hash from @@ -28,7 +29,7 @@ func TestNamespaceFromCID(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { // create DAH from rand data - squareSize := uint64(math.Sqrt(float64(len(tt.randData)))) + squareSize := utils.SquareSize(len(tt.randData)) eds, err := da.ExtendShares(squareSize, tt.randData) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(eds) From 53ee350345fe170164148c554c082dfb2cfd3b6e Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 9 Feb 2023 18:26:44 +0100 Subject: [PATCH 0363/1008] feat(nodebuilder/p2p): migrate to EDSStore Blockstore for Bitswap for FNs/BNs (#1719) --- fraud/service_test.go | 5 ++--- fraud/testing.go | 3 +-- nodebuilder/p2p/bitswap.go | 44 +++++++++++++++++++++++-------------- nodebuilder/p2p/module.go | 2 ++ nodebuilder/share/module.go | 2 +- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/fraud/service_test.go b/fraud/service_test.go index faafd1efff..a1f405e3d1 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -16,7 +16,6 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) func TestService_Subscribe(t *testing.T) { @@ -133,7 +132,7 @@ func TestService_ReGossiping(t *testing.T) { }, sync.MutexWrap(datastore.NewMapDatastore()), false, - string(p2p.Private), + "private", ) addrB := host.InfoFromHost(net.Hosts()[1]) // -> B @@ -149,7 +148,7 @@ func TestService_ReGossiping(t *testing.T) { }, sync.MutexWrap(datastore.NewMapDatastore()), false, - string(p2p.Private), + "private", ) // establish connections // connect peers: A -> B -> C, so A and C are not connected to each other diff --git a/fraud/testing.go b/fraud/testing.go index 368f6a5b75..355d0440e4 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -16,7 +16,6 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/headertest" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) type DummyService struct { @@ -144,6 +143,6 @@ func createTestServiceWithHost( store.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore()), enabledSyncer, - string(p2p.Private), + "private", ), store } diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index 36be9ab953..fc4c72638d 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -13,6 +13,8 @@ import ( hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/share/eds" ) const ( @@ -25,30 +27,40 @@ const ( ) // dataExchange provides a constructor for IPFS block's DataExchange over BitSwap. -func dataExchange(params bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { - bs, err := blockstore.CachedBlockstore( - params.Ctx, - blockstore.NewBlockstore(params.Ds), - blockstore.CacheOpts{ - HasBloomFilterSize: defaultBloomFilterSize, - HasBloomFilterHashes: defaultBloomFilterHashes, - HasARCCacheSize: defaultARCCacheSize, - }, - ) - if err != nil { - return nil, nil, err - } +func dataExchange(params bitSwapParams) exchange.Interface { prefix := protocol.ID(fmt.Sprintf("/celestia/%s", params.Net)) return bitswap.New( params.Ctx, network.NewFromIpfsHost(params.Host, &routinghelpers.Null{}, network.Prefix(prefix)), - bs, + params.Bs, bitswap.ProvideEnabled(false), // NOTE: These below ar required for our protocol to work reliably. // See https://github.com/celestiaorg/celestia-node/issues/732 bitswap.SetSendDontHaves(false), bitswap.SetSimulateDontHavesOnTimeout(false), - ), bs, nil + ) +} + +func blockstoreFromDatastore(ctx context.Context, ds datastore.Batching) (blockstore.Blockstore, error) { + return blockstore.CachedBlockstore( + ctx, + blockstore.NewBlockstore(ds), + blockstore.CacheOpts{ + HasBloomFilterSize: defaultBloomFilterSize, + HasBloomFilterHashes: defaultBloomFilterHashes, + HasARCCacheSize: defaultARCCacheSize, + }, + ) +} + +func blockstoreFromEDSStore(ctx context.Context, store *eds.Store) (blockstore.Blockstore, error) { + return blockstore.CachedBlockstore( + ctx, + store.Blockstore(), + blockstore.CacheOpts{ + HasARCCacheSize: defaultARCCacheSize, + }, + ) } type bitSwapParams struct { @@ -57,5 +69,5 @@ type bitSwapParams struct { Ctx context.Context Net Network Host hst.Host - Ds datastore.Batching + Bs blockstore.Blockstore } diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index c00f8ab60c..5b3e9f9d52 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -44,6 +44,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "p2p", baseComponents, + fx.Provide(blockstoreFromEDSStore), fx.Provide(func() (network.ResourceManager, error) { return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) }), @@ -52,6 +53,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "p2p", baseComponents, + fx.Provide(blockstoreFromDatastore), fx.Provide(func() (network.ResourceManager, error) { limits := rcmgr.DefaultLimits libp2p.SetDefaultServiceLimits(&limits) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index e39e0c84d0..cf9743f558 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -29,7 +29,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Options(options...), fx.Provide(discovery(*cfg)), fx.Provide(newModule), - fx.Invoke(share.EnsureEmptySquareExists), fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), ) @@ -38,6 +37,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, + fx.Invoke(share.EnsureEmptySquareExists), fx.Provide(fx.Annotate(light.NewShareAvailability)), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability From e62ddd9f4666dbcc080cd06782b47b4e9935386d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 9 Feb 2023 20:34:37 +0300 Subject: [PATCH 0364/1008] feat(das) add shrex.Sub integration into DASer (#1717) Besides integrations, it allows recent headers to be sampled right away, without need to wait next available worker to process the job. This way recent headers are not restricted by concurrency limit and it should reduce sampling latency to allow Broadcast happen faster. Closes https://github.com/celestiaorg/celestia-node/issues/1466 Closes https://github.com/celestiaorg/celestia-node/issues/1718 Co-authored-by: Ryan Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- das/coordinator.go | 15 ++++-- das/coordinator_test.go | 90 +++++++++++++++++++++------------ das/daser.go | 5 +- das/daser_test.go | 10 ++-- das/doc.go | 2 +- das/options.go | 12 ----- das/state.go | 87 +++++++++++++++---------------- das/worker.go | 36 +++++++++---- nodebuilder/das/constructors.go | 4 +- nodebuilder/das/module.go | 1 - nodebuilder/share/module.go | 8 +++ 11 files changed, 159 insertions(+), 111 deletions(-) diff --git a/das/coordinator.go b/das/coordinator.go index cf356349d9..83a9d65df8 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -6,14 +6,16 @@ import ( "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) // samplingCoordinator runs and coordinates sampling workers and updates current sampling state type samplingCoordinator struct { concurrencyLimit int - getter libhead.Getter[*header.ExtendedHeader] - sampleFn sampleFn + getter libhead.Getter[*header.ExtendedHeader] + sampleFn sampleFn + broadcastFn shrexsub.BroadcastFn state coordinatorState @@ -40,11 +42,13 @@ func newSamplingCoordinator( params Parameters, getter libhead.Getter[*header.ExtendedHeader], sample sampleFn, + broadcast shrexsub.BroadcastFn, ) *samplingCoordinator { return &samplingCoordinator{ concurrencyLimit: params.ConcurrencyLimit, getter: getter, sampleFn: sample, + broadcastFn: broadcast, state: newCoordinatorState(params), resultCh: make(chan result), updHeadCh: make(chan uint64), @@ -75,7 +79,10 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { select { case head := <-sc.updHeadCh: - if sc.state.updateHead(head) { + if sc.state.isNewHead(head) { + sc.runWorker(ctx, sc.state.newRecentJob(head)) + sc.state.updateHead(head) + // run worker without concurrency limit restrictions to reduced delay sc.metrics.observeNewHead(ctx) } case res := <-sc.resultCh: @@ -99,7 +106,7 @@ func (sc *samplingCoordinator) runWorker(ctx context.Context, j job) { sc.workersWg.Add(1) go func() { defer sc.workersWg.Done() - w.run(ctx, sc.getter, sc.sampleFn, sc.metrics, sc.resultCh) + w.run(ctx, sc.getter, sc.sampleFn, sc.broadcastFn, sc.metrics, sc.resultCh) }() } diff --git a/das/coordinator_test.go b/das/coordinator_test.go index a37ea73f08..016e7e0cf0 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -8,9 +8,11 @@ import ( "testing" "time" - "github.com/celestiaorg/celestia-node/header" - "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) func TestCoordinator(t *testing.T) { @@ -19,7 +21,7 @@ func TestCoordinator(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) - coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample)) + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample), nil) go coordinator.run(ctx, sampler.checkpoint) @@ -44,15 +46,12 @@ func TestCoordinator(t *testing.T) { sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) - coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, sampler.sample) + newhead := testParams.networkHead + 200 + coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, sampler.sample, newBroadcastMock(1)) go coordinator.run(ctx, sampler.checkpoint) - time.Sleep(50 * time.Millisecond) // discover new height - for i := 0; i < 200; i++ { - // mess the order by running in go-routine - sampler.discover(ctx, testParams.networkHead+uint64(i), coordinator.listen) - } + sampler.discover(ctx, newhead, coordinator.listen) // check if all jobs were sampled successfully assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") @@ -69,7 +68,6 @@ func TestCoordinator(t *testing.T) { }) t.Run("prioritize newly discovered over known", func(t *testing.T) { - testParams := defaultTestParams() testParams.dasParams.ConcurrencyLimit = 1 @@ -86,31 +84,35 @@ func TestCoordinator(t *testing.T) { // lock worker before start, to not let it indicateDone before discover lk := newLock(testParams.sampleFrom, testParams.sampleFrom) - // expect worker to prioritize newly discovered (20 -> 10) and then old (0 -> 10) - order := newCheckOrder().addInterval( - testParams.sampleFrom, - testParams.dasParams.SamplingRange, - ) // worker will pick up first job before discovery + order := newCheckOrder().addInterval(toBeDiscovered, toBeDiscovered) - order.addStacks(testParams.networkHead+1, toBeDiscovered, testParams.dasParams.SamplingRange) - order.addInterval(testParams.dasParams.SamplingRange+1, toBeDiscovered) + // expect worker to prioritize newly discovered + order.addInterval( + testParams.sampleFrom, + toBeDiscovered, + ) // start coordinator coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, lk.middleWare( order.middleWare(sampler.sample), ), + newBroadcastMock(1), ) go coordinator.run(ctx, sampler.checkpoint) - // wait for worker to pick up first job - time.Sleep(50 * time.Millisecond) - // discover new height sampler.discover(ctx, toBeDiscovered, coordinator.listen) // check if no header were sampled yet - assert.Equal(t, 0, sampler.sampledAmount()) + for sampler.sampledAmount() != 1 { + time.Sleep(time.Millisecond) + select { + case <-ctx.Done(): + assert.NoError(t, ctx.Err()) + default: + } + } // unblock worker lk.release(testParams.sampleFrom) @@ -129,7 +131,7 @@ func TestCoordinator(t *testing.T) { assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) }) - t.Run("priority routine should not lock other workers", func(t *testing.T) { + t.Run("recent headers sampling routine should not lock other workers", func(t *testing.T) { testParams := defaultTestParams() testParams.networkHead = uint64(20) @@ -139,10 +141,9 @@ func TestCoordinator(t *testing.T) { lk := newLock(testParams.sampleFrom, testParams.networkHead) // lock all workers before start coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, - lk.middleWare(sampler.sample)) + lk.middleWare(sampler.sample), newBroadcastMock(1)) go coordinator.run(ctx, sampler.checkpoint) - time.Sleep(50 * time.Millisecond) // discover new height and lock it discovered := testParams.networkHead + 1 lk.add(discovered) @@ -186,7 +187,12 @@ func TestCoordinator(t *testing.T) { bornToFail := []uint64{4, 8, 15, 16, 23, 42} sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead, bornToFail...) - coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample)) + coordinator := newSamplingCoordinator( + testParams.dasParams, + getterStub{}, + onceMiddleWare(sampler.sample), + newBroadcastMock(1), + ) go coordinator.run(ctx, sampler.checkpoint) // wait for coordinator to indicateDone catchup @@ -217,7 +223,12 @@ func TestCoordinator(t *testing.T) { sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead, failedAgain...) sampler.checkpoint.Failed = failedLastRun - coordinator := newSamplingCoordinator(testParams.dasParams, getterStub{}, onceMiddleWare(sampler.sample)) + coordinator := newSamplingCoordinator( + testParams.dasParams, + getterStub{}, + onceMiddleWare(sampler.sample), + newBroadcastMock(1), + ) go coordinator.run(ctx, sampler.checkpoint) // check if all jobs were sampled successfully @@ -249,8 +260,12 @@ func BenchmarkCoordinator(b *testing.B) { b.Run("bench run", func(b *testing.B) { ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) - coordinator := newSamplingCoordinator(params, newBenchGetter(), - func(ctx context.Context, h *header.ExtendedHeader) error { return nil }) + coordinator := newSamplingCoordinator( + params, + newBenchGetter(), + func(ctx context.Context, h *header.ExtendedHeader) error { return nil }, + newBroadcastMock(1), + ) go coordinator.run(ctx, checkpoint{ SampleFrom: 1, NetworkHead: uint64(b.N), @@ -436,8 +451,8 @@ func (o *checkOrder) middleWare(out sampleFn) sampleFn { if len(o.queue) > 0 { // check last item in queue to be same as input if o.queue[0] != uint64(h.Height()) { - o.lock.Unlock() - return fmt.Errorf("expected height: %v,got: %v", o.queue[0], h) + defer o.lock.Unlock() + return fmt.Errorf("expected height: %v,got: %v", o.queue[0], h.Height()) } o.queue = o.queue[1:] } @@ -547,7 +562,20 @@ func defaultTestParams() testParams { return testParams{ networkHead: uint64(500), sampleFrom: dasParamsDefault.SampleFrom, - timeoutDelay: 125 * time.Second, + timeoutDelay: 5 * time.Second, dasParams: dasParamsDefault, } } + +func newBroadcastMock(callLimit int) shrexsub.BroadcastFn { + var m sync.Mutex + return func(ctx context.Context, hash share.DataHash) error { + m.Lock() + defer m.Unlock() + if callLimit == 0 { + return errors.New("exceeded mock call limit") + } + callLimit-- + return nil + } +} diff --git a/das/daser.go b/das/daser.go index 1be0b2af87..c00707746d 100644 --- a/das/daser.go +++ b/das/daser.go @@ -6,6 +6,8 @@ import ( "fmt" "sync/atomic" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" + "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -46,6 +48,7 @@ func NewDASer( getter libhead.Getter[*header.ExtendedHeader], dstore datastore.Datastore, bcast fraud.Broadcaster, + shrexBroadcast shrexsub.BroadcastFn, options ...Option, ) (*DASer, error) { d := &DASer{ @@ -68,7 +71,7 @@ func NewDASer( return nil, err } - d.sampler = newSamplingCoordinator(d.params, getter, d.sample) + d.sampler = newSamplingCoordinator(d.params, getter, d.sample, shrexBroadcast) return d, nil } diff --git a/das/daser_test.go b/das/daser_test.go index 6333f4a781..4a13bbe7d9 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -43,7 +43,7 @@ func TestDASerLifecycle(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - daser, err := NewDASer(avail, sub, mockGet, ds, mockService) + daser, err := NewDASer(avail, sub, mockGet, ds, mockService, newBroadcastMock(1)) require.NoError(t, err) err = daser.Start(ctx) @@ -83,7 +83,7 @@ func TestDASer_Restart(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - daser, err := NewDASer(avail, sub, mockGet, ds, mockService) + daser, err := NewDASer(avail, sub, mockGet, ds, mockService, newBroadcastMock(1)) require.NoError(t, err) err = daser.Start(ctx) @@ -114,7 +114,7 @@ func TestDASer_Restart(t *testing.T) { restartCtx, restartCancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(restartCancel) - daser, err = NewDASer(avail, sub, mockGet, ds, mockService) + daser, err = NewDASer(avail, sub, mockGet, ds, mockService, newBroadcastMock(1)) require.NoError(t, err) err = daser.Start(restartCtx) @@ -162,7 +162,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { newCtx := context.Background() // create and start DASer - daser, err := NewDASer(avail, sub, mockGet, ds, f) + daser, err := NewDASer(avail, sub, mockGet, ds, f, newBroadcastMock(1)) require.NoError(t, err) resultCh := make(chan error) @@ -205,7 +205,7 @@ func TestDASerSampleTimeout(t *testing.T) { f := new(fraud.DummyService) // create and start DASer - daser, err := NewDASer(avail, sub, getter, ds, f) + daser, err := NewDASer(avail, sub, getter, ds, f, newBroadcastMock(1)) require.NoError(t, err) // assign directly to avoid params validation diff --git a/das/doc.go b/das/doc.go index c255530aef..bc67fcc7f3 100644 --- a/das/doc.go +++ b/das/doc.go @@ -18,6 +18,6 @@ workers that perform DAS on new ExtendedHeaders in the network. The DASer kicks loop by loading its last DASed headers snapshot (`checkpoint`) and kicking off worker pool to DAS all headers between the checkpoint and the current network head. It subscribes to notifications about to new ExtendedHeaders, received via gossipsub. Newly found headers -are being put into higher priority queue and will be sampled by the next available worker. +are being put into workers directly, without applying concurrency limiting restrictions. */ package das diff --git a/das/options.go b/das/options.go index eca8596de4..93bc09e2f3 100644 --- a/das/options.go +++ b/das/options.go @@ -32,9 +32,6 @@ type Parameters struct { // checkpoint backup. BackgroundStoreInterval time.Duration - // PriorityQueueSize defines the size limit of the priority queue - PriorityQueueSize int - // SampleFrom is the height sampling will start from if no previous checkpoint was saved SampleFrom uint64 @@ -51,7 +48,6 @@ func DefaultParameters() Parameters { SamplingRange: 100, ConcurrencyLimit: 16, BackgroundStoreInterval: 10 * time.Minute, - PriorityQueueSize: 16 * 4, SampleFrom: 1, SampleTimeout: time.Minute, } @@ -139,14 +135,6 @@ func WithBackgroundStoreInterval(backgroundStoreInterval time.Duration) Option { } } -// WithPriorityQueueSize is a functional option to configure the daser's `priorityQueuSize` -// parameter Refer to WithSamplingRange documentation to see an example of how to use this -func WithPriorityQueueSize(priorityQueueSize int) Option { - return func(d *DASer) { - d.params.PriorityQueueSize = priorityQueueSize - } -} - // WithSampleFrom is a functional option to configure the daser's `SampleFrom` parameter // Refer to WithSamplingRange documentation to see an example of how to use this func WithSampleFrom(sampleFrom uint64) Option { diff --git a/das/state.go b/das/state.go index b377c2e0f4..41c24e5849 100644 --- a/das/state.go +++ b/das/state.go @@ -10,10 +10,9 @@ type coordinatorState struct { sampleFrom uint64 // is the height from which the DASer will start sampling samplingRange uint64 // is the maximum amount of headers processed in one job. - priorityQueueSize int // the size of the priority queue - priority []job // list of headers heights that will be sampled with higher priority - inProgress map[int]func() workerState // keeps track of running workers - failed map[uint64]int // stores heights of failed headers with amount of attempt as value + retry []job // list of headers heights that will be retried after last run + inProgress map[int]func() workerState // keeps track of running workers + failed map[uint64]int // stores heights of failed headers with amount of attempt as value nextJobID int next uint64 // all headers before next were sent to workers @@ -26,26 +25,25 @@ type coordinatorState struct { // newCoordinatorState initiates state for samplingCoordinator func newCoordinatorState(params Parameters) coordinatorState { return coordinatorState{ - sampleFrom: params.SampleFrom, - samplingRange: params.SamplingRange, - priorityQueueSize: params.PriorityQueueSize, - priority: make([]job, 0), - inProgress: make(map[int]func() workerState), - failed: make(map[uint64]int), - nextJobID: 0, - next: params.SampleFrom, - networkHead: params.SampleFrom, - catchUpDoneCh: make(chan struct{}), + sampleFrom: params.SampleFrom, + samplingRange: params.SamplingRange, + retry: make([]job, 0), + inProgress: make(map[int]func() workerState), + failed: make(map[uint64]int), + nextJobID: 0, + next: params.SampleFrom, + networkHead: params.SampleFrom, + catchUpDoneCh: make(chan struct{}), } } func (s *coordinatorState) resumeFromCheckpoint(c checkpoint) { s.next = c.SampleFrom s.networkHead = c.NetworkHead - // put failed into priority to retry them on restart + // store failed to retry them on restart for h, count := range c.Failed { s.failed[h] = count - s.priority = append(s.priority, s.newJob(h, h)) + s.retry = append(s.retry, s.newJob(h, h)) } } @@ -74,30 +72,33 @@ func (s *coordinatorState) handleResult(res result) { s.checkDone() } -func (s *coordinatorState) updateHead(last uint64) bool { +func (s *coordinatorState) isNewHead(newHead uint64) bool { // seen this header before - if last <= s.networkHead { - log.Warnf("received head height: %v, which is lower or the same as previously known: %v", last, s.networkHead) + if newHead <= s.networkHead { + log.Warnf("received head height: %v, which is lower or the same as previously known: %v", newHead, s.networkHead) return false } + return true +} +func (s *coordinatorState) updateHead(newHead uint64) { if s.networkHead == s.sampleFrom { - s.networkHead = last log.Infow("found first header, starting sampling") - return true - } - - // add most recent headers into priority queue - from := s.networkHead + 1 - for from <= last && len(s.priority) < s.priorityQueueSize { - s.priority = append(s.priority, s.newJob(from, last)) - from += s.samplingRange } - log.Debugw("added recent headers to DASer priority queue", "from_height", s.networkHead, "to_height", last) - s.networkHead = last + s.networkHead = newHead + log.Debugw("updated head", "from_height", s.networkHead, "to_height", newHead) s.checkDone() - return true +} + +func (s *coordinatorState) newRecentJob(newHead uint64) job { + s.nextJobID++ + return job{ + id: s.nextJobID, + isRecentHeader: true, + From: newHead, + To: newHead, + } } // nextJob will return header height to be processed and done flag if there is none @@ -107,8 +108,8 @@ func (s *coordinatorState) nextJob() (next job, found bool) { return job{}, false } - // try to take from priority first - if next, found := s.nextFromPriority(); found { + // try to take from retry first + if next, found := s.nextFromRetry(); found { return next, found } @@ -122,19 +123,15 @@ func (s *coordinatorState) nextJob() (next job, found bool) { return j, true } -func (s *coordinatorState) nextFromPriority() (job, bool) { - for len(s.priority) > 0 { - next := s.priority[len(s.priority)-1] - s.priority = s.priority[:len(s.priority)-1] +func (s *coordinatorState) nextFromRetry() (job, bool) { + if len(s.retry) == 0 { + return job{}, false + } - // this job will be processed next normally, we can skip it - if next.From == s.next { - continue - } + next := s.retry[len(s.retry)-1] + s.retry = s.retry[:len(s.retry)-1] - return next, true - } - return job{}, false + return next, true } func (s *coordinatorState) putInProgress(jobID int, getState func() workerState) { @@ -207,7 +204,7 @@ func (s *coordinatorState) unsafeStats() SamplingStats { } func (s *coordinatorState) checkDone() { - if len(s.inProgress) == 0 && len(s.priority) == 0 && s.next > s.networkHead { + if len(s.inProgress) == 0 && len(s.retry) == 0 && s.next > s.networkHead { if s.catchUpDone.CompareAndSwap(false, true) { close(s.catchUpDoneCh) } diff --git a/das/worker.go b/das/worker.go index 13ab67a05e..3300777cb2 100644 --- a/das/worker.go +++ b/das/worker.go @@ -7,6 +7,8 @@ import ( "sync" "time" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" + "go.uber.org/multierr" "github.com/celestiaorg/celestia-node/header" @@ -30,7 +32,9 @@ type workerState struct { // job represents headers interval to be processed by worker type job struct { - id int + id int + isRecentHeader bool + From uint64 To uint64 } @@ -39,6 +43,7 @@ func (w *worker) run( ctx context.Context, getter libhead.Getter[*header.ExtendedHeader], sample sampleFn, + broadcast shrexsub.BroadcastFn, metrics *metrics, resultCh chan<- result) { jobStart := time.Now() @@ -78,6 +83,7 @@ func (w *worker) run( } w.setResult(curr, err) metrics.observeSample(ctx, h, time.Since(startSample), err) + if err != nil { log.Debugw( "failed to sampled header", @@ -87,15 +93,25 @@ func (w *worker) run( "data root", h.DAH.Hash(), "err", err, ) - } else { - log.Debugw( - "sampled header", - "height", h.Height(), - "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), - "finished (s)", time.Since(startSample), - ) + continue + } + + log.Debugw( + "sampled header", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "finished (s)", time.Since(startSample), + ) + + // notify network about availability of new block data (note: only full nodes can notify) + if w.state.isRecentHeader { + err = broadcast(ctx, h.DataHash.Bytes()) + if err != nil { + log.Warn("failed to broadcast availability message", + "height", h.Height(), "hash", h.Hash(), "err", err) + } } } diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index f73c9bbf53..5247a13597 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) var _ Module = (*daserStub)(nil) @@ -39,7 +40,8 @@ func newDASer( store libhead.Store[*header.ExtendedHeader], batching datastore.Batching, fraudServ fraud.Service, + bFn shrexsub.BroadcastFn, options ...das.Option, ) (*das.DASer, error) { - return das.NewDASer(da, hsub, store, batching, fraudServ, options...) + return das.NewDASer(da, hsub, store, batching, fraudServ, bFn, options...) } diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index d060a6a0b2..dbe13684a6 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -27,7 +27,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return []das.Option{ das.WithSamplingRange(c.SamplingRange), das.WithConcurrencyLimit(c.ConcurrencyLimit), - das.WithPriorityQueueSize(c.PriorityQueueSize), das.WithBackgroundStoreInterval(c.BackgroundStoreInterval), das.WithSampleFrom(c.SampleFrom), das.WithSampleTimeout(c.SampleTimeout), diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index cf9743f558..8a2776855d 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -42,6 +42,11 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability fx.Provide(cacheAvailability[*light.ShareAvailability]), + fx.Provide(func() shrexsub.BroadcastFn { + return func(context.Context, share.DataHash) error { + return nil + } + }), ) case node.Bridge, node.Full: return fx.Module( @@ -104,6 +109,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return pubsub.Stop(ctx) }), )), + fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { + return shrexSub.Broadcast + }), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability fx.Provide(cacheAvailability[*full.ShareAvailability]), From c63b7db6ef01bee7b6987e57a100507da834f905 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 10 Feb 2023 09:30:12 +0100 Subject: [PATCH 0365/1008] feat(share/getters): ShrexGetter retry logic (#1624) ## Overview Will eventually close #1489. Does not do anything for integration, does not even include a constructor - this PR is purely for the retry logic ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- share/getters/shrex.go | 87 ++++++++++++++++++++++++++++++++++++ share/p2p/shrexeds/client.go | 4 +- share/p2p/shrexnd/client.go | 2 +- 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 share/getters/shrex.go diff --git a/share/getters/shrex.go b/share/getters/shrex.go new file mode 100644 index 0000000000..a24d59011a --- /dev/null +++ b/share/getters/shrex.go @@ -0,0 +1,87 @@ +package getters + +import ( + "context" + "errors" + "time" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/p2p" + "github.com/celestiaorg/celestia-node/share/p2p/peers" + "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" + "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" + + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" +) + +var _ share.Getter = (*ShrexGetter)(nil) + +const MaxRequestDuration = time.Second * 10 + +// ShrexGetter is a share.Getter that uses the shrex/eds and shrex/nd protocol to retrieve shares. +type ShrexGetter struct { + edsClient *shrexeds.Client + ndClient *shrexnd.Client + + peerManager *peers.Manager + maxRequestDuration time.Duration +} + +func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { + return nil, errors.New("shrex-getter: GetShare is not supported") +} + +func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + for { + peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) + if err != nil { + log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) + return nil, err + } + + reqCtx, cancel := context.WithTimeout(ctx, sg.maxRequestDuration) + eds, err := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) + cancel() + switch err { + case nil: + setStatus(peers.ResultSuccess) + return eds, nil + case context.DeadlineExceeded: + log.Debugw("request exceeded deadline, trying with new peer", "datahash", root.String()) + case p2p.ErrInvalidResponse: + setStatus(peers.ResultPeerMisbehaved) + default: + setStatus(peers.ResultFail) + } + } +} + +func (sg *ShrexGetter) GetSharesByNamespace( + ctx context.Context, + root *share.Root, + id namespace.ID, +) (share.NamespacedShares, error) { + for { + peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) + if err != nil { + log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) + return nil, err + } + + reqCtx, cancel := context.WithTimeout(ctx, sg.maxRequestDuration) + nd, err := sg.ndClient.RequestND(reqCtx, root, id, peer) + cancel() + switch err { + case nil: + setStatus(peers.ResultSuccess) + return nd, nil + case context.DeadlineExceeded: + log.Debugw("request exceeded deadline, trying with new peer", "datahash", root.String()) + case p2p.ErrInvalidResponse: + setStatus(peers.ResultPeerMisbehaved) + default: + setStatus(peers.ResultFail) + } + } +} diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 3de122227a..3af05317b0 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -65,7 +65,7 @@ func (c *Client) RequestEDS( } } if err != p2p.ErrUnavailable { - log.Errorw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) + log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) } return nil, err } @@ -82,7 +82,7 @@ func (c *Client) doRequest( if dl, ok := ctx.Deadline(); ok { if err = stream.SetDeadline(dl); err != nil { - log.Debugw("error setting deadline: %s", err) + log.Debugf("error setting deadline: %s", err) } } diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 171c83d45d..b5e521ddaf 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -73,7 +73,7 @@ func (c *Client) RequestND( } } if err != p2p.ErrUnavailable { - log.Errorw("client-nd: peer returned err", "peer", peer, "err", err) + log.Debugw("client-nd: peer returned err", "peer", peer, "err", err) } return nil, err } From b79f56287aaf192ff5c8c06fcced21feb1e7ce6c Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 13 Feb 2023 11:57:41 +0100 Subject: [PATCH 0366/1008] refactor(das): Don't increment totalSampled if header `isRecent` (#1736) This (not very elegantly) fixes an issue with tracking `totalSampled` inside the DASer which results in 2x the amount of sampled headers as were actually sampled. From @walldiss: ``` It happens because DASer does not call Sampling directly, instead it calls SharesAvailable that ensures that block is sampled. Under the hood, SharesAvailable will check the internal cache and if it hits, it will return immediately. This logic was utilised in DASer design, to simplify independent tracking of recent headers sampling. So sampling recent headers could call SharesAvailable twice: first by recent sampling job and by catchup job later. It was done to simplify already complex DASer logic. ``` Eventually, `totalSampled` should probably be tracked inside `CacheAvailability` as that's the only place that will add an entry for a height one time only. --- das/metrics.go | 5 ++++- das/worker.go | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/das/metrics.go b/das/metrics.go index f498e02ae7..b11dcc8e5a 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -136,6 +136,7 @@ func (m *metrics) observeSample( h *header.ExtendedHeader, sampleTime time.Duration, err error, + isRecentHeader bool, ) { if m == nil { return @@ -152,7 +153,9 @@ func (m *metrics) observeSample( atomic.StoreUint64(&m.lastSampledTS, uint64(time.Now().UTC().Unix())) - if err == nil { + // only increment the counter if it's not a recent header job + // as those happen twice. + if err == nil && !isRecentHeader { atomic.AddUint64(&m.totalSampledInt, 1) } } diff --git a/das/worker.go b/das/worker.go index 3300777cb2..3aa3fd91e3 100644 --- a/das/worker.go +++ b/das/worker.go @@ -82,7 +82,8 @@ func (w *worker) run( break } w.setResult(curr, err) - metrics.observeSample(ctx, h, time.Since(startSample), err) + + metrics.observeSample(ctx, h, time.Since(startSample), err, w.state.isRecentHeader) if err != nil { log.Debugw( From 9f9a0e642ce780bd1cb77908955ed6429891ad92 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 13 Feb 2023 14:33:35 +0200 Subject: [PATCH 0367/1008] bugfix(swamp): fix fraud tests (#1726) ## Overview Blocks were empty, which is why fraud edses were not broadcasted via shrex-sub and were not stored in the store. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- das/daser_test.go | 2 +- headertest/testing.go | 14 +++++------- nodebuilder/tests/fraud_test.go | 34 +++++++++++++++++++---------- nodebuilder/tests/swamp/swamp_tx.go | 3 +++ share/eds/retriever_test.go | 2 +- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/das/daser_test.go b/das/daser_test.go index 4a13bbe7d9..b410b0891e 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -158,7 +158,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { // create fraud service and break one header f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, string(p2p.Private)) require.NoError(t, f.Start(ctx)) - mockGet.headers[1] = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) + mockGet.headers[1], _ = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() // create and start DASer diff --git a/headertest/testing.go b/headertest/testing.go index 68c7ef3a8f..71ce072762 100644 --- a/headertest/testing.go +++ b/headertest/testing.go @@ -309,14 +309,12 @@ func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService ValidatorSet: vals, } - eh = CreateFraudExtHeader(t, eh, bServ) + eh, dataSq := CreateFraudExtHeader(t, eh, bServ) + if eds != nil { + *eds = *dataSq + } return eh, nil } - flattened := eds.Flattened() - _, err := share.ImportShares(ctx, flattened, bServ) - if err != nil { - return nil, err - } return header.MakeExtendedHeader(ctx, b, comm, vals, eds) } } @@ -325,7 +323,7 @@ func CreateFraudExtHeader( t *testing.T, eh *header.ExtendedHeader, dag blockservice.BlockService, -) *header.ExtendedHeader { +) (*header.ExtendedHeader, *rsmt2d.ExtendedDataSquare) { extended := share.RandEDS(t, 2) shares := share.ExtractEDS(extended) copy(shares[0][share.NamespaceSize:], shares[1][share.NamespaceSize:]) @@ -334,7 +332,7 @@ func CreateFraudExtHeader( dah := da.NewDataAvailabilityHeader(extended) eh.DAH = &dah eh.RawHeader.DataHash = dah.Hash() - return eh + return eh, extended } type DummySubscriber struct { diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index b2d32de1c0..3ca4638fa8 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -32,15 +32,18 @@ Steps: Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. */ func TestFraudProofBroadcasting(t *testing.T) { - // we increase the timeout for this test to decrease flakiness in CI - testTimeout := time.Millisecond * 200 - sw := swamp.NewSwamp(t, swamp.WithBlockTime(testTimeout)) - - bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(headertest.FraudMaker(t, 20, mdutils.Bserv()))) - + const ( + blocks = 15 + bsize = 2 + btime = time.Millisecond * 300 + ) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) + bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv()))) + err := bridge.Start(ctx) require.NoError(t, err) addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) @@ -60,13 +63,13 @@ func TestFraudProofBroadcasting(t *testing.T) { require.NoError(t, err) p := <-subscr - require.Equal(t, 20, int(p.Height())) + require.Equal(t, 10, int(p.Height())) // This is an obscure way to check if the Syncer was stopped. // If we cannot get a height header within a timeframe it means the syncer was stopped // FIXME: Eventually, this should be a check on service registry managing and keeping // lifecycles of each Module. - syncCtx, syncCancel := context.WithTimeout(context.Background(), testTimeout) + syncCtx, syncCancel := context.WithTimeout(context.Background(), btime) _, err = full.HeaderServ.GetByHeight(syncCtx, 100) require.ErrorIs(t, err, context.DeadlineExceeded) syncCancel() @@ -80,6 +83,7 @@ func TestFraudProofBroadcasting(t *testing.T) { proofs, err := full.FraudServ.Get(ctx, fraud.BadEncoding) require.NoError(t, err) require.NotNil(t, proofs) + require.NoError(t, <-fillDn) } /* @@ -96,8 +100,16 @@ Steps: 7. Wait until LN will be connected to FN and fetch a fraud proof. */ func TestFraudProofSyncing(t *testing.T) { - sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*300)) + const ( + blocks = 15 + bsize = 2 + btime = time.Millisecond * 300 + ) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) cfg := nodebuilder.DefaultConfig(node.Bridge) store := nodebuilder.MockStore(t, cfg) bridge := sw.NewNodeWithStore( @@ -106,9 +118,6 @@ func TestFraudProofSyncing(t *testing.T) { core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv())), ) - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) - err := bridge.Start(ctx) require.NoError(t, err) addr := host.InfoFromHost(bridge.Host) @@ -152,4 +161,5 @@ func TestFraudProofSyncing(t *testing.T) { case <-ctx.Done(): t.Fatal("light node didn't get FP in time") } + require.NoError(t, <-fillDn) } diff --git a/nodebuilder/tests/swamp/swamp_tx.go b/nodebuilder/tests/swamp/swamp_tx.go index d8e7e29554..956c8cf7c4 100644 --- a/nodebuilder/tests/swamp/swamp_tx.go +++ b/nodebuilder/tests/swamp/swamp_tx.go @@ -2,6 +2,7 @@ package swamp import ( "context" + "time" "github.com/cosmos/cosmos-sdk/client/flags" @@ -14,6 +15,8 @@ func FillBlocks(ctx context.Context, cctx testnode.Context, accounts []string, b errCh := make(chan error) go func() { // TODO: FillBlock must respect the context + // fill blocks is not working correctly without sleep rn. + time.Sleep(time.Millisecond * 50) var err error for i := 0; i < blocks; i++ { _, err = cctx.FillBlock(bsize, accounts, flags.BroadcastBlock) diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index a7bb8353ec..cc398890d0 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -157,7 +157,7 @@ func generateByzantineError( h, err := store.GetByHeight(ctx, 1) require.NoError(t, err) - faultHeader := headertest.CreateFraudExtHeader(t, h, bServ) + faultHeader, _ := headertest.CreateFraudExtHeader(t, h, bServ) _, err = NewRetriever(bServ).Retrieve(ctx, faultHeader.DAH) return faultHeader, err } From cefa3857fe1c9c3295e80f1d0942b98cae37ed08 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 13 Feb 2023 20:20:26 +0300 Subject: [PATCH 0368/1008] fix(share/shrex/peer_manager): peer manager needs to do soft delete on synced pool instead of hard one (#1734) ## Overview This fix resolves an issue where peers who would broadcast hashes via shrex-sub that were already retrieved by the node would get blacklisted. This happened because after retrieval of a given hash, the `syncPool` corresponding to the given hash gets deleted, so when a delayed peer would broadcast the same hash after retrieval, the manager would create a new pool that goes unvalidated since the header was already received via header-sub, causing the delayed peers in the recreated pool to get blacklisted. The solution is to do a soft-delete of the pool such that delayed peers are GC'd rather than blacklisted. --- share/p2p/peers/manager.go | 35 ++++++++++++++++++++----------- share/p2p/peers/manager_test.go | 37 ++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 52e929c7a2..a3c8f12823 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -7,6 +7,7 @@ import ( "sync" "sync/atomic" "time" + "unsafe" "github.com/libp2p/go-libp2p/p2p/net/conngater" @@ -67,6 +68,7 @@ type syncPool struct { // isValidatedDataHash indicates if datahash was validated by receiving corresponding extended // header from headerSub isValidatedDataHash atomic.Bool + isSynced atomic.Bool createdAt time.Time } @@ -187,7 +189,7 @@ func (s *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { return func(result syncResult) { switch result { case ResultSuccess: - s.deletePool(datahash.String()) + s.getOrCreatePool(datahash.String()).markSynced() case ResultFail: case ResultPeerMisbehaved: s.blacklistPeers(peerID) @@ -246,12 +248,6 @@ func (s *Manager) getOrCreatePool(datahash string) *syncPool { return p } -func (s *Manager) deletePool(datahash string) { - s.lock.Lock() - defer s.lock.Unlock() - delete(s.pools, datahash) -} - func (s *Manager) blacklistPeers(peerIDs ...peer.ID) { for _, peerID := range peerIDs { s.fullNodes.remove(peerID) @@ -278,10 +274,6 @@ func (s *Manager) hashIsBlacklisted(hash share.DataHash) bool { return s.blacklistedHashes[hash.String()] } -func (p *syncPool) markValidated() { - p.isValidatedDataHash.Store(true) -} - func (s *Manager) GC(ctx context.Context) { ticker := time.NewTicker(gcInterval) defer ticker.Stop() @@ -289,7 +281,9 @@ func (s *Manager) GC(ctx context.Context) { var blacklist []peer.ID for { blacklist = s.cleanUp() - s.blacklistPeers(blacklist...) + if len(blacklist) > 0 { + s.blacklistPeers(blacklist...) + } select { case <-ticker.C: @@ -326,3 +320,20 @@ func (s *Manager) cleanUp() []peer.ID { } return blacklist } + +func (p *syncPool) markSynced() { + p.isSynced.Store(true) + old := (*unsafe.Pointer)(unsafe.Pointer(&p.pool)) + // release pointer to old pool to free up memory + atomic.StorePointer(old, unsafe.Pointer(newPool())) +} + +func (p *syncPool) markValidated() { + p.isValidatedDataHash.Store(true) +} + +func (p *syncPool) add(peers ...peer.ID) { + if !p.isSynced.Load() { + p.pool.add(peers...) + } +} diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 41518b1918..61093f39a3 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -69,11 +69,12 @@ func TestManager(t *testing.T) { require.Equal(t, peerID, pID) // check pool validation - require.True(t, manager.pools[h.DataHash.String()].isValidatedDataHash.Load()) + require.True(t, manager.getOrCreatePool(h.DataHash.String()).isValidatedDataHash.Load()) done(ResultSuccess) - // pool should be removed after success - require.Len(t, manager.pools, 0) + // pool should not be removed after success + require.Len(t, manager.pools, 1) + require.Len(t, manager.getOrCreatePool(h.DataHash.String()).pool.peersList, 0) }) t.Run("validator", func(t *testing.T) { @@ -198,6 +199,36 @@ func TestManager(t *testing.T) { stopManager(t, manager) }) + t.Run("get peer from discovery", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + h := testHeader() + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + peerID := peer.ID("peer1") + result := manager.validate(ctx, peerID, h.DataHash.Bytes()) + require.Equal(t, pubsub.ValidationIgnore, result) + + pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + require.NoError(t, err) + require.Equal(t, peerID, pID) + done(ResultSuccess) + + // check pool is soft deleted and marked synced + pool := manager.getOrCreatePool(h.DataHash.String()) + require.Len(t, pool.peersList, 0) + require.True(t, pool.isSynced.Load()) + + // add peer on synced pool should be noop + result = manager.validate(ctx, "peer2", h.DataHash.Bytes()) + require.Equal(t, pubsub.ValidationIgnore, result) + require.Len(t, pool.peersList, 0) + }) } func TestIntegration(t *testing.T) { From b2125bd149600ae84e99d1c9f50c9d1b8286cab2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Feb 2023 08:56:46 +0100 Subject: [PATCH 0369/1008] improvement(nodebuilder/p2p): Turn on PEX by default for bridges and fulls (#1727) Turns on peer exchange for bridges and fulls for better connectivity guarantees. --- nodebuilder/config.go | 2 +- nodebuilder/p2p/config.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nodebuilder/config.go b/nodebuilder/config.go index e034a303b5..ef9784461a 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -39,7 +39,7 @@ func DefaultConfig(tp node.Type) *Config { commonConfig := &Config{ Core: core.DefaultConfig(), State: state.DefaultConfig(), - P2P: p2p.DefaultConfig(), + P2P: p2p.DefaultConfig(tp), RPC: rpc.DefaultConfig(), Gateway: gateway.DefaultConfig(), Share: share.DefaultConfig(), diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 0388a883c9..b968100095 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -6,6 +6,8 @@ import ( "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) const defaultRoutingRefreshPeriod = time.Minute @@ -36,7 +38,7 @@ type Config struct { } // DefaultConfig returns default configuration for P2P subsystem. -func DefaultConfig() Config { +func DefaultConfig(tp node.Type) Config { return Config{ ListenAddresses: []string{ "/ip4/0.0.0.0/udp/2121/quic-v1", @@ -55,7 +57,7 @@ func DefaultConfig() Config { }, MutualPeers: []string{}, Bootstrapper: false, - PeerExchange: false, + PeerExchange: tp == node.Bridge || tp == node.Full, ConnManager: defaultConnManagerConfig(), RoutingTableRefreshPeriod: defaultRoutingRefreshPeriod, } From 4111b5f2657eccfa7f458ada800f88e4152958bd Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Feb 2023 08:57:52 +0100 Subject: [PATCH 0370/1008] refactor(core): `CoreListener` broadcasts EDS hash before it broadcasts `ExtendedHeader` (#1739) Increases the chances that the EDS hash will arrive before the `ExtendedHeader` so that by the time `SharesAvailable` is called on the `ExtendedHeader` by recipient peer, it is more likely to have more peers available for the given eds hash. --- core/listener.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/listener.go b/core/listener.go index 6ac3776076..96a37c7f15 100644 --- a/core/listener.go +++ b/core/listener.go @@ -117,13 +117,6 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { } } - // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers - err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) - if err != nil { - log.Errorw("listener: broadcasting next header", "height", eh.Height(), - "err", err) - } - // notify network of new EDS hash only if core is already synced if !syncing { err = cl.hashBroadcaster(ctx, eh.DataHash.Bytes()) @@ -132,6 +125,13 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { "hash", eh.Hash(), "err", err) } } + + // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers + err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) + if err != nil { + log.Errorw("listener: broadcasting next header", "height", eh.Height(), + "err", err) + } case <-ctx.Done(): return } From ea69879b1c245609c4be62f9f6d65ea5b282422f Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 15 Feb 2023 16:51:44 +0300 Subject: [PATCH 0371/1008] feat(share/shrex/peer_manager): add debug logs to peer manager (#1738) Add extensive debug logging to peer manager --- share/p2p/peers/manager.go | 47 +++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index a3c8f12823..1f30082b38 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -95,10 +95,17 @@ func NewManager( discovery.WithOnPeersUpdate( func(peerID peer.ID, isAdded bool) { - if isAdded && !s.peerIsBlacklisted(peerID) { + if isAdded { + if s.peerIsBlacklisted(peerID) { + log.Debugw("got blacklisted from discovery", "peer", peerID) + return + } + log.Debugw("added to full nodes", "peer", peerID) s.fullNodes.add(peerID) return } + + log.Debugw("removing peer from discovered full nodes", "peer", peerID) s.fullNodes.remove(peerID) }) @@ -154,16 +161,22 @@ func (s *Manager) Peer( ctx context.Context, datahash share.DataHash, ) (peer.ID, DoneFunc, error) { p := s.getOrCreatePool(datahash.String()) - p.markValidated() + if p.markValidated() { + log.Debugw("marked validated", "datahash", datahash.String()) + } // first, check if a peer is available for the given datahash peerID, ok := p.tryGet() if ok { // some pools could still have blacklisted peers in storage if s.peerIsBlacklisted(peerID) { + log.Debugw("removing blacklisted peer from pool", "hash", datahash.String(), + "peer", peerID.String()) p.remove(peerID) return s.Peer(ctx, datahash) } + log.Debugw("returning shrex-sub peer", "hash", datahash.String(), + "peer", peerID.String()) return peerID, s.doneFunc(datahash, peerID), nil } @@ -171,14 +184,17 @@ func (s *Manager) Peer( // obtained from discovery peerID, ok = s.fullNodes.tryGet() if ok { + log.Debugw("got peer from full nodes discovery pool", "peer", peerID, "datahash", datahash.String()) return peerID, s.doneFunc(datahash, peerID), nil } // no peers are available right now, wait for the first one select { case peerID = <-p.next(ctx): + log.Debugw("got peer from shrexSub pool after wait", "peer", peerID, "datahash", datahash.String()) return peerID, s.doneFunc(datahash, peerID), nil case peerID = <-s.fullNodes.next(ctx): + log.Debugw("got peer from discovery pool after wait", "peer", peerID, "datahash", datahash.String()) return peerID, s.doneFunc(datahash, peerID), nil case <-ctx.Done(): return "", nil, ctx.Err() @@ -187,6 +203,10 @@ func (s *Manager) Peer( func (s *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { return func(result syncResult) { + log.Debugw("set peer status", + "peer", peerID, + "datahash", datahash.String(), + result, result) switch result { case ResultSuccess: s.getOrCreatePool(datahash.String()).markSynced() @@ -212,7 +232,9 @@ func (s *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri continue } - s.getOrCreatePool(h.DataHash.String()).markValidated() + if s.getOrCreatePool(h.DataHash.String()).markValidated() { + log.Debugw("marked validated", "datahash", h.DataHash.String()) + } } } @@ -220,15 +242,23 @@ func (s *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri func (s *Manager) validate(ctx context.Context, peerID peer.ID, hash share.DataHash) pubsub.ValidationResult { // messages broadcasted from self should bypass the validation with Accept if peerID == s.host.ID() { + log.Debugw("received datahash from self", "datahash", hash.String()) return pubsub.ValidationAccept } // punish peer for sending invalid hash if it has misbehaved in the past - if s.hashIsBlacklisted(hash) || s.peerIsBlacklisted(peerID) { + if s.hashIsBlacklisted(hash) { + log.Debugw("received blacklisted hash, reject validation", "peer", peerID, "datahash", hash.String()) + return pubsub.ValidationReject + } + + if s.peerIsBlacklisted(peerID) { + log.Debugw("received message from blacklisted peer, reject validation", "peer", peerID, "datahash", hash.String()) return pubsub.ValidationReject } s.getOrCreatePool(hash.String()).add(peerID) + log.Debugw("got hash from shrex-sub", "peer", peerID, "datahash", hash.String()) return pubsub.ValidationIgnore } @@ -249,17 +279,18 @@ func (s *Manager) getOrCreatePool(datahash string) *syncPool { } func (s *Manager) blacklistPeers(peerIDs ...peer.ID) { + log.Debugw("blacklisting peers", "peers", peerIDs) for _, peerID := range peerIDs { s.fullNodes.remove(peerID) // add peer to the blacklist, so we can't connect to it in the future. err := s.connGater.BlockPeer(peerID) if err != nil { - log.Debugw("blocking peer failed", "peer_id", peerID, "err", err) + log.Warnw("failed tp block peer", "peer", peerID, "err", err) } // close connections to peer. err = s.host.Network().ClosePeer(peerID) if err != nil { - log.Debugw("closing connection with peer failed", "peer_id", peerID, "err", err) + log.Warnw("failed to close connection with peer", "peer", peerID, "err", err) } } } @@ -328,8 +359,8 @@ func (p *syncPool) markSynced() { atomic.StorePointer(old, unsafe.Pointer(newPool())) } -func (p *syncPool) markValidated() { - p.isValidatedDataHash.Store(true) +func (p *syncPool) markValidated() bool { + return p.isValidatedDataHash.CompareAndSwap(false, true) } func (p *syncPool) add(peers ...peer.ID) { From 15dfa7a2c1b1425475e52d4e77a3243024d2aff1 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 15 Feb 2023 17:54:13 +0300 Subject: [PATCH 0372/1008] feat(share/shrex/peer_manager): add backoff in peer manager (#1732) Closes https://github.com/celestiaorg/celestia-node/issues/1731 --- go.mod | 2 +- share/getters/shrex.go | 10 +- share/p2p/peers/manager.go | 163 ++++++++++++++++------------- share/p2p/peers/manager_test.go | 21 ++-- share/p2p/peers/pool.go | 80 ++++++++++---- share/p2p/peers/pool_test.go | 55 ++++++++-- share/p2p/peers/timedqueue.go | 91 ++++++++++++++++ share/p2p/peers/timedqueue_test.go | 61 +++++++++++ 8 files changed, 366 insertions(+), 117 deletions(-) create mode 100644 share/p2p/peers/timedqueue.go create mode 100644 share/p2p/peers/timedqueue_test.go diff --git a/go.mod b/go.mod index 3ce6c9e4d9..d116f81217 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 + github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.0-rc7 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.14.0 @@ -90,7 +91,6 @@ require ( github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.40.45 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect diff --git a/share/getters/shrex.go b/share/getters/shrex.go index a24d59011a..d0b9ab6eeb 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -45,14 +45,14 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex cancel() switch err { case nil: - setStatus(peers.ResultSuccess) + setStatus(peers.ResultSynced) return eds, nil case context.DeadlineExceeded: log.Debugw("request exceeded deadline, trying with new peer", "datahash", root.String()) case p2p.ErrInvalidResponse: - setStatus(peers.ResultPeerMisbehaved) + setStatus(peers.ResultBlacklistPeer) default: - setStatus(peers.ResultFail) + setStatus(peers.ResultCooldownPeer) } } } @@ -79,9 +79,9 @@ func (sg *ShrexGetter) GetSharesByNamespace( case context.DeadlineExceeded: log.Debugw("request exceeded deadline, trying with new peer", "datahash", root.String()) case p2p.ErrInvalidResponse: - setStatus(peers.ResultPeerMisbehaved) + setStatus(peers.ResultBlacklistPeer) default: - setStatus(peers.ResultFail) + setStatus(peers.ResultCooldownPeer) } } } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 1f30082b38..d09eb69f94 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -24,9 +24,16 @@ import ( ) const ( - ResultSuccess syncResult = iota - ResultFail - ResultPeerMisbehaved + // ResultSuccess indicates operation was successful and no extra action is required + ResultSuccess result = iota + // ResultSynced will save the status of pool as "synced" and will remove peers from it + ResultSynced + // ResultCooldownPeer will put returned peer on cooldown, meaning it won't be available by Peer + // method for some time + ResultCooldownPeer + // ResultBlacklistPeer will blacklist peer. Blacklisted peers will be disconnected and blocked from + // any p2p communication in future by libp2p Gater + ResultBlacklistPeer gcInterval = time.Second * 30 ) @@ -45,8 +52,9 @@ type Manager struct { connGater *conngater.BasicConnectionGater // pools collecting peers from shrexSub - pools map[string]*syncPool - poolSyncTimeout time.Duration + pools map[string]*syncPool + poolSyncTimeout time.Duration + peerCooldownTime time.Duration // fullNodes collects full nodes peer.ID found via discovery fullNodes *pool @@ -57,10 +65,11 @@ type Manager struct { done chan struct{} } -// DoneFunc is a function performs an action based on the given syncResult. -type DoneFunc func(result syncResult) +// DoneFunc updates internal state depending on call results. Should be called once per returned +// peer from Peer method +type DoneFunc func(result) -type syncResult int +type result int type syncPool struct { *pool @@ -79,6 +88,7 @@ func NewManager( host host.Host, connGater *conngater.BasicConnectionGater, syncTimeout time.Duration, + peerCooldownTime time.Duration, ) *Manager { s := &Manager{ headerSub: headerSub, @@ -88,7 +98,7 @@ func NewManager( host: host, pools: make(map[string]*syncPool), poolSyncTimeout: syncTimeout, - fullNodes: newPool(), + fullNodes: newPool(peerCooldownTime), blacklistedHashes: make(map[string]bool), done: make(chan struct{}), } @@ -112,41 +122,41 @@ func NewManager( return s } -func (s *Manager) Start(startCtx context.Context) error { +func (m *Manager) Start(startCtx context.Context) error { ctx, cancel := context.WithCancel(context.Background()) - s.cancel = cancel + m.cancel = cancel - err := s.shrexSub.Start(startCtx) + err := m.shrexSub.Start(startCtx) if err != nil { return fmt.Errorf("starting shrexsub: %w", err) } - err = s.shrexSub.AddValidator(s.validate) + err = m.shrexSub.AddValidator(m.validate) if err != nil { return fmt.Errorf("registering validator: %w", err) } - _, err = s.shrexSub.Subscribe() + _, err = m.shrexSub.Subscribe() if err != nil { return fmt.Errorf("subscribing to shrexsub: %w", err) } - sub, err := s.headerSub.Subscribe() + sub, err := m.headerSub.Subscribe() if err != nil { return fmt.Errorf("subscribing to headersub: %w", err) } - go s.disc.EnsurePeers(ctx) - go s.subscribeHeader(ctx, sub) - go s.GC(ctx) + go m.disc.EnsurePeers(ctx) + go m.subscribeHeader(ctx, sub) + go m.GC(ctx) return nil } -func (s *Manager) Stop(ctx context.Context) error { - s.cancel() +func (m *Manager) Stop(ctx context.Context) error { + m.cancel() select { - case <-s.done: + case <-m.done: return nil case <-ctx.Done(): return ctx.Err() @@ -154,13 +164,14 @@ func (s *Manager) Stop(ctx context.Context) error { } // Peer returns peer collected from shrex.Sub for given datahash if any available. -// If there is none, it will look for fullnodes collected from discovery. If there is no discovered +// If there is none, it will look for full nodes collected from discovery. If there is no discovered // full nodes, it will wait until any peer appear in either source or timeout happen. -// After fetching data using given peer, caller is required to call returned DoneFunc -func (s *Manager) Peer( +// After fetching data using given peer, caller is required to call returned DoneFunc using +// appropriate result value +func (m *Manager) Peer( ctx context.Context, datahash share.DataHash, ) (peer.ID, DoneFunc, error) { - p := s.getOrCreatePool(datahash.String()) + p := m.getOrCreatePool(datahash.String()) if p.markValidated() { log.Debugw("marked validated", "datahash", datahash.String()) } @@ -169,57 +180,59 @@ func (s *Manager) Peer( peerID, ok := p.tryGet() if ok { // some pools could still have blacklisted peers in storage - if s.peerIsBlacklisted(peerID) { + if m.peerIsBlacklisted(peerID) { log.Debugw("removing blacklisted peer from pool", "hash", datahash.String(), "peer", peerID.String()) p.remove(peerID) - return s.Peer(ctx, datahash) + return m.Peer(ctx, datahash) } log.Debugw("returning shrex-sub peer", "hash", datahash.String(), "peer", peerID.String()) - return peerID, s.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID), nil } // if no peer for datahash is currently available, try to use full node // obtained from discovery - peerID, ok = s.fullNodes.tryGet() + peerID, ok = m.fullNodes.tryGet() if ok { log.Debugw("got peer from full nodes discovery pool", "peer", peerID, "datahash", datahash.String()) - return peerID, s.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID), nil } // no peers are available right now, wait for the first one select { case peerID = <-p.next(ctx): log.Debugw("got peer from shrexSub pool after wait", "peer", peerID, "datahash", datahash.String()) - return peerID, s.doneFunc(datahash, peerID), nil - case peerID = <-s.fullNodes.next(ctx): + return peerID, m.doneFunc(datahash, peerID), nil + case peerID = <-m.fullNodes.next(ctx): log.Debugw("got peer from discovery pool after wait", "peer", peerID, "datahash", datahash.String()) - return peerID, s.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID), nil case <-ctx.Done(): return "", nil, ctx.Err() } } -func (s *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { - return func(result syncResult) { +func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { + return func(result result) { log.Debugw("set peer status", "peer", peerID, "datahash", datahash.String(), result, result) switch result { case ResultSuccess: - s.getOrCreatePool(datahash.String()).markSynced() - case ResultFail: - case ResultPeerMisbehaved: - s.blacklistPeers(peerID) + case ResultSynced: + m.getOrCreatePool(datahash.String()).markSynced() + case ResultCooldownPeer: + m.getOrCreatePool(datahash.String()).putOnCooldown(peerID) + case ResultBlacklistPeer: + m.blacklistPeers(peerID) } } } // subscribeHeader takes datahash from received header and validates corresponding peer pool. -func (s *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscription[*header.ExtendedHeader]) { - defer close(s.done) +func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscription[*header.ExtendedHeader]) { + defer close(m.done) defer headerSub.Cancel() for { @@ -232,88 +245,88 @@ func (s *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri continue } - if s.getOrCreatePool(h.DataHash.String()).markValidated() { + if m.getOrCreatePool(h.DataHash.String()).markValidated() { log.Debugw("marked validated", "datahash", h.DataHash.String()) } } } // Validate will collect peer.ID into corresponding peer pool -func (s *Manager) validate(ctx context.Context, peerID peer.ID, hash share.DataHash) pubsub.ValidationResult { - // messages broadcasted from self should bypass the validation with Accept - if peerID == s.host.ID() { +func (m *Manager) validate(ctx context.Context, peerID peer.ID, hash share.DataHash) pubsub.ValidationResult { + // messages broadcast from self should bypass the validation with Accept + if peerID == m.host.ID() { log.Debugw("received datahash from self", "datahash", hash.String()) return pubsub.ValidationAccept } // punish peer for sending invalid hash if it has misbehaved in the past - if s.hashIsBlacklisted(hash) { + if m.hashIsBlacklisted(hash) { log.Debugw("received blacklisted hash, reject validation", "peer", peerID, "datahash", hash.String()) return pubsub.ValidationReject } - if s.peerIsBlacklisted(peerID) { + if m.peerIsBlacklisted(peerID) { log.Debugw("received message from blacklisted peer, reject validation", "peer", peerID, "datahash", hash.String()) return pubsub.ValidationReject } - s.getOrCreatePool(hash.String()).add(peerID) + m.getOrCreatePool(hash.String()).add(peerID) log.Debugw("got hash from shrex-sub", "peer", peerID, "datahash", hash.String()) return pubsub.ValidationIgnore } -func (s *Manager) getOrCreatePool(datahash string) *syncPool { - s.lock.Lock() - defer s.lock.Unlock() +func (m *Manager) getOrCreatePool(datahash string) *syncPool { + m.lock.Lock() + defer m.lock.Unlock() - p, ok := s.pools[datahash] + p, ok := m.pools[datahash] if !ok { p = &syncPool{ - pool: newPool(), + pool: newPool(m.peerCooldownTime), createdAt: time.Now(), } - s.pools[datahash] = p + m.pools[datahash] = p } return p } -func (s *Manager) blacklistPeers(peerIDs ...peer.ID) { +func (m *Manager) blacklistPeers(peerIDs ...peer.ID) { log.Debugw("blacklisting peers", "peers", peerIDs) for _, peerID := range peerIDs { - s.fullNodes.remove(peerID) + m.fullNodes.remove(peerID) // add peer to the blacklist, so we can't connect to it in the future. - err := s.connGater.BlockPeer(peerID) + err := m.connGater.BlockPeer(peerID) if err != nil { log.Warnw("failed tp block peer", "peer", peerID, "err", err) } // close connections to peer. - err = s.host.Network().ClosePeer(peerID) + err = m.host.Network().ClosePeer(peerID) if err != nil { log.Warnw("failed to close connection with peer", "peer", peerID, "err", err) } } } -func (s *Manager) peerIsBlacklisted(peerID peer.ID) bool { - return !s.connGater.InterceptPeerDial(peerID) +func (m *Manager) peerIsBlacklisted(peerID peer.ID) bool { + return !m.connGater.InterceptPeerDial(peerID) } -func (s *Manager) hashIsBlacklisted(hash share.DataHash) bool { - s.lock.Lock() - defer s.lock.Unlock() - return s.blacklistedHashes[hash.String()] +func (m *Manager) hashIsBlacklisted(hash share.DataHash) bool { + m.lock.Lock() + defer m.lock.Unlock() + return m.blacklistedHashes[hash.String()] } -func (s *Manager) GC(ctx context.Context) { +func (m *Manager) GC(ctx context.Context) { ticker := time.NewTicker(gcInterval) defer ticker.Stop() var blacklist []peer.ID for { - blacklist = s.cleanUp() + blacklist = m.cleanUp() if len(blacklist) > 0 { - s.blacklistPeers(blacklist...) + m.blacklistPeers(blacklist...) } select { @@ -324,19 +337,19 @@ func (s *Manager) GC(ctx context.Context) { } } -func (s *Manager) cleanUp() []peer.ID { - s.lock.Lock() - defer s.lock.Unlock() +func (m *Manager) cleanUp() []peer.ID { + m.lock.Lock() + defer m.lock.Unlock() addToBlackList := make(map[peer.ID]struct{}) - for h, p := range s.pools { - if time.Since(p.createdAt) > s.poolSyncTimeout && !p.isValidatedDataHash.Load() { + for h, p := range m.pools { + if time.Since(p.createdAt) > m.poolSyncTimeout && !p.isValidatedDataHash.Load() { log.Debug("blacklisting datahash with all corresponding peers", "datahash", h, "peer_list", p.peersList) // blacklist hash - delete(s.pools, h) - s.blacklistedHashes[h] = true + delete(m.pools, h) + m.blacklistedHashes[h] = true // blacklist peers for _, peer := range p.peersList { @@ -356,7 +369,7 @@ func (p *syncPool) markSynced() { p.isSynced.Store(true) old := (*unsafe.Pointer)(unsafe.Pointer(&p.pool)) // release pointer to old pool to free up memory - atomic.StorePointer(old, unsafe.Pointer(newPool())) + atomic.StorePointer(old, unsafe.Pointer(newPool(time.Second))) } func (p *syncPool) markValidated() bool { diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 61093f39a3..3785b838fc 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -71,7 +71,7 @@ func TestManager(t *testing.T) { // check pool validation require.True(t, manager.getOrCreatePool(h.DataHash.String()).isValidatedDataHash.Load()) - done(ResultSuccess) + done(ResultSynced) // pool should not be removed after success require.Len(t, manager.pools, 1) require.Len(t, manager.getOrCreatePool(h.DataHash.String()).pool.peersList, 0) @@ -101,7 +101,7 @@ func TestManager(t *testing.T) { require.Equal(t, peerID, pID) // mark peer as misbehaved tp blacklist it - done(ResultPeerMisbehaved) + done(ResultBlacklistPeer) // misbehaved should be Rejected result = manager.validate(ctx, pID, h.DataHash.Bytes()) @@ -149,7 +149,7 @@ func TestManager(t *testing.T) { manager.fullNodes.add(peers...) peerID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) - done(ResultSuccess) + done(ResultSynced) require.NoError(t, err) require.Contains(t, peers, peerID) @@ -181,7 +181,7 @@ func TestManager(t *testing.T) { go func() { defer close(doneCh) peerID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) - done(ResultSuccess) + done(ResultSynced) require.NoError(t, err) require.Contains(t, peers, peerID) }() @@ -217,7 +217,7 @@ func TestManager(t *testing.T) { pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) require.NoError(t, err) require.Equal(t, peerID, pID) - done(ResultSuccess) + done(ResultSynced) // check pool is soft deleted and marked synced pool := manager.getOrCreatePool(h.DataHash.String()) @@ -233,8 +233,7 @@ func TestManager(t *testing.T) { func TestIntegration(t *testing.T) { t.Run("get peer from shrexsub", func(t *testing.T) { - t.SkipNow() - nw, err := mocknet.FullMeshConnected(3) + nw, err := mocknet.FullMeshLinked(2) require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -256,6 +255,10 @@ func TestIntegration(t *testing.T) { _, err = fnPubSub.Subscribe() require.NoError(t, err) + time.Sleep(time.Millisecond * 100) + require.NoError(t, nw.ConnectAllButSelf()) + time.Sleep(time.Millisecond * 100) + // broadcast from BN peerHash := share.DataHash("peer1") require.NoError(t, bnPubSub.Broadcast(ctx, peerHash)) @@ -317,7 +320,7 @@ func TestIntegration(t *testing.T) { // hook peer manager to discovery connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - fnPeerManager := NewManager(nil, nil, fnDisc, nil, connGater, time.Minute) + fnPeerManager := NewManager(nil, nil, fnDisc, nil, connGater, time.Minute, time.Second) waitCh := make(chan struct{}) fnDisc.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { @@ -356,7 +359,7 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten if err != nil { return nil, err } - manager := NewManager(headerSub, shrexSub, disc, host, connGater, time.Minute) + manager := NewManager(headerSub, shrexSub, disc, host, connGater, time.Minute, time.Second) err = manager.Start(ctx) return manager, err } diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index 37664e0d8a..89d79291bf 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -3,6 +3,7 @@ package peers import ( "context" "sync" + "time" "github.com/libp2p/go-libp2p/core/peer" ) @@ -13,7 +14,8 @@ const defaultCleanupThreshold = 2 type pool struct { m sync.Mutex peersList []peer.ID - active map[peer.ID]bool + statuses map[peer.ID]status + cooldown *timedQueue activeCount int nextIdx int @@ -23,14 +25,24 @@ type pool struct { cleanupThreshold int } +type status int + +const ( + active status = iota + cooldown + removed +) + // newPool returns new empty pool. -func newPool() *pool { - return &pool{ +func newPool(peerCooldownTime time.Duration) *pool { + p := &pool{ peersList: make([]peer.ID, 0), - active: make(map[peer.ID]bool), + statuses: make(map[peer.ID]status), hasPeerCh: make(chan struct{}), cleanupThreshold: defaultCleanupThreshold, } + p.cooldown = newTimedQueue(peerCooldownTime, p.afterCooldown) + return p } // tryGet returns peer along with bool flag indicating success of operation. @@ -51,7 +63,7 @@ func (p *pool) tryGet() (peer.ID, bool) { p.nextIdx = 0 } - if alive := p.active[peerID]; alive { + if p.statuses[peerID] == active { return peerID, true } @@ -87,15 +99,17 @@ func (p *pool) add(peers ...peer.ID) { defer p.m.Unlock() for _, peerID := range peers { - alive, ok := p.active[peerID] + status, ok := p.statuses[peerID] + if ok && status != removed { + continue + } + if !ok { p.peersList = append(p.peersList, peerID) } - if !ok || !alive { - p.active[peerID] = true - p.activeCount++ - } + p.statuses[peerID] = active + p.activeCount++ } p.checkHasPeers() } @@ -105,9 +119,11 @@ func (p *pool) remove(peers ...peer.ID) { defer p.m.Unlock() for _, peerID := range peers { - if alive, ok := p.active[peerID]; ok && alive { - p.active[peerID] = false - p.activeCount-- + if status, ok := p.statuses[peerID]; ok && status != removed { + p.statuses[peerID] = removed + if status == active { + p.activeCount-- + } } } @@ -122,16 +138,17 @@ func (p *pool) remove(peers ...peer.ID) { func (p *pool) cleanup() { newList := make([]peer.ID, 0, p.activeCount) for idx, peerID := range p.peersList { - alive := p.active[peerID] - if alive { + status := p.statuses[peerID] + switch status { + case active, cooldown: newList = append(newList, peerID) - } else { - delete(p.active, peerID) + case removed: + delete(p.statuses, peerID) } if idx == p.nextIdx { // if peer is not active and no more active peers left in list point to first peer - if !alive && len(newList) >= p.activeCount { + if status != active && len(newList) >= p.activeCount { p.nextIdx = 0 continue } @@ -141,6 +158,33 @@ func (p *pool) cleanup() { p.peersList = newList } +func (p *pool) putOnCooldown(peerID peer.ID) { + p.m.Lock() + defer p.m.Unlock() + + if status, ok := p.statuses[peerID]; ok && status == active { + p.cooldown.push(peerID) + + p.statuses[peerID] = cooldown + p.activeCount-- + p.checkHasPeers() + } +} + +func (p *pool) afterCooldown(peerID peer.ID) { + p.m.Lock() + defer p.m.Unlock() + + // item could have been already removed by the time afterCooldown is called + if status, ok := p.statuses[peerID]; !ok || status != cooldown { + return + } + + p.statuses[peerID] = active + p.activeCount++ + p.checkHasPeers() +} + // checkHasPeers will check and indicate if there are peers in the pool. func (p *pool) checkHasPeers() { if p.activeCount > 0 && !p.hasPeer { diff --git a/share/p2p/peers/pool_test.go b/share/p2p/peers/pool_test.go index 0fcfc7e339..3f4f6a5b9a 100644 --- a/share/p2p/peers/pool_test.go +++ b/share/p2p/peers/pool_test.go @@ -2,7 +2,6 @@ package peers import ( "context" - "fmt" "testing" "time" @@ -12,7 +11,7 @@ import ( func TestPool(t *testing.T) { t.Run("add / remove peers", func(t *testing.T) { - p := newPool() + p := newPool(time.Second) peers := []peer.ID{"peer1", "peer1", "peer2", "peer3"} // adding same peer twice should not produce copies @@ -31,11 +30,10 @@ func TestPool(t *testing.T) { require.Equal(t, 0, p.activeCount) _, ok = p.tryGet() require.False(t, ok) - fmt.Println(p) }) t.Run("round robin", func(t *testing.T) { - p := newPool() + p := newPool(time.Second) peers := []peer.ID{"peer1", "peer1", "peer2", "peer3"} // adding same peer twice should not produce copies @@ -75,7 +73,7 @@ func TestPool(t *testing.T) { longCtx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - p := newPool() + p := newPool(time.Second) done := make(chan struct{}) go func() { @@ -106,7 +104,7 @@ func TestPool(t *testing.T) { }) t.Run("nextIdx got removed", func(t *testing.T) { - p := newPool() + p := newPool(time.Second) peers := []peer.ID{"peer1", "peer2", "peer3"} p.add(peers...) @@ -120,7 +118,7 @@ func TestPool(t *testing.T) { }) t.Run("cleanup", func(t *testing.T) { - p := newPool() + p := newPool(time.Second) p.cleanupThreshold = 3 peers := []peer.ID{"peer1", "peer2", "peer3", "peer4", "peer5"} @@ -133,13 +131,52 @@ func TestPool(t *testing.T) { // remove some, but not trigger cleanup yet p.remove(peers[3:]...) require.Equal(t, len(peers)-2, p.activeCount) - require.Equal(t, len(peers), len(p.active)) + require.Equal(t, len(peers), len(p.statuses)) // trigger cleanup p.remove(peers[2]) require.Equal(t, len(peers)-3, p.activeCount) - require.Equal(t, len(peers)-3, len(p.active)) + require.Equal(t, len(peers)-3, len(p.statuses)) // nextIdx pointer should be updated require.Equal(t, 0, p.nextIdx) }) + + t.Run("cooldown blocks get", func(t *testing.T) { + ttl := time.Second / 10 + p := newPool(ttl) + + peerID := peer.ID("peer1") + p.add(peerID) + + _, ok := p.tryGet() + require.True(t, ok) + + p.putOnCooldown(peerID) + // item should be unavailable + _, ok = p.tryGet() + require.False(t, ok) + + ctx, cancel := context.WithTimeout(context.Background(), ttl*5) + defer cancel() + select { + case <-p.next(ctx): + case <-ctx.Done(): + t.Fatal("item should be already available") + } + }) + + t.Run("put on cooldown removed item should be noop", func(t *testing.T) { + p := newPool(time.Second) + p.cleanupThreshold = 3 + + peerID := peer.ID("peer1") + p.add(peerID) + + p.remove(peerID) + p.cleanup() + p.putOnCooldown(peerID) + + _, ok := p.tryGet() + require.False(t, ok) + }) } diff --git a/share/p2p/peers/timedqueue.go b/share/p2p/peers/timedqueue.go new file mode 100644 index 0000000000..3ed7e29a2c --- /dev/null +++ b/share/p2p/peers/timedqueue.go @@ -0,0 +1,91 @@ +package peers + +import ( + "sync" + "time" + + "github.com/benbjohnson/clock" + "github.com/libp2p/go-libp2p/core/peer" +) + +// timedQueue store items for ttl duration and releases it with calling onPop callback. Each item +// is tracked independently +type timedQueue struct { + sync.Mutex + items []item + + // ttl is the amount of time each item exist in the timedQueue + ttl time.Duration + clock clock.Clock + after *clock.Timer + // onPop will be called on item peer.ID after it is released + onPop func(peer.ID) +} + +type item struct { + peer.ID + createdAt time.Time +} + +func newTimedQueue(ttl time.Duration, onPop func(peer.ID)) *timedQueue { + return &timedQueue{ + items: make([]item, 0), + clock: clock.New(), + ttl: ttl, + onPop: onPop, + } +} + +// releaseExpired will release all expired items +func (q *timedQueue) releaseExpired() { + q.Lock() + defer q.Unlock() + q.releaseUnsafe() +} + +func (q *timedQueue) releaseUnsafe() { + if len(q.items) == 0 { + return + } + + var i int + for _, next := range q.items { + timeIn := q.clock.Since(next.createdAt) + if timeIn < q.ttl { + // item is not expired yet, create a timer that will call releaseExpired + q.after.Stop() + q.after = q.clock.AfterFunc(q.ttl-timeIn, q.releaseExpired) + break + } + + // item is expired + q.onPop(next.ID) + i++ + } + + if i > 0 { + copy(q.items, q.items[i:]) + q.items = q.items[:len(q.items)-i] + } +} + +func (q *timedQueue) push(peerID peer.ID) { + q.Lock() + defer q.Unlock() + + q.items = append(q.items, item{ + ID: peerID, + createdAt: q.clock.Now(), + }) + + // if it is the first item in queue, create a timer to call releaseExpired after its expiration + if len(q.items) == 1 { + q.after = q.clock.AfterFunc(q.ttl, q.releaseExpired) + } +} + +func (q *timedQueue) len() int { + q.Lock() + defer q.Unlock() + return len(q.items) +} diff --git a/share/p2p/peers/timedqueue_test.go b/share/p2p/peers/timedqueue_test.go new file mode 100644 index 0000000000..fb5ef9629f --- /dev/null +++ b/share/p2p/peers/timedqueue_test.go @@ -0,0 +1,61 @@ +package peers + +import ( + "testing" + "time" + + "github.com/benbjohnson/clock" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +func TestTimedQueue(t *testing.T) { + t.Run("push item", func(t *testing.T) { + peers := []peer.ID{"peer1", "peer2"} + ttl := time.Second + + popCh := make(chan struct{}, 1) + queue := newTimedQueue(ttl, func(id peer.ID) { + go func() { + require.Contains(t, peers, id) + popCh <- struct{}{} + }() + }) + mock := clock.NewMock() + queue.clock = mock + + // push first item | global time : 0 + queue.push(peers[0]) + require.Equal(t, queue.len(), 1) + + // push second item with ttl/2 gap | global time : ttl/2 + mock.Add(ttl / 2) + queue.push(peers[1]) + require.Equal(t, queue.len(), 2) + + // advance clock 1 nano sec before first item should expire | global time : ttl - 1 + mock.Add(ttl/2 - 1) + // check that releaseExpired doesn't remove items + queue.releaseExpired() + require.Equal(t, queue.len(), 2) + // first item should be released after its own timeout | global time : ttl + mock.Add(1) + + select { + case <-popCh: + case <-time.After(ttl): + t.Fatal("first item is not released") + + } + require.Equal(t, queue.len(), 1) + + // first item should be released after ttl/2 gap timeout | global time : 3/2*ttl + mock.Add(ttl / 2) + select { + case <-popCh: + case <-time.After(ttl): + t.Fatal("second item is not released") + } + require.Equal(t, queue.len(), 0) + }) +} From 4bc40487dcd7984bf7b326e759f09db5e72ab684 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 15 Feb 2023 15:54:25 +0100 Subject: [PATCH 0373/1008] fix(getters/shrex): removing implicit context assumption (#1740) ## Overview Prevents infinite processing during availability calls --- share/getters/shrex.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index d0b9ab6eeb..dd0119a215 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -34,6 +34,11 @@ func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) if err != nil { log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) @@ -63,6 +68,11 @@ func (sg *ShrexGetter) GetSharesByNamespace( id namespace.ID, ) (share.NamespacedShares, error) { for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) if err != nil { log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) From b5e1c65271a11813068c6549aa04c95e5b0057c6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 15 Feb 2023 21:32:30 +0100 Subject: [PATCH 0374/1008] feat(share): Integrate rearchitectured blocksync into fx (#1729) --- nodebuilder/share/constructors.go | 36 ++++++++++++++++++- nodebuilder/share/module.go | 57 ++++++++++++++++++++++++------- share/getters/shrex.go | 19 ++++++++++- share/getters/shrex_test.go | 8 +++-- share/p2p/shrexnd/server.go | 6 ++-- 5 files changed, 107 insertions(+), 19 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 0a5f5061aa..0d80eb725d 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -9,13 +9,21 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" - "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/p2p/peers" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" + + "github.com/celestiaorg/celestia-app/pkg/da" ) func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { @@ -57,3 +65,29 @@ func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { } return err } + +func peerManager( + headerSub libhead.Subscriber[*header.ExtendedHeader], + shrexSub *shrexsub.PubSub, + discovery *disc.Discovery, + host host.Host, + connGater *conngater.BasicConnectionGater, +) *peers.Manager { + // TODO: find better syncTimeout duration? + return peers.NewManager(headerSub, shrexSub, discovery, host, connGater, modp2p.BlockTime*5) +} + +func fullGetter( + store *eds.Store, + shrexGetter *getters.ShrexGetter, + ipldGetter *getters.IPLDGetter, +) share.Getter { + return getters.NewCascadeGetter( + []share.Getter{ + getters.NewStoreGetter(store), + getters.NewTeeGetter(shrexGetter, store), + getters.NewTeeGetter(ipldGetter, store), + }, + modp2p.BlockTime, + ) +} diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 8a2776855d..16866f90f2 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -16,6 +16,7 @@ import ( "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" + "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -29,7 +30,12 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Options(options...), fx.Provide(discovery(*cfg)), fx.Provide(newModule), - fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), + // TODO: Configure for light nodes + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { + return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(string(network))) + }, + ), ) switch tp { @@ -38,20 +44,24 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option "share", baseComponents, fx.Invoke(share.EnsureEmptySquareExists), - fx.Provide(fx.Annotate(light.NewShareAvailability)), - // cacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a cacheAvailability but the constructor returns a share.Availability - fx.Provide(cacheAvailability[*light.ShareAvailability]), + // shrexsub broadcaster stub for daser fx.Provide(func() shrexsub.BroadcastFn { return func(context.Context, share.DataHash) error { return nil } }), + fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), + fx.Provide(fx.Annotate(light.NewShareAvailability)), + // cacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a cacheAvailability but the constructor returns a share.Availability + fx.Provide(cacheAvailability[*light.ShareAvailability]), ) case node.Bridge, node.Full: return fx.Module( "share", baseComponents, + fx.Provide(getters.NewIPLDGetter), + fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(string(network))) @@ -63,6 +73,22 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return server.Stop(ctx) }), )), + fx.Provide(fx.Annotate( + func( + host host.Host, + store *eds.Store, + getter *getters.IPLDGetter, + network modp2p.Network, + ) (*shrexnd.Server, error) { + return shrexnd.NewServer(host, store, getter, shrexnd.WithProtocolSuffix(string(network))) + }, + fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *shrexnd.Server) error { + return server.Stop(ctx) + }), + )), // Bridge Nodes need a client as well, for requests over FullAvailability fx.Provide( func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { @@ -94,7 +120,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return avail.Stop(ctx) }), )), - fx.Provide(fx.Annotate( + fx.Provide( func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { return shrexsub.NewPubSub( ctx, @@ -102,19 +128,24 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option string(network), ) }, - fx.OnStart(func(ctx context.Context, pubsub *shrexsub.PubSub) error { - return pubsub.Start(ctx) + ), + // cacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a cacheAvailability but the constructor returns a share.Availability + fx.Provide(cacheAvailability[*full.ShareAvailability]), + fx.Provide(fx.Annotate( + getters.NewShrexGetter, + fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Start(ctx) }), - fx.OnStop(func(ctx context.Context, pubsub *shrexsub.PubSub) error { - return pubsub.Stop(ctx) + fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Stop(ctx) }), )), fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { return shrexSub.Broadcast }), - // cacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a cacheAvailability but the constructor returns a share.Availability - fx.Provide(cacheAvailability[*full.ShareAvailability]), + fx.Provide(peerManager), + fx.Provide(fullGetter), ) default: panic("invalid node type") diff --git a/share/getters/shrex.go b/share/getters/shrex.go index dd0119a215..d49b1c353b 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -17,7 +17,7 @@ import ( var _ share.Getter = (*ShrexGetter)(nil) -const MaxRequestDuration = time.Second * 10 +const defaultMaxRequestDuration = time.Second * 10 // ShrexGetter is a share.Getter that uses the shrex/eds and shrex/nd protocol to retrieve shares. type ShrexGetter struct { @@ -28,6 +28,23 @@ type ShrexGetter struct { maxRequestDuration time.Duration } +func NewShrexGetter(edsClient *shrexeds.Client, ndClient *shrexnd.Client, peerManager *peers.Manager) *ShrexGetter { + return &ShrexGetter{ + edsClient: edsClient, + ndClient: ndClient, + peerManager: peerManager, + maxRequestDuration: defaultMaxRequestDuration, + } +} + +func (sg *ShrexGetter) Start(ctx context.Context) error { + return sg.peerManager.Start(ctx) +} + +func (sg *ShrexGetter) Stop(ctx context.Context) error { + return sg.peerManager.Stop(ctx) +} + func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { return nil, errors.New("shrex-getter: GetShare is not supported") } diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 37cb864bb7..41a39749cf 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -46,8 +46,12 @@ func TestGetSharesWithProofByNamespace(t *testing.T) { srvHost := net.NewTestNode().Host srv, err := shrexnd.NewServer(srvHost, edsStore, NewIPLDGetter(bServ)) require.NoError(t, err) - srv.Start() - t.Cleanup(srv.Stop) + err = srv.Start(ctx) + require.NoError(t, err) + + t.Cleanup(func() { + _ = srv.Stop(ctx) + }) // create client and connect it to server client, err := shrexnd.NewClient(net.NewTestNode().Host) diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index df767b7f9d..2a6893188f 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -54,19 +54,21 @@ func NewServer(host host.Host, store *eds.Store, getter share.Getter, opts ...Op } // Start starts the server -func (srv *Server) Start() { +func (srv *Server) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) srv.cancel = cancel srv.host.SetStreamHandler(srv.protocolID, func(s network.Stream) { srv.handleNamespacedData(ctx, s) }) + return nil } // Stop stops the server -func (srv *Server) Stop() { +func (srv *Server) Stop(context.Context) error { srv.cancel() srv.host.RemoveStreamHandler(srv.protocolID) + return nil } func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { From 2fe9f0ca2b8a1a1ea763aec77df3af1d786b30ec Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 16 Feb 2023 12:37:22 +0100 Subject: [PATCH 0375/1008] feat!(share): adding config variables for blocksync (#1743) ## Overview Closes #1465 Based on #1729 --- nodebuilder/share/config.go | 4 ++ nodebuilder/share/constructors.go | 43 +++++++----------- nodebuilder/share/module.go | 3 +- share/p2p/peers/manager.go | 45 +++++++++++-------- share/p2p/peers/manager_test.go | 22 +++++++-- share/p2p/peers/options.go | 75 +++++++++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 share/p2p/peers/options.go diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 22564dd941..aeb37a93dc 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -18,6 +18,9 @@ type Config struct { // AdvertiseInterval is a interval between advertising sessions. // NOTE: only full and bridge can advertise themselves. AdvertiseInterval time.Duration + // UseShareExchange is a flag toggling the usage of shrex protocols for blocksync. + // NOTE: This config variable only has an effect on full and bridge nodes. + UseShareExchange bool } func DefaultConfig() Config { @@ -25,6 +28,7 @@ func DefaultConfig() Config { PeersLimit: 3, DiscoveryInterval: time.Second * 30, AdvertiseInterval: time.Second * 30, + UseShareExchange: true, } } diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 0d80eb725d..8105cf29c4 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -3,27 +3,21 @@ package share import ( "context" "errors" + "time" "github.com/filecoin-project/dagstore" "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" - "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" - modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" - "github.com/celestiaorg/celestia-node/share/p2p/peers" - "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" - - "github.com/celestiaorg/celestia-app/pkg/da" ) func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { @@ -66,28 +60,23 @@ func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { return err } -func peerManager( - headerSub libhead.Subscriber[*header.ExtendedHeader], - shrexSub *shrexsub.PubSub, - discovery *disc.Discovery, - host host.Host, - connGater *conngater.BasicConnectionGater, -) *peers.Manager { - // TODO: find better syncTimeout duration? - return peers.NewManager(headerSub, shrexSub, discovery, host, connGater, modp2p.BlockTime*5) -} - func fullGetter( store *eds.Store, shrexGetter *getters.ShrexGetter, ipldGetter *getters.IPLDGetter, + cfg Config, ) share.Getter { - return getters.NewCascadeGetter( - []share.Getter{ - getters.NewStoreGetter(store), - getters.NewTeeGetter(shrexGetter, store), - getters.NewTeeGetter(ipldGetter, store), - }, - modp2p.BlockTime, - ) + var cascade []share.Getter + // based on the default value of das.SampleTimeout + timeout := time.Minute + cascade = append(cascade, getters.NewStoreGetter(store)) + if cfg.UseShareExchange { + // if we are using share exchange, we split the timeout between the two getters + // once async cascadegetter is implemented, we can remove this + timeout /= 2 + cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + } + cascade = append(cascade, getters.NewTeeGetter(ipldGetter, store)) + + return getters.NewCascadeGetter(cascade, timeout) } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 16866f90f2..c810bf266f 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -15,6 +15,7 @@ import ( "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" @@ -144,7 +145,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { return shrexSub.Broadcast }), - fx.Provide(peerManager), + fx.Provide(peers.NewManager), fx.Provide(fullGetter), ) default: diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index d09eb69f94..92261884a5 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -34,8 +34,6 @@ const ( // ResultBlacklistPeer will blacklist peer. Blacklisted peers will be disconnected and blocked from // any p2p communication in future by libp2p Gater ResultBlacklistPeer - - gcInterval = time.Second * 30 ) var log = logging.Logger("shrex/peer-manager") @@ -52,9 +50,10 @@ type Manager struct { connGater *conngater.BasicConnectionGater // pools collecting peers from shrexSub - pools map[string]*syncPool - poolSyncTimeout time.Duration - peerCooldownTime time.Duration + pools map[string]*syncPool + poolValidationTimeout time.Duration + peerCooldownTime time.Duration + gcInterval time.Duration // fullNodes collects full nodes peer.ID found via discovery fullNodes *pool @@ -87,22 +86,30 @@ func NewManager( discovery *discovery.Discovery, host host.Host, connGater *conngater.BasicConnectionGater, - syncTimeout time.Duration, - peerCooldownTime time.Duration, + opts ...Option, ) *Manager { + params := DefaultParameters() + s := &Manager{ - headerSub: headerSub, - shrexSub: shrexSub, - disc: discovery, - connGater: connGater, - host: host, - pools: make(map[string]*syncPool), - poolSyncTimeout: syncTimeout, - fullNodes: newPool(peerCooldownTime), - blacklistedHashes: make(map[string]bool), - done: make(chan struct{}), + headerSub: headerSub, + shrexSub: shrexSub, + disc: discovery, + connGater: connGater, + host: host, + pools: make(map[string]*syncPool), + poolValidationTimeout: params.ValidationTimeout, + peerCooldownTime: params.PeerCooldown, + gcInterval: params.GcInterval, + blacklistedHashes: make(map[string]bool), + done: make(chan struct{}), } + for _, opt := range opts { + opt(s) + } + + s.fullNodes = newPool(s.peerCooldownTime) + discovery.WithOnPeersUpdate( func(peerID peer.ID, isAdded bool) { if isAdded { @@ -319,7 +326,7 @@ func (m *Manager) hashIsBlacklisted(hash share.DataHash) bool { } func (m *Manager) GC(ctx context.Context) { - ticker := time.NewTicker(gcInterval) + ticker := time.NewTicker(m.gcInterval) defer ticker.Stop() var blacklist []peer.ID @@ -343,7 +350,7 @@ func (m *Manager) cleanUp() []peer.ID { addToBlackList := make(map[peer.ID]struct{}) for h, p := range m.pools { - if time.Since(p.createdAt) > m.poolSyncTimeout && !p.isValidatedDataHash.Load() { + if time.Since(p.createdAt) > m.poolValidationTimeout && !p.isValidatedDataHash.Load() { log.Debug("blacklisting datahash with all corresponding peers", "datahash", h, "peer_list", p.peersList) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 3785b838fc..a9034f4586 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -125,7 +125,7 @@ func TestManager(t *testing.T) { peerID := peer.ID("peer1") manager.validate(ctx, peerID, h.DataHash.Bytes()) // set syncTimeout to 0 to allow cleanup to find outdated datahash - manager.poolSyncTimeout = 0 + manager.poolValidationTimeout = 0 blacklisted := manager.cleanUp() require.Contains(t, blacklisted, peerID) @@ -320,7 +320,15 @@ func TestIntegration(t *testing.T) { // hook peer manager to discovery connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - fnPeerManager := NewManager(nil, nil, fnDisc, nil, connGater, time.Minute, time.Second) + fnPeerManager := NewManager( + nil, + nil, + fnDisc, + nil, + connGater, + WithValidationTimeout(time.Minute), + WithPeerCooldown(time.Second), + ) waitCh := make(chan struct{}) fnDisc.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { @@ -359,7 +367,15 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten if err != nil { return nil, err } - manager := NewManager(headerSub, shrexSub, disc, host, connGater, time.Minute, time.Second) + manager := NewManager( + headerSub, + shrexSub, + disc, + host, + connGater, + WithValidationTimeout(time.Minute), + WithPeerCooldown(time.Second), + ) err = manager.Start(ctx) return manager, err } diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go new file mode 100644 index 0000000000..6858d3f222 --- /dev/null +++ b/share/p2p/peers/options.go @@ -0,0 +1,75 @@ +package peers + +import ( + "fmt" + "time" +) + +// Option is the functional option that is applied to the manager instance to configure peer manager +// parameters (the Parameters struct) +type Option func(manager *Manager) + +type Parameters struct { + // ValidationTimeout is the timeout used for validating incoming datahashes. Pools that have + // been created for datahashes from shrexsub that do not see this hash from headersub after this + // timeout will be garbage collected. + ValidationTimeout time.Duration + + // PeerCooldown is the time a peer is put on cooldown after a ResultCooldownPeer. + PeerCooldown time.Duration + + // GcInterval is the interval at which the manager will garbage collect unvalidated pools. + GcInterval time.Duration +} + +// Validate validates the values in Parameters +func (p *Parameters) Validate() error { + if p.ValidationTimeout <= 0 { + return fmt.Errorf("peer-manager: validation timeout must be positive") + } + + if p.PeerCooldown <= 0 { + return fmt.Errorf("peer-manager: peer cooldown must be positive") + } + + if p.GcInterval <= 0 { + return fmt.Errorf("peer-manager: garbage collection interval must be positive") + } + + return nil +} + +// DefaultParameters returns the default configuration values for the daser parameters +func DefaultParameters() Parameters { + return Parameters{ + // ValidationTimeout's default value is based on the default daser sampling timeout of 1 minute. + // If a received datahash has not tried to be sampled within these two minutes, the pool will be removed. + ValidationTimeout: 2 * time.Minute, + // PeerCooldown's default value is based on initial network tests that showed a ~3.5 second + // sync time for large blocks. This value gives our (discovery) peers enough time to sync + // the new block before we ask them again. + PeerCooldown: 3 * time.Second, + GcInterval: time.Second * 30, + } +} + +// WithValidationTimeout configures the manager's pool validation timeout. +func WithValidationTimeout(timeout time.Duration) Option { + return func(manager *Manager) { + manager.poolValidationTimeout = timeout + } +} + +// WithPeerCooldown configures the manager's peer cooldown time. +func WithPeerCooldown(cooldown time.Duration) Option { + return func(manager *Manager) { + manager.peerCooldownTime = cooldown + } +} + +// WithGcInterval configures the manager's garbage collection interval. +func WithGcInterval(interval time.Duration) Option { + return func(manager *Manager) { + manager.gcInterval = interval + } +} From 9f0014615c098f295ae5364aff60dd79ecdbdab9 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:10:52 +0300 Subject: [PATCH 0376/1008] feat(share/shrex): add Rate limit middleware for shrex servers (#1745) ## Overview Closes https://github.com/celestiaorg/celestia-node/issues/1747 Add concurrency rate limiting on server side of shrex. --- share/p2p/middleware.go | 30 +++++++++ share/p2p/shrexeds/client.go | 8 ++- share/p2p/shrexeds/exchange_test.go | 43 +++++++++++++ share/p2p/shrexeds/options.go | 22 +++++-- .../shrexeds/pb/extended_data_square.pb.go | 15 ++--- .../shrexeds/pb/extended_data_square.proto | 1 - share/p2p/shrexeds/server.go | 3 +- share/p2p/shrexnd/client.go | 7 ++- share/p2p/shrexnd/exchange_test.go | 62 +++++++++++++++++++ share/p2p/shrexnd/options.go | 20 +++++- share/p2p/shrexnd/pb/share.pb.go | 53 ++++++++-------- share/p2p/shrexnd/pb/share.proto | 1 - share/p2p/shrexnd/server.go | 7 ++- 13 files changed, 220 insertions(+), 52 deletions(-) create mode 100644 share/p2p/middleware.go create mode 100644 share/p2p/shrexnd/exchange_test.go diff --git a/share/p2p/middleware.go b/share/p2p/middleware.go new file mode 100644 index 0000000000..fa3f0e26ae --- /dev/null +++ b/share/p2p/middleware.go @@ -0,0 +1,30 @@ +package p2p + +import ( + "sync/atomic" + + logging "github.com/ipfs/go-log/v2" + + "github.com/libp2p/go-libp2p/core/network" +) + +var log = logging.Logger("shrex/middleware") + +func RateLimitMiddleware(inner network.StreamHandler, concurrencyLimit int) network.StreamHandler { + var parallelRequests int64 + limit := int64(concurrencyLimit) + return func(stream network.Stream) { + current := atomic.AddInt64(¶llelRequests, 1) + defer atomic.AddInt64(¶llelRequests, -1) + + if current > limit { + log.Debug("concurrency limit reached") + err := stream.Close() + if err != nil { + log.Errorw("server: closing stream", "err", err) + } + return + } + inner(stream) + } +} diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 3af05317b0..ffc59c82c0 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "net" "time" @@ -67,6 +68,7 @@ func (c *Client) RequestEDS( if err != p2p.ErrUnavailable { log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) } + return nil, err } @@ -105,6 +107,10 @@ func (c *Client) doRequest( resp := new(pb.EDSResponse) _, err = serde.Read(stream, resp) if err != nil { + // server is overloaded and closed the stream + if errors.Is(err, io.EOF) { + return nil, p2p.ErrUnavailable + } stream.Reset() //nolint:errcheck return nil, fmt.Errorf("failed to read status from stream: %w", err) } @@ -117,7 +123,7 @@ func (c *Client) doRequest( return nil, fmt.Errorf("failed to read eds from ods bytes: %w", err) } return eds, nil - case pb.Status_NOT_FOUND, pb.Status_REFUSED: + case pb.Status_NOT_FOUND: log.Debugf("client: peer %s couldn't serve eds %s with status %s", to.String(), dataHash.String(), resp.GetStatus()) return nil, p2p.ErrUnavailable case pb.Status_INVALID: diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index bfc8f076ee..376d3472c7 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -2,12 +2,14 @@ package shrexeds import ( "context" + "sync" "testing" "time" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" libhost "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -83,6 +85,47 @@ func TestExchange_RequestEDS(t *testing.T) { assert.ErrorIs(t, err, timeoutCtx.Err()) assert.Nil(t, requestedEDS) }) + + // Testcase: Concurrency limit reached + t.Run("EDS_concurrency_limit", func(t *testing.T) { + store, client, server := makeExchange(t) + + require.NoError(t, store.Start(ctx)) + require.NoError(t, server.Start(ctx)) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + rateLimit := 2 + wg := sync.WaitGroup{} + wg.Add(rateLimit) + + // mockHandler will block requests on server side until test is over + lock := make(chan struct{}) + defer close(lock) + mockHandler := func(network.Stream) { + wg.Done() + select { + case <-lock: + case <-ctx.Done(): + t.Fatal("timeout") + } + } + server.host.SetStreamHandler(server.protocolID, + p2p.RateLimitMiddleware(mockHandler, rateLimit)) + + // take server concurrency slots with blocked requests + for i := 0; i < rateLimit; i++ { + go func(i int) { + client.RequestEDS(ctx, nil, server.host.ID()) //nolint:errcheck + }(i) + } + + // wait until all server slots are taken + wg.Wait() + _, err = client.RequestEDS(ctx, nil, server.host.ID()) + require.ErrorIs(t, err, p2p.ErrUnavailable) + }) } func newStore(t *testing.T) *eds.Store { diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go index 973de4c530..ffb830fdce 100644 --- a/share/p2p/shrexeds/options.go +++ b/share/p2p/shrexeds/options.go @@ -33,14 +33,18 @@ type Parameters struct { // protocolSuffix is appended to the protocolID and represents the network the protocol is // running on. protocolSuffix string + + // concurrencyLimit is the maximum number of concurrently handled streams + concurrencyLimit int } func DefaultParameters() *Parameters { return &Parameters{ - ReadDeadline: time.Minute, - WriteDeadline: time.Second * 5, - ReadCARDeadline: time.Minute, - BufferSize: 32 * 1024, + ReadDeadline: time.Minute, + WriteDeadline: time.Second * 5, + ReadCARDeadline: time.Minute, + BufferSize: 32 * 1024, + concurrencyLimit: 10, } } @@ -59,6 +63,9 @@ func (p *Parameters) Validate() error { if p.BufferSize <= 0 { return fmt.Errorf("invalid buffer size: %s", errSuffix) } + if p.concurrencyLimit < 1 { + return fmt.Errorf("invalid concurrency limit: value should be greater than 0") + } return nil } @@ -69,6 +76,13 @@ func WithProtocolSuffix(protocolSuffix string) Option { } } +// WithConcurrencyLimit is a functional option that configures the `concurrencyLimit` parameter +func WithConcurrencyLimit(concurrencyLimit int) Option { + return func(parameters *Parameters) { + parameters.concurrencyLimit = concurrencyLimit + } +} + func protocolID(protocolSuffix string) protocol.ID { return protocol.ID(fmt.Sprintf("%s%s", protocolPrefix, protocolSuffix)) } diff --git a/share/p2p/shrexeds/pb/extended_data_square.pb.go b/share/p2p/shrexeds/pb/extended_data_square.pb.go index 2a62e4b503..c6f7e07123 100644 --- a/share/p2p/shrexeds/pb/extended_data_square.pb.go +++ b/share/p2p/shrexeds/pb/extended_data_square.pb.go @@ -28,21 +28,18 @@ const ( Status_INVALID Status = 0 Status_OK Status = 1 Status_NOT_FOUND Status = 2 - Status_REFUSED Status = 3 ) var Status_name = map[int32]string{ 0: "INVALID", 1: "OK", 2: "NOT_FOUND", - 3: "REFUSED", } var Status_value = map[string]int32{ "INVALID": 0, "OK": 1, "NOT_FOUND": 2, - "REFUSED": 3, } func (x Status) String() string { @@ -152,7 +149,7 @@ func init() { } var fileDescriptor_49d42aa96098056e = []byte{ - // 224 bytes of a gzipped FileDescriptorProto + // 213 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x28, 0xce, 0x48, 0x2c, 0x4a, 0xd5, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0xce, 0x28, 0x4a, 0xad, 0x48, 0x4d, 0x29, 0xd6, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x4f, 0x49, 0x2c, 0x49, 0x8c, @@ -160,13 +157,13 @@ var fileDescriptor_49d42aa96098056e = []byte{ 0x72, 0x75, 0x09, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x48, 0x2c, 0xce, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xb8, 0xc1, 0x2a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xe4, 0xb9, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, 0x4a, - 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x25, 0x17, + 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x0e, 0x17, 0x1b, 0x44, 0x44, 0x88, 0x9b, 0x8b, 0xdd, 0xd3, 0x2f, 0xcc, 0xd1, 0xc7, 0xd3, 0x45, 0x80, 0x41, 0x88, 0x8d, 0x8b, 0xc9, 0xdf, 0x5b, 0x80, 0x51, 0x88, 0x97, 0x8b, 0xd3, 0xcf, 0x3f, 0x24, 0xde, - 0xcd, 0x3f, 0xd4, 0xcf, 0x45, 0x80, 0x09, 0xa4, 0x26, 0xc8, 0xd5, 0x2d, 0x34, 0xd8, 0xd5, 0x45, - 0x80, 0xd9, 0x49, 0xe2, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, - 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0x92, 0xd8, 0xc0, - 0xae, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x57, 0xb5, 0xbb, 0xe1, 0x00, 0x00, 0x00, + 0xcd, 0x3f, 0xd4, 0xcf, 0x45, 0x80, 0xc9, 0x49, 0xe2, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, + 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, + 0xe5, 0x18, 0x92, 0xd8, 0xc0, 0x0e, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x34, 0x8f, 0xa7, + 0x57, 0xd4, 0x00, 0x00, 0x00, } func (m *EDSRequest) Marshal() (dAtA []byte, err error) { diff --git a/share/p2p/shrexeds/pb/extended_data_square.proto b/share/p2p/shrexeds/pb/extended_data_square.proto index f96493bf66..1830d85f8e 100644 --- a/share/p2p/shrexeds/pb/extended_data_square.proto +++ b/share/p2p/shrexeds/pb/extended_data_square.proto @@ -8,7 +8,6 @@ enum Status { INVALID = 0; OK = 1; // data found NOT_FOUND = 2; // data not found - REFUSED = 3; // request refused } message EDSResponse { diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index 1922cf9e00..60086517b0 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -12,6 +12,7 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/p2p" p2p_pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" "github.com/celestiaorg/go-libp2p-messenger/serde" ) @@ -50,7 +51,7 @@ func NewServer(host host.Host, store *eds.Store, opts ...Option) (*Server, error func (s *Server) Start(context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) - s.host.SetStreamHandler(s.protocolID, s.handleStream) + s.host.SetStreamHandler(s.protocolID, p2p.RateLimitMiddleware(s.handleStream, s.params.concurrencyLimit)) return nil } diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index b5e521ddaf..d284dba0b3 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "net" "time" @@ -111,6 +112,10 @@ func (c *Client) doRequest( var resp pb.GetSharesByNamespaceResponse _, err = serde.Read(stream, &resp) if err != nil { + // server is overloaded and closed the stream + if errors.Is(err, io.EOF) { + return nil, p2p.ErrUnavailable + } stream.Reset() //nolint:errcheck return nil, fmt.Errorf("client-nd: reading response: %w", err) } @@ -192,7 +197,7 @@ func statusToErr(code pb.StatusCode) error { switch code { case pb.StatusCode_OK: return nil - case pb.StatusCode_NOT_FOUND, pb.StatusCode_REFUSED: + case pb.StatusCode_NOT_FOUND: return p2p.ErrUnavailable case pb.StatusCode_INTERNAL, pb.StatusCode_INVALID: fallthrough diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go new file mode 100644 index 0000000000..3940474025 --- /dev/null +++ b/share/p2p/shrexnd/exchange_test.go @@ -0,0 +1,62 @@ +package shrexnd + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/network" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share/p2p" +) + +func TestExchange_RequestND(t *testing.T) { + // Testcase: Concurrency limit reached + t.Run("ND_concurrency_limit", func(t *testing.T) { + net, err := mocknet.FullMeshConnected(2) + require.NoError(t, err) + + client, err := NewClient(net.Hosts()[0]) + require.NoError(t, err) + server, err := NewServer(net.Hosts()[1], nil, nil) + require.NoError(t, err) + + require.NoError(t, server.Start(context.Background())) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + + rateLimit := 2 + wg := sync.WaitGroup{} + wg.Add(rateLimit) + + // mockHandler will block requests on server side until test is over + lock := make(chan struct{}) + defer close(lock) + mockHandler := func(network.Stream) { + wg.Done() + select { + case <-lock: + case <-ctx.Done(): + t.Fatal("timeout") + } + } + server.host.SetStreamHandler(server.protocolID, + p2p.RateLimitMiddleware(mockHandler, rateLimit)) + + // take server concurrency slots with blocked requests + for i := 0; i < rateLimit; i++ { + go func(i int) { + client.RequestND(ctx, nil, nil, server.host.ID()) //nolint:errcheck + }(i) + } + + // wait until all server slots are taken + wg.Wait() + _, err = client.RequestND(ctx, nil, nil, server.host.ID()) + require.ErrorIs(t, err, p2p.ErrUnavailable) + }) +} diff --git a/share/p2p/shrexnd/options.go b/share/p2p/shrexnd/options.go index 3c540a6bb6..9e75ebda66 100644 --- a/share/p2p/shrexnd/options.go +++ b/share/p2p/shrexnd/options.go @@ -30,13 +30,17 @@ type Parameters struct { // protocolSuffix is appended to the protocolID and represents the network the protocol is // running on. protocolSuffix string + + // concurrencyLimit is the maximum number of concurrently handled streams + concurrencyLimit int } func DefaultParameters() *Parameters { return &Parameters{ - readTimeout: time.Second * 5, - writeTimeout: time.Second * 10, - serveTimeout: time.Second * 10, + readTimeout: time.Second * 5, + writeTimeout: time.Second * 10, + serveTimeout: time.Second * 10, + concurrencyLimit: 10, } } @@ -52,6 +56,9 @@ func (p *Parameters) Validate() error { if p.serveTimeout <= 0 { return fmt.Errorf("invalid serve timeout: %v, %s", p.serveTimeout, errSuffix) } + if p.concurrencyLimit < 1 { + return fmt.Errorf("invalid concurrency limit: value should be greater than 0") + } return nil } @@ -83,6 +90,13 @@ func WithServeTimeout(serveTimeout time.Duration) Option { } } +// WithConcurrencyLimit is a functional option that configures the `concurrencyLimit` parameter +func WithConcurrencyLimit(concurrencyLimit int) Option { + return func(parameters *Parameters) { + parameters.concurrencyLimit = concurrencyLimit + } +} + func protocolID(protocolSuffix string) protocol.ID { return protocol.ID(fmt.Sprintf("%s%s", protocolPrefix, protocolSuffix)) } diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go index e703090d9a..bb9d1e4d40 100644 --- a/share/p2p/shrexnd/pb/share.pb.go +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -29,7 +29,6 @@ const ( StatusCode_OK StatusCode = 1 StatusCode_NOT_FOUND StatusCode = 2 StatusCode_INTERNAL StatusCode = 3 - StatusCode_REFUSED StatusCode = 4 ) var StatusCode_name = map[int32]string{ @@ -37,7 +36,6 @@ var StatusCode_name = map[int32]string{ 1: "OK", 2: "NOT_FOUND", 3: "INTERNAL", - 4: "REFUSED", } var StatusCode_value = map[string]int32{ @@ -45,7 +43,6 @@ var StatusCode_value = map[string]int32{ "OK": 1, "NOT_FOUND": 2, "INTERNAL": 3, - "REFUSED": 4, } func (x StatusCode) String() string { @@ -283,31 +280,31 @@ func init() { func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } var fileDescriptor_ed9f13149b0de397 = []byte{ - // 380 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xc1, 0xae, 0xd2, 0x40, - 0x14, 0x86, 0xdb, 0x0e, 0x54, 0x38, 0xad, 0xa6, 0x99, 0x18, 0xad, 0xc1, 0x34, 0xd8, 0x15, 0xd1, - 0xa4, 0x4d, 0x6a, 0xe2, 0x1e, 0xa4, 0x68, 0x03, 0x19, 0xcc, 0x00, 0xee, 0x0c, 0x29, 0x76, 0x4c, - 0x5d, 0xd8, 0x19, 0x3b, 0x43, 0xd0, 0xb5, 0x2f, 0xe0, 0x63, 0xb9, 0x64, 0xe9, 0xd2, 0xc0, 0x8b, - 0x98, 0x4e, 0xb9, 0xf7, 0x2e, 0x2e, 0xbb, 0xfe, 0xe7, 0x7c, 0xff, 0x7f, 0xce, 0xe9, 0xc0, 0x50, - 0x96, 0x79, 0xcd, 0x62, 0x91, 0x88, 0x58, 0x96, 0x35, 0xfb, 0x51, 0x15, 0xb1, 0xd8, 0xc5, 0xba, - 0x18, 0x89, 0x9a, 0x2b, 0x8e, 0xf1, 0x45, 0x24, 0x22, 0xd2, 0x44, 0x54, 0x15, 0xe1, 0x27, 0x18, - 0xbc, 0x63, 0x6a, 0xd5, 0x34, 0xe4, 0xe4, 0x27, 0xc9, 0xbf, 0x31, 0x29, 0xf2, 0xcf, 0x8c, 0xb2, - 0xef, 0x7b, 0x26, 0x15, 0x1e, 0x40, 0xbf, 0xe6, 0x5c, 0x6d, 0xcb, 0x5c, 0x96, 0xbe, 0x39, 0x34, - 0x47, 0x2e, 0xed, 0x35, 0x85, 0xf7, 0xb9, 0x2c, 0xf1, 0x0b, 0x70, 0xab, 0x1b, 0xc3, 0xf6, 0x6b, - 0xe1, 0x5b, 0xba, 0xef, 0xdc, 0xd6, 0xb2, 0x22, 0xfc, 0x65, 0xc2, 0xf3, 0xeb, 0xf9, 0x52, 0xf0, - 0x4a, 0x32, 0xfc, 0x06, 0x6c, 0xa9, 0x72, 0xb5, 0x97, 0x3a, 0xfd, 0x51, 0x12, 0x44, 0xf7, 0x97, - 0x8c, 0x56, 0x9a, 0x78, 0xcb, 0x0b, 0x46, 0x2f, 0x34, 0x7e, 0x05, 0x9d, 0x9a, 0x1f, 0xa4, 0x6f, - 0x0d, 0xd1, 0xc8, 0x49, 0x9e, 0x5e, 0x73, 0x51, 0x7e, 0xa0, 0x1a, 0x0a, 0x09, 0x20, 0xca, 0x0f, - 0xf8, 0x09, 0xd8, 0x1a, 0x6b, 0x66, 0xa1, 0x91, 0x4b, 0x2f, 0x0a, 0xc7, 0xd0, 0x15, 0x35, 0xe7, - 0x5f, 0xf4, 0x01, 0x4e, 0xf2, 0xec, 0x5a, 0xd8, 0x87, 0x06, 0xa0, 0x2d, 0x17, 0xa6, 0xd0, 0xd5, - 0x1a, 0x3f, 0x86, 0xae, 0x54, 0x79, 0xad, 0xf4, 0xf2, 0x88, 0xb6, 0x02, 0x7b, 0x80, 0x58, 0xd5, - 0xfe, 0x0e, 0x44, 0x9b, 0xcf, 0x86, 0x23, 0xbc, 0x60, 0xd2, 0x47, 0x7a, 0x70, 0x2b, 0x5e, 0xce, - 0x01, 0xee, 0x2e, 0xc3, 0x0e, 0x3c, 0xc8, 0xc8, 0xc7, 0xf1, 0x22, 0x9b, 0x7a, 0x06, 0xb6, 0xc1, - 0x5a, 0xce, 0x3d, 0x13, 0x3f, 0x84, 0x3e, 0x59, 0xae, 0xb7, 0xb3, 0xe5, 0x86, 0x4c, 0x3d, 0x0b, - 0xbb, 0xd0, 0xcb, 0xc8, 0x3a, 0xa5, 0x64, 0xbc, 0xf0, 0x50, 0xe3, 0xa0, 0xe9, 0x6c, 0xb3, 0x4a, - 0xa7, 0x5e, 0x67, 0xe2, 0xff, 0x39, 0x05, 0xe6, 0xf1, 0x14, 0x98, 0xff, 0x4e, 0x81, 0xf9, 0xfb, - 0x1c, 0x18, 0xc7, 0x73, 0x60, 0xfc, 0x3d, 0x07, 0xc6, 0xce, 0xd6, 0xaf, 0xff, 0xfa, 0x7f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x33, 0x30, 0xe8, 0xae, 0x21, 0x02, 0x00, 0x00, + // 374 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xc1, 0x6a, 0xdb, 0x40, + 0x10, 0x86, 0x25, 0x6d, 0xad, 0xda, 0x23, 0xb5, 0x88, 0xa5, 0xb4, 0x2a, 0x2e, 0xc2, 0xd5, 0xc9, + 0xb4, 0x20, 0x81, 0x0a, 0x3d, 0x16, 0xec, 0xda, 0x6d, 0x45, 0xcd, 0xba, 0xac, 0xdd, 0xdc, 0x82, + 0x91, 0xa3, 0x0d, 0xca, 0x21, 0xda, 0x8d, 0x76, 0x8d, 0x93, 0x73, 0x5e, 0x20, 0x8f, 0x95, 0xa3, + 0x8f, 0x39, 0x06, 0xfb, 0x45, 0x82, 0x56, 0x4e, 0x72, 0x88, 0x6f, 0xfa, 0xff, 0xf9, 0xe6, 0x9f, + 0x19, 0x2d, 0xf4, 0x64, 0x91, 0x55, 0x2c, 0x16, 0x89, 0x88, 0x65, 0x51, 0xb1, 0xcb, 0x32, 0x8f, + 0xc5, 0x32, 0xd6, 0x66, 0x24, 0x2a, 0xae, 0x38, 0xc6, 0x7b, 0x91, 0x88, 0x48, 0x13, 0x51, 0x99, + 0x87, 0xc7, 0xd0, 0xfd, 0xcd, 0xd4, 0xac, 0x2e, 0xc8, 0xe1, 0x15, 0xc9, 0xce, 0x99, 0x14, 0xd9, + 0x09, 0xa3, 0xec, 0x62, 0xc5, 0xa4, 0xc2, 0x5d, 0xe8, 0x54, 0x9c, 0xab, 0x45, 0x91, 0xc9, 0xc2, + 0x37, 0x7b, 0x66, 0xdf, 0xa5, 0xed, 0xda, 0xf8, 0x93, 0xc9, 0x02, 0x7f, 0x06, 0xb7, 0x7c, 0x6c, + 0x58, 0x9c, 0xe5, 0xbe, 0xa5, 0xeb, 0xce, 0x93, 0x97, 0xe6, 0xe1, 0xb5, 0x09, 0x9f, 0x0e, 0xe7, + 0x4b, 0xc1, 0x4b, 0xc9, 0xf0, 0x77, 0xb0, 0xa5, 0xca, 0xd4, 0x4a, 0xea, 0xf4, 0xb7, 0x49, 0x10, + 0xbd, 0x5c, 0x32, 0x9a, 0x69, 0xe2, 0x27, 0xcf, 0x19, 0xdd, 0xd3, 0xf8, 0x2b, 0xbc, 0xaa, 0xf8, + 0x5a, 0xfa, 0x56, 0x0f, 0xf5, 0x9d, 0xe4, 0xc3, 0xa1, 0x2e, 0xca, 0xd7, 0x54, 0x43, 0x21, 0x01, + 0x44, 0xf9, 0x1a, 0xbf, 0x07, 0x5b, 0x63, 0xf5, 0x2c, 0xd4, 0x77, 0xe9, 0x5e, 0xe1, 0x18, 0x5a, + 0xa2, 0xe2, 0xfc, 0x54, 0x1f, 0xe0, 0x24, 0x1f, 0x0f, 0x85, 0xfd, 0xab, 0x01, 0xda, 0x70, 0xe1, + 0x18, 0x5a, 0x5a, 0xe3, 0x77, 0xd0, 0x92, 0x2a, 0xab, 0x94, 0x5e, 0x1e, 0xd1, 0x46, 0x60, 0x0f, + 0x10, 0x2b, 0x9b, 0xdf, 0x81, 0x68, 0xfd, 0x59, 0x73, 0x84, 0xe7, 0x4c, 0xfa, 0x48, 0x0f, 0x6e, + 0xc4, 0x97, 0x1f, 0x00, 0xcf, 0x97, 0x61, 0x07, 0x5e, 0xa7, 0xe4, 0x68, 0x30, 0x49, 0x47, 0x9e, + 0x81, 0x6d, 0xb0, 0xa6, 0x7f, 0x3d, 0x13, 0xbf, 0x81, 0x0e, 0x99, 0xce, 0x17, 0xbf, 0xa6, 0xff, + 0xc9, 0xc8, 0xb3, 0xb0, 0x0b, 0xed, 0x94, 0xcc, 0xc7, 0x94, 0x0c, 0x26, 0x1e, 0x1a, 0xfa, 0xb7, + 0xdb, 0xc0, 0xdc, 0x6c, 0x03, 0xf3, 0x7e, 0x1b, 0x98, 0x37, 0xbb, 0xc0, 0xd8, 0xec, 0x02, 0xe3, + 0x6e, 0x17, 0x18, 0x4b, 0x5b, 0x3f, 0xf8, 0xb7, 0x87, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x23, + 0xba, 0xf9, 0x14, 0x02, 0x00, 0x00, } func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto index 59dac8c82e..d181dd7010 100644 --- a/share/p2p/shrexnd/pb/share.proto +++ b/share/p2p/shrexnd/pb/share.proto @@ -17,7 +17,6 @@ enum StatusCode { OK = 1; NOT_FOUND = 2; INTERNAL = 3; - REFUSED = 4; }; message Row { diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 2a6893188f..54b74ddc1e 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -13,8 +13,8 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" - "github.com/celestiaorg/go-libp2p-messenger/serde" ) @@ -58,9 +58,10 @@ func (srv *Server) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) srv.cancel = cancel - srv.host.SetStreamHandler(srv.protocolID, func(s network.Stream) { + handler := func(s network.Stream) { srv.handleNamespacedData(ctx, s) - }) + } + srv.host.SetStreamHandler(srv.protocolID, p2p.RateLimitMiddleware(handler, srv.params.concurrencyLimit)) return nil } From aa99b455773f7f56eaa901120c7c98fe63479bbc Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 16 Feb 2023 14:51:53 +0100 Subject: [PATCH 0377/1008] refactor(share/getters): cleaning up error logs, wrapping cascadegetter with teegetter (#1761) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview Closes #1755 Also, cleans up error logs. Before: ``` 2023-02-16T14:22:10.256+0100 ERROR share/full full/availability.go:62 availability validation failed {“root”: “9bVWFLfSHJwY9Fotfkdb//8i9GcQWESAtX3cGIFNYnI=“, “err”: “getter/store: failed to retrieve eds: failed to get CAR file: failed to get accessor: failed to initialize shard acquisition: F5B55614B7D21C9C18F45A2D7E475BFFFF22F46710584480B57DDC18814D6272: shard not found; getter/tee: failed to store eds: failed to write EDS to file: share: creating eds writer: committing inner nodes to the dag: before batch commit: context deadline exceeded; getter/ipld: failed to retrieve eds: context deadline exceeded”, “errCauses”: [{“error”: “getter/store: failed to retrieve eds: failed to get CAR file: failed to get accessor: failed to initialize shard acquisition: F5B55614B7D21C9C18F45A2D7E475BFFFF22F46710584480B57DDC18814D6272: shard not found”}, {“error”: “getter/tee: failed to store eds: failed to write EDS to file: share: creating eds writer: committing inner nodes to the dag: before batch commit: context deadline exceeded”}, {“error”: “getter/ipld: failed to retrieve eds: context deadline exceeded”}]} ``` After: ``` 2023-02-16T14:37:02.763+0100 ERROR share/full full/availability.go:62 availability validation failed {“root”: “UEJtkWCDsi2MLAo2VdstjorpRRtq+1Dytd9Miy1AvvM=“, “err”: “getter/store: eds does not yet exist; getter/shrex: context deadline exceeded; getter/ipld: failed to retrieve eds: context deadline exceeded”} ``` --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- nodebuilder/share/constructors.go | 9 ++++++--- share/availability/full/availability.go | 2 +- share/getters/shrex.go | 11 ++++++----- share/getters/store.go | 5 +++++ share/p2p/peers/manager.go | 2 +- share/p2p/shrexeds/options.go | 4 ++-- share/p2p/shrexnd/options.go | 4 ++-- 7 files changed, 23 insertions(+), 14 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 8105cf29c4..4f2f146034 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -74,9 +74,12 @@ func fullGetter( // if we are using share exchange, we split the timeout between the two getters // once async cascadegetter is implemented, we can remove this timeout /= 2 - cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + cascade = append(cascade, shrexGetter) } - cascade = append(cascade, getters.NewTeeGetter(ipldGetter, store)) + cascade = append(cascade, ipldGetter) - return getters.NewCascadeGetter(cascade, timeout) + return getters.NewTeeGetter( + getters.NewCascadeGetter(cascade, timeout), + store, + ) } diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 31d72d0111..6fdb2398f6 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -59,7 +59,7 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro _, err := fa.getter.GetEDS(ctx, root) if err != nil { - log.Errorw("availability validation failed", "root", root.Hash(), "err", err) + log.Errorw("availability validation failed", "root", root.Hash(), "err", err.Error()) if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { return share.ErrNotAvailable } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index d49b1c353b..2c5a9d36d8 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -3,6 +3,7 @@ package getters import ( "context" "errors" + "fmt" "time" "github.com/celestiaorg/celestia-node/share" @@ -46,20 +47,20 @@ func (sg *ShrexGetter) Stop(ctx context.Context) error { } func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { - return nil, errors.New("shrex-getter: GetShare is not supported") + return nil, errors.New("getter/shrex: GetShare is not supported") } func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { for { select { case <-ctx.Done(): - return nil, ctx.Err() + return nil, fmt.Errorf("getter/shrex: %w", ctx.Err()) default: } peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) if err != nil { log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) - return nil, err + return nil, fmt.Errorf("getter/shrex: %w", err) } reqCtx, cancel := context.WithTimeout(ctx, sg.maxRequestDuration) @@ -87,13 +88,13 @@ func (sg *ShrexGetter) GetSharesByNamespace( for { select { case <-ctx.Done(): - return nil, ctx.Err() + return nil, fmt.Errorf("getter/shrex: %w", ctx.Err()) default: } peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) if err != nil { log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) - return nil, err + return nil, fmt.Errorf("getter/shrex: %w", err) } reqCtx, cancel := context.WithTimeout(ctx, sg.maxRequestDuration) diff --git a/share/getters/store.go b/share/getters/store.go index f2a222894e..cc87f0e6f1 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -2,8 +2,10 @@ package getters import ( "context" + "errors" "fmt" + "github.com/filecoin-project/dagstore" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -70,6 +72,9 @@ func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2 }() eds, err = sg.store.Get(ctx, root.Hash()) + if errors.Is(err, dagstore.ErrShardUnknown) { + return nil, fmt.Errorf("getter/store: eds not found") + } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve eds: %w", err) } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 92261884a5..ae4e75abb2 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -224,7 +224,7 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { log.Debugw("set peer status", "peer", peerID, "datahash", datahash.String(), - result, result) + "result", result) switch result { case ResultSuccess: case ResultSynced: diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go index ffb830fdce..4239ec5bea 100644 --- a/share/p2p/shrexeds/options.go +++ b/share/p2p/shrexeds/options.go @@ -63,8 +63,8 @@ func (p *Parameters) Validate() error { if p.BufferSize <= 0 { return fmt.Errorf("invalid buffer size: %s", errSuffix) } - if p.concurrencyLimit < 1 { - return fmt.Errorf("invalid concurrency limit: value should be greater than 0") + if p.concurrencyLimit <= 0 { + return fmt.Errorf("invalid concurrency limit: %s", errSuffix) } return nil } diff --git a/share/p2p/shrexnd/options.go b/share/p2p/shrexnd/options.go index 9e75ebda66..44f79e13f2 100644 --- a/share/p2p/shrexnd/options.go +++ b/share/p2p/shrexnd/options.go @@ -56,8 +56,8 @@ func (p *Parameters) Validate() error { if p.serveTimeout <= 0 { return fmt.Errorf("invalid serve timeout: %v, %s", p.serveTimeout, errSuffix) } - if p.concurrencyLimit < 1 { - return fmt.Errorf("invalid concurrency limit: value should be greater than 0") + if p.concurrencyLimit <= 0 { + return fmt.Errorf("invalid concurrency limit: %v, %s", p.concurrencyLimit, errSuffix) } return nil } From e162b3cc0d61f6b46715554d05123322ac025151 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:03:38 +0100 Subject: [PATCH 0378/1008] fix(das | share): handle `ErrByzantine` from `multierr`s when found with `context.Canceled` (#1762) Getters returning context errors to the `CascadeGetter` instance were appended to a multierr. This meant that `ErrByzantine` errors were short-circuited in the daser, returning the context error instead of the `ErrByzantine`. --- das/daser.go | 5 ++--- nodebuilder/tests/fraud_test.go | 15 +++++++++++++-- share/availability/full/availability.go | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/das/daser.go b/das/daser.go index c00707746d..47c5960326 100644 --- a/das/daser.go +++ b/das/daser.go @@ -152,9 +152,6 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { err := d.da.SharesAvailable(ctx, h.DAH) if err != nil { - if err == context.Canceled { - return err - } var byzantineErr *byzantine.ErrByzantine if errors.As(err, &byzantineErr) { log.Warn("Propagating proof...") @@ -162,6 +159,8 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { if sendErr != nil { log.Errorw("fraud proof propagating failed", "err", sendErr) } + } else if err == context.Canceled { + return err } log.Errorw("sampling failed", "height", h.Height(), "hash", h.Hash(), diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 3ca4638fa8..ba065a2551 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -30,6 +30,7 @@ Steps: 5. Subscribe to a fraud proof and wait when it will be received. 6. Check FN is not synced to 15. Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. +Another note: this test disables share exchange to speed up test results. */ func TestFraudProofBroadcasting(t *testing.T) { const ( @@ -42,14 +43,21 @@ func TestFraudProofBroadcasting(t *testing.T) { t.Cleanup(cancel) fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) - bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv()))) + cfg := nodebuilder.DefaultConfig(node.Bridge) + cfg.Share.UseShareExchange = false + bridge := sw.NewNodeWithConfig( + node.Bridge, + cfg, + core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv())), + ) err := bridge.Start(ctx) require.NoError(t, err) addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - cfg := nodebuilder.DefaultConfig(node.Full) + cfg = nodebuilder.DefaultConfig(node.Full) + cfg.Share.UseShareExchange = false cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) store := nodebuilder.MockStore(t, cfg) full := sw.NewNodeWithStore(node.Full, store) @@ -98,6 +106,7 @@ Steps: 5. Subscribe to a fraud proof and wait when it will be received. 6. Start LN once a fraud proof is received and verified by FN. 7. Wait until LN will be connected to FN and fetch a fraud proof. +Note: this test disables share exchange to speed up test results. */ func TestFraudProofSyncing(t *testing.T) { const ( @@ -111,6 +120,7 @@ func TestFraudProofSyncing(t *testing.T) { fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) cfg := nodebuilder.DefaultConfig(node.Bridge) + cfg.Share.UseShareExchange = false store := nodebuilder.MockStore(t, cfg) bridge := sw.NewNodeWithStore( node.Bridge, @@ -125,6 +135,7 @@ func TestFraudProofSyncing(t *testing.T) { require.NoError(t, err) fullCfg := nodebuilder.DefaultConfig(node.Full) + fullCfg.Share.UseShareExchange = false fullCfg.Header.TrustedPeers = append(fullCfg.Header.TrustedPeers, addrs[0].String()) full := sw.NewNodeWithStore(node.Full, nodebuilder.MockStore(t, fullCfg)) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 6fdb2398f6..deaa86dec3 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) var log = logging.Logger("share/full") @@ -60,7 +61,8 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro _, err := fa.getter.GetEDS(ctx, root) if err != nil { log.Errorw("availability validation failed", "root", root.Hash(), "err", err.Error()) - if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { + var byzantineErr *byzantine.ErrByzantine + if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { return share.ErrNotAvailable } From 6a80c8bdba28d4139a303db237e7d9113284a840 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 16 Feb 2023 17:22:01 +0100 Subject: [PATCH 0379/1008] chore(nodebuilder/p2p)!: Upgrade arabica chainID for shrex upgrade (#1760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrades to `arabica-6` in preparation for shrex 🚀 --- nodebuilder/p2p/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 07a9a8b0ac..b13c5d7311 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-5" + Arabica Network = "arabica-6" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha" // Private can be used to set up any private network, including local testing setups. From aae5e7d5789fddb66243fb1854020849c219a138 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 16 Feb 2023 17:51:21 +0100 Subject: [PATCH 0380/1008] feat(share): allow LNs to GetSharesByNamespace and GetEDS over shrex (#1751) ## Overview Enables use of `GetSharesByNamespace` and `GetEDS` over shrex for LNs. --- nodebuilder/share/constructors.go | 15 +++++++++ nodebuilder/share/module.go | 55 +++++++++++++++---------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 4f2f146034..d039d535b0 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -60,6 +60,21 @@ func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { return err } +func lightGetter( + shrexGetter *getters.ShrexGetter, + ipldGetter *getters.IPLDGetter, +) share.Getter { + return getters.NewCascadeGetter( + []share.Getter{ + shrexGetter, + ipldGetter, + }, + // based on the default value of das.SampleTimeout. + // will no longer be needed when async cascadegetter is merged + time.Minute, + ) +} + func fullGetter( store *eds.Store, shrexGetter *getters.ShrexGetter, diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index c810bf266f..2099a9d46d 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -7,7 +7,6 @@ import ( "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" @@ -31,12 +30,36 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Options(options...), fx.Provide(discovery(*cfg)), fx.Provide(newModule), - // TODO: Configure for light nodes + fx.Provide(getters.NewIPLDGetter), + fx.Provide(peers.NewManager), + fx.Provide( + func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { + return shrexsub.NewPubSub( + ctx, + h, + string(network), + ) + }, + ), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(string(network))) }, ), + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { + return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(string(network))) + }, + ), + fx.Provide(fx.Annotate( + getters.NewShrexGetter, + fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Stop(ctx) + }), + )), ) switch tp { @@ -45,13 +68,13 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option "share", baseComponents, fx.Invoke(share.EnsureEmptySquareExists), + fx.Provide(lightGetter), // shrexsub broadcaster stub for daser fx.Provide(func() shrexsub.BroadcastFn { return func(context.Context, share.DataHash) error { return nil } }), - fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), fx.Provide(fx.Annotate(light.NewShareAvailability)), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability @@ -61,7 +84,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, - fx.Provide(getters.NewIPLDGetter), fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { @@ -90,12 +112,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return server.Stop(ctx) }), )), - // Bridge Nodes need a client as well, for requests over FullAvailability - fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(string(network))) - }, - ), fx.Provide(fx.Annotate( func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { return eds.NewStore(string(path), ds) @@ -121,31 +137,12 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return avail.Stop(ctx) }), )), - fx.Provide( - func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { - return shrexsub.NewPubSub( - ctx, - h, - string(network), - ) - }, - ), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability fx.Provide(cacheAvailability[*full.ShareAvailability]), - fx.Provide(fx.Annotate( - getters.NewShrexGetter, - fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Stop(ctx) - }), - )), fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { return shrexSub.Broadcast }), - fx.Provide(peers.NewManager), fx.Provide(fullGetter), ) default: From a0b5bd6a75fbc8eb3ce541e35846ad41b3b38ed4 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 17 Feb 2023 16:05:15 +0100 Subject: [PATCH 0381/1008] fix(nodebuilder/share): Revert providing `shrex.Getter` and its associated components to light node (#1770) This PR reverts providing shrex-getter and its associated components to light nodes as they are both unnecessary for now and are buggy, causing the following error to occur: light node tries to sample a recent header and fails on the first try with ``` "PT2agmOhBwBuDZu++Vq+LGcATmrI9UZbJKEsvlHKlls=", "err": "getter/shrex: GetShare is not supported; getter/ipld: failed to retrieve share: promise channel was closed", "errCauses": [{"error": "getter/shrex: GetShare is not supported"}, {"error": "getter/ipld: failed to retrieve share: **promise channel was closed**"}]} ``` but succeeds on a second try when the `catchUp` routine inside the DASer samples once again over that height. For now, we will remove this feature (which will only impact the "speed" of the RPC call for requesting data by namespace) and debug it at a later date. --- nodebuilder/share/constructors.go | 16 +------- nodebuilder/share/module.go | 63 ++++++++++++++++--------------- 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index d039d535b0..bfaf3a7324 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -13,6 +13,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" @@ -60,21 +61,6 @@ func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { return err } -func lightGetter( - shrexGetter *getters.ShrexGetter, - ipldGetter *getters.IPLDGetter, -) share.Getter { - return getters.NewCascadeGetter( - []share.Getter{ - shrexGetter, - ipldGetter, - }, - // based on the default value of das.SampleTimeout. - // will no longer be needed when async cascadegetter is merged - time.Minute, - ) -} - func fullGetter( store *eds.Store, shrexGetter *getters.ShrexGetter, diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 2099a9d46d..9fff079c26 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -7,6 +7,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" @@ -30,36 +31,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Options(options...), fx.Provide(discovery(*cfg)), fx.Provide(newModule), - fx.Provide(getters.NewIPLDGetter), - fx.Provide(peers.NewManager), - fx.Provide( - func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { - return shrexsub.NewPubSub( - ctx, - h, - string(network), - ) - }, - ), - fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { - return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(string(network))) - }, - ), - fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(string(network))) - }, - ), - fx.Provide(fx.Annotate( - getters.NewShrexGetter, - fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Stop(ctx) - }), - )), ) switch tp { @@ -68,7 +39,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option "share", baseComponents, fx.Invoke(share.EnsureEmptySquareExists), - fx.Provide(lightGetter), + fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), // shrexsub broadcaster stub for daser fx.Provide(func() shrexsub.BroadcastFn { return func(context.Context, share.DataHash) error { @@ -144,6 +115,36 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return shrexSub.Broadcast }), fx.Provide(fullGetter), + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { + return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(string(network))) + }, + ), + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { + return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(string(network))) + }, + ), + fx.Provide(fx.Annotate( + getters.NewShrexGetter, + fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Stop(ctx) + }), + )), + fx.Provide( + func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { + return shrexsub.NewPubSub( + ctx, + h, + string(network), + ) + }, + ), + fx.Provide(peers.NewManager), + fx.Provide(getters.NewIPLDGetter), ) default: panic("invalid node type") From 5aee280e4c22208baec5e25490c1a558cad2238f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 17 Feb 2023 17:47:21 +0100 Subject: [PATCH 0382/1008] chore(nodebuilder/p2p)!: Update genesis hash (#1772) Self-explanatory. --- nodebuilder/p2p/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 24464950a5..dbc43183f0 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "896C2935D8688C288870F99300D8A580D07DDA5DAA0D197D33C6557E57E4AF87", + Arabica: "EE310062CBB13CE98CBC7EAD3F6A827F0E4A86043FDEB2DA42048821877FE45C", Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", Private: "", } From 3d2c121297ea466659072840577785238b58a0a6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 17 Feb 2023 22:01:34 +0100 Subject: [PATCH 0383/1008] refactor(share): use Relay instead of Subscription for shrexsub and reduce lightavail log verbosity (#1774) ## Overview Fixes the following log: ``` 2023-02-17T18:30:32.190+0100 INFO pubsub go-libp2p-pubsub@v0.8.3/pubsub.go:981 Can't deliver message to subscription for topic /eds-sub/v0.0.1/arabica-6; subscriber too slow ``` Also snuck in removing multierr's `errCauses` field from light availability logs (like we do in full availability), as it leads to duplicate information inside when cascadegetter fails. --- share/availability/light/availability.go | 2 +- share/p2p/peers/manager.go | 10 +++------- share/p2p/shrexsub/pubsub.go | 8 ++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 7fd13adb4e..64d971ffc5 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -77,7 +77,7 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo if err != nil { if !errors.Is(err, context.Canceled) { - log.Errorw("availability validation failed", "root", dah.Hash(), "err", err) + log.Errorw("availability validation failed", "root", dah.Hash(), "err", err.Error()) } if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { return share.ErrNotAvailable diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index ae4e75abb2..2fc1208ba5 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -143,18 +143,13 @@ func (m *Manager) Start(startCtx context.Context) error { return fmt.Errorf("registering validator: %w", err) } - _, err = m.shrexSub.Subscribe() - if err != nil { - return fmt.Errorf("subscribing to shrexsub: %w", err) - } - - sub, err := m.headerSub.Subscribe() + headerSub, err := m.headerSub.Subscribe() if err != nil { return fmt.Errorf("subscribing to headersub: %w", err) } go m.disc.EnsurePeers(ctx) - go m.subscribeHeader(ctx, sub) + go m.subscribeHeader(ctx, headerSub) go m.GC(ctx) return nil @@ -162,6 +157,7 @@ func (m *Manager) Start(startCtx context.Context) error { func (m *Manager) Stop(ctx context.Context) error { m.cancel() + select { case <-m.done: return nil diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 51797a045f..042d0e6668 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -35,6 +35,7 @@ type PubSub struct { topic *pubsub.Topic pubsubTopic string + cancelRelay pubsub.RelayCancelFunc } // NewPubSub creates a libp2p.PubSub wrapper. @@ -57,6 +58,12 @@ func (s *PubSub) Start(context.Context) error { return err } + cancel, err := topic.Relay() + if err != nil { + return err + } + + s.cancelRelay = cancel s.topic = topic return nil } @@ -65,6 +72,7 @@ func (s *PubSub) Start(context.Context) error { // * Unregisters all the added Validators // * Closes the `ShrEx/Sub` topic func (s *PubSub) Stop(context.Context) error { + s.cancelRelay() err := s.pubSub.UnregisterTopicValidator(s.pubsubTopic) if err != nil { log.Warnw("unregistering topic", "err", err) From de409de0f9185552ec4ee6ada274351c05e78497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:24:43 +0100 Subject: [PATCH 0384/1008] refactor: Cleanup old Pipeline (#1764) --- ...ld-common.yml => docker-build-publish.yml} | 0 .github/workflows/docker-build.yml | 58 ------------------- 2 files changed, 58 deletions(-) rename .github/workflows/{docker-build-common.yml => docker-build-publish.yml} (100%) delete mode 100644 .github/workflows/docker-build.yml diff --git a/.github/workflows/docker-build-common.yml b/.github/workflows/docker-build-publish.yml similarity index 100% rename from .github/workflows/docker-build-common.yml rename to .github/workflows/docker-build-publish.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index d250833bab..0000000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: "docker-build" - -on: - push: - workflow_dispatch: - -env: - IMAGE_NAME: ${{ github.repository }} - REGISTRY: ghcr.io - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - docker-build: - runs-on: "ubuntu-latest" - permissions: - contents: write - packages: write - steps: - - uses: "actions/checkout@v3" - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=sha - type=semver,pattern={{version}} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to GHCR - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and Push amd64 Image - if: | - github.ref != 'refs/heads/main' && - !startsWith(github.ref, 'refs/tags/v') - uses: docker/build-push-action@v3 - with: - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - file: Dockerfile - - name: Build and Push amd64+arm64 Images - if: | - github.ref == 'refs/heads/main' || - startsWith(github.ref, 'refs/tags/v') - uses: docker/build-push-action@v3 - with: - platforms: linux/arm64, linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - file: Dockerfile From ae2c637e88f700c998d27a1597dae70e43ee19b8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:06:22 +0100 Subject: [PATCH 0385/1008] das: Add timeout log for how long failed sampling routine took (#1783) Self explanatory --- das/worker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/das/worker.go b/das/worker.go index 3aa3fd91e3..edb272f6b7 100644 --- a/das/worker.go +++ b/das/worker.go @@ -93,6 +93,7 @@ func (w *worker) run( "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err, + "timeout", time.Since(startSample), ) continue } From c34c677e8429d79f92f1e270aa7fb7997aff7f76 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 21 Feb 2023 10:17:49 +0100 Subject: [PATCH 0386/1008] refactor(nodebuilder): Allow setting keyring backend `file` with `--keyring.backend` flag (#1565) This PR introduces customisability for the keyring backend used by celestia-node. The default backend will still remain `test`, but support is added specifically for `file`. **NOTE** that no other backends have explicitly tested support, but are still able to be specified and used at a user's own discretion. ## celestia-node binary 1. Config for state now includes a field `KeyringBackend` which can direct the node to use a backend other than the default (`test`), so `file` for example. 2. This config field can be modified using a flag `--keyring.backend` but it must be passed both on `init` and `start` to direct the node to access the correct keyring for both operations. 3. `node.Init` now checks for existing keys, prompting the user for a password **if `file` keyring backend is specified** (an existing password if the keys already exist, and generates a new key with the given password if no keys already exist). 4. `node.Start` will now block until user enters their password for the keyring if keyring backend `file` is specified. Note that `KeyringAccname` still works as intended. # TODO - [x] ensure key actually works (balance / sendtx) # Using `--keyring.backend file` ``` celestia init --keyring.backend file celestia start --keyring.backend file ``` Also resolves #603 and #1704 --------- Co-authored-by: Viacheslav --- cmd/auth.go | 2 +- cmd/cel-key/node_types.go | 8 ++++- cmd/cel-shed/header.go | 2 +- cmd/flags_node.go | 22 +++++++++++- cmd/start.go | 25 +++++++++++-- libs/keystore/fs_keystore.go | 15 ++++++-- libs/keystore/fs_keystore_test.go | 2 +- libs/keystore/keystore.go | 5 +++ libs/keystore/map_keystore.go | 15 +++++++- nodebuilder/init.go | 58 +++++++++++++++++++++++++++++-- nodebuilder/module.go | 11 ++++++ nodebuilder/state/config.go | 6 ++++ nodebuilder/state/flags.go | 12 ++++++- nodebuilder/state/keyring.go | 53 ++++++++++------------------ nodebuilder/state/module.go | 1 - nodebuilder/store.go | 25 ++++++------- nodebuilder/store_test.go | 6 ++-- nodebuilder/testing.go | 15 ++++---- nodebuilder/tests/swamp/swamp.go | 5 ++- 19 files changed, 213 insertions(+), 75 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index 7d5523618e..40d028cc14 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -44,7 +44,7 @@ func newToken(cmd *cobra.Command, args []string) error { if err != nil { return err } - ks, err := keystore.NewFSKeystore(expanded + "/keys") + ks, err := keystore.NewFSKeystore(filepath.Join(expanded, "keys"), nil) if err != nil { return err } diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 2727ec0c15..76cc690af7 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" + nodecmd "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -54,7 +55,12 @@ func ParseDirectoryFlags(cmd *cobra.Command) error { } switch nodeType { case "bridge", "full", "light": - keyPath := fmt.Sprintf("~/.celestia-%s-%s/keys", nodeType, strings.ToLower(network)) + path, err := nodecmd.DefaultNodeStorePath(nodeType, network) + if err != nil { + return err + } + + keyPath := fmt.Sprintf("%s/keys", path) fmt.Println("using directory: ", keyPath) if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, keyPath); err != nil { return err diff --git a/cmd/cel-shed/header.go b/cmd/cel-shed/header.go index f9b2edbb2a..1069e7035f 100644 --- a/cmd/cel-shed/header.go +++ b/cmd/cel-shed/header.go @@ -45,7 +45,7 @@ Custom store path is not supported yet.`, } s, err := nodebuilder.OpenStore(fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), - strings.ToLower(network))) + strings.ToLower(network)), nil) if err != nil { return err } diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 8799f6a627..6cc3919b4e 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "os" "path/filepath" "strings" @@ -42,7 +43,11 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network store := cmd.Flag(nodeStoreFlag).Value.String() if store == "" { tp := NodeType(ctx) - store = fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(string(network))) + var err error + store, err = DefaultNodeStorePath(tp.String(), string(network)) + if err != nil { + return ctx, err + } } ctx = WithStorePath(ctx, store) @@ -69,3 +74,18 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network } return ctx, nil } + +// DefaultNodeStorePath constructs the default node store path using the given +// node type and network. +func DefaultNodeStorePath(tp string, network string) (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return fmt.Sprintf( + "%s/.celestia-%s-%s", + home, + strings.ToLower(tp), + strings.ToLower(network), + ), nil +} diff --git a/cmd/start.go b/cmd/start.go index 4e94a8206a..81d03439c3 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -1,12 +1,18 @@ package cmd import ( + "os" "os/signal" + "path/filepath" "syscall" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/spf13/cobra" flag "github.com/spf13/pflag" + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-node/nodebuilder" ) @@ -22,12 +28,25 @@ Options passed on start override configuration options only on start and are not RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - store, err := nodebuilder.OpenStore(StorePath(ctx)) + // override config with all modifiers passed on start + cfg := NodeConfig(ctx) + + storePath := StorePath(ctx) + keysPath := filepath.Join(storePath, "keys") + + // construct ring + // TODO @renaynay: Include option for setting custom `userInput` parameter with + // implementation of https://github.com/celestiaorg/celestia-node/issues/415. + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + ring, err := keyring.New(app.Name, cfg.State.KeyringBackend, keysPath, os.Stdin, encConf.Codec) + if err != nil { + return err + } + + store, err := nodebuilder.OpenStore(storePath, ring) if err != nil { return err } - // override config with all modifiers passed on start - cfg := NodeConfig(ctx) nd, err := nodebuilder.NewWithConfig(NodeType(ctx), Network(ctx), store, &cfg, NodeOptions(ctx)...) if err != nil { diff --git a/libs/keystore/fs_keystore.go b/libs/keystore/fs_keystore.go index 79087c8397..02361f0084 100644 --- a/libs/keystore/fs_keystore.go +++ b/libs/keystore/fs_keystore.go @@ -7,6 +7,8 @@ import ( "io/fs" "os" "path/filepath" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" ) // ErrNotFound is returned when the key does not exist. @@ -15,16 +17,21 @@ var ErrNotFound = errors.New("keystore: key not found") // fsKeystore implements persistent Keystore over OS filesystem. type fsKeystore struct { path string + + ring keyring.Keyring } // NewFSKeystore creates a new Keystore over OS filesystem. // The path must point to a directory. It is created automatically if necessary. -func NewFSKeystore(path string) (Keystore, error) { +func NewFSKeystore(path string, ring keyring.Keyring) (Keystore, error) { err := os.Mkdir(path, 0755) if err != nil && !os.IsExist(err) { return nil, fmt.Errorf("keystore: failed to make a dir: %w", err) } - return &fsKeystore{path: path}, nil + return &fsKeystore{ + path: path, + ring: ring, + }, nil } func (f *fsKeystore) Put(n KeyName, pk PrivKey) error { @@ -122,6 +129,10 @@ func (f *fsKeystore) Path() string { return f.path } +func (f *fsKeystore) Keyring() keyring.Keyring { + return f.ring +} + func (f *fsKeystore) pathTo(file string) string { return filepath.Join(f.path, file) } diff --git a/libs/keystore/fs_keystore_test.go b/libs/keystore/fs_keystore_test.go index c20f00fb41..2baba2d8fc 100644 --- a/libs/keystore/fs_keystore_test.go +++ b/libs/keystore/fs_keystore_test.go @@ -8,7 +8,7 @@ import ( ) func TestFSKeystore(t *testing.T) { - kstore, err := NewFSKeystore(t.TempDir() + "/keystore") + kstore, err := NewFSKeystore(t.TempDir()+"/keystore", nil) require.NoError(t, err) err = kstore.Put("test", PrivKey{Body: []byte("test_private_key")}) diff --git a/libs/keystore/keystore.go b/libs/keystore/keystore.go index 522389cdfa..d9bc21a486 100644 --- a/libs/keystore/keystore.go +++ b/libs/keystore/keystore.go @@ -3,6 +3,7 @@ package keystore import ( "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/multiformats/go-base32" ) @@ -34,6 +35,10 @@ type Keystore interface { // Path reports the path of the Keystore. Path() string + + // Keyring returns the keyring corresponding to the node's + // keystore. + Keyring() keyring.Keyring } // KeyNameFromBase32 decodes KeyName from Base32 format. diff --git a/libs/keystore/map_keystore.go b/libs/keystore/map_keystore.go index 8439e59ab9..84de91458e 100644 --- a/libs/keystore/map_keystore.go +++ b/libs/keystore/map_keystore.go @@ -3,17 +3,26 @@ package keystore import ( "fmt" "sync" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" ) // mapKeystore is a simple in-memory Keystore implementation. type mapKeystore struct { keys map[KeyName]PrivKey keysLk sync.Mutex + ring keyring.Keyring } // NewMapKeystore constructs in-memory Keystore. func NewMapKeystore() Keystore { - return &mapKeystore{keys: make(map[KeyName]PrivKey)} + return &mapKeystore{ + keys: make(map[KeyName]PrivKey), + ring: keyring.NewInMemory(encoding.MakeConfig(app.ModuleEncodingRegisters...).Codec), + } } func (m *mapKeystore) Put(n KeyName, k PrivKey) error { @@ -69,3 +78,7 @@ func (m *mapKeystore) List() ([]KeyName, error) { func (m *mapKeystore) Path() string { return "" } + +func (m *mapKeystore) Keyring() keyring.Keyring { + return m.ring +} diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 5172cacab5..57ce4c755b 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -1,9 +1,16 @@ package nodebuilder import ( + "fmt" "os" "path/filepath" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -32,7 +39,8 @@ func Init(cfg Config, path string, tp node.Type) error { } defer flock.Unlock() //nolint: errcheck - err = initDir(keysPath(path)) + ksPath := keysPath(path) + err = initDir(ksPath) if err != nil { return err } @@ -47,7 +55,15 @@ func Init(cfg Config, path string, tp node.Type) error { if err != nil { return err } - log.Infow("Saving config", "path", cfgPath) + log.Infow("Saved config", "path", cfgPath) + + log.Infow("Accessing keyring...") + err = generateKeys(cfg, ksPath) + if err != nil { + log.Errorw("generating account keys", "err", err) + return err + } + log.Info("Node Store initialized") return nil } @@ -105,3 +121,41 @@ func initDir(path string) error { } return os.Mkdir(path, perms) } + +// generateKeys will construct a keyring from the given keystore path and check +// if account keys already exist. If not, it will generate a new account key and +// store it. +func generateKeys(cfg Config, ksPath string) error { + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + + if cfg.State.KeyringBackend == keyring.BackendTest { + log.Warn("Detected plaintext keyring backend. For elevated security properties, consider using" + + "the `file` keyring backend.") + } + ring, err := keyring.New(app.Name, cfg.State.KeyringBackend, ksPath, os.Stdin, encConf.Codec) + if err != nil { + return err + } + keys, err := ring.List() + if err != nil { + return err + } + if len(keys) > 0 { + // at least one key is already present + return nil + } + log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ksPath) + keyInfo, mn, err := ring.NewMnemonic("my_celes_key", keyring.English, "", + "", hd.Secp256k1) + if err != nil { + return err + } + log.Info("NEW KEY GENERATED...") + addr, err := keyInfo.GetAddress() + if err != nil { + return err + } + fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n", + keyInfo.Name, addr.String(), mn) + return nil +} diff --git a/nodebuilder/module.go b/nodebuilder/module.go index ca44ba3478..3f3b08e68b 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -19,6 +19,16 @@ import ( ) func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store) fx.Option { + log.Infow("Accessing keyring...") + ks, err := store.Keystore() + if err != nil { + fx.Error(err) + } + signer, err := state.KeyringSigner(cfg.State, ks, network) + if err != nil { + fx.Error(err) + } + baseComponents := fx.Options( fx.Supply(tp), fx.Supply(network), @@ -31,6 +41,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store fx.Provide(store.Datastore), fx.Provide(store.Keystore), fx.Supply(node.StorePath(store.Path())), + fx.Supply(signer), // modules provided by the node p2p.ConstructModule(tp, &cfg.P2P), state.ConstructModule(tp, &cfg.State), diff --git a/nodebuilder/state/config.go b/nodebuilder/state/config.go index 93969dcd28..e6db813e06 100644 --- a/nodebuilder/state/config.go +++ b/nodebuilder/state/config.go @@ -1,14 +1,20 @@ package state +import "github.com/cosmos/cosmos-sdk/crypto/keyring" + +var defaultKeyringBackend = keyring.BackendTest + // Config contains configuration parameters for constructing // the node's keyring signer. type Config struct { KeyringAccName string + KeyringBackend string } func DefaultConfig() Config { return Config{ KeyringAccName: "", + KeyringBackend: defaultKeyringBackend, } } diff --git a/nodebuilder/state/flags.go b/nodebuilder/state/flags.go index 7b9123eda2..7e35bfa078 100644 --- a/nodebuilder/state/flags.go +++ b/nodebuilder/state/flags.go @@ -1,11 +1,16 @@ package state import ( + "fmt" + "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) -var keyringAccNameFlag = "keyring.accname" +var ( + keyringAccNameFlag = "keyring.accname" + keyringBackendFlag = "keyring.backend" +) // Flags gives a set of hardcoded State flags. func Flags() *flag.FlagSet { @@ -13,6 +18,9 @@ func Flags() *flag.FlagSet { flags.String(keyringAccNameFlag, "", "Directs node's keyring signer to use the key prefixed with the "+ "given string.") + flags.String(keyringBackendFlag, defaultKeyringBackend, fmt.Sprintf("Directs node's keyring signer to use the given "+ + "backend. Default is %s.", defaultKeyringBackend)) + return flags } @@ -22,4 +30,6 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) { if keyringAccName != "" { cfg.KeyringAccName = keyringAccName } + + cfg.KeyringBackend = cmd.Flag(keyringBackendFlag).Value.String() } diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go index 42e76625d9..277bce2fd0 100644 --- a/nodebuilder/state/keyring.go +++ b/nodebuilder/state/keyring.go @@ -2,36 +2,26 @@ package state import ( "fmt" - "os" - "github.com/cosmos/cosmos-sdk/crypto/hd" kr "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/app/encoding" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -func keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.KeyringSigner, error) { - // TODO @renaynay: Include option for setting custom `userInput` parameter with - // implementation of https://github.com/celestiaorg/celestia-node/issues/415. - // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed - // here instead of hardcoded `BackendTest`: - // https://github.com/celestiaorg/celestia-node/issues/603. - encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - ring, err := kr.New(app.Name, kr.BackendTest, ks.Path(), os.Stdin, encConf.Codec) - if err != nil { - return nil, err - } - +// KeyringSigner constructs a new keyring signer. +// NOTE: we construct keyring signer before constructing node for easier UX +// as having keyring-backend set to `file` prompts user for password. +func KeyringSigner(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.KeyringSigner, error) { + ring := ks.Keyring() var info *kr.Record // if custom keyringAccName provided, find key for that name if cfg.KeyringAccName != "" { keyInfo, err := ring.Key(cfg.KeyringAccName) if err != nil { + log.Errorw("failed to find key by given name", "keyring.accname", cfg.KeyringAccName) return nil, err } info = keyInfo @@ -43,30 +33,23 @@ func keyring(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes.Keyri } // if no key was found in keystore path, generate new key for node if len(keys) == 0 { - log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ks.Path()) - keyInfo, mn, err := ring.NewMnemonic("my_celes_key", kr.English, "", - "", hd.Secp256k1) - if err != nil { - return nil, err - } - log.Info("NEW KEY GENERATED...") - addr, err := keyInfo.GetAddress() - if err != nil { - return nil, err - } - fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n", - keyInfo.Name, addr.String(), mn) - - info = keyInfo - } else { - // if one or more keys are present and no keyringAccName was given, use the first key in list - info = keys[0] + log.Errorw("no keys found in path", "path", ks.Path(), "keyring backend", + cfg.KeyringBackend) + return nil, fmt.Errorf("no keys found in path %s using keyring backend %s", ks.Path(), + cfg.KeyringBackend) } + // if one or more keys are present and no keyringAccName was given, use the first key in list + keyInfo, err := ring.Key(keys[0].Name) + if err != nil { + log.Errorw("could not access key in keyring", "name", keys[0].Name) + return nil, err + } + info = keyInfo } // construct signer using the default key found / generated above signer := apptypes.NewKeyringSigner(ring, info.Name, string(net)) signerInfo := signer.GetSignerInfo() - log.Infow("constructed keyring signer", "backend", kr.BackendTest, "path", ks.Path(), + log.Infow("constructed keyring signer", "backend", cfg.KeyringBackend, "path", ks.Path(), "key name", signerInfo.Name, "chain-id", string(net)) return signer, nil diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index 3c50f88f05..b8553715a9 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -23,7 +23,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents := fx.Options( fx.Supply(*cfg), fx.Error(cfgErr), - fx.Provide(keyring), fx.Provide(fx.Annotate( coreAccessor, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, ca *state.CoreAccessor) error { diff --git a/nodebuilder/store.go b/nodebuilder/store.go index ba75beaeb9..072295d9fe 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -6,6 +6,7 @@ import ( "path/filepath" "sync" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/dgraph-io/badger/v2/options" "github.com/ipfs/go-datastore" dsbadger "github.com/ipfs/go-ds-badger2" @@ -48,7 +49,7 @@ type Store interface { // To be opened the Store must be initialized first, otherwise ErrNotInited is thrown. // OpenStore takes a file Lock on directory, hence only one Store can be opened at a time under the // given 'path', otherwise ErrOpened is thrown. -func OpenStore(path string) (Store, error) { +func OpenStore(path string, ring keyring.Keyring) (Store, error) { path, err := storePath(path) if err != nil { return nil, err @@ -68,9 +69,15 @@ func OpenStore(path string) (Store, error) { return nil, ErrNotInited } + ks, err := keystore.NewFSKeystore(keysPath(path), ring) + if err != nil { + return nil, err + } + return &fsStore{ path: path, dirLock: flock, + keys: ks, }, nil } @@ -98,20 +105,10 @@ func (f *fsStore) PutConfig(cfg *Config) error { func (f *fsStore) Keystore() (_ keystore.Keystore, err error) { f.lock.RLock() - if f.keys != nil { - f.lock.RUnlock() - return f.keys, nil + defer f.lock.RUnlock() + if f.keys == nil { + return nil, fmt.Errorf("node: no Keystore found") } - f.lock.RUnlock() - - f.lock.Lock() - defer f.lock.Unlock() - - f.keys, err = keystore.NewFSKeystore(keysPath(f.path)) - if err != nil { - return nil, fmt.Errorf("node: can't open Keystore: %w", err) - } - return f.keys, nil } diff --git a/nodebuilder/store_test.go b/nodebuilder/store_test.go index d16edc1d60..512d45bb70 100644 --- a/nodebuilder/store_test.go +++ b/nodebuilder/store_test.go @@ -21,16 +21,16 @@ func TestRepo(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) { dir := t.TempDir() - _, err := OpenStore(dir) + _, err := OpenStore(dir, nil) assert.ErrorIs(t, err, ErrNotInited) err = Init(*DefaultConfig(tt.tp), dir, tt.tp) require.NoError(t, err) - store, err := OpenStore(dir) + store, err := OpenStore(dir, nil) require.NoError(t, err) - _, err = OpenStore(dir) + _, err = OpenStore(dir, nil) assert.ErrorIs(t, err, ErrOpened) ks, err := store.Keystore() diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index f0dac2c9a7..ccbb9f3a1d 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -8,8 +8,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/fx" - "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/app/encoding" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/core" @@ -39,9 +37,14 @@ func TestNode(t *testing.T, tp node.Type, opts ...fx.Option) *Node { func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Option) *Node { // avoids port conflicts cfg.RPC.Port = "0" + + store := MockStore(t, cfg) + ks, err := store.Keystore() + require.NoError(t, err) + opts = append(opts, // avoid writing keyring on disk - state.WithKeyringSigner(TestKeyringSigner(t)), + state.WithKeyringSigner(TestKeyringSigner(t, ks.Keyring())), // temp dir for the eds store FIXME: Should be in mem fx.Replace(node.StorePath(t.TempDir())), // avoid requesting trustedPeer during initialization @@ -59,14 +62,12 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti ) } - nd, err := New(tp, p2p.Private, MockStore(t, cfg), opts...) + nd, err := New(tp, p2p.Private, store, opts...) require.NoError(t, err) return nd } -func TestKeyringSigner(t *testing.T) *apptypes.KeyringSigner { - encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - ring := keyring.NewInMemory(encConf.Codec) +func TestKeyringSigner(t *testing.T, ring keyring.Keyring) *apptypes.KeyringSigner { signer := apptypes.NewKeyringSigner(ring, "", string(p2p.Private)) _, _, err := signer.NewMnemonic("test_celes", keyring.English, "", "", hd.Secp256k1) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 32434788a2..b4ab8fd925 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -221,8 +221,11 @@ func (s *Swamp) NewNodeWithStore( ) *nodebuilder.Node { var n *nodebuilder.Node + ks, err := store.Keystore() + require.NoError(s.t, err) + options = append(options, - state.WithKeyringSigner(nodebuilder.TestKeyringSigner(s.t)), + state.WithKeyringSigner(nodebuilder.TestKeyringSigner(s.t, ks.Keyring())), ) switch t { From af4d94e7faf54a29dfa28938fd1b2fab897693bc Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 21 Feb 2023 13:42:51 +0100 Subject: [PATCH 0387/1008] fix(eds): don't commit reconstructed EDS in Retriever (#1788) --- share/eds/retriever.go | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 5a4ac6932c..6bbdb8c1f4 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -21,7 +21,6 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" ) @@ -104,9 +103,8 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader // quadrant request retries. Also, provides an API // to reconstruct the block once enough shares are fetched. type retrievalSession struct { - dah *da.DataAvailabilityHeader - bget blockservice.BlockGetter - adder *ipld.NmtNodeAdder + dah *da.DataAvailabilityHeader + bget blockservice.BlockGetter // TODO(@Wondertan): Extract into a separate data structure // https://github.com/celestiaorg/rsmt2d/issues/135 @@ -124,14 +122,8 @@ type retrievalSession struct { // newSession creates a new retrieval session and kicks off requesting process. func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHeader) (*retrievalSession, error) { size := len(dah.RowsRoots) - adder := ipld.NewNmtNodeAdder( - ctx, - r.bServ, - ipld.MaxSizeBatchOption(size), - ) - treeFn := func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index, nmt.NodeVisitor(adder.Visit)) + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index) return &tree } @@ -143,7 +135,6 @@ func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHead ses := &retrievalSession{ dah: dah, bget: blockservice.NewSession(ctx, r.bServ), - adder: adder, squareQuadrants: newQuadrants(dah), squareCellsLks: make([][]sync.Mutex, size), squareSig: make(chan struct{}, 1), @@ -202,14 +193,7 @@ func (rs *retrievalSession) isReconstructed() bool { func (rs *retrievalSession) Close() error { defer rs.span.End() - // All shares which were requested or repaired are written to disk via `Commit`. - // Note that we store *all*, so they are served to the network, including commit of incorrect - // data(BEFP/ErrByzantineCase case), so that the network can check BEFP. - err := rs.adder.Commit() - if err != nil { - log.Errorw("committing DAG", "err", err) - } - return err + return nil } // request kicks off quadrants requests. @@ -276,8 +260,9 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { // NOTE: Each share can appear twice here, for a Row and Col, respectively. // These shares are always equal, and we allow only the first one to be written // in the square. - // NOTE-2: We never actually fetch shares from the network *twice*. - // Once a share is downloaded from the network it is cached on the IPLD(blockservice) level. + // NOTE-2: We may never actually fetch shares from the network *twice*. + // Once a share is downloaded from the network it may be cached on the IPLD(blockservice) level. + // // calc position of the share x, y := q.pos(i, j) // try to lock the share From 633d2ffa86b2a2734c252b7ea8ae547420a8fa68 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 21 Feb 2023 14:48:39 +0100 Subject: [PATCH 0388/1008] fix(header): add timeout for the head retrieval (#1793) --- header/metrics.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/header/metrics.go b/header/metrics.go index dbf78d1e57..61378a6251 100644 --- a/header/metrics.go +++ b/header/metrics.go @@ -2,6 +2,7 @@ package header import ( "context" + "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/global" @@ -27,6 +28,11 @@ func WithMetrics(store libhead.Store[*ExtendedHeader], syncer *sync.Syncer[*Exte headC, }, func(ctx context.Context) { + // add timeout to limit the time it takes to get the head + // in case there is a deadlock + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + head, err := store.Head(ctx) if err != nil { headC.Observe(ctx, 0, attribute.String("err", err.Error())) From 0dd0296509ce43a52394a88ae95b8a14efd7b1d2 Mon Sep 17 00:00:00 2001 From: Richard Gregory Date: Tue, 21 Feb 2023 16:44:15 +0100 Subject: [PATCH 0389/1008] swamp: rename Components to Config (#1787) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- nodebuilder/tests/swamp/config.go | 14 +++++++------- nodebuilder/tests/swamp/swamp.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index 2c998b73cf..f14243d342 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -6,28 +6,28 @@ import ( "github.com/celestiaorg/celestia-node/core" ) -// Components struct represents a set of pre-requisite attributes from the test scenario -type Components struct { +// Config struct represents a set of pre-requisite attributes from the test scenario +type Config struct { *core.TestConfig } -// DefaultComponents creates a celestia-app instance with a block time of around +// DefaultConfig creates a celestia-app instance with a block time of around // 100ms -func DefaultComponents() *Components { +func DefaultConfig() *Config { cfg := core.DefaultTestConfig() // timeout commits faster than this tend to be flakier cfg.Tendermint.Consensus.TimeoutCommit = 200 * time.Millisecond - return &Components{ + return &Config{ cfg, } } // Option for modifying Swamp's Config. -type Option func(*Components) +type Option func(*Config) // WithBlockTime sets a custom interval for block creation. func WithBlockTime(t time.Duration) Option { - return func(c *Components) { + return func(c *Config) { // for empty block c.Tendermint.Consensus.CreateEmptyBlocksInterval = t // for filled block diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index b4ab8fd925..5e1426f28d 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -49,7 +49,7 @@ type Swamp struct { BridgeNodes []*nodebuilder.Node FullNodes []*nodebuilder.Node LightNodes []*nodebuilder.Node - comps *Components + comps *Config ClientContext testnode.Context Accounts []string @@ -63,7 +63,7 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { logs.SetDebugLogging() } - ic := DefaultComponents() + ic := DefaultConfig() for _, option := range options { option(ic) } From 1fdf1c7b8d7505696aa14804de11c9a46ec91e9d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 21 Feb 2023 19:29:32 +0100 Subject: [PATCH 0390/1008] refactor(share/p2p): Discovery controls `ensurePeers` lifecycle (#1789) Extracts `ensurePeers` lifecycle management from `peer.Manager`, and privatizes `ensurePeers`. As All the node types call it, so we can do it by default. --- nodebuilder/share/module.go | 11 ++++++- share/availability/discovery/discovery.go | 35 ++++++++++++++++------- share/p2p/peers/manager.go | 3 -- share/p2p/peers/manager_test.go | 10 +++++-- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 9fff079c26..64aeabcdfa 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" + disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" @@ -29,7 +30,15 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Supply(*cfg), fx.Error(cfgErr), fx.Options(options...), - fx.Provide(discovery(*cfg)), + fx.Provide(fx.Annotate( + discovery(*cfg), + fx.OnStart(func(ctx context.Context, d *disc.Discovery) error { + return d.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, d *disc.Discovery) error { + return d.Stop(ctx) + }), + )), fx.Provide(newModule), ) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index f8fc129724..47733a9281 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -46,6 +46,8 @@ type Discovery struct { advertiseInterval time.Duration // onUpdatedPeers will be called on peer set changes onUpdatedPeers OnUpdatedPeers + + cancel context.CancelFunc } type OnUpdatedPeers func(peerID peer.ID, isAdded bool) @@ -59,17 +61,30 @@ func NewDiscovery( advertiseInterval time.Duration, ) *Discovery { return &Discovery{ - newLimitedSet(peersLimit), - h, - d, - newBackoffConnector(h, defaultBackoffFactory), - peersLimit, - discInterval, - advertiseInterval, - func(peer.ID, bool) {}, + set: newLimitedSet(peersLimit), + host: h, + disc: d, + connector: newBackoffConnector(h, defaultBackoffFactory), + peersLimit: peersLimit, + discoveryInterval: discInterval, + advertiseInterval: advertiseInterval, + onUpdatedPeers: func(peer.ID, bool) {}, } } +func (d *Discovery) Start(context.Context) error { + ctx, cancel := context.WithCancel(context.Background()) + d.cancel = cancel + + go d.ensurePeers(ctx) + return nil +} + +func (d *Discovery) Stop(context.Context) error { + d.cancel() + return nil +} + // WithOnPeersUpdate chains OnPeersUpdate callbacks on every update of discovered peers list. func (d *Discovery) WithOnPeersUpdate(f OnUpdatedPeers) { prev := d.onUpdatedPeers @@ -104,10 +119,10 @@ func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer d.host.ConnManager().TagPeer(peer.ID, topic, peerWeight) } -// EnsurePeers ensures we always have 'peerLimit' connected peers. +// ensurePeers ensures we always have 'peerLimit' connected peers. // It starts peer discovery every 30 seconds until peer cache reaches peersLimit. // Discovery is restarted if any previously connected peers disconnect. -func (d *Discovery) EnsurePeers(ctx context.Context) { +func (d *Discovery) ensurePeers(ctx context.Context) { if d.peersLimit == 0 { log.Warn("peers limit is set to 0. Skipping discovery...") return diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 2fc1208ba5..0bec7a19c0 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -45,7 +45,6 @@ type Manager struct { // header subscription is necessary in order to validate the inbound eds hash headerSub libhead.Subscriber[*header.ExtendedHeader] shrexSub *shrexsub.PubSub - disc *discovery.Discovery host host.Host connGater *conngater.BasicConnectionGater @@ -93,7 +92,6 @@ func NewManager( s := &Manager{ headerSub: headerSub, shrexSub: shrexSub, - disc: discovery, connGater: connGater, host: host, pools: make(map[string]*syncPool), @@ -148,7 +146,6 @@ func (m *Manager) Start(startCtx context.Context) error { return fmt.Errorf("subscribing to headersub: %w", err) } - go m.disc.EnsurePeers(ctx) go m.subscribeHeader(ctx, headerSub) go m.GC(ctx) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index a9034f4586..8b1e4d1f43 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -315,7 +315,14 @@ func TestIntegration(t *testing.T) { routingdisc.NewRoutingDiscovery(router2), 10, time.Second, - time.Second) + time.Second, + ) + err = fnDisc.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = fnDisc.Stop(ctx) + require.NoError(t, err) + }) // hook peer manager to discovery connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) @@ -340,7 +347,6 @@ func TestIntegration(t *testing.T) { require.NoError(t, router1.Bootstrap(ctx)) require.NoError(t, router2.Bootstrap(ctx)) - go fnDisc.EnsurePeers(ctx) go bnDisc.Advertise(ctx) select { From a5fecefbd0a2fc33fb56cb5231c74580309703b1 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Feb 2023 11:40:12 +0100 Subject: [PATCH 0391/1008] deps: bump libp2p and all supporting libs (#1800) --- go.mod | 59 +++++------ go.sum | 128 +++++++++++++----------- nodebuilder/p2p/bitswap.go | 4 +- share/availability/test/corrupt_data.go | 2 +- share/availability/test/testing.go | 4 +- share/eds/adapters.go | 2 +- share/eds/adapters_test.go | 2 +- share/eds/blockstore.go | 2 +- share/ipld/nmt.go | 2 +- share/p2p/peers/options.go | 3 +- share/p2p/shrexsub/pubsub.go | 3 +- 11 files changed, 112 insertions(+), 99 deletions(-) diff --git a/go.mod b/go.mod index d116f81217..d07382b2c8 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,6 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/ipfs/go-bitswap v0.11.0 - github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 @@ -38,12 +36,13 @@ require ( github.com/ipfs/go-ipfs-routing v0.3.0 github.com/ipfs/go-ipld-cbor v0.0.6 github.com/ipfs/go-ipld-format v0.4.0 + github.com/ipfs/go-libipfs v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 - github.com/ipfs/go-merkledag v0.7.0 + github.com/ipfs/go-merkledag v0.9.0 github.com/ipld/go-car v0.5.0 - github.com/libp2p/go-libp2p v0.24.1 - github.com/libp2p/go-libp2p-kad-dht v0.20.0 - github.com/libp2p/go-libp2p-pubsub v0.8.3 + github.com/libp2p/go-libp2p v0.25.1 + github.com/libp2p/go-libp2p-kad-dht v0.21.0 + github.com/libp2p/go-libp2p-pubsub v0.9.1 github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.6.0 github.com/minio/sha256-simd v1.0.0 @@ -67,7 +66,7 @@ require ( go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.5.0 - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db + golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 golang.org/x/sync v0.1.0 golang.org/x/text v0.6.0 google.golang.org/grpc v1.52.0 @@ -129,7 +128,6 @@ require ( github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect - github.com/emirpasic/gods v1.18.1 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect github.com/ethereum/go-ethereum v1.10.26 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect @@ -179,6 +177,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/huin/goupnp v1.0.3 // indirect @@ -186,19 +185,21 @@ require ( github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/ipfs/bbloom v0.0.4 // indirect + github.com/ipfs/go-bitswap v0.12.0 // indirect + github.com/ipfs/go-block-format v0.1.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect - github.com/ipfs/go-ipfs-pq v0.0.2 // indirect + github.com/ipfs/go-ipfs-pq v0.0.3 // indirect github.com/ipfs/go-ipfs-util v0.0.2 // indirect - github.com/ipfs/go-ipld-legacy v0.1.0 // indirect - github.com/ipfs/go-ipns v0.2.0 // indirect + github.com/ipfs/go-ipld-legacy v0.1.1 // indirect + github.com/ipfs/go-ipns v0.3.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect - github.com/ipfs/go-peertaskqueue v0.8.0 // indirect - github.com/ipfs/go-verifcid v0.0.1 // indirect - github.com/ipld/go-car/v2 v2.4.1 // indirect - github.com/ipld/go-codec-dagpb v1.3.1 // indirect - github.com/ipld/go-ipld-prime v0.16.0 // indirect + github.com/ipfs/go-peertaskqueue v0.8.1 // indirect + github.com/ipfs/go-verifcid v0.0.2 // indirect + github.com/ipld/go-car/v2 v2.5.1 // indirect + github.com/ipld/go-codec-dagpb v1.5.0 // indirect + github.com/ipld/go-ipld-prime v0.19.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect @@ -206,7 +207,7 @@ require ( github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.15.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.1 // indirect + github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect github.com/lib/pq v1.10.6 // indirect @@ -215,22 +216,17 @@ require ( github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect - github.com/libp2p/go-msgio v0.2.0 // indirect + github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/libp2p/go-openssl v0.1.0 // indirect github.com/libp2p/go-reuseport v0.2.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.0 // indirect - github.com/lucas-clemente/quic-go v0.31.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/manifoldco/promptui v0.9.0 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect - github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect - github.com/mattn/go-pointer v0.0.1 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.50 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect @@ -246,7 +242,7 @@ require ( github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect github.com/multiformats/go-multicodec v0.7.0 // indirect - github.com/multiformats/go-multistream v0.3.3 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/onsi/ginkgo/v2 v2.5.1 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect @@ -258,11 +254,17 @@ require ( github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect + github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-18 v0.2.0 // indirect + github.com/quic-go/qtls-go1-19 v0.2.0 // indirect + github.com/quic-go/qtls-go1-20 v0.1.0 // indirect + github.com/quic-go/quic-go v0.32.0 // indirect + github.com/quic-go/webtransport-go v0.5.1 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect @@ -273,7 +275,6 @@ require ( github.com/rs/zerolog v1.27.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil v3.21.6+incompatible // indirect - github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect @@ -290,7 +291,7 @@ require ( github.com/ulikunitz/xz v0.5.8 // indirect github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect - github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 // indirect + github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.0 // indirect @@ -317,7 +318,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect - nhooyr.io/websocket v1.8.6 // indirect + nhooyr.io/websocket v1.8.7 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index fde8f37ed5..1b3f58f3b0 100644 --- a/go.sum +++ b/go.sum @@ -106,7 +106,6 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= @@ -392,8 +391,6 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -433,7 +430,6 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -515,6 +511,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= @@ -596,7 +593,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -724,6 +720,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= +github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -771,11 +769,12 @@ github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitfield v1.0.0 h1:y/XHm2GEmD9wKngheWNNCNL0pzrWXZwCdQGv1ikXknQ= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= -github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= -github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= +github.com/ipfs/go-bitswap v0.12.0 h1:ClbLaufwv8SRQK0sBhl4wDVqJoZGAGMVxdjQy5CTt6c= +github.com/ipfs/go-bitswap v0.12.0/go.mod h1:Iwjkd6+vaDjVIa6b6ogmZgs+b5U3EkIFEX79kQ4DjnI= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= -github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= +github.com/ipfs/go-block-format v0.1.1 h1:129vSO3zwbsYADcyQWcOYiuCpAqt462SFfqFHdFJhhI= +github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= github.com/ipfs/go-blockservice v0.5.0 h1:B2mwhhhVQl2ntW2EIpaWPwSCxSuqr5fFA93Ms4bYLEY= github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= @@ -833,8 +832,9 @@ github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSO github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= -github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= +github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= +github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= @@ -849,10 +849,13 @@ github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-format v0.4.0 h1:yqJSaJftjmjc9jEOFYlpkwOLVKv68OD27jFLlSghBlQ= github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= -github.com/ipfs/go-ipld-legacy v0.1.0 h1:wxkkc4k8cnvIGIjPO0waJCe7SHEyFgl+yQdafdjGrpA= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= -github.com/ipfs/go-ipns v0.2.0 h1:BgmNtQhqOw5XEZ8RAfWEpK4DhqaYiuP6h71MhIp7xXU= -github.com/ipfs/go-ipns v0.2.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= +github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2cdcc= +github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= +github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A= +github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= +github.com/ipfs/go-libipfs v0.6.0 h1:3FuckAJEm+zdHbHbf6lAyk0QUzc45LsFcGw102oBCZM= +github.com/ipfs/go-libipfs v0.6.0/go.mod h1:UjjDIuehp2GzlNP0HEr5I9GfFT7zWgst+YfpUEIThtw= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= @@ -870,27 +873,27 @@ github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOL github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= -github.com/ipfs/go-merkledag v0.7.0 h1:PHdWOGwx+J2uRAuP9Mu+bz89ulmf3W2QmbSS/N6O29U= -github.com/ipfs/go-merkledag v0.7.0/go.mod h1:/1cuN4VbcDn/xbVMAqjPUwejJYr8W9SvizmyYLU/B7k= +github.com/ipfs/go-merkledag v0.9.0 h1:DFC8qZ96Dz1hMT7dtIpcY524eFFDiEWAF8hNJHWW2pk= +github.com/ipfs/go-merkledag v0.9.0/go.mod h1:bPHqkHt5OZ0p1n3iqPeDiw2jIBkjAytRjS3WSBwjq90= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= -github.com/ipfs/go-peertaskqueue v0.8.0 h1:JyNO144tfu9bx6Hpo119zvbEL9iQ760FHOiJYsUjqaU= -github.com/ipfs/go-peertaskqueue v0.8.0/go.mod h1:cz8hEnnARq4Du5TGqiWKgMr/BOSQ5XOgMOh1K5YYKKM= -github.com/ipfs/go-unixfsnode v1.4.0 h1:9BUxHBXrbNi8mWHc6j+5C580WJqtVw9uoeEKn4tMhwA= +github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= +github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= +github.com/ipfs/go-unixfsnode v1.5.1 h1:JcR3t5C2nM1V7PMzhJ/Qmo19NkoFIKweDSZyDx+CjkI= github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= -github.com/ipld/go-car/v2 v2.4.1 h1:9S+FYbQzQJ/XzsdiOV13W5Iu/i+gUnr6csbSD9laFEg= -github.com/ipld/go-car/v2 v2.4.1/go.mod h1:zjpRf0Jew9gHqSvjsKVyoq9OY9SWoEKdYCQUKVaaPT0= +github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk= +github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= -github.com/ipld/go-codec-dagpb v1.3.1 h1:yVNlWRQexCa54ln3MSIiUN++ItH7pdhBFhh0hSgZu1w= -github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= +github.com/ipld/go-codec-dagpb v1.5.0 h1:RspDRdsJpLfgCI0ONhTAnbHdySGD4t+LHSPK4X1+R0k= +github.com/ipld/go-codec-dagpb v1.5.0/go.mod h1:0yRIutEFD8o1DGVqw4RSHh+BUTlJA9XWldxaaWR/o4g= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= -github.com/ipld/go-ipld-prime v0.16.0 h1:RS5hhjB/mcpeEPJvfyj0qbOj/QL+/j05heZ0qa97dVo= -github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= +github.com/ipld/go-ipld-prime v0.19.0 h1:5axC7rJmPc17Emw6TelxGwnzALk0PdupZ2oj2roDj04= +github.com/ipld/go-ipld-prime v0.19.0/go.mod h1:Q9j3BaVXwaA3o5JUDNvptDDr/x8+F7FG6XJ8WI3ILg4= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= @@ -958,8 +961,8 @@ github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM52 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI= -github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= @@ -1015,8 +1018,8 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= -github.com/libp2p/go-libp2p v0.24.1 h1:+lS4fqj7RF9egcPq9Yo3iqdRTcDMApzoBbQMhxtwOVw= -github.com/libp2p/go-libp2p v0.24.1/go.mod h1:5LJqbrqFsUzWrq70JHCYqjATlX4ey8Klpct3OEe8hSI= +github.com/libp2p/go-libp2p v0.25.1 h1:YK+YDCHpYyTvitKWVxa5PfElgIpOONU01X5UcLEwJGA= +github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= @@ -1058,8 +1061,8 @@ github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfx github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.20.0 h1:1bcMa74JFwExCHZMFEmjtHzxX5DovhJ07EtR6UOTEpc= -github.com/libp2p/go-libp2p-kad-dht v0.20.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= +github.com/libp2p/go-libp2p-kad-dht v0.21.0 h1:J0Yd22VA+sk0CJRGMgtfHvLVIkZDyJ3AJGiljywIw5U= +github.com/libp2p/go-libp2p-kad-dht v0.21.0/go.mod h1:Bhm9diAFmc6qcWAr084bHNL159srVZRKADdp96Qqd1I= github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= @@ -1084,8 +1087,8 @@ github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuD github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= -github.com/libp2p/go-libp2p-pubsub v0.8.3 h1:T4+pcfcFm1K2v5oFyk68peSjVroaoM8zFygf6Y5WOww= -github.com/libp2p/go-libp2p-pubsub v0.8.3/go.mod h1:eje970FXxjhtFbVEoiae+VUw24ZoSlk67BsiZPLRzlw= +github.com/libp2p/go-libp2p-pubsub v0.9.1 h1:A6LBg9BaoLf3NwRz+E974sAxTVcbUZYg95IhK2BZz9g= +github.com/libp2p/go-libp2p-pubsub v0.9.1/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= @@ -1142,8 +1145,8 @@ github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= -github.com/libp2p/go-msgio v0.2.0 h1:W6shmB+FeynDrUVl2dgFQvzfBZcXiyqY4VmpQLu9FqU= -github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= +github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= @@ -1159,8 +1162,6 @@ github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo= -github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= @@ -1200,8 +1201,6 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= -github.com/lucas-clemente/quic-go v0.31.1 h1:O8Od7hfioqq0PMYHDyBkxU2aA7iZ2W9pjbrWuja2YR4= -github.com/lucas-clemente/quic-go v0.31.1/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1218,20 +1217,14 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= -github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI= -github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= -github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE= -github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/marten-seemann/webtransport-go v0.4.2 h1:8ZRr9AsPuDiLQwnX2PxGs2t35GPvUaqPJnvk+c2SFSs= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1250,10 +1243,9 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= -github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1383,8 +1375,8 @@ github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wS github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= -github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o= -github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -1494,8 +1486,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= -github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e h1:ZOcivgkkFRnjfoTcGsDq3UQYiBmekwLA+qg0OjyB/ls= github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= +github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= +github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -1547,6 +1540,18 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= +github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= +github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk= +github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI= +github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA= +github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= +github.com/quic-go/webtransport-go v0.5.1 h1:1eVb7WDWCRoaeTtFHpFBJ6WDN1bSrPrRoW6tZgSw0Ow= +github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= @@ -1622,18 +1627,19 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= @@ -1729,8 +1735,9 @@ github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= +github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -1742,15 +1749,17 @@ github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1 github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/warpfork/go-testmark v0.3.0 h1:Q81c4u7hT+BR5kNfNQhEF0VT2pmL7+Kk0wD+ORYl7iA= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= +github.com/warpfork/go-testmark v0.10.0 h1:E86YlUMYfwIacEsQGlnTvjk1IgYkyTGjPhF0RnwTCmw= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= -github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= -github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 h1:WXhVOwj2USAXB5oMDwRl3piOux2XMV9TANaYxXHdkoE= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= +github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= +github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= @@ -1915,8 +1924,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 h1:5sPMf9HJXrvBWIamTw+rTST0bZ3Mho2n1p58M0+W99c= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2445,8 +2454,9 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= pgregory.net/rapid v0.5.3 h1:163N50IHFqr1phZens4FQOdPgfJscR7a562mjQqeo4M= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index fc4c72638d..3a99dd2c7d 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -4,11 +4,11 @@ import ( "context" "fmt" - "github.com/ipfs/go-bitswap" - "github.com/ipfs/go-bitswap/network" "github.com/ipfs/go-datastore" blockstore "github.com/ipfs/go-ipfs-blockstore" exchange "github.com/ipfs/go-ipfs-exchange-interface" + "github.com/ipfs/go-libipfs/bitswap" + "github.com/ipfs/go-libipfs/bitswap/network" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index c2cc19e529..db6d262d09 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -6,11 +6,11 @@ import ( mrand "math/rand" "testing" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" blockstore "github.com/ipfs/go-ipfs-blockstore" + blocks "github.com/ipfs/go-libipfs/blocks" ) var _ blockstore.Blockstore = (*FraudulentBlockstore)(nil) diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 6e665a8a0e..65cf88f9da 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -4,13 +4,13 @@ import ( "context" "testing" - "github.com/ipfs/go-bitswap" - "github.com/ipfs/go-bitswap/network" "github.com/ipfs/go-blockservice" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" blockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/ipfs/go-ipfs-routing/offline" + "github.com/ipfs/go-libipfs/bitswap" + "github.com/ipfs/go-libipfs/bitswap/network" record "github.com/libp2p/go-libp2p-record" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" diff --git a/share/eds/adapters.go b/share/eds/adapters.go index fe498400b3..8d98f092d7 100644 --- a/share/eds/adapters.go +++ b/share/eds/adapters.go @@ -5,9 +5,9 @@ import ( "sync" "github.com/filecoin-project/dagstore" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" + "github.com/ipfs/go-libipfs/blocks" ) var _ blockservice.BlockGetter = (*BlockGetter)(nil) diff --git a/share/eds/adapters_test.go b/share/eds/adapters_test.go index 4e98f4141e..31744aa23e 100644 --- a/share/eds/adapters_test.go +++ b/share/eds/adapters_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + "github.com/ipfs/go-libipfs/blocks" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/share/ipld" diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 68992a94aa..746f364bac 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -6,10 +6,10 @@ import ( "fmt" "github.com/filecoin-project/dagstore" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" bstore "github.com/ipfs/go-ipfs-blockstore" ipld "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" ) var _ bstore.Blockstore = (*blockstore)(nil) diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 9122faee3f..e3b14cb8c5 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -8,10 +8,10 @@ import ( "hash" "math/rand" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" mhcore "github.com/multiformats/go-multihash/core" diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index 6858d3f222..4dea72e472 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -43,7 +43,8 @@ func (p *Parameters) Validate() error { func DefaultParameters() Parameters { return Parameters{ // ValidationTimeout's default value is based on the default daser sampling timeout of 1 minute. - // If a received datahash has not tried to be sampled within these two minutes, the pool will be removed. + // If a received datahash has not tried to be sampled within these two minutes, the pool will be + // removed. ValidationTimeout: 2 * time.Minute, // PeerCooldown's default value is based on initial network tests that showed a ~3.5 second // sync time for large blocks. This value gives our (discovery) peers enough time to sync diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 042d0e6668..5b732174bc 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -40,7 +40,8 @@ type PubSub struct { // NewPubSub creates a libp2p.PubSub wrapper. func NewPubSub(ctx context.Context, h host.Host, suffix string) (*PubSub, error) { - // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same msgId) + // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same + // msgId) pubsub, err := pubsub.NewFloodSub(ctx, h, pubsub.WithSeenMessagesTTL(0)) if err != nil { return nil, err From d47850227673be4db75562f91bd82d4c34884908 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:00:57 +0100 Subject: [PATCH 0392/1008] chore(.github): Add @walldiss to codeowners (#1769) Add @walldiss :) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 782d0dcfab..e7e610edf8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,6 +7,6 @@ # global owners are only requested if there isn't a more specific # codeowner specified below. For this reason, the global codeowners # are often repeated in package-level definitions. -* @renaynay @Wondertan @vgonkivs @distractedm1nd +* @renaynay @Wondertan @vgonkivs @distractedm1nd @walldiss docs/adr @adlerjohn @liamsi From dabe2c454b655f0bfa744e7cb3711d2f867ccdcc Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Feb 2023 13:00:07 +0100 Subject: [PATCH 0393/1008] refactor(modp2p): Introduce String method for Network and use it everywhere (#1765) --- cmd/flags_node.go | 2 +- nodebuilder/fraud/constructors.go | 2 +- nodebuilder/header/module.go | 6 +++--- nodebuilder/p2p/network.go | 5 +++++ nodebuilder/share/module.go | 10 +++++----- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 6cc3919b4e..1dcdc46a27 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -44,7 +44,7 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network if store == "" { tp := NodeType(ctx) var err error - store, err = DefaultNodeStorePath(tp.String(), string(network)) + store, err = DefaultNodeStorePath(tp.String(), network.String()) if err != nil { return ctx, err } diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index 8399b1b477..fc22e6afad 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -28,7 +28,7 @@ func newFraudService(syncerEnabled bool) func( ds datastore.Batching, network p2p.Network, ) (Module, fraud.Service, error) { - pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, syncerEnabled, string(network)) + pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, syncerEnabled, network.String()) lc.Append(fx.Hook{ OnStart: pservice.Start, OnStop: pservice.Stop, diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 743f601f4d..41180a0f5f 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -45,7 +45,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithReadDeadline(cfg.Server.ReadDeadline), p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), p2p.WithRequestTimeout[p2p.ServerParameters](cfg.Server.RequestTimeout), - p2p.WithProtocolSuffix[p2p.ServerParameters](string(network)), + p2p.WithProtocolSuffix[p2p.ServerParameters](network.String()), } }), fx.Provide(newHeaderService), @@ -87,7 +87,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { )), fx.Provide(fx.Annotate( func(ps *pubsub.PubSub, network modp2p.Network) *p2p.Subscriber[*header.ExtendedHeader] { - return p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, string(network)) + return p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, network.String()) }, fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber[*header.ExtendedHeader]) error { return sub.Start(ctx) @@ -122,7 +122,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithDefaultScore(cfg.Client.DefaultScore), p2p.WithRequestTimeout[p2p.ClientParameters](cfg.Client.RequestTimeout), p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), - p2p.WithProtocolSuffix[p2p.ClientParameters](string(network)), + p2p.WithProtocolSuffix[p2p.ClientParameters](network.String()), } }, ), diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index b13c5d7311..fc37451ec9 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -43,6 +43,11 @@ func (n Network) Validate() (Network, error) { return n, nil } +// String returns string representation of the Network. +func (n Network) String() string { + return string(n) +} + // networksList is a strict list of all known long-standing networks. var networksList = map[Network]struct{}{ Arabica: {}, diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 64aeabcdfa..1f963b9987 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -67,7 +67,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { - return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(string(network))) + return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(network.String())) }, fx.OnStart(func(ctx context.Context, server *shrexeds.Server) error { return server.Start(ctx) @@ -83,7 +83,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option getter *getters.IPLDGetter, network modp2p.Network, ) (*shrexnd.Server, error) { - return shrexnd.NewServer(host, store, getter, shrexnd.WithProtocolSuffix(string(network))) + return shrexnd.NewServer(host, store, getter, shrexnd.WithProtocolSuffix(network.String())) }, fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { return server.Start(ctx) @@ -126,12 +126,12 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(fullGetter), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { - return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(string(network))) + return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(network.String())) }, ), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(string(network))) + return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(network.String())) }, ), fx.Provide(fx.Annotate( @@ -148,7 +148,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return shrexsub.NewPubSub( ctx, h, - string(network), + network.String(), ) }, ), From 96fd24b6307a0631029c47234340f43796183ea9 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 22 Feb 2023 16:49:49 +0100 Subject: [PATCH 0394/1008] fix(nodebuilder/share): Only provide necessary components to Bridge (#1778) Bridge does not need: * shrexeds.Client * shrexnd.Client * *shrex.Getter * peer.Manager Bridge and Lights will also spawn EnsurePeers lifecycle from nodebuilder instead (as they don't use peer.Manager yet) --------- Co-authored-by: Wondertan --- nodebuilder/share/constructors.go | 5 +- nodebuilder/share/module.go | 158 +++++++++++++++++------------- 2 files changed, 92 insertions(+), 71 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index bfaf3a7324..89c4edcc87 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -17,6 +17,7 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -36,8 +37,8 @@ func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discove } } -// cacheAvailability wraps either Full or Light availability with a cache for result sampling. -func cacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batching, avail A) share.Availability { +// cacheAvailability wraps light availability with a cache for result sampling. +func cacheAvailability(lc fx.Lifecycle, ds datastore.Batching, avail *light.ShareAvailability) share.Availability { ca := cache.NewShareAvailability(avail, ds) lc.Append(fx.Hook{ OnStop: ca.Close, diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 1f963b9987..b855c6ef76 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -42,6 +42,77 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(newModule), ) + bridgeAndFullComponents := fx.Options( + fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), + fx.Provide(fx.Annotate( + func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { + return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(network.String())) + }, + fx.OnStart(func(ctx context.Context, server *shrexeds.Server) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *shrexeds.Server) error { + return server.Stop(ctx) + }), + )), + fx.Provide(fx.Annotate( + func( + host host.Host, + store *eds.Store, + getter share.Getter, + network modp2p.Network, + ) (*shrexnd.Server, error) { + return shrexnd.NewServer(host, store, getter, shrexnd.WithProtocolSuffix(network.String())) + }, + fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *shrexnd.Server) error { + return server.Stop(ctx) + }), + )), + fx.Provide(fx.Annotate( + func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { + return eds.NewStore(string(path), ds) + }, + fx.OnStart(func(ctx context.Context, store *eds.Store) error { + err := store.Start(ctx) + if err != nil { + return err + } + + return ensureEmptyCARExists(ctx, store) + }), + fx.OnStop(func(ctx context.Context, store *eds.Store) error { + return store.Stop(ctx) + }), + )), + fx.Provide(fx.Annotate( + full.NewShareAvailability, + fx.OnStart(func(ctx context.Context, avail *full.ShareAvailability) error { + return avail.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, avail *full.ShareAvailability) error { + return avail.Stop(ctx) + }), + )), + fx.Provide(func(avail *full.ShareAvailability) share.Availability { + return avail + }), + fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { + return shrexSub.Broadcast + }), + fx.Provide( + func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { + return shrexsub.NewPubSub( + ctx, + h, + network.String(), + ) + }, + ), + ) + switch tp { case node.Light: return fx.Module( @@ -58,71 +129,29 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(fx.Annotate(light.NewShareAvailability)), // cacheAvailability's lifecycle continues to use a fx hook, // since the LC requires a cacheAvailability but the constructor returns a share.Availability - fx.Provide(cacheAvailability[*light.ShareAvailability]), + fx.Provide(cacheAvailability), ) - case node.Bridge, node.Full: + case node.Bridge: return fx.Module( "share", baseComponents, - fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), - fx.Provide(fx.Annotate( - func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { - return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(network.String())) - }, - fx.OnStart(func(ctx context.Context, server *shrexeds.Server) error { - return server.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, server *shrexeds.Server) error { - return server.Stop(ctx) - }), - )), - fx.Provide(fx.Annotate( - func( - host host.Host, - store *eds.Store, - getter *getters.IPLDGetter, - network modp2p.Network, - ) (*shrexnd.Server, error) { - return shrexnd.NewServer(host, store, getter, shrexnd.WithProtocolSuffix(network.String())) - }, - fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { - return server.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, server *shrexnd.Server) error { - return server.Stop(ctx) - }), - )), - fx.Provide(fx.Annotate( - func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { - return eds.NewStore(string(path), ds) - }, - fx.OnStart(func(ctx context.Context, store *eds.Store) error { - err := store.Start(ctx) - if err != nil { - return err - } - - return ensureEmptyCARExists(ctx, store) - }), - fx.OnStop(func(ctx context.Context, store *eds.Store) error { - return store.Stop(ctx) - }), - )), - fx.Provide(fx.Annotate( - full.NewShareAvailability, - fx.OnStart(func(ctx context.Context, avail *full.ShareAvailability) error { - return avail.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, avail *full.ShareAvailability) error { - return avail.Stop(ctx) - }), - )), - // cacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a cacheAvailability but the constructor returns a share.Availability - fx.Provide(cacheAvailability[*full.ShareAvailability]), - fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { - return shrexSub.Broadcast + bridgeAndFullComponents, + fx.Provide(func(store *eds.Store) share.Getter { + return getters.NewStoreGetter(store) + }), + fx.Invoke(func(lc fx.Lifecycle, sub *shrexsub.PubSub) error { + lc.Append(fx.Hook{ + OnStart: sub.Start, + OnStop: sub.Stop, + }) + return nil }), + ) + case node.Full: + return fx.Module( + "share", + baseComponents, + bridgeAndFullComponents, fx.Provide(fullGetter), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { @@ -143,15 +172,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return getter.Stop(ctx) }), )), - fx.Provide( - func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { - return shrexsub.NewPubSub( - ctx, - h, - network.String(), - ) - }, - ), fx.Provide(peers.NewManager), fx.Provide(getters.NewIPLDGetter), ) From 8213e7b99dfc50b459bde375a500ff8013bae8f5 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Feb 2023 17:23:48 +0100 Subject: [PATCH 0395/1008] feat(libhead): introduce peer scoring params for HeaderSub topic (#1766) Extracted out of #1756 --- libs/header/p2p/gossip_score.go | 32 ++++++++++++++++++++++++++++++++ libs/header/p2p/helpers.go | 2 +- libs/header/p2p/subscriber.go | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 libs/header/p2p/gossip_score.go diff --git a/libs/header/p2p/gossip_score.go b/libs/header/p2p/gossip_score.go new file mode 100644 index 0000000000..6deb918626 --- /dev/null +++ b/libs/header/p2p/gossip_score.go @@ -0,0 +1,32 @@ +package p2p + +import ( + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" +) + +// GossibSubScore provides a set of recommended parameters for header GossipSub topic, a.k.a +// HeaderSub. +var GossibSubScore = &pubsub.TopicScoreParams{ + // expected > 1 tx/second + TopicWeight: 0.1, // max cap is 5, single invalid message is -100 + + // 1 tick per second, maxes at 1 hour + TimeInMeshWeight: 0.0002778, // ~1/3600 + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 1, + + // deliveries decay after 1 hour, cap at 100 blocks + FirstMessageDeliveriesWeight: 5, // max value is 500 + FirstMessageDeliveriesDecay: pubsub.ScoreParameterDecay(time.Hour), + FirstMessageDeliveriesCap: 100, // 100 blocks in an hour + + // invalid messages decay after 1 hour + InvalidMessageDeliveriesWeight: -1000, + InvalidMessageDeliveriesDecay: pubsub.ScoreParameterDecay(time.Hour), + + // Mesh Delivery Failure is currently turned off for messages + // This is on purpose as the network is still too small, which results in + // asymmetries and potential unmeshing from negative scores. +} diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go index da75c28f87..10f9d47975 100644 --- a/libs/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -20,7 +20,7 @@ func protocolID(protocolSuffix string) protocol.ID { return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) } -func pubsubTopicID(protocolSuffix string) string { +func PubsubTopicID(protocolSuffix string) string { return fmt.Sprintf("/header-sub/v0.0.1/%s", protocolSuffix) } diff --git a/libs/header/p2p/subscriber.go b/libs/header/p2p/subscriber.go index 464d55828e..2d3ed537d3 100644 --- a/libs/header/p2p/subscriber.go +++ b/libs/header/p2p/subscriber.go @@ -28,7 +28,7 @@ func NewSubscriber[H header.Header]( protocolSuffix string, ) *Subscriber[H] { return &Subscriber[H]{ - pubsubTopicID: pubsubTopicID(protocolSuffix), + pubsubTopicID: PubsubTopicID(protocolSuffix), pubsub: ps, msgID: msgID, } From 7e88f1082512f0d4c2912b671133f0b203b957f8 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Feb 2023 17:45:45 +0100 Subject: [PATCH 0396/1008] feat(libfraud): introduce peer scoring params for FraudSub topic (#1767) Co-authored-by: Viacheslav --- fraud/gossip_score.go | 37 +++++++++++++++++++++++++++++++++++++ fraud/helpers.go | 4 ++-- fraud/registry.go | 5 +++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 fraud/gossip_score.go diff --git a/fraud/gossip_score.go b/fraud/gossip_score.go new file mode 100644 index 0000000000..36c51f1bad --- /dev/null +++ b/fraud/gossip_score.go @@ -0,0 +1,37 @@ +package fraud + +import ( + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" +) + +// GossibSubScore provides a set of recommended parameters for header GossipSub topic, a.k.a +// FraudSub. TODO(@Wondertan): We should disable mesh on publish for this topic to minimize +// chances of censoring FPs by eclipsing nodes producing them. +var GossibSubScore = &pubsub.TopicScoreParams{ + // expected > 1 tx/second + TopicWeight: 0.1, // max cap is 5, single invalid message is -100 + + // 1 tick per second, maxes at 1 hour + TimeInMeshWeight: 0.0002778, // ~1/3600 + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 1, + + // messages in such topics should almost never exist, but very valuable if happens + // so giving max weight + FirstMessageDeliveriesWeight: 50, + FirstMessageDeliveriesDecay: pubsub.ScoreParameterDecay(10 * time.Hour), + // no cap, if the peer is giving us *valid* FPs, just keep increasing peer's score with no limit + // again, this is such a rare case to happen, but if it happens, we should prefer the peer who + // gave it to us + FirstMessageDeliveriesCap: 0, + + // we don't really need this, as we block list peers who give us a bad message, + // so disabled + InvalidMessageDeliveriesWeight: 0, + + // Mesh Delivery Scoring is turned off as well. + // This is on purpose as the network is still too small, which results in + // asymmetries and potential unmeshing from negative scores. +} diff --git a/fraud/helpers.go b/fraud/helpers.go index c9c1c66735..868574207b 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -9,7 +9,7 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" ) -func pubsubTopicID(fraudType, protocolSuffix string) string { +func PubsubTopicID(fraudType, protocolSuffix string) string { return fmt.Sprintf("/fraud-sub/%s/v0.0.1/%s", fraudType, protocolSuffix) } @@ -19,7 +19,7 @@ func protocolID(protocolSuffix string) protocol.ID { func join(p *pubsub.PubSub, proofType ProofType, protocolSuffix string, validate func(context.Context, ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult) (*pubsub.Topic, error) { - topic := pubsubTopicID(string(proofType), protocolSuffix) + topic := PubsubTopicID(string(proofType), protocolSuffix) log.Infow("joining topic", "id", topic) t, err := p.Join(topic) if err != nil { diff --git a/fraud/registry.go b/fraud/registry.go index 5e76ae2b37..f22a7b54fe 100644 --- a/fraud/registry.go +++ b/fraud/registry.go @@ -28,6 +28,11 @@ func Register(p Proof) { } } +// Registered reports a set of registered proof types by Register. +func Registered() []ProofType { + return registeredProofTypes() +} + // registeredProofTypes returns all available proofTypes. func registeredProofTypes() []ProofType { unmarshalersLk.Lock() From 85f1a37fee999f6bf8751f8c480984693cde11ce Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Feb 2023 18:35:09 +0100 Subject: [PATCH 0397/1008] feat(fraud): String for FraudType (#1805) --- fraud/helpers.go | 2 +- fraud/proof.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/fraud/helpers.go b/fraud/helpers.go index 868574207b..6049b784b8 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -19,7 +19,7 @@ func protocolID(protocolSuffix string) protocol.ID { func join(p *pubsub.PubSub, proofType ProofType, protocolSuffix string, validate func(context.Context, ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult) (*pubsub.Topic, error) { - topic := PubsubTopicID(string(proofType), protocolSuffix) + topic := PubsubTopicID(proofType.String(), protocolSuffix) log.Infow("joining topic", "id", topic) t, err := p.Join(topic) if err != nil { diff --git a/fraud/proof.go b/fraud/proof.go index e87e34440c..6bcfa80f27 100644 --- a/fraud/proof.go +++ b/fraud/proof.go @@ -32,8 +32,14 @@ func (e *errNoUnmarshaler) Error() string { return fmt.Sprintf("fraud: unmarshaler for %s type is not registered", e.proofType) } +// ProofType type defines a unique proof type string. type ProofType string +// String returns string representation of ProofType. +func (pt ProofType) String() string { + return string(pt) +} + const ( BadEncoding ProofType = "badencoding" ) From f7c10da451cb73a05c060b096771f7e0c44e404d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Feb 2023 09:39:49 +0100 Subject: [PATCH 0398/1008] fix(core): Don't store eds if its hash already exists in eds.Store (#1786) Resolves #1785 TODO: - [ ] ideally a test that can do duplicate blocks --- core/eds.go | 20 ++++++++++++++++++++ core/exchange.go | 21 ++++++++------------- core/exchange_test.go | 2 +- core/fetcher.go | 2 +- core/listener.go | 12 +++++------- share/eds/store.go | 18 +++++++++++++++--- share/eds/store_test.go | 4 ++-- share/getters/getter_test.go | 2 +- 8 files changed, 53 insertions(+), 28 deletions(-) diff --git a/core/eds.go b/core/eds.go index e996c0111e..b5f9918945 100644 --- a/core/eds.go +++ b/core/eds.go @@ -1,6 +1,11 @@ package core import ( + "context" + "errors" + + "github.com/filecoin-project/dagstore" + "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" @@ -8,6 +13,8 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" ) // extendBlock extends the given block data, returning the resulting @@ -24,3 +31,16 @@ func extendBlock(data types.Data) (*rsmt2d.ExtendedDataSquare, error) { size := utils.SquareSize(len(shares)) return da.ExtendShares(size, appshares.ToBytes(shares)) } + +// storeEDS will only store extended block if it is not empty and doesn't already exist. +func storeEDS(ctx context.Context, hash share.DataHash, eds *rsmt2d.ExtendedDataSquare, store *eds.Store) error { + if eds == nil { + return nil + } + err := store.Put(ctx, hash, eds) + if errors.Is(err, dagstore.ErrShardExists) { + // block with given root already exists, return nil + return nil + } + return err +} diff --git a/core/exchange.go b/core/exchange.go index 71dd3b13d6..29d32216ca 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -99,14 +99,11 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende if !bytes.Equal(hash, eh.Hash()) { return nil, fmt.Errorf("incorrect hash in header: expected %x, got %x", hash, eh.Hash()) } - // store extended block if it is not empty - if eds != nil { - err = ce.store.Put(ctx, eh.DAH.Hash(), eds) - if err != nil { - return nil, err - } + err = storeEDS(ctx, eh.DAH.Hash(), eds, ce.store) + if err != nil { + log.Errorw("storing EDS to eds.Store", "err", err) + return nil, err } - return eh, nil } @@ -136,12 +133,10 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 if err != nil { return nil, err } - // only store extended block if it's not empty - if eds != nil { - err = ce.store.Put(ctx, eh.DAH.Hash(), eds) - if err != nil { - return nil, err - } + err = storeEDS(ctx, eh.DAH.Hash(), eds, ce.store) + if err != nil { + log.Errorw("storing EDS to eds.Store", "err", err) + return nil, err } return eh, nil } diff --git a/core/exchange_test.go b/core/exchange_test.go index aae310d482..928d15a9e5 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -35,7 +35,7 @@ func createCoreFetcher(t *testing.T, cfg *TestConfig) (*BlockFetcher, testnode.C cctx := StartTestNodeWithConfig(t, cfg) // wait for height 2 in order to be able to start submitting txs (this prevents // flakiness with accessing account state) - _, err := cctx.WaitForHeightWithTimeout(2, time.Second) // TODO @renaynay: configure? + _, err := cctx.WaitForHeightWithTimeout(2, time.Second*2) // TODO @renaynay: configure? require.NoError(t, err) return NewBlockFetcher(cctx.Client), cctx } diff --git a/core/fetcher.go b/core/fetcher.go index ab1c45158d..758c23289a 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -13,7 +13,7 @@ import ( const newBlockSubscriber = "NewBlock/Events" var ( - log = logging.Logger("core/fetcher") + log = logging.Logger("core") newBlockEventQuery = types.QueryForEvent(types.EventNewBlock).String() ) diff --git a/core/listener.go b/core/listener.go index 96a37c7f15..da8e1503bf 100644 --- a/core/listener.go +++ b/core/listener.go @@ -108,13 +108,11 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { log.Errorw("listener: making extended header", "err", err) return } - // store block data if not empty - if eds != nil { - err = cl.store.Put(ctx, eh.DAH.Hash(), eds) - if err != nil { - log.Errorw("listener: storing extended header", "err", err) - return - } + // attempt to store block data if not empty + err = storeEDS(ctx, eh.DAH.Hash(), eds, cl.store) + if err != nil { + log.Errorw("listener: storing EDS", "err", err) + return } // notify network of new EDS hash only if core is already synced diff --git a/share/eds/store.go b/share/eds/store.go index 68e7795cfb..46a416df52 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -151,6 +151,15 @@ func (s *Store) gc(ctx context.Context) { // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. // Additionally, the file gets indexed s.t. store.Blockstore can access them. func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) (err error) { + // if root already exists, short-circuit + has, err := s.Has(ctx, root) + if err != nil { + return fmt.Errorf("failed to check if root already exists in index: %w", err) + } + if has { + return dagstore.ErrShardExists + } + ctx, span := tracer.Start(ctx, "store/put", trace.WithAttributes( attribute.String("root", root.String()), attribute.Int("width", int(square.Width())), @@ -372,11 +381,14 @@ func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { key := root.String() info, err := s.dgstr.GetShardInfo(shard.KeyFromString(key)) - if err == dagstore.ErrShardUnknown { + switch err { + case nil: + return true, info.Error + case dagstore.ErrShardUnknown: + return false, info.Error + default: return false, err } - - return true, info.Error } func setupPath(basepath string) error { diff --git a/share/eds/store_test.go b/share/eds/store_test.go index a613ff1572..e59f740f3d 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -36,7 +36,7 @@ func TestEDSStore(t *testing.T) { // shard hasn't been registered yet has, err := edsStore.Has(ctx, dah.Hash()) assert.False(t, has) - assert.Error(t, err, "shard not found") + assert.NoError(t, err) err = edsStore.Put(ctx, dah.Hash(), eds) assert.NoError(t, err) @@ -116,7 +116,7 @@ func TestEDSStore(t *testing.T) { eds, dah := randomEDS(t) ok, err := edsStore.Has(ctx, dah.Hash()) - assert.Error(t, err, "shard not found") + assert.NoError(t, err) assert.False(t, ok) err = edsStore.Put(ctx, dah.Hash(), eds) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 65b480a3aa..9695eeadb5 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -42,7 +42,7 @@ func TestTeeGetter(t *testing.T) { // eds store doesn't have the EDS yet ok, err := edsStore.Has(ctx, dah.Hash()) assert.False(t, ok) - assert.Error(t, err) + assert.NoError(t, err) retrievedEDS, err := tg.GetEDS(ctx, &dah) require.NoError(t, err) From 635125fdf18fc6429580217363fe2b1cc7625ad2 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 23 Feb 2023 09:43:49 +0100 Subject: [PATCH 0399/1008] chore(modp2p): update blocktime to match core (#1773) ## Overview Updates blocktime to match core. I am not yet updating the default daser sync timeout because of the downstream issues it may cause for large (128 sq size) blocks in the near term (before async cascade getter is merged). If IPLD timeout may take up to 28 seconds for sq size 128, then the fallback needs to run for a minimum of 30 seconds. With sync cascade getter the daser SamplingTimeout is split in half, meaning it should stay a minute right now. Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- nodebuilder/header/module.go | 1 - nodebuilder/p2p/network.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 41180a0f5f..6506027e51 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -28,7 +28,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents := fx.Options( fx.Supply(*cfg), fx.Error(cfgErr), - fx.Supply(modp2p.BlockTime), fx.Provide( func(cfg Config) []store.Option { return []store.Option{ diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index fc37451ec9..b184c26871 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -19,7 +19,7 @@ const ( Private Network = "private" // BlockTime is a network block time. // TODO @renaynay @Wondertan (#790) - BlockTime = time.Second * 30 + BlockTime = time.Second * 15 ) // Network is a type definition for DA network run by Celestia Node. From a3c69a713580c379c977bfc35ed75c9e80db4665 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 23 Feb 2023 13:50:52 +0300 Subject: [PATCH 0400/1008] improvement(das): use header from header.sub instead of store for recent jobs (#1802) ## Overview Part of fixing bugs https://github.com/celestiaorg/celestia-node/issues/1779 - For recent jobs, header will be taken from header.Sub instead of header store. - Add timeout for header store call. Will target https://github.com/celestiaorg/celestia-node/issues/1799 --------- Co-authored-by: Hlib Kanunnikov --- das/coordinator.go | 19 +++-- das/coordinator_test.go | 9 ++- das/daser.go | 11 +-- das/state.go | 17 +++-- das/subscriber.go | 2 +- das/worker.go | 163 +++++++++++++++++++++++----------------- 6 files changed, 126 insertions(+), 95 deletions(-) diff --git a/das/coordinator.go b/das/coordinator.go index 83a9d65df8..bb2d82d6f1 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -3,6 +3,7 @@ package das import ( "context" "sync" + "time" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" @@ -12,6 +13,7 @@ import ( // samplingCoordinator runs and coordinates sampling workers and updates current sampling state type samplingCoordinator struct { concurrencyLimit int + samplingTimeout time.Duration getter libhead.Getter[*header.ExtendedHeader] sampleFn sampleFn @@ -22,7 +24,7 @@ type samplingCoordinator struct { // resultCh fans-in sampling results from worker to coordinator resultCh chan result // updHeadCh signals to update network head header height - updHeadCh chan uint64 + updHeadCh chan *header.ExtendedHeader // waitCh signals to block coordinator for external access to state waitCh chan *sync.WaitGroup @@ -46,12 +48,13 @@ func newSamplingCoordinator( ) *samplingCoordinator { return &samplingCoordinator{ concurrencyLimit: params.ConcurrencyLimit, + samplingTimeout: params.SampleTimeout, getter: getter, sampleFn: sample, broadcastFn: broadcast, state: newCoordinatorState(params), resultCh: make(chan result), - updHeadCh: make(chan uint64), + updHeadCh: make(chan *header.ExtendedHeader), waitCh: make(chan *sync.WaitGroup), done: newDone("sampling coordinator"), } @@ -79,9 +82,9 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { select { case head := <-sc.updHeadCh: - if sc.state.isNewHead(head) { + if sc.state.isNewHead(head.Height()) { sc.runWorker(ctx, sc.state.newRecentJob(head)) - sc.state.updateHead(head) + sc.state.updateHead(head.Height()) // run worker without concurrency limit restrictions to reduced delay sc.metrics.observeNewHead(ctx) } @@ -99,21 +102,21 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { // runWorker runs job in separate worker go-routine func (sc *samplingCoordinator) runWorker(ctx context.Context, j job) { - w := newWorker(j) + w := newWorker(j, sc.getter, sc.sampleFn, sc.broadcastFn, sc.metrics) sc.state.putInProgress(j.id, w.getState) // launch worker go-routine sc.workersWg.Add(1) go func() { defer sc.workersWg.Done() - w.run(ctx, sc.getter, sc.sampleFn, sc.broadcastFn, sc.metrics, sc.resultCh) + w.run(ctx, sc.samplingTimeout, sc.resultCh) }() } // listen notifies the coordinator about a new network head received via subscription. -func (sc *samplingCoordinator) listen(ctx context.Context, height uint64) { +func (sc *samplingCoordinator) listen(ctx context.Context, h *header.ExtendedHeader) { select { - case sc.updHeadCh <- height: + case sc.updHeadCh <- h: case <-ctx.Done(): } } diff --git a/das/coordinator_test.go b/das/coordinator_test.go index 016e7e0cf0..e567b6b829 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" @@ -368,7 +369,7 @@ func (m *mockSampler) finalState() checkpoint { return finalState } -func (m *mockSampler) discover(ctx context.Context, newHeight uint64, emit func(ctx context.Context, h uint64)) { +func (m *mockSampler) discover(ctx context.Context, newHeight uint64, emit listenFn) { m.lock.Lock() if newHeight > m.checkpoint.NetworkHead { @@ -379,7 +380,11 @@ func (m *mockSampler) discover(ctx context.Context, newHeight uint64, emit func( } } m.lock.Unlock() - emit(ctx, newHeight) + emit(ctx, &header.ExtendedHeader{ + Commit: &types.Commit{}, + RawHeader: header.RawHeader{Height: int64(newHeight)}, + DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}, + }) } func (m *mockSampler) sampledAmount() int { diff --git a/das/daser.go b/das/daser.go index 47c5960326..a68001b68b 100644 --- a/das/daser.go +++ b/das/daser.go @@ -38,7 +38,7 @@ type DASer struct { running int32 } -type listenFn func(ctx context.Context, height uint64) +type listenFn func(context.Context, *header.ExtendedHeader) type sampleFn func(context.Context, *header.ExtendedHeader) error // NewDASer creates a new DASer. @@ -147,9 +147,6 @@ func (d *DASer) Stop(ctx context.Context) error { } func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { - ctx, cancel := context.WithTimeout(ctx, d.params.SampleTimeout) - defer cancel() - err := d.da.SharesAvailable(ctx, h.DAH) if err != nil { var byzantineErr *byzantine.ErrByzantine @@ -159,15 +156,9 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { if sendErr != nil { log.Errorw("fraud proof propagating failed", "err", sendErr) } - } else if err == context.Canceled { - return err } - - log.Errorw("sampling failed", "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err) return err } - return nil } diff --git a/das/state.go b/das/state.go index 41c24e5849..e45a8ab074 100644 --- a/das/state.go +++ b/das/state.go @@ -3,6 +3,8 @@ package das import ( "context" "sync/atomic" + + "github.com/celestiaorg/celestia-node/header" ) // coordinatorState represents the current state of sampling @@ -72,32 +74,33 @@ func (s *coordinatorState) handleResult(res result) { s.checkDone() } -func (s *coordinatorState) isNewHead(newHead uint64) bool { +func (s *coordinatorState) isNewHead(newHead int64) bool { // seen this header before - if newHead <= s.networkHead { + if uint64(newHead) <= s.networkHead { log.Warnf("received head height: %v, which is lower or the same as previously known: %v", newHead, s.networkHead) return false } return true } -func (s *coordinatorState) updateHead(newHead uint64) { +func (s *coordinatorState) updateHead(newHead int64) { if s.networkHead == s.sampleFrom { log.Infow("found first header, starting sampling") } - s.networkHead = newHead + s.networkHead = uint64(newHead) log.Debugw("updated head", "from_height", s.networkHead, "to_height", newHead) s.checkDone() } -func (s *coordinatorState) newRecentJob(newHead uint64) job { +func (s *coordinatorState) newRecentJob(header *header.ExtendedHeader) job { s.nextJobID++ return job{ id: s.nextJobID, isRecentHeader: true, - From: newHead, - To: newHead, + header: header, + From: uint64(header.Height()), + To: uint64(header.Height()), } } diff --git a/das/subscriber.go b/das/subscriber.go index 121301eb7f..54f93a550e 100644 --- a/das/subscriber.go +++ b/das/subscriber.go @@ -33,6 +33,6 @@ func (s *subscriber) run(ctx context.Context, sub libhead.Subscription[*header.E } log.Infow("new header received via subscription", "height", h.Height()) - emit(ctx, uint64(h.Height())) + emit(ctx, h) } } diff --git a/das/worker.go b/das/worker.go index edb272f6b7..eb41669991 100644 --- a/das/worker.go +++ b/das/worker.go @@ -18,6 +18,11 @@ import ( type worker struct { lock sync.Mutex state workerState + + getter libhead.Getter[*header.ExtendedHeader] + sampleFn sampleFn + broadcast shrexsub.BroadcastFn + metrics *metrics } // workerState contains important information about the state of a @@ -34,87 +39,45 @@ type workerState struct { type job struct { id int isRecentHeader bool + header *header.ExtendedHeader From uint64 To uint64 } -func (w *worker) run( - ctx context.Context, +func newWorker(j job, getter libhead.Getter[*header.ExtendedHeader], sample sampleFn, broadcast shrexsub.BroadcastFn, metrics *metrics, - resultCh chan<- result) { +) worker { + return worker{ + getter: getter, + sampleFn: sample, + broadcast: broadcast, + metrics: metrics, + state: workerState{ + job: j, + Curr: j.From, + failed: make([]uint64, 0), + }, + } +} + +func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- result) { jobStart := time.Now() log.Debugw("start sampling worker", "from", w.state.From, "to", w.state.To) for curr := w.state.From; curr <= w.state.To; curr++ { - startGet := time.Now() - // TODO: get headers in batches - h, err := getter.GetByHeight(ctx, curr) + err := w.sample(ctx, timeout, curr) if err != nil { if errors.Is(err, context.Canceled) { // sampling worker will resume upon restart break } w.setResult(curr, err) - log.Errorw("failed to get header from header store", "height", curr, - "finished (s)", time.Since(startGet)) - continue - } - - metrics.observeGetHeader(ctx, time.Since(startGet)) - - log.Debugw( - "got header from header store", - "height", h.Height(), - "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), - "finished (s)", time.Since(startGet), - ) - - startSample := time.Now() - err = sample(ctx, h) - if errors.Is(err, context.Canceled) { - // sampling worker will resume upon restart - break - } - w.setResult(curr, err) - - metrics.observeSample(ctx, h, time.Since(startSample), err, w.state.isRecentHeader) - - if err != nil { - log.Debugw( - "failed to sampled header", - "height", h.Height(), - "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), - "err", err, - "timeout", time.Since(startSample), - ) continue } - - log.Debugw( - "sampled header", - "height", h.Height(), - "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), - "finished (s)", time.Since(startSample), - ) - - // notify network about availability of new block data (note: only full nodes can notify) - if w.state.isRecentHeader { - err = broadcast(ctx, h.DataHash.Bytes()) - if err != nil { - log.Warn("failed to broadcast availability message", - "height", h.Height(), "hash", h.Hash(), "err", err) - } - } } if w.state.Curr > w.state.From { @@ -138,14 +101,80 @@ func (w *worker) run( } } -func newWorker(j job) worker { - return worker{ - state: workerState{ - job: j, - Curr: j.From, - failed: make([]uint64, 0), - }, +func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint64) error { + h, err := w.getHeader(ctx, height) + if err != nil { + return err } + + start := time.Now() + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + err = w.sampleFn(ctx, h) + w.metrics.observeSample(ctx, h, time.Since(start), err, w.state.isRecentHeader) + if err != nil { + if !errors.Is(err, context.Canceled) { + log.Debugw( + "failed to sample header", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "err", err, + "finished (s)", time.Since(start), + ) + } + return err + } + + log.Debugw( + "sampled header", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "finished (s)", time.Since(start), + ) + + // notify network about availability of new block data (note: only full nodes can notify) + if w.state.isRecentHeader { + err = w.broadcast(ctx, h.DataHash.Bytes()) + if err != nil { + log.Warn("failed to broadcast availability message", + "height", h.Height(), "hash", h.Hash(), "err", err) + } + } + return nil +} + +func (w *worker) getHeader(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + if w.state.header != nil { + return w.state.header, nil + } + + // TODO: get headers in batches + start := time.Now() + h, err := w.getter.GetByHeight(ctx, height) + if err != nil { + if !errors.Is(err, context.Canceled) { + log.Errorw("failed to get header from header store", "height", height, + "finished (s)", time.Since(start)) + } + return nil, err + } + + w.metrics.observeGetHeader(ctx, time.Since(start)) + + log.Debugw( + "got header from header store", + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.Hash(), + "finished (s)", time.Since(start), + ) + return h, nil } func (w *worker) setResult(curr uint64, err error) { From 26e423d1332d82bbba13558a1624200f45715671 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 23 Feb 2023 14:04:13 +0300 Subject: [PATCH 0401/1008] refactor(das|share): Cleanup timeouts through sampling stack (#1791) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1790 and closes https://github.com/celestiaorg/celestia-node/issues/10 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- das/options.go | 10 ++++--- nodebuilder/das/config.go | 6 ++++- nodebuilder/share/constructors.go | 9 +------ share/availability.go | 6 ----- share/availability/full/availability.go | 2 -- share/availability/light/availability.go | 2 -- share/getters/cascade.go | 33 ++++++++++++------------ share/getters/cascade_test.go | 18 ++++++------- share/getters/shrex.go | 29 ++++++++++++++------- share/getters/utils.go | 23 +++++++++++++++++ 10 files changed, 81 insertions(+), 57 deletions(-) diff --git a/das/options.go b/das/options.go index 93bc09e2f3..665fc5a7d6 100644 --- a/das/options.go +++ b/das/options.go @@ -36,7 +36,9 @@ type Parameters struct { SampleFrom uint64 // SampleTimeout is a maximum amount time sampling of single block may take until it will be - // canceled + // canceled. High ConcurrencyLimit value may increase sampling time due to node resources being + // divided between parallel workers. SampleTimeout should be adjusted proportionally to + // ConcurrencyLimit. SampleTimeout time.Duration } @@ -44,12 +46,14 @@ type Parameters struct { func DefaultParameters() Parameters { // TODO(@derrandz): parameters needs performance testing on real network to define optimal values // (#1261) + concurrencyLimit := 16 return Parameters{ SamplingRange: 100, - ConcurrencyLimit: 16, + ConcurrencyLimit: concurrencyLimit, BackgroundStoreInterval: 10 * time.Minute, SampleFrom: 1, - SampleTimeout: time.Minute, + // SampleTimeout = block time * max amount of catchup workers + SampleTimeout: 15 * time.Second * time.Duration(concurrencyLimit), } } diff --git a/nodebuilder/das/config.go b/nodebuilder/das/config.go index b892635d96..c76dcb2650 100644 --- a/nodebuilder/das/config.go +++ b/nodebuilder/das/config.go @@ -2,8 +2,10 @@ package das import ( "fmt" + "time" "github.com/celestiaorg/celestia-node/das" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // Config contains configuration parameters for the DASer (or DASing process) @@ -16,7 +18,9 @@ type Config das.Parameters // // TODO(@derrandz): Address #1261 func DefaultConfig() Config { - return Config(das.DefaultParameters()) + cfg := das.DefaultParameters() + cfg.SampleTimeout = modp2p.BlockTime * time.Duration(cfg.ConcurrencyLimit) + return Config(cfg) } // Validate performs basic validation of the config. diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 89c4edcc87..ba32c97b84 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -3,7 +3,6 @@ package share import ( "context" "errors" - "time" "github.com/filecoin-project/dagstore" "github.com/ipfs/go-datastore" @@ -13,7 +12,6 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" @@ -69,19 +67,14 @@ func fullGetter( cfg Config, ) share.Getter { var cascade []share.Getter - // based on the default value of das.SampleTimeout - timeout := time.Minute cascade = append(cascade, getters.NewStoreGetter(store)) if cfg.UseShareExchange { - // if we are using share exchange, we split the timeout between the two getters - // once async cascadegetter is implemented, we can remove this - timeout /= 2 cascade = append(cascade, shrexGetter) } cascade = append(cascade, ipldGetter) return getters.NewTeeGetter( - getters.NewCascadeGetter(cascade, timeout), + getters.NewCascadeGetter(cascade), store, ) } diff --git a/share/availability.go b/share/availability.go index 045de350e8..f02a9f55f2 100644 --- a/share/availability.go +++ b/share/availability.go @@ -3,7 +3,6 @@ package share import ( "context" "errors" - "time" "github.com/celestiaorg/celestia-app/pkg/da" ) @@ -11,11 +10,6 @@ import ( // ErrNotAvailable is returned whenever DA sampling fails. var ErrNotAvailable = errors.New("share: data not available") -// AvailabilityTimeout specifies timeout for DA validation during which data have to be found on -// the network, otherwise ErrNotAvailable is fired. -// TODO: https://github.com/celestiaorg/celestia-node/issues/10 -const AvailabilityTimeout = 20 * time.Minute - // Root represents root commitment to multiple Shares. // In practice, it is a commitment to all the Data in a square. type Root = da.DataAvailabilityHeader diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index deaa86dec3..b6ede4d6ec 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -48,8 +48,6 @@ func (fa *ShareAvailability) Stop(context.Context) error { // SharesAvailable reconstructs the data committed to the given Root by requesting // enough Shares from the network. func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { - ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) - defer cancel() // we assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. if err := root.ValidateBasic(); err != nil { diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 64d971ffc5..57f044526c 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -46,8 +46,6 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo // indicate to the share.Getter that a blockservice session should be created. This // functionality is optional and must be supported by the used share.Getter. ctx = getters.WithSession(ctx) - ctx, cancel := context.WithTimeout(ctx, share.AvailabilityTimeout) - defer cancel() log.Debugw("starting sampling session", "root", dah.Hash()) errs := make(chan error, len(samples)) diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 8981fc37e1..3709f8f278 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -3,17 +3,15 @@ package getters import ( "context" "errors" - "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.uber.org/multierr" - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" ) var _ share.Getter = (*CascadeGetter)(nil) @@ -23,16 +21,13 @@ var _ share.Getter = (*CascadeGetter)(nil) // // See cascade func for details on cascading. type CascadeGetter struct { - interval time.Duration - getters []share.Getter } // NewCascadeGetter instantiates a new CascadeGetter from given share.Getters with given interval. -func NewCascadeGetter(getters []share.Getter, interval time.Duration) *CascadeGetter { +func NewCascadeGetter(getters []share.Getter) *CascadeGetter { return &CascadeGetter{ - interval: interval, - getters: getters, + getters: getters, } } @@ -49,7 +44,7 @@ func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, co return get.GetShare(ctx, root, row, col) } - return cascadeGetters(ctx, cg.getters, get, cg.interval) + return cascadeGetters(ctx, cg.getters, get) } // GetEDS gets a full EDS from any of registered share.Getters in cascading order. @@ -63,7 +58,7 @@ func (cg *CascadeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d. return get.GetEDS(ctx, root) } - return cascadeGetters(ctx, cg.getters, get, cg.interval) + return cascadeGetters(ctx, cg.getters, get) } // GetSharesByNamespace gets NamespacedShares from any of registered share.Getters in cascading @@ -83,7 +78,7 @@ func (cg *CascadeGetter) GetSharesByNamespace( return get.GetSharesByNamespace(ctx, root, id) } - return cascadeGetters(ctx, cg.getters, get, cg.interval) + return cascadeGetters(ctx, cg.getters, get) } // cascade implements a cascading retry algorithm for getting a value from multiple sources. @@ -98,14 +93,17 @@ func cascadeGetters[V any]( ctx context.Context, getters []share.Getter, get func(context.Context, share.Getter) (V, error), - interval time.Duration, ) (V, error) { var ( zero V err error ) + + if len(getters) == 0 { + return zero, errors.New("no getters provided") + } + ctx, span := tracer.Start(ctx, "cascade", trace.WithAttributes( - attribute.String("interval", interval.String()), attribute.Int("total-getters", len(getters)), )) defer func() { @@ -117,8 +115,11 @@ func cascadeGetters[V any]( for i, getter := range getters { log.Debugf("cascade: launching getter #%d", i) span.AddEvent("getter launched", trace.WithAttributes(attribute.Int("getter_idx", i))) - ctx, cancel := context.WithTimeout(ctx, interval) - val, getErr := get(ctx, getter) + + // we split the timeout between left getters + // once async cascadegetter is implemented, we can remove this + getCtx, cancel := ctxWithSplitTimeout(ctx, len(getters)-i, 0) + val, getErr := get(getCtx, getter) cancel() if getErr == nil { return val, nil diff --git a/share/getters/cascade_test.go b/share/getters/cascade_test.go index f124e6ffe8..f2688773be 100644 --- a/share/getters/cascade_test.go +++ b/share/getters/cascade_test.go @@ -4,16 +4,14 @@ import ( "context" "errors" "testing" - "time" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "go.uber.org/multierr" - "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/mocks" + "github.com/celestiaorg/rsmt2d" ) func TestCascadeGetter(t *testing.T) { @@ -27,7 +25,7 @@ func TestCascadeGetter(t *testing.T) { getters[i], roots[i] = TestGetter(t) } - getter := NewCascadeGetter(getters, time.Millisecond) + getter := NewCascadeGetter(getters) t.Run("GetShare", func(t *testing.T) { for _, r := range roots { sh, err := getter.GetShare(ctx, r, 0, 0) @@ -69,38 +67,38 @@ func TestCascade(t *testing.T) { t.Run("SuccessFirst", func(t *testing.T) { getters := []share.Getter{successGetter, timeoutGetter, immediateFailGetter} - _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + _, err := cascadeGetters(ctx, getters, get) assert.NoError(t, err) }) t.Run("SuccessSecond", func(t *testing.T) { getters := []share.Getter{immediateFailGetter, successGetter} - _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + _, err := cascadeGetters(ctx, getters, get) assert.NoError(t, err) }) t.Run("SuccessSecondAfterFirst", func(t *testing.T) { getters := []share.Getter{timeoutGetter, successGetter} - _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + _, err := cascadeGetters(ctx, getters, get) assert.NoError(t, err) }) t.Run("SuccessAfterMultipleTimeouts", func(t *testing.T) { getters := []share.Getter{timeoutGetter, immediateFailGetter, timeoutGetter, timeoutGetter, successGetter} - _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + _, err := cascadeGetters(ctx, getters, get) assert.NoError(t, err) }) t.Run("Error", func(t *testing.T) { getters := []share.Getter{immediateFailGetter, timeoutGetter, immediateFailGetter} - _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + _, err := cascadeGetters(ctx, getters, get) assert.Error(t, err) assert.Len(t, multierr.Errors(err), 3) }) t.Run("Single", func(t *testing.T) { getters := []share.Getter{successGetter} - _, err := cascadeGetters(ctx, getters, get, time.Millisecond*10) + _, err := cascadeGetters(ctx, getters, get) assert.NoError(t, err) }) } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 2c5a9d36d8..6302c243f7 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -18,23 +18,34 @@ import ( var _ share.Getter = (*ShrexGetter)(nil) -const defaultMaxRequestDuration = time.Second * 10 +const ( + // defaultMinRequestTimeout value is set according to observed time taken by healthy peer to + // serve getEDS request for block size 256 + defaultMinRequestTimeout = time.Second * 10 + defaultMinAttemptsCount = 3 +) // ShrexGetter is a share.Getter that uses the shrex/eds and shrex/nd protocol to retrieve shares. type ShrexGetter struct { edsClient *shrexeds.Client ndClient *shrexnd.Client - peerManager *peers.Manager - maxRequestDuration time.Duration + peerManager *peers.Manager + + // minRequestTimeout limits minimal timeout given to single peer by getter for serving the request. + minRequestTimeout time.Duration + // minAttemptsCount will be used to split request timeout into multiple attempts. It will allow to + // attempt multiple peers in scope of one request before context timeout is reached + minAttemptsCount int } func NewShrexGetter(edsClient *shrexeds.Client, ndClient *shrexnd.Client, peerManager *peers.Manager) *ShrexGetter { return &ShrexGetter{ - edsClient: edsClient, - ndClient: ndClient, - peerManager: peerManager, - maxRequestDuration: defaultMaxRequestDuration, + edsClient: edsClient, + ndClient: ndClient, + peerManager: peerManager, + minRequestTimeout: defaultMinRequestTimeout, + minAttemptsCount: defaultMinAttemptsCount, } } @@ -63,7 +74,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex return nil, fmt.Errorf("getter/shrex: %w", err) } - reqCtx, cancel := context.WithTimeout(ctx, sg.maxRequestDuration) + reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount, sg.minRequestTimeout) eds, err := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) cancel() switch err { @@ -97,7 +108,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( return nil, fmt.Errorf("getter/shrex: %w", err) } - reqCtx, cancel := context.WithTimeout(ctx, sg.maxRequestDuration) + reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount, sg.minRequestTimeout) nd, err := sg.ndClient.RequestND(reqCtx, root, id, peer) cancel() switch err { diff --git a/share/getters/utils.go b/share/getters/utils.go index 7771f68017..e848e06165 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -3,6 +3,7 @@ package getters import ( "context" "fmt" + "time" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" @@ -91,3 +92,25 @@ func verifyNIDSize(nID namespace.ID) error { } return nil } + +// ctxWithSplitTimeout will split timeout stored in context by splitFactor and return the result if +// it is greater than minTimeout. minTimeout == 0 will be ignored +func ctxWithSplitTimeout( + ctx context.Context, + splitFactor int, + minTimeout time.Duration, +) (context.Context, context.CancelFunc) { + deadline, ok := ctx.Deadline() + if !ok { + if minTimeout == 0 { + return context.WithCancel(ctx) + } + return context.WithTimeout(ctx, minTimeout) + } + + timeout := time.Until(deadline) / time.Duration(splitFactor) + if minTimeout == 0 || timeout > minTimeout { + return context.WithTimeout(ctx, timeout) + } + return context.WithTimeout(ctx, minTimeout) +} From cc0d85855b3aade992b027b87ac62471ef3664d8 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 23 Feb 2023 13:43:34 +0100 Subject: [PATCH 0402/1008] feat(modp2p): add whitelist for ResourceManager, as eclipse protection (#1809) --- nodebuilder/p2p/module.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 5b3e9f9d52..2d2ebc604d 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -6,6 +6,7 @@ import ( "github.com/libp2p/go-libp2p/core/metrics" "github.com/libp2p/go-libp2p/core/network" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" + ma "github.com/multiformats/go-multiaddr" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -54,10 +55,27 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "p2p", baseComponents, fx.Provide(blockstoreFromDatastore), - fx.Provide(func() (network.ResourceManager, error) { + fx.Provide(func(bootstrappers Bootstrappers) (network.ResourceManager, error) { limits := rcmgr.DefaultLimits libp2p.SetDefaultServiceLimits(&limits) - return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(limits.AutoScale())) + + mutual, err := cfg.mutualPeers() + if err != nil { + return nil, err + } + + allowlist := make([]ma.Multiaddr, 0, len(bootstrappers)+len(mutual)) + for _, b := range bootstrappers { + allowlist = append(allowlist, b.Addrs...) + } + for _, m := range mutual { + allowlist = append(allowlist, m.Addrs...) + } + + return rcmgr.NewResourceManager( + rcmgr.NewFixedLimiter(limits.AutoScale()), + rcmgr.WithAllowlistedMultiaddrs(allowlist), + ) }), ) default: From 0f46aa6fe4fb617877ab2ee9b03d6ad61a31762f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Feb 2023 14:04:58 +0100 Subject: [PATCH 0403/1008] feat(nodebuilder/header)!: Implement remainder of `header.Module` (#1748) This PR implements several additional endpoints for the `header.Module` to bring it closer to its specification (defined in the public API ADR). This PR is breaking as it includes renaming for an endpoint: `header.Head` is now `header.LocalHead` Two notes: * this PR does **not** include the `header.Subscribe` endpoint as gomock does not yet support generics (so we have to wait for that in order to be able to test the module properly) * Some additional decisions need to be made around the header API (for example, deprecating `GetRangeByHeight` and putting `GetVerifiedRangeByHeight` in its place) that will follow in later PRs --- api/gateway/header.go | 2 +- api/gateway/share.go | 5 +- api/rpc_test.go | 11 ++-- docs/adr/adr-009-public-api.md | 26 +++++----- libs/header/sync/sync.go | 4 +- libs/header/sync/sync_test.go | 4 +- nodebuilder/header/header.go | 69 +++++++++++++++++++++---- nodebuilder/header/mocks/api.go | 91 +++++++++++++++++++++++++++------ nodebuilder/header/service.go | 26 ++++++++-- 9 files changed, 186 insertions(+), 52 deletions(-) diff --git a/api/gateway/header.go b/api/gateway/header.go index 108519b924..50fb058213 100644 --- a/api/gateway/header.go +++ b/api/gateway/header.go @@ -20,7 +20,7 @@ var ( ) func (h *Handler) handleHeadRequest(w http.ResponseWriter, r *http.Request) { - head, err := h.header.Head(r.Context()) + head, err := h.header.LocalHead(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, headEndpoint, err) return diff --git a/api/gateway/share.go b/api/gateway/share.go index 4326168d86..caa1714eef 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -10,9 +10,10 @@ import ( "github.com/gorilla/mux" "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/nmt/namespace" ) const ( @@ -99,7 +100,7 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID ) switch height { case 0: - header, err = h.header.Head(ctx) + header, err = h.header.LocalHead(ctx) default: header, err = h.header.GetByHeight(ctx, height) } diff --git a/api/rpc_test.go b/api/rpc_test.go index 218f31d2de..581e382ac7 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -15,6 +15,8 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/fx" + headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" + "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/api/rpc/perms" @@ -26,7 +28,6 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraudMock "github.com/celestiaorg/celestia-node/nodebuilder/fraud/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/header" - headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/node" nodeMock "github.com/celestiaorg/celestia-node/nodebuilder/node/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -163,8 +164,8 @@ func TestAuthedRPC(t *testing.T) { require.NoError(t, err) // 1. Test method with public permissions - server.Header.EXPECT().Head(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) - got, err := rpcClient.Header.Head(ctx) + server.Header.EXPECT().NetworkHead(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) + got, err := rpcClient.Header.NetworkHead(ctx) require.NoError(t, err) require.NotNil(t, got) @@ -249,8 +250,8 @@ func TestPublicClient(t *testing.T) { require.NoError(t, err) // 1. Test method with public permissions - server.Header.EXPECT().Head(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) - got, err := rpcClient.Header.Head(ctx) + server.Header.EXPECT().NetworkHead(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) + got, err := rpcClient.Header.NetworkHead(ctx) require.NoError(t, err) require.NotNil(t, got) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 7670647140..98d7d279b5 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -91,25 +91,27 @@ microservice architectures. ```go type HeaderModule interface { -// Head returns the node's local head (tip of the chain of the header store). -Head(ctx context.Context) (*header.ExtendedHeader, error) -// Get returns the header of the given hash from the node's header store. -Get(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) +// LocalHead returns the node's local head (tip of the chain of the header store). +LocalHead(ctx context.Context) (*header.ExtendedHeader, error) +// GetByHash returns the header of the given hash from the node's header store. +GetByHash(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) // GetByHeight returns the header of the given height from the node's header store. // If the header of the given height is not yet available, the request will hang // until it becomes available in the node's header store. GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) -// GetRangeByHeight returns the given range [from:to) of ExtendedHeaders. -GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) -// Subscribe creates long-living Subscription for validated ExtendedHeaders. -// Multiple Subscriptions can be created. -Subscribe() (Subscription, error) +// GetVerifiedRangeByHeight returns the given range [from:to) of ExtendedHeaders +// from the node's header store and verifies that the returned headers are +// adjacent to each other. +GetVerifiedRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) +// Subscribe creates long-living Subscription for newly validated +// ExtendedHeaders. Multiple Subscriptions can be created. +Subscribe(context.Context) (Subscription, error) // SyncState returns the current state of the header Syncer. -SyncState() sync.State +SyncState(context.Context) (sync.State, error) // SyncWait blocks until the header Syncer is synced to network head. SyncWait(ctx context.Context) error -// SyncHead provides the Syncer's view of the current network head. -SyncHead(ctx context.Context) (*header.ExtendedHeader, error) +// NetworkHead provides the Syncer's view of the current network head. +NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) } ``` diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 8e2bc3444a..9c040a9a49 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -107,8 +107,8 @@ func (s *Syncer[H]) Stop(context.Context) error { return nil } -// WaitSync blocks until ongoing sync is done. -func (s *Syncer[H]) WaitSync(ctx context.Context) error { +// SyncWait blocks until ongoing sync is done. +func (s *Syncer[H]) SyncWait(ctx context.Context) error { state := s.State() if state.Finished() { return nil diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index 0f6526b577..f78f709568 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -43,7 +43,7 @@ func TestSyncSimpleRequestingHead(t *testing.T) { require.NoError(t, err) time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing - err = syncer.WaitSync(ctx) + err = syncer.SyncWait(ctx) require.NoError(t, err) exp, err := remoteStore.Head(ctx) @@ -91,7 +91,7 @@ func TestSyncCatchUp(t *testing.T) { assert.Equal(t, pubsub.ValidationAccept, res) time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing - err = syncer.WaitSync(ctx) + err = syncer.SyncWait(ctx) require.NoError(t, err) exp, err := remoteStore.Head(ctx) require.NoError(t, err) diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 082285ddb3..36f06b8cf0 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -4,6 +4,8 @@ import ( "context" "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/sync" ) // Module exposes the functionality needed for querying headers from the network. @@ -11,33 +13,80 @@ import ( // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { + // LocalHead returns the ExtendedHeader of the chain head. + LocalHead(context.Context) (*header.ExtendedHeader, error) + + // GetByHash returns the header of the given hash from the node's header store. + GetByHash(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) + // GetVerifiedRangeByHeight returns the given range [from:to) of ExtendedHeaders + // from the node's header store and verifies that the returned headers are + // adjacent to each other. + GetVerifiedRangeByHeight( + ctx context.Context, + from *header.ExtendedHeader, + to uint64, + ) ([]*header.ExtendedHeader, error) // GetByHeight returns the ExtendedHeader at the given height, blocking // until header has been processed by the store or context deadline is exceeded. GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) - // Head returns the ExtendedHeader of the chain head. - Head(context.Context) (*header.ExtendedHeader, error) - // IsSyncing returns the status of sync - IsSyncing(context.Context) bool + + // SyncState returns the current state of the header Syncer. + SyncState(context.Context) (sync.State, error) + // SyncWait blocks until the header Syncer is synced to network head. + SyncWait(ctx context.Context) error + // NetworkHead provides the Syncer's view of the current network head. + NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) } // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { Internal struct { + LocalHead func(context.Context) (*header.ExtendedHeader, error) `perm:"read"` + GetByHash func( + ctx context.Context, + hash libhead.Hash, + ) (*header.ExtendedHeader, error) `perm:"public"` + GetVerifiedRangeByHeight func( + context.Context, + *header.ExtendedHeader, + uint64, + ) ([]*header.ExtendedHeader, error) `perm:"public"` GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` - Head func(context.Context) (*header.ExtendedHeader, error) `perm:"public"` - IsSyncing func(context.Context) bool `perm:"public"` + SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` + SyncWait func(ctx context.Context) error `perm:"read"` + NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"public"` } } +func (api *API) GetByHash(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) { + return api.Internal.GetByHash(ctx, hash) +} + +func (api *API) GetVerifiedRangeByHeight( + ctx context.Context, + from *header.ExtendedHeader, + to uint64, +) ([]*header.ExtendedHeader, error) { + return api.Internal.GetVerifiedRangeByHeight(ctx, from, to) +} + func (api *API) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { return api.Internal.GetByHeight(ctx, u) } -func (api *API) Head(ctx context.Context) (*header.ExtendedHeader, error) { - return api.Internal.Head(ctx) +func (api *API) LocalHead(ctx context.Context) (*header.ExtendedHeader, error) { + return api.Internal.LocalHead(ctx) +} + +func (api *API) SyncState(ctx context.Context) (sync.State, error) { + return api.Internal.SyncState(ctx) +} + +func (api *API) SyncWait(ctx context.Context) error { + return api.Internal.SyncWait(ctx) } -func (api *API) IsSyncing(ctx context.Context) bool { - return api.Internal.IsSyncing(ctx) +func (api *API) NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) { + return api.Internal.NetworkHead(ctx) } diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 683370078f..70ece76954 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" + header0 "github.com/celestiaorg/celestia-node/libs/header" + sync "github.com/celestiaorg/celestia-node/libs/header/sync" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -36,6 +37,21 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { return m.recorder } +// GetByHash mocks base method. +func (m *MockModule) GetByHash(arg0 context.Context, arg1 header0.Hash) (*header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetByHash", arg0, arg1) + ret0, _ := ret[0].(*header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetByHash indicates an expected call of GetByHash. +func (mr *MockModuleMockRecorder) GetByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByHash", reflect.TypeOf((*MockModule)(nil).GetByHash), arg0, arg1) +} + // GetByHeight mocks base method. func (m *MockModule) GetByHeight(arg0 context.Context, arg1 uint64) (*header.ExtendedHeader, error) { m.ctrl.T.Helper() @@ -51,31 +67,76 @@ func (mr *MockModuleMockRecorder) GetByHeight(arg0, arg1 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByHeight", reflect.TypeOf((*MockModule)(nil).GetByHeight), arg0, arg1) } -// Head mocks base method. -func (m *MockModule) Head(arg0 context.Context) (*header.ExtendedHeader, error) { +// GetVerifiedRangeByHeight mocks base method. +func (m *MockModule) GetVerifiedRangeByHeight(arg0 context.Context, arg1 *header.ExtendedHeader, arg2 uint64) ([]*header.ExtendedHeader, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Head", arg0) + ret := m.ctrl.Call(m, "GetVerifiedRangeByHeight", arg0, arg1, arg2) + ret0, _ := ret[0].([]*header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVerifiedRangeByHeight indicates an expected call of GetVerifiedRangeByHeight. +func (mr *MockModuleMockRecorder) GetVerifiedRangeByHeight(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerifiedRangeByHeight", reflect.TypeOf((*MockModule)(nil).GetVerifiedRangeByHeight), arg0, arg1, arg2) +} + +// LocalHead mocks base method. +func (m *MockModule) LocalHead(arg0 context.Context) (*header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalHead", arg0) + ret0, _ := ret[0].(*header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LocalHead indicates an expected call of LocalHead. +func (mr *MockModuleMockRecorder) LocalHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalHead", reflect.TypeOf((*MockModule)(nil).LocalHead), arg0) +} + +// NetworkHead mocks base method. +func (m *MockModule) NetworkHead(arg0 context.Context) (*header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetworkHead", arg0) ret0, _ := ret[0].(*header.ExtendedHeader) ret1, _ := ret[1].(error) return ret0, ret1 } -// Head indicates an expected call of Head. -func (mr *MockModuleMockRecorder) Head(arg0 interface{}) *gomock.Call { +// NetworkHead indicates an expected call of NetworkHead. +func (mr *MockModuleMockRecorder) NetworkHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetworkHead", reflect.TypeOf((*MockModule)(nil).NetworkHead), arg0) +} + +// SyncState mocks base method. +func (m *MockModule) SyncState(arg0 context.Context) (sync.State, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncState", arg0) + ret0, _ := ret[0].(sync.State) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncState indicates an expected call of SyncState. +func (mr *MockModuleMockRecorder) SyncState(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockModule)(nil).Head), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncState", reflect.TypeOf((*MockModule)(nil).SyncState), arg0) } -// IsSyncing mocks base method. -func (m *MockModule) IsSyncing(arg0 context.Context) bool { +// SyncWait mocks base method. +func (m *MockModule) SyncWait(arg0 context.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsSyncing", arg0) - ret0, _ := ret[0].(bool) + ret := m.ctrl.Call(m, "SyncWait", arg0) + ret0, _ := ret[0].(error) return ret0 } -// IsSyncing indicates an expected call of IsSyncing. -func (mr *MockModuleMockRecorder) IsSyncing(arg0 interface{}) *gomock.Call { +// SyncWait indicates an expected call of SyncWait. +func (mr *MockModuleMockRecorder) SyncWait(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSyncing", reflect.TypeOf((*MockModule)(nil).IsSyncing), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWait", reflect.TypeOf((*MockModule)(nil).SyncWait), arg0) } diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 90c78bec46..35e4d290c1 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -37,14 +37,34 @@ func newHeaderService( } } +func (s *Service) GetByHash(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) { + return s.store.Get(ctx, hash) +} + +func (s *Service) GetVerifiedRangeByHeight( + ctx context.Context, + from *header.ExtendedHeader, + to uint64, +) ([]*header.ExtendedHeader, error) { + return s.store.GetVerifiedRange(ctx, from, to) +} + func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { return s.store.GetByHeight(ctx, height) } -func (s *Service) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (s *Service) LocalHead(ctx context.Context) (*header.ExtendedHeader, error) { return s.store.Head(ctx) } -func (s *Service) IsSyncing(context.Context) bool { - return !s.syncer.State().Finished() +func (s *Service) SyncState(context.Context) (sync.State, error) { + return s.syncer.State(), nil +} + +func (s *Service) SyncWait(ctx context.Context) error { + return s.syncer.SyncWait(ctx) +} + +func (s *Service) NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) { + return s.syncer.Head(ctx) } From 9307fbc13e5fcbdd8daf343db75cd1f3eb70b531 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 23 Feb 2023 15:14:16 +0200 Subject: [PATCH 0404/1008] feat!(p2p): put networkID as the first sub-string in protocolID (#1808) ## Overview * Changed protocolID representation to `networkID/domain/*sub-domain*/version`, where `sub-domain` is optional. * Remove dependency on node builder in libs --- core/listener_test.go | 7 ++++--- das/daser_test.go | 3 +-- fraud/helpers.go | 12 ++++++------ fraud/service.go | 26 +++++++++++++------------- libs/header/p2p/exchange.go | 2 +- libs/header/p2p/exchange_test.go | 23 +++++++++++------------ libs/header/p2p/helpers.go | 8 ++++---- libs/header/p2p/options.go | 18 +++++++++--------- libs/header/p2p/server.go | 2 +- libs/header/p2p/server_test.go | 3 +-- libs/header/p2p/subscriber.go | 4 ++-- libs/header/p2p/subscription_test.go | 5 ++--- nodebuilder/header/constructors.go | 2 +- nodebuilder/header/module.go | 4 ++-- nodebuilder/share/module.go | 8 ++++---- share/p2p/shrexeds/client.go | 2 +- share/p2p/shrexeds/options.go | 16 ++++++++-------- share/p2p/shrexeds/server.go | 5 +++-- share/p2p/shrexnd/client.go | 2 +- share/p2p/shrexnd/options.go | 16 ++++++++-------- share/p2p/shrexnd/server.go | 5 +++-- share/p2p/shrexsub/pubsub.go | 13 ++++++------- 22 files changed, 92 insertions(+), 94 deletions(-) diff --git a/core/listener_test.go b/core/listener_test.go index 87b32ab995..c38509a75c 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -15,11 +15,12 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" - network "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) +const networkID = "private" + // TestListener tests the lifecycle of the core listener. func TestListener(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) @@ -27,7 +28,7 @@ func TestListener(t *testing.T) { // create mocknet with two pubsub endpoints ps0, ps1 := createMocknetWithTwoPubsubEndpoints(ctx, t) - subscriber := p2p.NewSubscriber[*header.ExtendedHeader](ps1, header.MsgID, string(network.Private)) + subscriber := p2p.NewSubscriber[*header.ExtendedHeader](ps1, header.MsgID, networkID) err := subscriber.AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult { return pubsub.ValidationAccept }) @@ -163,7 +164,7 @@ func createListener( edsSub *shrexsub.PubSub, store *eds.Store, ) *Listener { - p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, string(network.Private)) + p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, networkID) err := p2pSub.Start(ctx) require.NoError(t, err) t.Cleanup(func() { diff --git a/das/daser_test.go b/das/daser_test.go index b410b0891e..cba84a0663 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -20,7 +20,6 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/headertest" libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" @@ -156,7 +155,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) // create fraud service and break one header - f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, string(p2p.Private)) + f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, "private") require.NoError(t, f.Start(ctx)) mockGet.headers[1], _ = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() diff --git a/fraud/helpers.go b/fraud/helpers.go index 6049b784b8..04d4dd55e0 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -9,17 +9,17 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" ) -func PubsubTopicID(fraudType, protocolSuffix string) string { - return fmt.Sprintf("/fraud-sub/%s/v0.0.1/%s", fraudType, protocolSuffix) +func PubsubTopicID(fraudType, networkID string) string { + return fmt.Sprintf("%s/fraud-sub/%s/v0.0.1", networkID, fraudType) } -func protocolID(protocolSuffix string) protocol.ID { - return protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", protocolSuffix)) +func protocolID(networkID string) protocol.ID { + return protocol.ID(fmt.Sprintf("%s/fraud/v0.0.1", networkID)) } -func join(p *pubsub.PubSub, proofType ProofType, protocolSuffix string, +func join(p *pubsub.PubSub, proofType ProofType, networkID string, validate func(context.Context, ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult) (*pubsub.Topic, error) { - topic := PubsubTopicID(proofType.String(), protocolSuffix) + topic := PubsubTopicID(proofType.String(), networkID) log.Infow("joining topic", "id", topic) t, err := p.Join(topic) if err != nil { diff --git a/fraud/service.go b/fraud/service.go index 6194edf693..1e68a80f25 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -24,7 +24,7 @@ const fraudRequests = 5 // ProofService is responsible for validating and propagating Fraud Proofs. // It implements the Service interface. type ProofService struct { - protocolSuffix string + networkID string ctx context.Context cancel context.CancelFunc @@ -49,24 +49,24 @@ func NewProofService( getter headerFetcher, ds datastore.Datastore, syncerEnabled bool, - protocolSuffix string, + networkID string, ) *ProofService { return &ProofService{ - pubsub: p, - host: host, - getter: getter, - topics: make(map[ProofType]*pubsub.Topic), - stores: make(map[ProofType]datastore.Datastore), - ds: ds, - protocolSuffix: protocolSuffix, - syncerEnabled: syncerEnabled, + pubsub: p, + host: host, + getter: getter, + topics: make(map[ProofType]*pubsub.Topic), + stores: make(map[ProofType]datastore.Datastore), + ds: ds, + networkID: networkID, + syncerEnabled: syncerEnabled, } } // registerProofTopics registers proofTypes as pubsub topics to be joined. func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { for _, proofType := range proofTypes { - t, err := join(f.pubsub, proofType, f.protocolSuffix, f.processIncoming) + t, err := join(f.pubsub, proofType, f.networkID, f.processIncoming) if err != nil { return err } @@ -84,7 +84,7 @@ func (f *ProofService) Start(context.Context) error { if err := f.registerProofTopics(registeredProofTypes()...); err != nil { return err } - id := protocolID(f.protocolSuffix) + id := protocolID(f.networkID) log.Infow("starting fraud proof service", "protocol ID", id) f.host.SetStreamHandler(id, f.handleFraudMessageRequest) @@ -96,7 +96,7 @@ func (f *ProofService) Start(context.Context) error { // Stop removes the stream handler and cancels the underlying ProofService func (f *ProofService) Stop(context.Context) error { - f.host.RemoveStreamHandler(protocolID(f.protocolSuffix)) + f.host.RemoveStreamHandler(protocolID(f.networkID)) f.cancel() return nil } diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index 50459534c0..567a5600c7 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -53,7 +53,7 @@ func NewExchange[H header.Header]( return &Exchange[H]{ host: host, - protocolID: protocolID(params.protocolSuffix), + protocolID: protocolID(params.networkID), trustedPeers: peers, peerTracker: newPeerTracker( host, diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index b816d65f1e..550394c7c0 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -24,10 +24,9 @@ import ( headerMock "github.com/celestiaorg/celestia-node/libs/header/mocks" p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" "github.com/celestiaorg/celestia-node/libs/header/test" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -var privateProtocolID = protocolID(string(p2p.Private)) +const networkID = "private" func TestExchange_RequestHead(t *testing.T) { hosts := createMocknet(t, 2) @@ -99,7 +98,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { require.NoError(t, err) // create new exchange exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{}, connGater, - WithProtocolSuffix[ClientParameters](string(p2p.Private)), + WithNetworkID[ClientParameters](networkID), ) require.NoError(t, err) exchange.Params.MaxHeadersPerRequest = 10 @@ -111,7 +110,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { servers[index], err = NewExchangeServer[*test.DummyHeader]( hosts[index], store, - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) servers[index].Start(context.Background()) //nolint:errcheck @@ -147,7 +146,7 @@ func TestExchange_RequestHeadersFromAnotherPeer(t *testing.T) { // create one more server(with more headers in the store) serverSideEx, err := NewExchangeServer[*test.DummyHeader]( hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) require.NoError(t, serverSideEx.Start(context.Background())) @@ -179,7 +178,7 @@ func TestExchange_RequestByHash(t *testing.T) { serv, err := NewExchangeServer[*test.DummyHeader]( host, store, - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) err = serv.Start(ctx) @@ -189,7 +188,7 @@ func TestExchange_RequestByHash(t *testing.T) { }) // start a new stream via Peer to see if Host can handle inbound requests - stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, privateProtocolID) + stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, protocolID(networkID)) require.NoError(t, err) // create request for a header at a random height reqHeight := store.HeadHeight - 2 @@ -291,7 +290,7 @@ func TestExchange_RequestByHashFails(t *testing.T) { host, peer := net.Hosts()[0], net.Hosts()[1] serv, err := NewExchangeServer[*test.DummyHeader]( host, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 0), - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) err = serv.Start(ctx) @@ -300,7 +299,7 @@ func TestExchange_RequestByHashFails(t *testing.T) { serv.Stop(context.Background()) //nolint:errcheck }) - stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, privateProtocolID) + stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, protocolID(networkID)) require.NoError(t, err) req := &p2p_pb.HeaderRequest{ Data: &p2p_pb.HeaderRequest_Hash{Hash: []byte("dummy_hash")}, @@ -343,7 +342,7 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { // create one more server(with more headers in the store) serverSideEx, err := NewExchangeServer[*test.DummyHeader]( host2, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) // change store implementation @@ -376,7 +375,7 @@ func createP2PExAndServer( ) (*Exchange[*test.DummyHeader], *headerMock.MockStore[*test.DummyHeader]) { store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) serverSideEx, err := NewExchangeServer[*test.DummyHeader](tpeer, store, - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) err = serverSideEx.Start(context.Background()) @@ -384,7 +383,7 @@ func createP2PExAndServer( connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) ex, err := NewExchange[*test.DummyHeader](host, []peer.ID{tpeer.ID()}, connGater, - WithProtocolSuffix[ClientParameters](string(p2p.Private)), + WithNetworkID[ClientParameters](networkID), ) require.NoError(t, err) require.NoError(t, ex.Start(context.Background())) diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go index 10f9d47975..2f7aa96b07 100644 --- a/libs/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -16,12 +16,12 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) -func protocolID(protocolSuffix string) protocol.ID { - return protocol.ID(fmt.Sprintf("/header-ex/v0.0.3/%s", protocolSuffix)) +func protocolID(networkID string) protocol.ID { + return protocol.ID(fmt.Sprintf("%s/header-ex/v0.0.3", networkID)) } -func PubsubTopicID(protocolSuffix string) string { - return fmt.Sprintf("/header-sub/v0.0.1/%s", protocolSuffix) +func PubsubTopicID(networkID string) string { + return fmt.Sprintf("%s/header-sub/v0.0.1", networkID) } // sendMessage opens the stream to the given peers and sends HeaderRequest to fetch diff --git a/libs/header/p2p/options.go b/libs/header/p2p/options.go index 66bcebee34..75b62208ab 100644 --- a/libs/header/p2p/options.go +++ b/libs/header/p2p/options.go @@ -26,9 +26,9 @@ type ServerParameters struct { // RequestTimeout defines a timeout after which the session will try to re-request headers // from another peer. RequestTimeout time.Duration - // protocolSuffix is a network suffix that will be used to create a protocol.ID + // networkID is a network that will be used to create a protocol.ID // Is empty by default - protocolSuffix string + networkID string } // DefaultServerParameters returns the default params to configure the store. @@ -106,15 +106,15 @@ func WithRequestTimeout[T parameters](duration time.Duration) Option[T] { } } -// WithProtocolSuffix is a functional option that configures the -// `protocolSuffix` parameter. -func WithProtocolSuffix[T parameters](protocolSuffix string) Option[T] { +// WithNetworkID is a functional option that configures the +// `networkID` parameter. +func WithNetworkID[T parameters](networkID string) Option[T] { return func(p *T) { switch t := any(p).(type) { case *ClientParameters: - t.protocolSuffix = protocolSuffix + t.networkID = networkID case *ServerParameters: - t.protocolSuffix = protocolSuffix + t.networkID = networkID } } } @@ -140,8 +140,8 @@ type ClientParameters struct { TrustedPeersRequestTimeout time.Duration // MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker. MaxPeerTrackerSize int - // protocolSuffix is a network suffix that will be used to create a protocol.ID - protocolSuffix string + // networkID is a network that will be used to create a protocol.ID + networkID string } // DefaultClientParameters returns the default params to configure the store. diff --git a/libs/header/p2p/server.go b/libs/header/p2p/server.go index e9d54aaa16..29a980dd06 100644 --- a/libs/header/p2p/server.go +++ b/libs/header/p2p/server.go @@ -53,7 +53,7 @@ func NewExchangeServer[H header.Header]( } return &ExchangeServer[H]{ - protocolID: protocolID(params.protocolSuffix), + protocolID: protocolID(params.networkID), host: host, store: store, Params: params, diff --git a/libs/header/p2p/server_test.go b/libs/header/p2p/server_test.go index 9b073a2dea..cb123b36d5 100644 --- a/libs/header/p2p/server_test.go +++ b/libs/header/p2p/server_test.go @@ -9,7 +9,6 @@ import ( "github.com/celestiaorg/celestia-node/libs/header/store" "github.com/celestiaorg/celestia-node/libs/header/test" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) func TestExchangeServer_handleRequestTimeout(t *testing.T) { @@ -19,7 +18,7 @@ func TestExchangeServer_handleRequestTimeout(t *testing.T) { server, err := NewExchangeServer[*test.DummyHeader]( peer[0], s, - WithProtocolSuffix[ServerParameters](string(p2p.Private)), + WithNetworkID[ServerParameters](networkID), ) require.NoError(t, err) err = server.Start(context.Background()) diff --git a/libs/header/p2p/subscriber.go b/libs/header/p2p/subscriber.go index 2d3ed537d3..1917df94b4 100644 --- a/libs/header/p2p/subscriber.go +++ b/libs/header/p2p/subscriber.go @@ -25,10 +25,10 @@ type Subscriber[H header.Header] struct { func NewSubscriber[H header.Header]( ps *pubsub.PubSub, msgID pubsub.MsgIdFunction, - protocolSuffix string, + networkID string, ) *Subscriber[H] { return &Subscriber[H]{ - pubsubTopicID: PubsubTopicID(protocolSuffix), + pubsubTopicID: PubsubTopicID(networkID), pubsub: ps, msgID: msgID, } diff --git a/libs/header/p2p/subscription_test.go b/libs/header/p2p/subscription_test.go index 331988a2a8..c95fe7bf5a 100644 --- a/libs/header/p2p/subscription_test.go +++ b/libs/header/p2p/subscription_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/libs/header/test" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) // TestSubscriber tests the header Module's implementation of Subscriber. @@ -31,7 +30,7 @@ func TestSubscriber(t *testing.T) { require.NoError(t, err) // create sub-service lifecycles for header service 1 - p2pSub1 := NewSubscriber[*test.DummyHeader](pubsub1, pubsub.DefaultMsgIdFn, string(p2p.Private)) + p2pSub1 := NewSubscriber[*test.DummyHeader](pubsub1, pubsub.DefaultMsgIdFn, networkID) err = p2pSub1.Start(context.Background()) require.NoError(t, err) @@ -41,7 +40,7 @@ func TestSubscriber(t *testing.T) { require.NoError(t, err) // create sub-service lifecycles for header service 2 - p2pSub2 := NewSubscriber[*test.DummyHeader](pubsub2, pubsub.DefaultMsgIdFn, string(p2p.Private)) + p2pSub2 := NewSubscriber[*test.DummyHeader](pubsub2, pubsub.DefaultMsgIdFn, networkID) err = p2pSub2.Start(context.Background()) require.NoError(t, err) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index fb3c0f5223..f24c7d6d62 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -17,7 +17,7 @@ import ( modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -// newP2PServer constructs a new ExchangeServer using the given Network as a protocolID suffix. +// newP2PServer constructs a new ExchangeServer using the given Network as a protocolID prefix. func newP2PServer( host host.Host, store libhead.Store[*header.ExtendedHeader], diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 6506027e51..c25ccad882 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -44,7 +44,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithReadDeadline(cfg.Server.ReadDeadline), p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), p2p.WithRequestTimeout[p2p.ServerParameters](cfg.Server.RequestTimeout), - p2p.WithProtocolSuffix[p2p.ServerParameters](network.String()), + p2p.WithNetworkID[p2p.ServerParameters](network.String()), } }), fx.Provide(newHeaderService), @@ -121,7 +121,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithDefaultScore(cfg.Client.DefaultScore), p2p.WithRequestTimeout[p2p.ClientParameters](cfg.Client.RequestTimeout), p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), - p2p.WithProtocolSuffix[p2p.ClientParameters](network.String()), + p2p.WithNetworkID[p2p.ClientParameters](network.String()), } }, ), diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index b855c6ef76..d5f0aec065 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -46,7 +46,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { - return shrexeds.NewServer(host, store, shrexeds.WithProtocolSuffix(network.String())) + return shrexeds.NewServer(host, store, shrexeds.WithNetworkID(network.String())) }, fx.OnStart(func(ctx context.Context, server *shrexeds.Server) error { return server.Start(ctx) @@ -62,7 +62,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option getter share.Getter, network modp2p.Network, ) (*shrexnd.Server, error) { - return shrexnd.NewServer(host, store, getter, shrexnd.WithProtocolSuffix(network.String())) + return shrexnd.NewServer(host, store, getter, shrexnd.WithNetworkID(network.String())) }, fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { return server.Start(ctx) @@ -155,12 +155,12 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(fullGetter), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { - return shrexnd.NewClient(host, shrexnd.WithProtocolSuffix(network.String())) + return shrexnd.NewClient(host, shrexnd.WithNetworkID(network.String())) }, ), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - return shrexeds.NewClient(host, shrexeds.WithProtocolSuffix(network.String())) + return shrexeds.NewClient(host, shrexeds.WithNetworkID(network.String())) }, ), fx.Provide(fx.Annotate( diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index ffc59c82c0..82c7ba9b63 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -40,7 +40,7 @@ func NewClient(host host.Host, opts ...Option) (*Client, error) { return &Client{ host: host, - protocolID: protocolID(params.protocolSuffix), + protocolID: protocolID(params.networkID), }, nil } diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go index 4239ec5bea..3575a36253 100644 --- a/share/p2p/shrexeds/options.go +++ b/share/p2p/shrexeds/options.go @@ -8,7 +8,7 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" ) -const protocolPrefix = "/shrex/eds/v0.0.1/" +const protocolString = "/shrex/eds/v0.0.1" var log = logging.Logger("shrex-eds") @@ -30,9 +30,9 @@ type Parameters struct { // BufferSize defines the size of the buffer used for writing an ODS over the stream. BufferSize uint64 - // protocolSuffix is appended to the protocolID and represents the network the protocol is + // networkID is prepended to the protocolID and represents the network the protocol is // running on. - protocolSuffix string + networkID string // concurrencyLimit is the maximum number of concurrently handled streams concurrencyLimit int @@ -69,10 +69,10 @@ func (p *Parameters) Validate() error { return nil } -// WithProtocolSuffix is a functional option that configures the `protocolSuffix` parameter -func WithProtocolSuffix(protocolSuffix string) Option { +// WithNetworkID is a functional option that configures the `networkID` parameter +func WithNetworkID(networkID string) Option { return func(parameters *Parameters) { - parameters.protocolSuffix = protocolSuffix + parameters.networkID = networkID } } @@ -83,6 +83,6 @@ func WithConcurrencyLimit(concurrencyLimit int) Option { } } -func protocolID(protocolSuffix string) protocol.ID { - return protocol.ID(fmt.Sprintf("%s%s", protocolPrefix, protocolSuffix)) +func protocolID(networkID string) protocol.ID { + return protocol.ID(fmt.Sprintf("%s%s", networkID, protocolString)) } diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index 60086517b0..7d3e7d00b0 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -10,11 +10,12 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p" p2p_pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" - "github.com/celestiaorg/go-libp2p-messenger/serde" ) // Server is responsible for serving ODSs for blocksync over the ShrEx/EDS protocol. @@ -44,7 +45,7 @@ func NewServer(host host.Host, store *eds.Store, opts ...Option) (*Server, error return &Server{ host: host, store: store, - protocolID: protocolID(params.protocolSuffix), + protocolID: protocolID(params.networkID), params: params, }, nil } diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index d284dba0b3..69f48bbbf0 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -45,7 +45,7 @@ func NewClient(host host.Host, opts ...Option) (*Client, error) { return &Client{ host: host, - protocolID: protocolID(params.protocolSuffix), + protocolID: protocolID(params.networkID), params: params, }, nil } diff --git a/share/p2p/shrexnd/options.go b/share/p2p/shrexnd/options.go index 44f79e13f2..fcc7d11b2d 100644 --- a/share/p2p/shrexnd/options.go +++ b/share/p2p/shrexnd/options.go @@ -8,7 +8,7 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" ) -const protocolPrefix = "/shrex/nd/0.0.1/" +const protocolString = "/shrex/nd/0.0.1" var log = logging.Logger("shrex/nd") @@ -27,9 +27,9 @@ type Parameters struct { // serveTimeout defines the deadline for serving request. serveTimeout time.Duration - // protocolSuffix is appended to the protocolID and represents the network the protocol is + // networkID is prepended to the protocolID and represents the network the protocol is // running on. - protocolSuffix string + networkID string // concurrencyLimit is the maximum number of concurrently handled streams concurrencyLimit int @@ -62,10 +62,10 @@ func (p *Parameters) Validate() error { return nil } -// WithProtocolSuffix is a functional option that configures the `protocolSuffix` parameter -func WithProtocolSuffix(protocolSuffix string) Option { +// WithNetworkID is a functional option that configures the `networkID` parameter +func WithNetworkID(networkID string) Option { return func(parameters *Parameters) { - parameters.protocolSuffix = protocolSuffix + parameters.networkID = networkID } } @@ -97,6 +97,6 @@ func WithConcurrencyLimit(concurrencyLimit int) Option { } } -func protocolID(protocolSuffix string) protocol.ID { - return protocol.ID(fmt.Sprintf("%s%s", protocolPrefix, protocolSuffix)) +func protocolID(networkID string) protocol.ID { + return protocol.ID(fmt.Sprintf("%s%s", networkID, protocolString)) } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 54b74ddc1e..224db4faf1 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -10,12 +10,13 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" "github.com/minio/sha256-simd" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" - "github.com/celestiaorg/go-libp2p-messenger/serde" ) // Server implements server side of shrex/nd protocol to serve namespaced share to remote @@ -47,7 +48,7 @@ func NewServer(host host.Host, store *eds.Store, getter share.Getter, opts ...Op store: store, host: host, params: params, - protocolID: protocolID(params.protocolSuffix), + protocolID: protocolID(params.networkID), } return srv, nil diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 5b732174bc..a15a1f1b75 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -15,9 +15,9 @@ import ( var log = logging.Logger("shrex-sub") -// pubsubTopic hardcodes the name of the EDS floodsub topic with the provided suffix. -func pubsubTopic(suffix string) string { - return fmt.Sprintf("/eds-sub/v0.0.1/%s", suffix) +// pubsubTopic hardcodes the name of the EDS floodsub topic with the provided networkID. +func pubsubTopicID(networkID string) string { + return fmt.Sprintf("%s/eds-sub/v0.0.1", networkID) } // Validator is an injectable func and governs EDS notification or DataHash validity. @@ -39,16 +39,15 @@ type PubSub struct { } // NewPubSub creates a libp2p.PubSub wrapper. -func NewPubSub(ctx context.Context, h host.Host, suffix string) (*PubSub, error) { - // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same - // msgId) +func NewPubSub(ctx context.Context, h host.Host, networkID string) (*PubSub, error) { + // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same msgId) pubsub, err := pubsub.NewFloodSub(ctx, h, pubsub.WithSeenMessagesTTL(0)) if err != nil { return nil, err } return &PubSub{ pubSub: pubsub, - pubsubTopic: pubsubTopic(suffix), + pubsubTopic: pubsubTopicID(networkID), }, nil } From 51dc98f0a16280646e0952a10c17f9d90f57956d Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 23 Feb 2023 14:34:54 +0100 Subject: [PATCH 0405/1008] feat(modp2p): tune GossipSub (#1756) Closes #1330 Ideally, we should test how our network performance under these new parameters before merging. However, the parameters here do not substantially diverge from parameters from Prysm and Filecoin, so it is safe to go without it. Some refs: gist.github.com/blacktemplar/5c1862cb3f0e32a1a7fb0b25e79e6e2c ethereum-optimism/optimism@develop/op-node/p2p/gossip.go prysmaticlabs/prysm@develop/beacon-chain/p2p/gossip_scoring_params.go filecoin-project/lotus@master/node/modules/lp2p/pubsub.go --- fraud/gossip_score.go | 7 +- libs/header/p2p/gossip_score.go | 2 +- nodebuilder/header/mocks/api.go | 3 +- nodebuilder/p2p/pubsub.go | 130 +++++++++++++++++++++++++++++--- share/p2p/shrexsub/pubsub.go | 3 +- 5 files changed, 130 insertions(+), 15 deletions(-) diff --git a/fraud/gossip_score.go b/fraud/gossip_score.go index 36c51f1bad..2a97948d9b 100644 --- a/fraud/gossip_score.go +++ b/fraud/gossip_score.go @@ -1,6 +1,7 @@ package fraud import ( + "math" "time" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -9,7 +10,9 @@ import ( // GossibSubScore provides a set of recommended parameters for header GossipSub topic, a.k.a // FraudSub. TODO(@Wondertan): We should disable mesh on publish for this topic to minimize // chances of censoring FPs by eclipsing nodes producing them. -var GossibSubScore = &pubsub.TopicScoreParams{ +var GossibSubScore = pubsub.TopicScoreParams{ + SkipAtomicValidation: true, + // expected > 1 tx/second TopicWeight: 0.1, // max cap is 5, single invalid message is -100 @@ -25,7 +28,7 @@ var GossibSubScore = &pubsub.TopicScoreParams{ // no cap, if the peer is giving us *valid* FPs, just keep increasing peer's score with no limit // again, this is such a rare case to happen, but if it happens, we should prefer the peer who // gave it to us - FirstMessageDeliveriesCap: 0, + FirstMessageDeliveriesCap: math.MaxFloat64, // we don't really need this, as we block list peers who give us a bad message, // so disabled diff --git a/libs/header/p2p/gossip_score.go b/libs/header/p2p/gossip_score.go index 6deb918626..b00ac02451 100644 --- a/libs/header/p2p/gossip_score.go +++ b/libs/header/p2p/gossip_score.go @@ -8,7 +8,7 @@ import ( // GossibSubScore provides a set of recommended parameters for header GossipSub topic, a.k.a // HeaderSub. -var GossibSubScore = &pubsub.TopicScoreParams{ +var GossibSubScore = pubsub.TopicScoreParams{ // expected > 1 tx/second TopicWeight: 0.1, // max cap is 5, single invalid message is -100 diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 70ece76954..b236367726 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/celestia-node/libs/header" sync "github.com/celestiaorg/celestia-node/libs/header/sync" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 256848ebcf..084a81d677 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -2,15 +2,37 @@ package p2p import ( "context" + "time" pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" hst "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" "go.uber.org/fx" "golang.org/x/crypto/blake2b" + + "github.com/celestiaorg/celestia-node/fraud" + headp2p "github.com/celestiaorg/celestia-node/libs/header/p2p" ) +func init() { + // TODO(@Wondertan): Requires deeper analysis + // configure larger overlay parameters + // the default ones are pretty conservative + pubsub.GossipSubD = 8 + pubsub.GossipSubDscore = 6 + pubsub.GossipSubDout = 3 + pubsub.GossipSubDlo = 6 + pubsub.GossipSubDhi = 12 + pubsub.GossipSubDlazy = 12 + + pubsub.GossipSubIWantFollowupTime = 5 * time.Second + pubsub.GossipSubHistoryLength = 10 // cache msgs longer + // MutualPeers will wait for 30secs before connecting + pubsub.GossipSubDirectConnectInitialDelay = 30 * time.Second +} + // pubSub provides a constructor for PubSub protocol with GossipSub routing. func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { fpeers, err := cfg.mutualPeers() @@ -18,15 +40,33 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { return nil, err } - // TODO(@Wondertan) for PubSub options: - // * Hash-based MsgId function. - // * Validate default peer scoring params for our use-case. - // * Strict subscription filter - // * For different network types(mainnet/testnet/devnet) we should have different network topic - // names. * Hardcode positive score for bootstrap peers - // * Bootstrappers should only gossip and PX - // * Peers should trust boostrappers, so peerscore for them should always be high. + isBootstrapper := cfg.Bootstrapper + if isBootstrapper { + // Turn off the mesh in bootstrappers as per: + // + // https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators + pubsub.GossipSubD = 0 + pubsub.GossipSubDscore = 0 + pubsub.GossipSubDlo = 0 + pubsub.GossipSubDhi = 0 + pubsub.GossipSubDout = 0 + pubsub.GossipSubDlazy = 64 + pubsub.GossipSubGossipFactor = 0.25 + pubsub.GossipSubPruneBackoff = 5 * time.Minute + } + + // TODO(@Wondertan) Validate and improve default peer scoring params + // Сurrent parameters are based on: + // * https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#peer-scoring + // * lotus + // * prysm + topicScores := topicScoreParams(params.Network) + peerScores := peerScoreParams(isBootstrapper, params.Bootstrappers) + peerScores.Topics = topicScores + scoreThresholds := peerScoreThresholds() + opts := []pubsub.Option{ + pubsub.WithPeerScore(peerScores, scoreThresholds), pubsub.WithPeerExchange(cfg.PeerExchange || cfg.Bootstrapper), pubsub.WithDirectPeers(fpeers), pubsub.WithMessageIdFn(hashMsgID), @@ -50,6 +90,76 @@ func hashMsgID(m *pubsub_pb.Message) string { type pubSubParams struct { fx.In - Ctx context.Context - Host hst.Host + Ctx context.Context + Host hst.Host + Bootstrappers Bootstrappers + Network Network +} + +func topicScoreParams(network Network) map[string]*pubsub.TopicScoreParams { + mp := map[string]*pubsub.TopicScoreParams{ + headp2p.PubsubTopicID(network.String()): &headp2p.GossibSubScore, + } + + for _, pt := range fraud.Registered() { + mp[fraud.PubsubTopicID(pt.String(), network.String())] = &fraud.GossibSubScore + } + + return mp +} + +func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers) *pubsub.PeerScoreParams { + bootstrapperSet := map[peer.ID]struct{}{} + for _, b := range bootstrappers { + bootstrapperSet[b.ID] = struct{}{} + } + + // See + // https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#the-score-function + return &pubsub.PeerScoreParams{ + AppSpecificScore: func(p peer.ID) float64 { + // return a heavy positive score for bootstrappers so that we don't unilaterally prune + // them and accept PX from them + _, ok := bootstrapperSet[p] + if ok && !isBootstrapper { + return 2500 + } + + // TODO(@Wondertan): + // Plug the application specific score to the node itself in order + // to provide feedback to the pubsub system based on observed behavior + return 0 + }, + AppSpecificWeight: 1, + + // This sets the IP colocation threshold to 5 peers before we apply penalties + // The aim is to protect the PubSub from naive bots collocated on the same machine/datacenter + IPColocationFactorThreshold: 10, + IPColocationFactorWeight: -100, + // TODO(@Wondertan): Make this configurable, e.g. we might have Testground bots for testing purposes + IPColocationFactorWhitelist: nil, + + BehaviourPenaltyThreshold: 6, + BehaviourPenaltyWeight: -10, + BehaviourPenaltyDecay: pubsub.ScoreParameterDecay(time.Hour), + + // Scores should not only grow and this defines a decay function equal for each peer + DecayInterval: pubsub.DefaultDecayInterval, + DecayToZero: pubsub.DefaultDecayToZero, + + // this retains *non-positive* scores for 6 hours + RetainScore: 6 * time.Hour, + } +} + +func peerScoreThresholds() *pubsub.PeerScoreThresholds { + // + //https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#overview-of-new-parameters + return &pubsub.PeerScoreThresholds{ + GossipThreshold: -1000, + PublishThreshold: -2000, + GraylistThreshold: -8000, + AcceptPXThreshold: 1000, + OpportunisticGraftThreshold: 5, + } } diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index a15a1f1b75..d68be42d9b 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -40,7 +40,8 @@ type PubSub struct { // NewPubSub creates a libp2p.PubSub wrapper. func NewPubSub(ctx context.Context, h host.Host, networkID string) (*PubSub, error) { - // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same msgId) + // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same + // msgId) pubsub, err := pubsub.NewFloodSub(ctx, h, pubsub.WithSeenMessagesTTL(0)) if err != nil { return nil, err From d61313718bc2661affc1ae283ca6509f1ed7d79b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Feb 2023 14:04:47 +0000 Subject: [PATCH 0406/1008] chore(deps): bump github.com/ipld/go-car from 0.5.0 to 0.6.0 (#1691) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d07382b2c8..2e6ec6b421 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/ipfs/go-libipfs v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.9.0 - github.com/ipld/go-car v0.5.0 + github.com/ipld/go-car v0.6.0 github.com/libp2p/go-libp2p v0.25.1 github.com/libp2p/go-libp2p-kad-dht v0.21.0 github.com/libp2p/go-libp2p-pubsub v0.9.1 @@ -55,13 +55,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 - go.opentelemetry.io/otel v1.11.2 + go.opentelemetry.io/otel v1.12.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 go.opentelemetry.io/otel/metric v0.34.0 go.opentelemetry.io/otel/sdk v1.11.2 go.opentelemetry.io/otel/sdk/metric v0.34.0 - go.opentelemetry.io/otel/trace v1.11.2 + go.opentelemetry.io/otel/trace v1.12.0 go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 diff --git a/go.sum b/go.sum index 1b3f58f3b0..db5a3b809d 100644 --- a/go.sum +++ b/go.sum @@ -881,8 +881,8 @@ github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68 github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= github.com/ipfs/go-unixfsnode v1.5.1 h1:JcR3t5C2nM1V7PMzhJ/Qmo19NkoFIKweDSZyDx+CjkI= -github.com/ipld/go-car v0.5.0 h1:kcCEa3CvYMs0iE5BzD5sV7O2EwMiCIp3uF8tA6APQT8= -github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= +github.com/ipld/go-car v0.6.0 h1:d5QrGLnHAxiNLHor+DKGrLdqnM0dQJh2whfSXRDq6J0= +github.com/ipld/go-car v0.6.0/go.mod h1:tBrW1XZ3L2XipLxA69RnTVGW3rve6VX4TbaTYkq8aEA= github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk= github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E= @@ -1806,8 +1806,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0= -go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= +go.opentelemetry.io/otel v1.12.0 h1:IgfC7kqQrRccIKuB7Cl+SRUmsKbEwSGPr0Eu+/ht1SQ= +go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 h1:kpskzLZ60cJ48SJ4uxWa6waBL+4kSV6nVK8rP+QM8Wg= @@ -1828,8 +1828,8 @@ go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rq go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0= -go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= +go.opentelemetry.io/otel/trace v1.12.0 h1:p28in++7Kd0r2d8gSt931O57fdjUyWxkVbESuILAeUc= +go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= From 927fd1af0e37292d8c5bb7edf9e34b4ae90cc756 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 23 Feb 2023 16:41:39 +0200 Subject: [PATCH 0407/1008] fix: fix protocolID representation (#1810) --- fraud/helpers.go | 4 ++-- libs/header/p2p/helpers.go | 4 ++-- share/p2p/shrexeds/options.go | 2 +- share/p2p/shrexnd/options.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fraud/helpers.go b/fraud/helpers.go index 04d4dd55e0..3ba55a1c86 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -10,11 +10,11 @@ import ( ) func PubsubTopicID(fraudType, networkID string) string { - return fmt.Sprintf("%s/fraud-sub/%s/v0.0.1", networkID, fraudType) + return fmt.Sprintf("/%s/fraud-sub/%s/v0.0.1", networkID, fraudType) } func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("%s/fraud/v0.0.1", networkID)) + return protocol.ID(fmt.Sprintf("/%s/fraud/v0.0.1", networkID)) } func join(p *pubsub.PubSub, proofType ProofType, networkID string, diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go index 2f7aa96b07..32b3ee8008 100644 --- a/libs/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -17,11 +17,11 @@ import ( ) func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("%s/header-ex/v0.0.3", networkID)) + return protocol.ID(fmt.Sprintf("/%s/header-ex/v0.0.3", networkID)) } func PubsubTopicID(networkID string) string { - return fmt.Sprintf("%s/header-sub/v0.0.1", networkID) + return fmt.Sprintf("/%s/header-sub/v0.0.1", networkID) } // sendMessage opens the stream to the given peers and sends HeaderRequest to fetch diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go index 3575a36253..d0a305302c 100644 --- a/share/p2p/shrexeds/options.go +++ b/share/p2p/shrexeds/options.go @@ -84,5 +84,5 @@ func WithConcurrencyLimit(concurrencyLimit int) Option { } func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("%s%s", networkID, protocolString)) + return protocol.ID(fmt.Sprintf("/%s%s", networkID, protocolString)) } diff --git a/share/p2p/shrexnd/options.go b/share/p2p/shrexnd/options.go index fcc7d11b2d..4548e9bdfd 100644 --- a/share/p2p/shrexnd/options.go +++ b/share/p2p/shrexnd/options.go @@ -98,5 +98,5 @@ func WithConcurrencyLimit(concurrencyLimit int) Option { } func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("%s%s", networkID, protocolString)) + return protocol.ID(fmt.Sprintf("/%s%s", networkID, protocolString)) } From c0b17f274e6734c367d58ab7ad72060e5eae2f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:01:21 +0100 Subject: [PATCH 0408/1008] refactor: Keep only the common CI (#1750) From 7226f02794bdf11a91116d0d4cd88399f05149ad Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 23 Feb 2023 16:17:47 +0100 Subject: [PATCH 0409/1008] fix(modp2p): pass only resolved addresses to resource manager (#1811) --- go.mod | 2 +- nodebuilder/p2p/module.go | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2e6ec6b421..b36522ee94 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.8.0 + github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/spf13/cobra v1.6.1 @@ -238,7 +239,6 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect github.com/multiformats/go-multicodec v0.7.0 // indirect diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 2d2ebc604d..ed7ffbbee4 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -1,12 +1,15 @@ package p2p import ( + "context" + logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/metrics" "github.com/libp2p/go-libp2p/core/network" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" ma "github.com/multiformats/go-multiaddr" + madns "github.com/multiformats/go-multiaddr-dns" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -55,7 +58,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "p2p", baseComponents, fx.Provide(blockstoreFromDatastore), - fx.Provide(func(bootstrappers Bootstrappers) (network.ResourceManager, error) { + fx.Provide(func(ctx context.Context, bootstrappers Bootstrappers) (network.ResourceManager, error) { limits := rcmgr.DefaultLimits libp2p.SetDefaultServiceLimits(&limits) @@ -64,12 +67,28 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return nil, err } + // TODO(@Wondertan): We should resolve their addresses only once, but currently + // we resolve it here and libp2p stuck does that as well internally allowlist := make([]ma.Multiaddr, 0, len(bootstrappers)+len(mutual)) for _, b := range bootstrappers { - allowlist = append(allowlist, b.Addrs...) + for _, baddr := range b.Addrs { + resolved, err := madns.DefaultResolver.Resolve(ctx, baddr) + if err != nil { + log.Warnw("error resolving bootstrapper DNS", "addr", baddr.String(), "err", err) + continue + } + allowlist = append(allowlist, resolved...) + } } for _, m := range mutual { - allowlist = append(allowlist, m.Addrs...) + for _, maddr := range m.Addrs { + resolved, err := madns.DefaultResolver.Resolve(ctx, maddr) + if err != nil { + log.Warnw("error resolving mutual peer DNS", "addr", maddr.String(), "err", err) + continue + } + allowlist = append(allowlist, resolved...) + } } return rcmgr.NewResourceManager( From e84dfaa2cac7eba670e1ce0c9e08e53389fc60ce Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 24 Feb 2023 13:35:40 +0300 Subject: [PATCH 0410/1008] feat(das): Don't sample recent jobs twice (#1815) ## Overview - Prevent DASer from doing same job twice in case of recent sampling is run on synced node. - Log recent jobs with info level --- das/checkpoint.go | 9 --------- das/coordinator.go | 3 --- das/daser_test.go | 5 +---- das/metrics.go | 20 ++------------------ das/state.go | 9 +++++++-- das/stats.go | 21 ++++++++++++++------- das/worker.go | 17 +++++++---------- 7 files changed, 31 insertions(+), 53 deletions(-) diff --git a/das/checkpoint.go b/das/checkpoint.go index 04e6cafaf5..860c72cb35 100644 --- a/das/checkpoint.go +++ b/das/checkpoint.go @@ -48,12 +48,3 @@ func (c checkpoint) String() string { return str } - -// totalSampled returns the total amount of sampled headers -func (c checkpoint) totalSampled() uint64 { - var totalInProgress uint64 - for _, w := range c.Workers { - totalInProgress += (w.To - w.From) + 1 - } - return c.SampleFrom - totalInProgress - uint64(len(c.Failed)) -} diff --git a/das/coordinator.go b/das/coordinator.go index bb2d82d6f1..2aca548364 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -63,9 +63,6 @@ func newSamplingCoordinator( func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { sc.state.resumeFromCheckpoint(cp) - // the amount of sampled headers from the last checkpoint - sc.metrics.recordTotalSampled(cp.totalSampled()) - // resume workers for _, wk := range cp.Workers { sc.runWorker(ctx, sc.state.newJob(wk.From, wk.To)) diff --git a/das/daser_test.go b/das/daser_test.go index cba84a0663..8d0a33e01c 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -204,12 +204,9 @@ func TestDASerSampleTimeout(t *testing.T) { f := new(fraud.DummyService) // create and start DASer - daser, err := NewDASer(avail, sub, getter, ds, f, newBroadcastMock(1)) + daser, err := NewDASer(avail, sub, getter, ds, f, newBroadcastMock(1), WithSampleTimeout(1)) require.NoError(t, err) - // assign directly to avoid params validation - daser.params.SampleTimeout = 0 - require.NoError(t, daser.Start(ctx)) require.NoError(t, daser.sampler.state.waitCatchUp(ctx)) diff --git a/das/metrics.go b/das/metrics.go index b11dcc8e5a..2a4be8fe84 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -25,8 +25,7 @@ type metrics struct { getHeaderTime syncfloat64.Histogram newHead syncint64.Counter - lastSampledTS uint64 - totalSampledInt uint64 + lastSampledTS uint64 } func (d *DASer) InitMetrics() error { @@ -117,8 +116,7 @@ func (d *DASer) InitMetrics() error { lastSampledTS.Observe(ctx, int64(ts)) } - totalSampledInt := atomic.LoadUint64(&d.sampler.metrics.totalSampledInt) - totalSampled.Observe(ctx, int64(totalSampledInt)) + totalSampled.Observe(ctx, int64(stats.totalSampled())) }, ) @@ -152,12 +150,6 @@ func (m *metrics) observeSample( ) atomic.StoreUint64(&m.lastSampledTS, uint64(time.Now().UTC().Unix())) - - // only increment the counter if it's not a recent header job - // as those happen twice. - if err == nil && !isRecentHeader { - atomic.AddUint64(&m.totalSampledInt, 1) - } } // observeGetHeader records the time it took to get a header from the header store. @@ -175,11 +167,3 @@ func (m *metrics) observeNewHead(ctx context.Context) { } m.newHead.Add(ctx, 1) } - -// recordTotalSampled records the total sampled headers. -func (m *metrics) recordTotalSampled(totalSampled uint64) { - if m == nil { - return - } - atomic.StoreUint64(&m.totalSampledInt, totalSampled) -} diff --git a/das/state.go b/das/state.go index e45a8ab074..8e854e7fb3 100644 --- a/das/state.go +++ b/das/state.go @@ -94,13 +94,18 @@ func (s *coordinatorState) updateHead(newHead int64) { } func (s *coordinatorState) newRecentJob(header *header.ExtendedHeader) job { + height := uint64(header.Height()) + // move next, to prevent catchup job from processing same height + if s.next == height { + s.next++ + } s.nextJobID++ return job{ id: s.nextJobID, isRecentHeader: true, header: header, - From: uint64(header.Height()), - To: uint64(header.Height()), + From: height, + To: height, } } diff --git a/das/stats.go b/das/stats.go index 3b0a61ea05..a07e8fadaf 100644 --- a/das/stats.go +++ b/das/stats.go @@ -1,21 +1,19 @@ package das -// SamplingStats collects information about the DASer process. Currently, there are -// only two sampling routines: the main sampling routine which performs sampling -// over current network headers, and the `catchUp` routine which performs sampling -// over past headers from the last sampled checkpoint. +// SamplingStats collects information about the DASer process. type SamplingStats struct { // all headers before SampledChainHead were successfully sampled SampledChainHead uint64 `json:"head_of_sampled_chain"` - // all headers before CatchupHead were submitted to sampling workers + // all headers before CatchupHead were submitted to sampling workers. They could be either already + // sampled, failed or still in progress. For in progress items check Workers stat. CatchupHead uint64 `json:"head_of_catchup"` // NetworkHead is the height of the most recent header in the network NetworkHead uint64 `json:"network_head_height"` - // Failed contains all skipped header's heights with corresponding try count + // Failed contains all skipped headers heights with corresponding try count Failed map[uint64]int `json:"failed,omitempty"` // Workers has information about each currently running worker stats Workers []WorkerStats `json:"workers,omitempty"` - // Concurrency currently running parallel workers + // Concurrency amount of currently running parallel workers Concurrency int `json:"concurrency"` // CatchUpDone indicates whether all known headers are sampled CatchUpDone bool `json:"catch_up_done"` @@ -30,3 +28,12 @@ type WorkerStats struct { ErrMsg string `json:"error,omitempty"` } + +// totalSampled returns the total amount of sampled headers +func (s SamplingStats) totalSampled() uint64 { + var inProgress uint64 + for _, w := range s.Workers { + inProgress += w.To - w.Curr + 1 + } + return s.CatchupHead - inProgress - uint64(len(s.Failed)) +} diff --git a/das/worker.go b/das/worker.go index eb41669991..516cd833a3 100644 --- a/das/worker.go +++ b/das/worker.go @@ -80,16 +80,13 @@ func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- } } - if w.state.Curr > w.state.From { - jobTime := time.Since(jobStart) - log.Infow( - "sampled headers", - "from", w.state.From, - "to", w.state.Curr, - "finished (s)", - jobTime.Seconds(), - ) - } + log.Infow( + "finished sampling headers", + "from", w.state.From, + "to", w.state.Curr, + "errors", len(w.state.failed), + "finished (s)", time.Since(jobStart), + ) select { case resultCh <- result{ From acb98ebd1fb7de0c65638772ad489719acdab5c5 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 24 Feb 2023 12:09:57 +0100 Subject: [PATCH 0411/1008] perf(share): don't broadcast datahash of empty EDS (#1814) --- nodebuilder/p2p/pubsub.go | 3 ++- share/empty.go | 35 ++++++++++++++++++++++++++++------- share/p2p/peers/manager.go | 5 +++++ share/p2p/shrexsub/pubsub.go | 5 +++++ share/share.go | 6 ++++++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 084a81d677..088769180c 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -44,7 +44,8 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { if isBootstrapper { // Turn off the mesh in bootstrappers as per: // - // https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators + // + //https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators pubsub.GossipSubD = 0 pubsub.GossipSubDscore = 0 pubsub.GossipSubDlo = 0 diff --git a/share/empty.go b/share/empty.go index 5fee787d6a..d32e751969 100644 --- a/share/empty.go +++ b/share/empty.go @@ -13,6 +13,33 @@ import ( "github.com/celestiaorg/rsmt2d" ) +var ( + emptyRoot *Root + emptyEDS *rsmt2d.ExtendedDataSquare +) + +func init() { + // compute empty block EDS and DAH for it + shares := emptyDataSquare() + squareSize := uint64(math.Sqrt(float64(appconsts.DefaultMinSquareSize))) + eds, err := da.ExtendShares(squareSize, shares) + if err != nil { + panic(fmt.Errorf("failed to create empty EDS: %w", err)) + } + + emptyEDS = eds + dah := da.NewDataAvailabilityHeader(eds) + emptyRoot = &dah + + // precompute Hash, so it's cached internally to avoid potential races + emptyRoot.Hash() +} + +// EmptyRoot returns Root of an empty EDS. +func EmptyRoot() *Root { + return emptyRoot +} + // EnsureEmptySquareExists checks if the given DAG contains an empty block data square. // If it does not, it stores an empty block. This optimization exists to prevent // redundant storing of empty block data so that it is only stored once and returned @@ -23,13 +50,7 @@ func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockServic // EmptyExtendedDataSquare returns the EDS of the empty block data square. func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { - shares := emptyDataSquare() - squareSize := uint64(math.Sqrt(float64(appconsts.DefaultMinSquareSize))) - eds, err := da.ExtendShares(squareSize, shares) - if err != nil { - panic(fmt.Errorf("failed to create empty EDS: %w", err)) - } - return eds + return emptyEDS } // tail is filler for all tail padded shares diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 0bec7a19c0..d31b7a2bda 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -253,6 +253,11 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri // Validate will collect peer.ID into corresponding peer pool func (m *Manager) validate(ctx context.Context, peerID peer.ID, hash share.DataHash) pubsub.ValidationResult { + if hash.IsEmptyRoot() { + // we don't send empty EDS data hashes, but If someone sent it to us - do hard reject + return pubsub.ValidationReject + } + // messages broadcast from self should bypass the validation with Accept if peerID == m.host.ID() { log.Debugw("received datahash from self", "datahash", hash.String()) diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index d68be42d9b..a808fc4174 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -100,5 +100,10 @@ func (s *PubSub) Subscribe() (*Subscription, error) { // Broadcast sends the EDS notification (DataHash) to every connected peer. func (s *PubSub) Broadcast(ctx context.Context, data share.DataHash) error { + if data.IsEmptyRoot() { + // no need to broadcast datahash of an empty block EDS + return nil + } + return s.topic.Publish(ctx, data) } diff --git a/share/share.go b/share/share.go index 66d2c0df29..643f0c477a 100644 --- a/share/share.go +++ b/share/share.go @@ -1,6 +1,7 @@ package share import ( + "bytes" "fmt" "go.opentelemetry.io/otel" @@ -55,3 +56,8 @@ func (dh DataHash) Validate() error { func (dh DataHash) String() string { return fmt.Sprintf("%X", []byte(dh)) } + +// IsEmptyRoot check whether DataHash corresponds to the root of an empty block EDS. +func (dh DataHash) IsEmptyRoot() bool { + return bytes.Equal(EmptyRoot().Hash(), dh) +} From 32dcb8af10f869dd919b6d73db8bb0ae76e6ea35 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 24 Feb 2023 17:06:54 +0200 Subject: [PATCH 0412/1008] misc(libs/header): verify chainID in incoming headers (#1746) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Nina Barbakadze --- libs/header/p2p/exchange.go | 25 +++++++++++++++++-------- libs/header/p2p/exchange_test.go | 22 ++++++++++++++++++++++ libs/header/p2p/helpers.go | 10 ++++++++++ libs/header/p2p/options.go | 13 +++++++++++++ libs/header/test/testing.go | 2 +- nodebuilder/header/module.go | 1 + 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index 567a5600c7..d922a7064e 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -115,6 +115,7 @@ func (ex *Exchange[H]) Head(ctx context.Context) (H, error) { // request head from each trusted peer for _, from := range ex.trustedPeers { go func(from peer.ID) { + // request ensures that the result slice will have at least one Header headers, err := ex.request(ctx, from, req) if err != nil { log.Errorw("head request to trusted peer failed", "trustedPeer", from, "err", err) @@ -122,7 +123,6 @@ func (ex *Exchange[H]) Head(ctx context.Context) (H, error) { headerCh <- zero return } - // doRequest ensures that the result slice will have at least one Header headerCh <- headers[0] }(from) } @@ -226,22 +226,25 @@ func (ex *Exchange[H]) performRequest( if len(ex.trustedPeers) == 0 { return nil, fmt.Errorf("no trusted peers") } - - for { + var reqErr error + for i := 0; i < len(ex.trustedPeers); i++ { //nolint:gosec // G404: Use of weak random number generator idx := rand.Intn(len(ex.trustedPeers)) - ctx, cancel := context.WithTimeout(ctx, ex.Params.TrustedPeersRequestTimeout) + ctx, cancel := context.WithTimeout(ctx, ex.Params.TrustedPeersRequestTimeout) h, err := ex.request(ctx, ex.trustedPeers[idx], req) cancel() switch err { default: - log.Debug(err) + reqErr = err + log.Debugw("requesting header from trustedPeer failed", + "trustedPeer", ex.trustedPeers[idx], "err", err) continue case context.Canceled, context.DeadlineExceeded, nil: return h, err } } + return nil, reqErr } // request sends the HeaderRequest to a remote peer. @@ -256,9 +259,7 @@ func (ex *Exchange[H]) request( log.Debugw("err sending request", "peer", to, "err", err) return nil, err } - if len(responses) == 0 { - return nil, header.ErrNotFound - } + headers := make([]H, 0, len(responses)) for _, response := range responses { if err = convertStatusCodeToError(response.StatusCode); err != nil { @@ -270,8 +271,16 @@ func (ex *Exchange[H]) request( if err != nil { return nil, err } + err = validateChainID(ex.Params.chainID, header.(H).ChainID()) + if err != nil { + return nil, err + } headers = append(headers, header.(H)) } + + if len(headers) == 0 { + return nil, header.ErrNotFound + } return headers, nil } diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index 550394c7c0..7d6175d250 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -99,6 +99,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { // create new exchange exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{}, connGater, WithNetworkID[ClientParameters](networkID), + WithChainID(networkID), ) require.NoError(t, err) exchange.Params.MaxHeadersPerRequest = 10 @@ -315,6 +316,26 @@ func TestExchange_RequestByHashFails(t *testing.T) { require.Equal(t, resp.StatusCode, p2p_pb.StatusCode_NOT_FOUND) } +// TestExchange_HandleHeaderWithDifferentChainID ensures that headers with different +// chainIDs will not be served and stored. +func TestExchange_HandleHeaderWithDifferentChainID(t *testing.T) { + hosts := createMocknet(t, 2) + exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) + exchg.Params.chainID = "test" + require.NoError(t, exchg.Start(context.TODO())) + + _, err := exchg.Head(context.Background()) + require.Error(t, err) + + _, err = exchg.GetByHeight(context.Background(), 1) + require.Error(t, err) + + h, err := store.GetByHeight(context.Background(), 1) + require.NoError(t, err) + _, err = exchg.Get(context.Background(), h.Hash()) + require.Error(t, err) +} + // TestExchange_RequestHeadersFromAnotherPeer tests that the Exchange instance will request range // from another peer with lower score after receiving header.ErrNotFound func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { @@ -384,6 +405,7 @@ func createP2PExAndServer( require.NoError(t, err) ex, err := NewExchange[*test.DummyHeader](host, []peer.ID{tpeer.ID()}, connGater, WithNetworkID[ClientParameters](networkID), + WithChainID(networkID), ) require.NoError(t, err) require.NoError(t, ex.Start(context.Background())) diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go index 32b3ee8008..27f8f02ff5 100644 --- a/libs/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" "time" "github.com/libp2p/go-libp2p/core/host" @@ -24,6 +25,15 @@ func PubsubTopicID(networkID string) string { return fmt.Sprintf("/%s/header-sub/v0.0.1", networkID) } +func validateChainID(want, have string) error { + if want != "" && !strings.EqualFold(want, have) { + return fmt.Errorf("header with different chainID received.want=%s,have=%s", + want, have, + ) + } + return nil +} + // sendMessage opens the stream to the given peers and sends HeaderRequest to fetch // Headers. As a result sendMessage returns HeaderResponse, the size of fetched // data, the duration of the request and an error. diff --git a/libs/header/p2p/options.go b/libs/header/p2p/options.go index 75b62208ab..dd4d7a6ccd 100644 --- a/libs/header/p2p/options.go +++ b/libs/header/p2p/options.go @@ -142,6 +142,8 @@ type ClientParameters struct { MaxPeerTrackerSize int // networkID is a network that will be used to create a protocol.ID networkID string + // chainID is an identifier of the chain. + chainID string } // DefaultClientParameters returns the default params to configure the store. @@ -254,3 +256,14 @@ func WithMaxTrackerSize[T ClientParameters](size int) Option[T] { } } } + +// WithChainID is a functional option that configures the +// `chainID` parameter. +func WithChainID[T ClientParameters](chainID string) Option[T] { + return func(p *T) { + switch t := any(p).(type) { //nolint:gocritic + case *ClientParameters: + t.chainID = chainID + } + } +} diff --git a/libs/header/test/testing.go b/libs/header/test/testing.go index c17eaed242..b1c9016a44 100644 --- a/libs/header/test/testing.go +++ b/libs/header/test/testing.go @@ -41,7 +41,7 @@ func (d *DummyHeader) IsZero() bool { } func (d *DummyHeader) ChainID() string { - return d.Raw.ChainID + return "private" } func (d *DummyHeader) Hash() header.Hash { diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index c25ccad882..c460e266ae 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -122,6 +122,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { p2p.WithRequestTimeout[p2p.ClientParameters](cfg.Client.RequestTimeout), p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), p2p.WithNetworkID[p2p.ClientParameters](network.String()), + p2p.WithChainID(network.String()), } }, ), From c9f6323533a444deec5378f33472efe6a21d5178 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 24 Feb 2023 16:23:50 +0100 Subject: [PATCH 0413/1008] perf(share/full): avoid loading EDS for subsequent availability checks (#1819) --- share/availability/full/availability.go | 13 ++++++++++++- share/availability/full/testing.go | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index b6ede4d6ec..8b35dbaeac 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -7,6 +7,8 @@ import ( ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds/byzantine" @@ -18,6 +20,7 @@ var log = logging.Logger("share/full") // recovery technique. It is considered "full" because it is required // to download enough shares to fully reconstruct the data square. type ShareAvailability struct { + store *eds.Store getter share.Getter disc *discovery.Discovery @@ -25,8 +28,9 @@ type ShareAvailability struct { } // NewShareAvailability creates a new full ShareAvailability. -func NewShareAvailability(getter share.Getter, disc *discovery.Discovery) *ShareAvailability { +func NewShareAvailability(store *eds.Store, getter share.Getter, disc *discovery.Discovery) *ShareAvailability { return &ShareAvailability{ + store: store, getter: getter, disc: disc, } @@ -56,6 +60,13 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro panic(err) } + // a hack to avoid loading the whole EDS in mem if we store it already. + if fa.store != nil { + if ok, _ := fa.store.Has(ctx, root.Hash()); ok { + return nil + } + } + _, err := fa.getter.GetEDS(ctx, root) if err != nil { log.Errorw("availability validation failed", "root", root.Hash(), "err", err.Error()) diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index c792c1e70a..ca5d0f10c5 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -38,5 +38,5 @@ func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { func TestAvailability(getter share.Getter) *ShareAvailability { disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) - return NewShareAvailability(getter, disc) + return NewShareAvailability(nil, getter, disc) } From 6d4cb7036f01e19a94faead98a358d02a87fbbee Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Fri, 24 Feb 2023 17:47:55 +0100 Subject: [PATCH 0414/1008] feat: add libheader exchange metrics (#1794) Co-authored-by: Hlib Kanunnikov Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- libs/header/p2p/exchange.go | 5 ++- libs/header/p2p/metrics.go | 63 +++++++++++++++++++++++++++++++++++++ nodebuilder/header/opts.go | 17 ++++++++++ nodebuilder/settings.go | 9 +++--- 4 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 libs/header/p2p/metrics.go create mode 100644 nodebuilder/header/opts.go diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index d922a7064e..0346f50b5d 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -33,6 +33,8 @@ type Exchange[H header.Header] struct { peerTracker *peerTracker Params ClientParameters + + metrics *metrics } func NewExchange[H header.Header]( @@ -254,7 +256,8 @@ func (ex *Exchange[H]) request( req *p2p_pb.HeaderRequest, ) ([]H, error) { log.Debugw("requesting peer", "peer", to) - responses, _, _, err := sendMessage(ctx, ex.host, to, ex.protocolID, req) + responses, size, duration, err := sendMessage(ctx, ex.host, to, ex.protocolID, req) + ex.metrics.observeResponse(ctx, size, duration, err) if err != nil { log.Debugw("err sending request", "peer", to, "err", err) return nil, err diff --git a/libs/header/p2p/metrics.go b/libs/header/p2p/metrics.go new file mode 100644 index 0000000000..20bec81248 --- /dev/null +++ b/libs/header/p2p/metrics.go @@ -0,0 +1,63 @@ +package p2p + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" +) + +type metrics struct { + responseSize syncfloat64.Histogram + responseDuration syncfloat64.Histogram +} + +var ( + meter = global.MeterProvider().Meter("header/p2p") +) + +func (ex *Exchange[H]) RegisterMetrics() error { + responseSize, err := meter. + SyncFloat64(). + Histogram( + "header_p2p_headers_response_size", + instrument.WithDescription("Size of get headers response in bytes"), + ) + if err != nil { + return err + } + + responseDuration, err := meter. + SyncFloat64(). + Histogram( + "header_p2p_headers_request_duration", + instrument.WithDescription("Duration of get headers request in seconds"), + ) + if err != nil { + return err + } + + ex.metrics = &metrics{ + responseSize: responseSize, + responseDuration: responseDuration, + } + return nil +} + +func (m *metrics) observeResponse(ctx context.Context, size uint64, duration uint64, err error) { + if m == nil { + return + } + m.responseSize.Record( + ctx, + float64(size), + attribute.Bool("failed", err != nil), + ) + m.responseDuration.Record( + ctx, + float64(duration), + attribute.Bool("failed", err != nil), + ) +} diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go new file mode 100644 index 0000000000..c07720cf60 --- /dev/null +++ b/nodebuilder/header/opts.go @@ -0,0 +1,17 @@ +package header + +import ( + header "github.com/celestiaorg/celestia-node/header" + libhead "github.com/celestiaorg/celestia-node/libs/header" + p2p "github.com/celestiaorg/celestia-node/libs/header/p2p" +) + +// WithMetrics provides sets `MetricsEnabled` to true on ClientParameters for the header exchange +func WithMetrics(ex libhead.Exchange[*header.ExtendedHeader]) error { + switch p2pex := any(ex).(type) { + case *p2p.Exchange[*header.ExtendedHeader]: + return p2pex.RegisterMetrics() + default: + return nil + } +} diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index bfa9f99667..a8b9f05d3f 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -13,10 +13,9 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" - fraudPkg "github.com/celestiaorg/celestia-node/fraud" - headerPkg "github.com/celestiaorg/celestia-node/header" - + fraud "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/das" + modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/state" @@ -39,10 +38,10 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti baseComponents := fx.Options( fx.Supply(metricOpts), fx.Invoke(initializeMetrics), - fx.Invoke(headerPkg.WithMetrics), fx.Invoke(state.WithMetrics), - fx.Invoke(fraudPkg.WithMetrics), + fx.Invoke(fraud.WithMetrics), fx.Invoke(node.WithMetrics), + fx.Invoke(modheader.WithMetrics), ) var opts fx.Option From 190df0b94bdf876705aee00f1748ab029e7c5769 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:30:07 +0100 Subject: [PATCH 0415/1008] improvement(cmd): Auth cmd generates new priv key for jwt if not yet found in node store (#1823) This means that users will no longer have to run `celestia start` in order to be able to use `auth` cmd. Results in better UX :) --- cmd/auth.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index 40d028cc14..c4eb057a29 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -1,7 +1,10 @@ package cmd import ( + "crypto/rand" + "errors" "fmt" + "io" "path/filepath" "github.com/cristalhq/jwt" @@ -49,10 +52,19 @@ func newToken(cmd *cobra.Command, args []string) error { return err } - key, err := ks.Get(nodemod.SecretName) + var key keystore.PrivKey + key, err = ks.Get(nodemod.SecretName) if err != nil { - return err + if !errors.Is(err, keystore.ErrNotFound) { + return err + } + // otherwise, generate and save new priv key + key, err = generateNewKey(ks) + if err != nil { + return err + } } + signer, err := jwt.NewHS256(key.Body) if err != nil { return err @@ -69,6 +81,20 @@ func newToken(cmd *cobra.Command, args []string) error { return nil } +func generateNewKey(ks keystore.Keystore) (keystore.PrivKey, error) { + sk, err := io.ReadAll(io.LimitReader(rand.Reader, 32)) + if err != nil { + return keystore.PrivKey{}, err + } + // save key + key := keystore.PrivKey{Body: sk} + err = ks.Put(nodemod.SecretName, key) + if err != nil { + return keystore.PrivKey{}, err + } + return key, nil +} + func convertToPerms(perm string) ([]auth.Permission, error) { perms, ok := stringsToPerms[perm] if !ok { From 0b81f591fddf53080c57162ea57e6544288971d4 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Feb 2023 15:19:59 +0100 Subject: [PATCH 0416/1008] refactor(share/p2p/shrexeds): Increase ODS write deadline to block time and add hash to log (#1816) Increases ODS write deadline to block time and adds the hash to failure log --- share/p2p/shrexeds/options.go | 2 +- share/p2p/shrexeds/server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go index d0a305302c..f3215e4704 100644 --- a/share/p2p/shrexeds/options.go +++ b/share/p2p/shrexeds/options.go @@ -41,7 +41,7 @@ type Parameters struct { func DefaultParameters() *Parameters { return &Parameters{ ReadDeadline: time.Minute, - WriteDeadline: time.Second * 5, + WriteDeadline: time.Second * 30, // based on block time ReadCARDeadline: time.Minute, BufferSize: 32 * 1024, concurrencyLimit: 10, diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index 7d3e7d00b0..7001f97fd8 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -108,7 +108,7 @@ func (s *Server) handleStream(stream network.Stream) { // start streaming the ODS to the client err = s.writeODS(edsReader, stream) if err != nil { - log.Errorw("server: writing ods to stream", "err", err) + log.Errorw("server: writing ods to stream", "hash", hash.String(), "err", err) stream.Reset() //nolint:errcheck return } From ecc4288252a549c61c7204fae2d441d89e6e24b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 17:33:27 +0000 Subject: [PATCH 0417/1008] chore(deps): bump go.opentelemetry.io/otel from 1.11.2 to 1.13.0 (#1709) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b36522ee94..1046d5886b 100644 --- a/go.mod +++ b/go.mod @@ -56,13 +56,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 - go.opentelemetry.io/otel v1.12.0 + go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 go.opentelemetry.io/otel/metric v0.34.0 go.opentelemetry.io/otel/sdk v1.11.2 go.opentelemetry.io/otel/sdk/metric v0.34.0 - go.opentelemetry.io/otel/trace v1.12.0 + go.opentelemetry.io/otel/trace v1.13.0 go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 diff --git a/go.sum b/go.sum index db5a3b809d..6f702acafd 100644 --- a/go.sum +++ b/go.sum @@ -1806,8 +1806,8 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.12.0 h1:IgfC7kqQrRccIKuB7Cl+SRUmsKbEwSGPr0Eu+/ht1SQ= -go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0= +go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y= +go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 h1:kpskzLZ60cJ48SJ4uxWa6waBL+4kSV6nVK8rP+QM8Wg= @@ -1828,8 +1828,8 @@ go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rq go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.12.0 h1:p28in++7Kd0r2d8gSt931O57fdjUyWxkVbESuILAeUc= -go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ= +go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= +go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= From c43e5c3c7da7e1d99c5d2aae696185ee4434e87a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 17:34:03 +0000 Subject: [PATCH 0418/1008] chore(deps): bump go.opentelemetry.io/otel/trace from 1.11.2 to 1.13.0 (#1710) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 55ee447d6d8ca22713cf527e1319071898c8a3dc Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Feb 2023 20:51:31 +0100 Subject: [PATCH 0419/1008] test(share/getters): Fix cascade test (#1820) Test was hanging before on waiting for context to time out, now `timedoutGetter` returns immediately with context.DeadlineExceeded --- share/getters/cascade.go | 5 +++-- share/getters/cascade_test.go | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 3709f8f278..061d973147 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -8,10 +8,11 @@ import ( "go.opentelemetry.io/otel/trace" "go.uber.org/multierr" - "github.com/celestiaorg/celestia-node/libs/utils" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/share" ) var _ share.Getter = (*CascadeGetter)(nil) diff --git a/share/getters/cascade_test.go b/share/getters/cascade_test.go index f2688773be..c7b32a689b 100644 --- a/share/getters/cascade_test.go +++ b/share/getters/cascade_test.go @@ -9,9 +9,10 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/multierr" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/mocks" - "github.com/celestiaorg/rsmt2d" ) func TestCascadeGetter(t *testing.T) { @@ -53,8 +54,7 @@ func TestCascade(t *testing.T) { successGetter := mocks.NewMockGetter(ctrl) timeoutGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, _ *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - <-ctx.Done() - return nil, ctx.Err() + return nil, context.DeadlineExceeded }).AnyTimes() immediateFailGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). Return(nil, errors.New("second getter fails immediately")).AnyTimes() From 4d1ab88bbdd1431539c13d7c06925519f9ef782a Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 1 Mar 2023 08:18:33 +0100 Subject: [PATCH 0420/1008] chore: bump app to official v0.12.0 (#1843) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 1046d5886b..61ec0229cd 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 - github.com/celestiaorg/celestia-app v0.12.0-rc7 + github.com/celestiaorg/celestia-app v0.12.0 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 @@ -326,5 +326,5 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 6f702acafd..4b3b7a7d38 100644 --- a/go.sum +++ b/go.sum @@ -200,10 +200,10 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.0-rc7 h1:83Ig3eTc0rAapUZyc4RbygJFLVfZ4z3TGgloN0fIXJw= -github.com/celestiaorg/celestia-app v0.12.0-rc7/go.mod h1:60QSrI+bbGzL/m80ocWrYpdg/ECLxmVvVets6vPuG8k= -github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 h1:8zE523TUe5W33/nheJ9umHF2d1q6iHQlqJfMXMTPe3k= -github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23/go.mod h1:fGDSg7aw2OH/Uze1zymop0x0y1kAPEO9OII2A2cb99Q= +github.com/celestiaorg/celestia-app v0.12.0 h1:s7Xkd3+NKZIbMd3++d1l76ak7+rFJWqspud7P/cFaWg= +github.com/celestiaorg/celestia-app v0.12.0/go.mod h1:j/QeQxYeOesDyVRLQOPcG4fVyJhxOmf1k9Hc7oEkWiA= +github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/xBFIHj93llcw9ZQfNxyVRlI= +github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= From 233c9aae9fa24754e812aa91ff99c1007d9bf3aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 08:31:32 +0100 Subject: [PATCH 0421/1008] chore(deps): bump golang.org/x/crypto from 0.5.0 to 0.6.0 (#1826) --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 61ec0229cd..5b536dc66a 100644 --- a/go.mod +++ b/go.mod @@ -66,10 +66,10 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 - golang.org/x/crypto v0.5.0 + golang.org/x/crypto v0.6.0 golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 golang.org/x/sync v0.1.0 - golang.org/x/text v0.6.0 + golang.org/x/text v0.7.0 google.golang.org/grpc v1.52.0 google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) @@ -304,10 +304,10 @@ require ( go.uber.org/dig v1.15.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.5.0 // indirect + golang.org/x/net v0.6.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/term v0.4.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect golang.org/x/tools v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.102.0 // indirect diff --git a/go.sum b/go.sum index 4b3b7a7d38..4051ffe445 100644 --- a/go.sum +++ b/go.sum @@ -1905,8 +1905,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2025,8 +2025,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2163,13 +2163,13 @@ golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2179,8 +2179,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 39d5937616dfd64878035c8168451d499bb86246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 07:43:09 +0000 Subject: [PATCH 0422/1008] chore(deps): bump github.com/hashicorp/go-retryablehttp from 0.7.1-0.20211018174820-ff6d014e72d9 to 0.7.2 (#1827) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5b536dc66a..54daeab513 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 - github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 + github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.3.2 diff --git a/go.sum b/go.sum index 4051ffe445..42dfe032d1 100644 --- a/go.sum +++ b/go.sum @@ -700,8 +700,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 h1:RSp277I12pTdb5wiZWy9qtE0IN4oC4BH5a/LIcNxw0w= -github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= +github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= From 6efc8d1c94b2b4096c3e45d7256bddbccf0a400a Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 1 Mar 2023 11:55:32 +0200 Subject: [PATCH 0423/1008] bugfix(libs/header/exchange): do not request an empty range (#1818) --- libs/header/p2p/exchange.go | 6 +++++ libs/header/sync/sync.go | 6 ++--- libs/header/sync/sync_test.go | 45 +++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index 0346f50b5d..a881747785 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -172,6 +172,9 @@ func (ex *Exchange[H]) GetByHeight(ctx context.Context, height uint64) (H, error // GetRangeByHeight performs a request for the given range of Headers // to the network. Note that the Headers must be verified thereafter. func (ex *Exchange[H]) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) { + if amount == 0 { + return make([]H, 0), nil + } if amount > ex.Params.MaxRequestSize { return nil, header.ErrHeadersLimitExceeded } @@ -187,6 +190,9 @@ func (ex *Exchange[H]) GetVerifiedRange( from H, amount uint64, ) ([]H, error) { + if amount == 0 { + return make([]H, 0), nil + } session := newSession[H]( ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout, withValidation(from), ) diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 9c040a9a49..15a0dce26d 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -278,11 +278,11 @@ func (s *Syncer[H]) findHeaders(ctx context.Context, from, to uint64) ([]H, erro } out := make([]H, 0, amount) - for from < to { + for from <= to { // if we have some range cached - use it r, ok := s.pending.FirstRangeWithin(from, to) if !ok { - hs, err := s.exchange.GetRangeByHeight(ctx, from, amount) + hs, err := s.exchange.GetRangeByHeight(ctx, from, to-from+1) return append(out, hs...), err } @@ -294,7 +294,7 @@ func (s *Syncer[H]) findHeaders(ctx context.Context, from, to uint64) ([]H, erro out = append(out, hs...) from += uint64(len(hs)) - // then, apply cached range if any + // apply cached range if any cached, ln := r.Before(to) out = append(out, cached...) from += ln diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index f78f709568..b318da9313 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -209,6 +209,51 @@ func TestSyncer_OnlyOneRecentRequest(t *testing.T) { assert.Equal(t, 1, exchange.counter) } +// TestSyncer_FindHeadersReturnsCorrectRange ensures that `findHeaders` returns +// range [from;to] +func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { + // Test consists of 3 steps: + // 1. get range of headers from pending; [2;11] + // 2. get headers from the remote store; [12;20] + // 3. apply last header from pending; + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + t.Cleanup(cancel) + + suite := test.NewTestSuite(t) + head := suite.Head() + + remoteStore := store.NewTestStore(ctx, t, head) + localStore := store.NewTestStore(ctx, t, head) + syncer, err := NewSyncer[*test.DummyHeader]( + local.NewExchange(remoteStore), + localStore, + &test.DummySubscriber{}, + ) + require.NoError(t, err) + + range1 := suite.GenDummyHeaders(10) + // manually add to pending + for _, h := range range1 { + syncer.pending.Add(h) + } + _, err = remoteStore.Append(ctx, range1...) + require.NoError(t, err) + _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(9)...) + require.NoError(t, err) + + syncer.pending.Add(suite.GetRandomHeader()) + h, err := syncer.findHeaders(ctx, 2, 21) + require.NoError(t, err) + require.NotNil(t, h) + require.Equal(t, h[0].Height(), int64(2)) + require.Equal(t, h[len(h)-1].Height(), int64(21)) + header := h[0] + for i := 1; i < len(h); i++ { + require.NoError(t, header.VerifyAdjacent(h[i])) + header = h[i] + } +} + type exchangeCountingHead struct { header *test.DummyHeader counter int From 52a716201ca08ac36d06d6cac9ea86a445578ed5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 1 Mar 2023 20:05:12 +0200 Subject: [PATCH 0424/1008] fix(libs/header/syncer): fix bounds during syncing (#1850) --- libs/header/sync/sync.go | 3 ++- libs/header/sync/sync_test.go | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 15a0dce26d..834a50f6ab 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -274,7 +274,8 @@ func (s *Syncer[H]) processHeaders(ctx context.Context, from, to uint64) (int, e func (s *Syncer[H]) findHeaders(ctx context.Context, from, to uint64) ([]H, error) { amount := to - from + 1 // + 1 to include 'to' height as well if amount > s.Params.MaxRequestSize { - to, amount = from+s.Params.MaxRequestSize, s.Params.MaxRequestSize + to = from + s.Params.MaxRequestSize - 1 // `from` is already included in range + amount = s.Params.MaxRequestSize } out := make([]H, 0, amount) diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index b318da9313..89d3e9ebb8 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -61,6 +61,45 @@ func TestSyncSimpleRequestingHead(t *testing.T) { assert.True(t, state.Finished(), state) } +func TestDoSyncFullRangeFromExternalPeer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + suite := test.NewTestSuite(t) + head := suite.Head() + + remoteStore := store.NewTestStore(ctx, t, head) + localStore := store.NewTestStore(ctx, t, head) + syncer, err := NewSyncer[*test.DummyHeader]( + local.NewExchange(remoteStore), + localStore, + &test.DummySubscriber{}, + WithMaxRequestSize(10), + ) + require.NoError(t, err) + require.NoError(t, syncer.Start(ctx)) + + _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(10)...) + require.NoError(t, err) + // give store time to update heightSub index + time.Sleep(time.Millisecond * 100) + + localHead, err := localStore.Head(ctx) + require.NoError(t, err) + + remoteHead, err := remoteStore.Head(ctx) + require.NoError(t, err) + + err = syncer.doSync(ctx, localHead, remoteHead) + require.NoError(t, err) + // give store time to update heightSub index + time.Sleep(time.Millisecond * 100) + + newHead, err := localStore.Head(ctx) + require.NoError(t, err) + require.Equal(t, newHead.Height(), remoteHead.Height()) +} + func TestSyncCatchUp(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) From 6d874baff34c66fcbfa1a919de7462b0cfa8b5dd Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Thu, 2 Mar 2023 11:17:39 +0100 Subject: [PATCH 0425/1008] feat(modp2p): Make IP Colocation configurable for pubsub parameters (#1849) ## Overview Closes #1842 Co-authored-by: Hlib Kanunnikov --- nodebuilder/p2p/config.go | 3 +++ nodebuilder/p2p/pubsub.go | 24 +++++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index b968100095..64bd094de1 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -35,6 +35,9 @@ type Config struct { // ConnManager is a configuration tuple for ConnectionManager. ConnManager connManagerConfig RoutingTableRefreshPeriod time.Duration + + // Allowlist for IPColocation PubSub parameter, a list of string CIDRs + IPColocationWhitelist []string } // DefaultConfig returns default configuration for P2P subsystem. diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 088769180c..4fae70566a 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -2,6 +2,8 @@ package p2p import ( "context" + "fmt" + "net" "time" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -62,7 +64,11 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { // * lotus // * prysm topicScores := topicScoreParams(params.Network) - peerScores := peerScoreParams(isBootstrapper, params.Bootstrappers) + peerScores, err := peerScoreParams(isBootstrapper, params.Bootstrappers, cfg) + if err != nil { + return nil, err + } + peerScores.Topics = topicScores scoreThresholds := peerScoreThresholds() @@ -109,12 +115,21 @@ func topicScoreParams(network Network) map[string]*pubsub.TopicScoreParams { return mp } -func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers) *pubsub.PeerScoreParams { +func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers, cfg Config) (*pubsub.PeerScoreParams, error) { bootstrapperSet := map[peer.ID]struct{}{} for _, b := range bootstrappers { bootstrapperSet[b.ID] = struct{}{} } + ipColocFactWl := make([]*net.IPNet, 0, len(cfg.IPColocationWhitelist)) + for _, strIP := range cfg.IPColocationWhitelist { + _, ipNet, err := net.ParseCIDR(strIP) + if err != nil { + return nil, fmt.Errorf("error while parsing whitelist collocation CIDR string: %w", err) + } + ipColocFactWl = append(ipColocFactWl, ipNet) + } + // See // https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#the-score-function return &pubsub.PeerScoreParams{ @@ -137,8 +152,7 @@ func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers) *pubsub.P // The aim is to protect the PubSub from naive bots collocated on the same machine/datacenter IPColocationFactorThreshold: 10, IPColocationFactorWeight: -100, - // TODO(@Wondertan): Make this configurable, e.g. we might have Testground bots for testing purposes - IPColocationFactorWhitelist: nil, + IPColocationFactorWhitelist: ipColocFactWl, BehaviourPenaltyThreshold: 6, BehaviourPenaltyWeight: -10, @@ -150,7 +164,7 @@ func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers) *pubsub.P // this retains *non-positive* scores for 6 hours RetainScore: 6 * time.Hour, - } + }, nil } func peerScoreThresholds() *pubsub.PeerScoreThresholds { From 61a66a56b36e5772e892448c43ad01753d576af7 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 2 Mar 2023 16:29:23 +0100 Subject: [PATCH 0426/1008] refactor(libhead): unify VerifyAdjacent and VerifyNonAdjacent into Verify (#1777) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- core/exchange.go | 2 +- core/header_test.go | 3 +- das/daser_test.go | 3 +- fraud/testing.go | 3 +- .../headertest}/serde_test.go | 0 {headertest => header/headertest}/testing.go | 0 header/headertest/verify_test.go | 96 +++++++++++++++++++ header/verify.go | 74 +++++++------- headertest/verify_test.go | 67 ------------- libs/header/errors.go | 2 +- libs/header/header.go | 6 +- libs/header/p2p/exchange_test.go | 2 +- libs/header/p2p/session.go | 23 ++--- libs/header/store/store.go | 13 ++- libs/header/sync/sync_head.go | 7 +- libs/header/sync/sync_test.go | 2 +- libs/header/test/testing.go | 26 +---- nodebuilder/testing.go | 2 +- nodebuilder/tests/fraud_test.go | 3 +- share/eds/retriever_test.go | 3 +- 20 files changed, 179 insertions(+), 158 deletions(-) rename {headertest => header/headertest}/serde_test.go (100%) rename {headertest => header/headertest}/testing.go (100%) create mode 100644 header/headertest/verify_test.go delete mode 100644 headertest/verify_test.go diff --git a/core/exchange.go b/core/exchange.go index 29d32216ca..49e5074c0d 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -64,7 +64,7 @@ func (ce *Exchange) GetVerifiedRange( } for _, h := range headers { - err := from.VerifyAdjacent(h) + err := from.Verify(h) if err != nil { return nil, err } diff --git a/core/header_test.go b/core/header_test.go index 17337405c9..4d156dfd25 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -8,8 +8,9 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/rand" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/headertest" ) func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { diff --git a/das/daser_test.go b/das/daser_test.go index 8d0a33e01c..4de5331530 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -16,9 +16,10 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/headertest" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" diff --git a/fraud/testing.go b/fraud/testing.go index 355d0440e4..feb110440f 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -14,8 +14,9 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/headertest" ) type DummyService struct { diff --git a/headertest/serde_test.go b/header/headertest/serde_test.go similarity index 100% rename from headertest/serde_test.go rename to header/headertest/serde_test.go diff --git a/headertest/testing.go b/header/headertest/testing.go similarity index 100% rename from headertest/testing.go rename to header/headertest/testing.go diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go new file mode 100644 index 0000000000..9a338b8f4e --- /dev/null +++ b/header/headertest/verify_test.go @@ -0,0 +1,96 @@ +package headertest + +import ( + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + libhead "github.com/celestiaorg/celestia-node/libs/header" + + tmrand "github.com/tendermint/tendermint/libs/rand" +) + +func TestVerify(t *testing.T) { + h := NewTestSuite(t, 2).GenExtendedHeaders(3) + trusted, untrustedAdj, untrustedNonAdj := h[0], h[1], h[2] + tests := []struct { + prepare func() libhead.Header + err bool + }{ + { + prepare: func() libhead.Header { return untrustedAdj }, + err: false, + }, + { + prepare: func() libhead.Header { + return untrustedNonAdj + }, + err: false, + }, + { + prepare: func() libhead.Header { + untrusted := *untrustedAdj + untrusted.ValidatorsHash = tmrand.Bytes(32) + return &untrusted + }, + err: true, + }, + { + prepare: func() libhead.Header { + untrusted := *untrustedNonAdj + untrusted.Commit = NewTestSuite(t, 2).Commit(RandRawHeader(t)) + return &untrusted + }, + err: true, + }, + { + prepare: func() libhead.Header { + untrusted := *untrustedAdj + untrusted.RawHeader.LastBlockID.Hash = tmrand.Bytes(32) + return &untrusted + }, + err: true, + }, + { + prepare: func() libhead.Header { + untrustedAdj.RawHeader.Time = untrustedAdj.RawHeader.Time.Add(time.Minute) + return untrustedAdj + }, + err: true, + }, + { + prepare: func() libhead.Header { + untrustedAdj.RawHeader.Time = untrustedAdj.RawHeader.Time.Truncate(time.Hour) + return untrustedAdj + }, + err: true, + }, + { + prepare: func() libhead.Header { + untrustedAdj.RawHeader.ChainID = "toaster" + return untrustedAdj + }, + err: true, + }, + { + prepare: func() libhead.Header { + untrustedAdj.RawHeader.Height++ + return untrustedAdj + }, + err: true, + }, + } + + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + err := trusted.Verify(test.prepare()) + if test.err { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/header/verify.go b/header/verify.go index e9a12e2a98..eebb39c08b 100644 --- a/header/verify.go +++ b/header/verify.go @@ -10,53 +10,47 @@ import ( libhead "github.com/celestiaorg/celestia-node/libs/header" ) -// TODO(@Wondertan): We should request TrustingPeriod from the network's state params or -// listen for network params changes to always have a topical value. - -// VerifyNonAdjacent validates non-adjacent untrusted header against trusted 'eh'. -func (eh *ExtendedHeader) VerifyNonAdjacent(untrusted libhead.Header) error { +// Verify validates given untrusted Header against trusted ExtendedHeader. +func (eh *ExtendedHeader) Verify(untrusted libhead.Header) error { untrst, ok := untrusted.(*ExtendedHeader) if !ok { - return &libhead.VerifyError{Reason: fmt.Errorf("invalid header type: expected %T, got %T", eh, untrusted)} - } - if err := eh.verify(untrst); err != nil { - return &libhead.VerifyError{Reason: err} + // if the header of the type was given, something very wrong happens + panic(fmt.Sprintf("invalid header type: expected %T, got %T", eh, untrusted)) } - // Ensure that untrusted commit has enough of trusted commit's power. - err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel) - if err != nil { + if err := eh.verify(untrst); err != nil { return &libhead.VerifyError{Reason: err} } - return nil -} + isAdjacent := eh.Height()+1 == untrst.Height() + if isAdjacent { + // Optimized verification for adjacent headers + // Check the validator hashes are the same + if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) { + return &libhead.VerifyError{ + Reason: fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", + eh.NextValidatorsHash, + untrst.ValidatorsHash, + ), + } + } -// VerifyAdjacent validates adjacent untrusted header against trusted 'eh'. -func (eh *ExtendedHeader) VerifyAdjacent(untrusted libhead.Header) error { - untrst, ok := untrusted.(*ExtendedHeader) - if !ok { - return &libhead.VerifyError{Reason: fmt.Errorf("invalid header type: expected %T, got %T", eh, untrusted)} - } - if untrst.Height() != eh.Height()+1 { - return &libhead.ErrNonAdjacent{ - Head: eh.Height(), - Attempted: untrst.Height(), + if !bytes.Equal(untrst.LastHeader(), eh.Hash()) { + return &libhead.VerifyError{ + Reason: fmt.Errorf("expected new header to point to last header hash (%X), but got %X)", + eh.Hash(), + untrst.LastHeader(), + ), + } } - } - if err := eh.verify(untrst); err != nil { - return &libhead.VerifyError{Reason: err} + return nil } - // Check the validator hashes are the same - if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) { - return &libhead.VerifyError{ - Reason: fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", - eh.NextValidatorsHash, - untrst.ValidatorsHash, - ), - } + // Ensure that untrusted commit has enough of trusted commit's power. + err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel) + if err != nil { + return &libhead.VerifyError{Reason: err} } return nil @@ -67,13 +61,17 @@ func (eh *ExtendedHeader) VerifyAdjacent(untrusted libhead.Header) error { var clockDrift = 10 * time.Second // verify performs basic verification of untrusted header. -func (eh *ExtendedHeader) verify(untrst *ExtendedHeader) error { +func (eh *ExtendedHeader) verify(untrst libhead.Header) error { + if untrst.Height() <= eh.Height() { + return fmt.Errorf("untrusted header height(%d) <= current trusted header(%d)", untrst.Height(), eh.Height()) + } + if untrst.ChainID() != eh.ChainID() { - return fmt.Errorf("new untrusted header has different chain %s, not %s", untrst.ChainID(), eh.ChainID()) + return fmt.Errorf("untrusted header has different chain %s, not %s", untrst.ChainID(), eh.ChainID()) } if !untrst.Time().After(eh.Time()) { - return fmt.Errorf("expected new untrusted header time %v to be after old header time %v", untrst.Time(), eh.Time()) + return fmt.Errorf("untrusted header time(%v) must be after current trusted header(%v)", untrst.Time(), eh.Time()) } now := time.Now() diff --git a/headertest/verify_test.go b/headertest/verify_test.go deleted file mode 100644 index 03aab15b89..0000000000 --- a/headertest/verify_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package headertest - -import ( - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - tmrand "github.com/tendermint/tendermint/libs/rand" -) - -func TestVerifyAdjacent(t *testing.T) { - h := NewTestSuite(t, 2).GenExtendedHeaders(2) - trusted, untrusted := h[0], h[1] - tests := []struct { - prepare func() - err bool - }{ - { - prepare: func() {}, - err: false, - }, - { - prepare: func() { - untrusted.ValidatorsHash = tmrand.Bytes(32) - }, - err: true, - }, - { - prepare: func() { - untrusted.RawHeader.Time = untrusted.RawHeader.Time.Add(time.Minute) - }, - err: true, - }, - { - prepare: func() { - untrusted.RawHeader.Time = untrusted.RawHeader.Time.Truncate(time.Hour) - }, - err: true, - }, - { - prepare: func() { - untrusted.RawHeader.ChainID = "toaster" - }, - err: true, - }, - { - prepare: func() { - untrusted.RawHeader.Height++ - }, - err: true, - }, - } - - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - test.prepare() - err := trusted.VerifyAdjacent(untrusted) - if test.err { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/libs/header/errors.go b/libs/header/errors.go index 6f00876a93..8a04f6b686 100644 --- a/libs/header/errors.go +++ b/libs/header/errors.go @@ -2,7 +2,7 @@ package header import "fmt" -// VerifyError is thrown on during VerifyAdjacent and VerifyNonAdjacent if verification fails. +// VerifyError is thrown on during Verify if it fails. type VerifyError struct { Reason error } diff --git a/libs/header/header.go b/libs/header/header.go index 72d1698f4d..cdf27db423 100644 --- a/libs/header/header.go +++ b/libs/header/header.go @@ -21,10 +21,8 @@ type Header interface { LastHeader() Hash // Time returns time when header was created. Time() time.Time - // VerifyAdjacent validates adjacent untrusted header against trusted header. - VerifyAdjacent(Header) error - // VerifyNonAdjacent validates non-adjacent untrusted header against trusted header. - VerifyNonAdjacent(Header) error + // Verify validates given untrusted Header against trusted Header. + Verify(Header) error // Validate performs basic validation to check for missed/incorrect fields. Validate() error diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index 7d6175d250..c9a24141ac 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -79,7 +79,7 @@ func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) t.Cleanup(cancel) _, err := exchg.GetVerifiedRange(ctx, h, 3) - require.Error(t, err) + assert.Error(t, err) // ensure that peer was added to the blacklist peers := exchg.peerTracker.connGater.ListBlockedPeers() diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index d637acd9c1..b00ac57a96 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -3,6 +3,7 @@ package p2p import ( "context" "errors" + "fmt" "sort" "time" @@ -160,6 +161,7 @@ func (s *session[H]) doRequest( case <-s.ctx.Done(): case s.reqCh <- req: } + log.Errorw("processing response", "err", err) return } log.Debugw("request headers from peer succeeded", "peer", stat.peerID, "amount", req.Amount) @@ -201,23 +203,18 @@ func (s *session[H]) validate(headers []H) error { return nil } - // verify that the first header in range is valid against the trusted header. - err := s.from.VerifyNonAdjacent(headers[0]) - if err != nil { - return nil - } - - if len(headers) == 1 { - return nil - } - - trusted := headers[0] + trusted := s.from // verify that the whole range is valid. - for i := 1; i < len(headers); i++ { - err = trusted.VerifyAdjacent(headers[i]) + for i := 0; i < len(headers); i++ { + err := trusted.Verify(headers[i]) if err != nil { return err } + if trusted.Height()+1 != headers[i].Height() { + // Exchange requires requested ranges to always consist of adjacent headers + return fmt.Errorf("peer sent valid but non-adjacent header") + } + trusted = headers[i] } return nil diff --git a/libs/header/store/store.go b/libs/header/store/store.go index 1f36fa839a..7daad91229 100644 --- a/libs/header/store/store.go +++ b/libs/header/store/store.go @@ -262,7 +262,7 @@ func (s *Store[H]) GetVerifiedRange( } for _, h := range headers { - err := from.VerifyAdjacent(h) + err := from.Verify(h) if err != nil { return nil, err } @@ -309,7 +309,16 @@ func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { // collect valid headers verified := make([]H, 0, lh) for i, h := range headers { - err = head.VerifyAdjacent(h) + // currently store requires all headers to be appended sequentially and adjacently + // TODO(@Wondertan): Further pruning friendly Store design should reevaluate this requirement + if h.Height() != head.Height()+1 { + return 0, &header.ErrNonAdjacent{ + Head: head.Height(), + Attempted: h.Height(), + } + } + + err = head.Verify(h) if err != nil { var verErr *header.VerifyError if errors.As(err, &verErr) { diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go index c6a007abd9..6ff046916c 100644 --- a/libs/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -118,7 +118,7 @@ func (s *Syncer[H]) incomingNetHead(ctx context.Context, netHead H) pubsub.Valid var nonAdj *header.ErrNonAdjacent if errors.As(err, &nonAdj) { // not adjacent, maybe we've missed a few headers or its from the past - log.Debugw("attempted to append non-adjacent header", "store head", + log.Debugw("attempted to append gossiped non-adjacent header", "store head", nonAdj.Head, "attempted", nonAdj.Attempted) } else { var verErr *header.VerifyError @@ -168,7 +168,7 @@ func (s *Syncer[H]) validate(ctx context.Context, new H) pubsub.ValidationResult return pubsub.ValidationIgnore } // perform verification - err = sbjHead.VerifyNonAdjacent(new) + err = sbjHead.Verify(new) var verErr *header.VerifyError if errors.As(err, &verErr) { log.Errorw("invalid network header", @@ -183,6 +183,9 @@ func (s *Syncer[H]) validate(ctx context.Context, new H) pubsub.ValidationResult return pubsub.ValidationAccept } +// TODO(@Wondertan): We should request TrustingPeriod from the network's state params or +// listen for network params changes to always have a topical value. + // isExpired checks if header is expired against trusting period. func isExpired(header header.Header, period time.Duration) bool { expirationTime := header.Time().Add(period) diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index 89d3e9ebb8..44dd0179b9 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -288,7 +288,7 @@ func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { require.Equal(t, h[len(h)-1].Height(), int64(21)) header := h[0] for i := 1; i < len(h); i++ { - require.NoError(t, header.VerifyAdjacent(h[i])) + require.NoError(t, header.Verify(h[i])) header = h[i] } } diff --git a/libs/header/test/testing.go b/libs/header/test/testing.go index b1c9016a44..5f0a3d4cc0 100644 --- a/libs/header/test/testing.go +++ b/libs/header/test/testing.go @@ -6,7 +6,6 @@ import ( "crypto/rand" "encoding/binary" "encoding/gob" - "errors" "fmt" "math" "testing" @@ -84,35 +83,18 @@ func (d *DummyHeader) IsExpired(period time.Duration) bool { return expirationTime.Before(time.Now()) } -func (d *DummyHeader) VerifyAdjacent(other header.Header) error { - if other.Height() != d.Height()+1 { - return fmt.Errorf("invalid Height, expected: %d, got: %d", d.Height()+1, other.Height()) - } - - if err := d.verify(other); err != nil { - return err - } - - return nil -} - -func (d *DummyHeader) VerifyNonAdjacent(other header.Header) error { - return d.verify(other) -} - -func (d *DummyHeader) verify(header header.Header) error { - // wee1 +func (d *DummyHeader) Verify(header header.Header) error { epsilon := 10 * time.Second if header.Time().After(time.Now().Add(epsilon)) { - return errors.New("header Time too far in the future") + return fmt.Errorf("header Time too far in the future") } if header.Height() <= d.Height() { - return errors.New("expected new header Height to be larger than old header Time") + return fmt.Errorf("expected new header Height to be larger than old header Time") } if header.Time().Before(d.Time()) { - return errors.New("expected new header Time to be after old header Time") + return fmt.Errorf("expected new header Time to be after old header Time") } return nil diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index ccbb9f3a1d..78063254f8 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -12,7 +12,7 @@ import ( "github.com/celestiaorg/celestia-node/core" coreheader "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/headertest" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/libs/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/header" diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index ba065a2551..ac25106f95 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -10,8 +10,9 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/headertest" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index cc398890d0..182d57205c 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" @@ -19,7 +21,6 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" From e48ffd5d929a912c6354a158184971be2725ca63 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 3 Mar 2023 11:21:45 +0200 Subject: [PATCH 0427/1008] fix(libs/header/p2p): store already fetched headers in case of timeout (#1803) --- libs/header/p2p/helpers.go | 37 +++++++++++++------- libs/header/p2p/session.go | 70 ++++++++++++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go index 27f8f02ff5..d703c1db3f 100644 --- a/libs/header/p2p/helpers.go +++ b/libs/header/p2p/helpers.go @@ -72,28 +72,41 @@ func sendMessage( } headers := make([]*p2p_pb.HeaderResponse, 0) - totalRequestSize := uint64(0) + + var totalRespLn uint64 for i := 0; i < int(req.Amount); i++ { resp := new(p2p_pb.HeaderResponse) - msgSize, err := serde.Read(stream, resp) - if err != nil { - if err == io.EOF { - break - } - stream.Reset() //nolint:errcheck - return nil, 0, 0, fmt.Errorf("header/p2p: failed to read a response: %w", err) + respLn, readErr := serde.Read(stream, resp) + if readErr != nil { + err = readErr + break } - totalRequestSize += uint64(msgSize) + totalRespLn += uint64(respLn) headers = append(headers, resp) } duration := time.Since(startTime).Milliseconds() - if err = stream.Close(); err != nil { - log.Errorw("closing stream", "err", err) + + // we allow the server side to explicitly close the connection + // if it does not have the requested range. + // In this case, server side will send us a response with ErrNotFound status code inside + // and then will close the stream. + // If the server side will have a part of the requested range, then it will send this part + // and then will close the connection + if err == io.EOF { + err = nil } - return headers, totalRequestSize, uint64(duration), nil + if err == nil { + if closeErr := stream.Close(); closeErr != nil { + log.Errorw("closing stream", "err", closeErr) + } + } else { + // reset stream in case of an error + stream.Reset() //nolint:errcheck + } + return headers, totalRespLn, uint64(duration), err } // convertStatusCodeToError converts passed status code into an error. diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index b00ac57a96..983ff02c23 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -14,6 +14,9 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) +// errEmptyResponse means that server side closes the connection without sending at least 1 response. +var errEmptyResponse = errors.New("empty response") + type option[H header.Header] func(*session[H]) func withValidation[H header.Header](from H) option[H] { @@ -66,7 +69,7 @@ func newSession[H header.Header]( return ses } -// GetRangeByHeight requests headers from different peers. +// getRangeByHeight requests headers from different peers. func (s *session[H]) getRangeByHeight( ctx context.Context, from, amount, headersPerPeer uint64, @@ -83,7 +86,8 @@ func (s *session[H]) getRangeByHeight( } headers := make([]H, 0, amount) - for i := 0; i < len(requests); i++ { +LOOP: + for { select { case <-s.ctx.Done(): return nil, errors.New("header/p2p: exchange is closed") @@ -91,8 +95,12 @@ func (s *session[H]) getRangeByHeight( return nil, ctx.Err() case res := <-result: headers = append(headers, res...) + if uint64(len(headers)) == amount { + break LOOP + } } } + sort.Slice(headers, func(i, j int) bool { return headers[i].Height() < headers[j].Height() }) @@ -140,23 +148,25 @@ func (s *session[H]) doRequest( r, size, duration, err := sendMessage(ctx, s.host, stat.peerID, s.protocolID, req) if err != nil { - log.Errorw("requesting headers from peer failed", "failed peer", stat.peerID, "err", err) - select { - case <-s.ctx.Done(): - case s.reqCh <- req: - stat.decreaseScore() - log.Debug("retrying the request from different peer") - } - return + // we should not punish peer at this point and should try to parse responses, despite that error was received. + log.Debugw("requesting headers from peer failed", "peer", stat.peerID, "err", err) } h, err := s.processResponse(r) if err != nil { switch err { - case header.ErrNotFound: + case header.ErrNotFound, errEmptyResponse: + stat.decreaseScore() default: s.peerTracker.blockPeer(stat.peerID, err) } + + // exclude header.ErrNotFound from being logged as it is a `valid` error + // and peer may not have the range(peer just connected and syncing). + if err != header.ErrNotFound { + log.Errorw("processing headers response failed", "peer", stat.peerID, "err", err) + } + select { case <-s.ctx.Done(): case s.reqCh <- req: @@ -164,30 +174,60 @@ func (s *session[H]) doRequest( log.Errorw("processing response", "err", err) return } - log.Debugw("request headers from peer succeeded", "peer", stat.peerID, "amount", req.Amount) + + log.Debugw("request headers from peer succeeded", + "peer", stat.peerID, + "receivedAmount", len(h), + "requestedAmount", req.Amount, + ) + + defer func() { + stat.updateStats(size, duration) + }() + + // ensure that we received the correct amount of headers. + if uint64(len(h)) < req.Amount { + from := uint64(h[len(h)-1].Height()) + amount := req.Amount - from + + select { + case <-s.ctx.Done(): + return + // create a new request with the remaining headers. + // prepareRequests will return a slice with 1 element at this point + case s.reqCh <- prepareRequests(from+1, amount, req.Amount)[0]: + log.Debugw("sending additional request to get remaining headers") + } + } + // send headers to the channel, update peer stats and return peer to the queue, so it can be // re-used in case if there are other requests awaiting headers <- h - stat.updateStats(size, duration) s.queue.push(stat) } // processResponse converts HeaderResponse to Header. func (s *session[H]) processResponse(responses []*p2p_pb.HeaderResponse) ([]H, error) { + if len(responses) == 0 { + return nil, errEmptyResponse + } + headers := make([]H, 0) for _, resp := range responses { err := convertStatusCodeToError(resp.StatusCode) if err != nil { return nil, err } + var empty H header := empty.New() err = header.UnmarshalBinary(resp.Body) if err != nil { - return nil, errors.New("unmarshalling error") + return nil, err } headers = append(headers, header.(H)) } + if len(headers) == 0 { return nil, header.ErrNotFound } @@ -228,6 +268,7 @@ func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.HeaderReques request := &p2p_pb.HeaderRequest{ Data: &p2p_pb.HeaderRequest_Origin{Origin: from}, } + if amount < headersPerPeer { requestSize = amount amount = 0 @@ -236,6 +277,7 @@ func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.HeaderReques from += headersPerPeer requestSize = headersPerPeer } + request.Amount = requestSize requests = append(requests, request) } From b4f8ef324d6e5bff74cfa52ef3537d701233de0a Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:44:35 +0300 Subject: [PATCH 0428/1008] fix(share/peer-manager): round robin pointer out of range (#1859) Fixes possible situations when pointer gets out of range --- share/p2p/peers/pool.go | 16 ++++++---------- share/p2p/peers/pool_test.go | 6 ++++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index 89d79291bf..a31c4345b7 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -54,6 +54,11 @@ func (p *pool) tryGet() (peer.ID, bool) { return "", false } + // if pointer is out of range, point to first element + if p.nextIdx > len(p.peersList)-1 { + p.nextIdx = 0 + } + start := p.nextIdx for { peerID := p.peersList[p.nextIdx] @@ -137,7 +142,7 @@ func (p *pool) remove(peers ...peer.ID) { // cleanup will reduce memory footprint of pool. func (p *pool) cleanup() { newList := make([]peer.ID, 0, p.activeCount) - for idx, peerID := range p.peersList { + for _, peerID := range p.peersList { status := p.statuses[peerID] switch status { case active, cooldown: @@ -145,15 +150,6 @@ func (p *pool) cleanup() { case removed: delete(p.statuses, peerID) } - - if idx == p.nextIdx { - // if peer is not active and no more active peers left in list point to first peer - if status != active && len(newList) >= p.activeCount { - p.nextIdx = 0 - continue - } - p.nextIdx = len(newList) - } } p.peersList = newList } diff --git a/share/p2p/peers/pool_test.go b/share/p2p/peers/pool_test.go index 3f4f6a5b9a..ac9d38f261 100644 --- a/share/p2p/peers/pool_test.go +++ b/share/p2p/peers/pool_test.go @@ -137,8 +137,10 @@ func TestPool(t *testing.T) { p.remove(peers[2]) require.Equal(t, len(peers)-3, p.activeCount) require.Equal(t, len(peers)-3, len(p.statuses)) - // nextIdx pointer should be updated - require.Equal(t, 0, p.nextIdx) + + // nextIdx pointer should be updated after next tryGet + p.tryGet() + require.Equal(t, 1, p.nextIdx) }) t.Run("cooldown blocks get", func(t *testing.T) { From 03ef11312efff1da2cb934ac6752ee08e4e99f92 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:55:10 +0300 Subject: [PATCH 0429/1008] fix(share/test): TestShareAvailable_DisconnectedFullNodes wait before exit (#1858) fix TestShareAvailable_DisconnectedFullNodes test logging after exit --- share/availability/full/reconstruction_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/share/availability/full/reconstruction_test.go b/share/availability/full/reconstruction_test.go index adde01523d..03b998d49e 100644 --- a/share/availability/full/reconstruction_test.go +++ b/share/availability/full/reconstruction_test.go @@ -4,6 +4,7 @@ package full import ( "context" + "sync" "testing" "time" @@ -213,9 +214,13 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { lights1, lights2 := make( []*availability_test.TestNode, lightNodes/2), make([]*availability_test.TestNode, lightNodes/2) + + var wg sync.WaitGroup + wg.Add(lightNodes) for i := 0; i < len(lights1); i++ { lights1[i] = light.Node(net) go func(i int) { + defer wg.Done() err := lights1[i].SharesAvailable(ctx, root) if err != nil { t.Log("light1 errors:", err) @@ -224,6 +229,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { lights2[i] = light.Node(net) go func(i int) { + defer wg.Done() err := lights2[i].SharesAvailable(ctx, root) if err != nil { t.Log("light2 errors:", err) @@ -260,4 +266,6 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { require.NoError(t, err, share.ErrNotAvailable) err = full2.SharesAvailable(ctx, root) require.NoError(t, err, share.ErrNotAvailable) + // wait for all routines to finish before exit, in case there are any errors to log + wg.Wait() } From e15f1ebf3322b7348d7c5ef82afbc57be0814d19 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 3 Mar 2023 14:01:31 +0100 Subject: [PATCH 0430/1008] refactor(nodebuilder/tests/swamp): Wait til height 2 to begin filling blocks (#1847) Resolves flakiness for macos users. Originally part of #1548. --- nodebuilder/tests/swamp/swamp.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 5e1426f28d..ee188af2e1 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -120,15 +120,10 @@ func (s *Swamp) WaitTillHeight(ctx context.Context, height int64) libhead.Hash { case <-ctx.Done(): require.NoError(s.t, ctx.Err()) case <-t.C: - status, err := s.ClientContext.Client.Status(ctx) + latest, err := s.ClientContext.LatestHeight() require.NoError(s.t, err) - - latest := status.SyncInfo.LatestBlockHeight - switch { - case latest == height: - return libhead.Hash(status.SyncInfo.LatestBlockHash) - case latest > height: - res, err := s.ClientContext.Client.Block(ctx, &height) + if latest >= height { + res, err := s.ClientContext.Client.Block(ctx, &latest) require.NoError(s.t, err) return libhead.Hash(res.BlockID.Hash) } @@ -163,7 +158,8 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { // setupGenesis sets up genesis Header. // This is required to initialize and start correctly. func (s *Swamp) setupGenesis(ctx context.Context) { - s.WaitTillHeight(ctx, 1) + // ensure core has surpassed genesis block + s.WaitTillHeight(ctx, 2) store, err := eds.NewStore(s.t.TempDir(), ds_sync.MutexWrap(ds.NewMapDatastore())) require.NoError(s.t, err) From 218b71bad4cbc0eaa0f9ad0717c5381ecab68238 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:19:26 +0300 Subject: [PATCH 0431/1008] fix(share/ipld): use parent context for ipld session instead of request context (#1855) Introduction of cascade getter created a case where ctx is created just before call to `ipld.GetShare` and this local context is canceled right after the call. It turns out ipld.Session is created with ctx from first call that created it, instead of parent context from Availability. Session context should common parent context from ShareAvailable call, instead of local single request call. --- share/getters/ipld.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/share/getters/ipld.go b/share/getters/ipld.go index 0c5c1fa63e..d1aecda481 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -111,12 +111,13 @@ var sessionKey = &session{} type session struct { sync.Mutex atomic.Pointer[blockservice.Session] + ctx context.Context } // WithSession stores an empty session in the context, indicating that a blockservice session should // be created. func WithSession(ctx context.Context) context.Context { - return context.WithValue(ctx, sessionKey, &session{}) + return context.WithValue(ctx, sessionKey, &session{ctx: ctx}) } func getGetter(ctx context.Context, service blockservice.BlockService) blockservice.BlockGetter { @@ -134,7 +135,7 @@ func getGetter(ctx context.Context, service blockservice.BlockService) blockserv defer s.Unlock() val = s.Load() if val == nil { - val = blockservice.NewSession(ctx, service) + val = blockservice.NewSession(s.ctx, service) s.Store(val) } return val From 671e779f5bc8d59cecd410ea12842d1ae44db7d6 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 3 Mar 2023 16:51:46 +0300 Subject: [PATCH 0432/1008] lint(everything): sort imports (#1806) Sort imports by using amazing consol tool. Add a command to use it with MakeFile and as part of `fmt`. IDE users could set filewatcher rule with same tool, to forget about unsorted imports problem forever! --- .golangci.yml | 2 +- Makefile | 16 +++++++++++++--- api/gateway/share_test.go | 4 ++-- api/gateway/state.go | 4 ++-- api/rpc_test.go | 3 +-- core/client.go | 1 - core/eds.go | 1 - core/header_test.go | 3 +-- das/daser.go | 3 +-- das/daser_test.go | 3 +-- das/state_test.go | 1 - das/worker.go | 3 +-- fraud/testing.go | 3 +-- header/headertest/verify_test.go | 3 +-- libs/header/p2p/exchange_test.go | 3 +-- nodebuilder/node_test.go | 4 +--- nodebuilder/p2p/opts.go | 3 +-- nodebuilder/share/constructors.go | 1 + nodebuilder/share/share_test.go | 1 + nodebuilder/state/core.go | 1 + nodebuilder/state/state.go | 4 ++-- nodebuilder/tests/fraud_test.go | 3 +-- nodebuilder/tests/reconstruct_test.go | 5 ++--- share/availability/cache/availability.go | 1 + share/availability/full/availability.go | 3 +-- share/availability/light/availability_test.go | 1 + share/availability/test/testing.go | 1 + share/eds/byzantine/bad_encoding.go | 3 ++- share/eds/byzantine/bad_encoding_test.go | 3 ++- share/eds/byzantine/byzantine.go | 3 ++- share/eds/byzantine/share_proof.go | 3 ++- share/eds/byzantine/share_proof_test.go | 1 + share/eds/retriever.go | 3 ++- share/eds/retriever_test.go | 3 +-- share/eds/store_test.go | 4 ++-- share/get.go | 3 ++- share/getter.go | 4 ++-- share/getters/ipld.go | 6 +++--- share/getters/shrex.go | 6 +++--- share/getters/shrex_test.go | 6 +++--- share/getters/store.go | 6 +++--- share/getters/tee.go | 6 +++--- share/getters/testing.go | 3 ++- share/getters/utils.go | 6 +++--- share/p2p/middleware.go | 1 - share/p2p/peers/manager.go | 3 +-- share/p2p/peers/manager_test.go | 5 ++--- share/p2p/shrexeds/client.go | 6 +++--- share/p2p/shrexeds/exchange_test.go | 4 ++-- share/p2p/shrexnd/client.go | 6 +++--- share/p2p/shrexsub/pubsub.go | 1 - state/integration_test.go | 3 +-- 52 files changed, 90 insertions(+), 89 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 599e5bdea1..5f7b13e6a5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -63,6 +63,6 @@ linters-settings: misspell: locale: US goimports: - local-prefixes: github.com/celestiaorg + local-prefixes: github.com/celestiaorg/celestia-node dupl: threshold: 200 diff --git a/Makefile b/Makefile index 974f156c8a..3ee18e3e3f 100644 --- a/Makefile +++ b/Makefile @@ -78,16 +78,16 @@ install-key: .PHONY: install-key ## fmt: Formats only *.go (excluding *.pb.go *pb_test.go). Runs `gofmt & goimports` internally. -fmt: +fmt: sort-imports @find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs gofmt -w -s @find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs goimports -w -local github.com/celestiaorg @go mod tidy -compat=1.17 @cfmt -w -m=100 ./... @markdownlint --fix --quiet --config .markdownlint.yaml . -.PHONY: fmt +.PHONY: sort-imports ## lint: Linting *.go files using golangci-lint. Look for .golangci.yml for the list of linters. -lint: +lint: lint-imports @echo "--> Running linter" @golangci-lint run @markdownlint --config .markdownlint.yaml '**/*.md' @@ -155,3 +155,13 @@ openrpc-gen: @go run ./cmd/docgen fraud header state share das p2p node .PHONY: openrpc-gen +lint-imports: + @echo "--> Running imports linter" + @for file in `find . -type f -name '*.go'`; \ + do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout $$file; \ + done; +.PHONY: lint-imports + +sort-imports: + goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... +.PHONY: sort-imports diff --git a/api/gateway/share_test.go b/api/gateway/share_test.go index 58bf458515..aeccc9eb02 100644 --- a/api/gateway/share_test.go +++ b/api/gateway/share_test.go @@ -4,9 +4,9 @@ import ( "bytes" "testing" - "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" ) func Test_dataFromShares(t *testing.T) { diff --git a/api/gateway/state.go b/api/gateway/state.go index 87288d7d13..3ccaf92e1c 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -6,10 +6,10 @@ import ( "errors" "net/http" - "github.com/celestiaorg/celestia-node/state" - "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" + + "github.com/celestiaorg/celestia-node/state" ) const ( diff --git a/api/rpc_test.go b/api/rpc_test.go index 581e382ac7..855aea4422 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -15,8 +15,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/fx" - headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" - "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/api/rpc/perms" @@ -28,6 +26,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraudMock "github.com/celestiaorg/celestia-node/nodebuilder/fraud/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/header" + headerMock "github.com/celestiaorg/celestia-node/nodebuilder/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/node" nodeMock "github.com/celestiaorg/celestia-node/nodebuilder/node/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" diff --git a/core/client.go b/core/client.go index 4ea3ac10a9..9636619b02 100644 --- a/core/client.go +++ b/core/client.go @@ -4,7 +4,6 @@ import ( "fmt" retryhttp "github.com/hashicorp/go-retryablehttp" - "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client/http" ) diff --git a/core/eds.go b/core/eds.go index b5f9918945..5ffa52ac32 100644 --- a/core/eds.go +++ b/core/eds.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/filecoin-project/dagstore" - "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" diff --git a/core/header_test.go b/core/header_test.go index 4d156dfd25..4f70963cde 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -8,9 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/rand" - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" ) func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { diff --git a/das/daser.go b/das/daser.go index a68001b68b..97b7f99623 100644 --- a/das/daser.go +++ b/das/daser.go @@ -6,8 +6,6 @@ import ( "fmt" "sync/atomic" - "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" - "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -16,6 +14,7 @@ import ( libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) var log = logging.Logger("das") diff --git a/das/daser_test.go b/das/daser_test.go index 4de5331530..2dd4389488 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -16,10 +16,9 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" diff --git a/das/state_test.go b/das/state_test.go index f67e9a47af..da326cc6e8 100644 --- a/das/state_test.go +++ b/das/state_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.uber.org/multierr" ) diff --git a/das/worker.go b/das/worker.go index 516cd833a3..7d99f72d20 100644 --- a/das/worker.go +++ b/das/worker.go @@ -7,12 +7,11 @@ import ( "sync" "time" - "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" - "go.uber.org/multierr" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) type worker struct { diff --git a/fraud/testing.go b/fraud/testing.go index feb110440f..bfed3eefa8 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -14,9 +14,8 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" ) type DummyService struct { diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go index 9a338b8f4e..4dce036b94 100644 --- a/header/headertest/verify_test.go +++ b/header/headertest/verify_test.go @@ -6,10 +6,9 @@ import ( "time" "github.com/stretchr/testify/assert" + tmrand "github.com/tendermint/tendermint/libs/rand" libhead "github.com/celestiaorg/celestia-node/libs/header" - - tmrand "github.com/tendermint/tendermint/libs/rand" ) func TestVerify(t *testing.T) { diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index c9a24141ac..e425317ba8 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -13,13 +13,12 @@ import ( blankhost "github.com/libp2p/go-libp2p/p2p/host/blank" "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + swarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/go-libp2p-messenger/serde" - swarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" - "github.com/celestiaorg/celestia-node/libs/header" headerMock "github.com/celestiaorg/celestia-node/libs/header/mocks" p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 3ed16d51ee..0f4809495b 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -5,16 +5,14 @@ import ( "net/http" "net/http/httptest" "strconv" + "strings" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" - collectormetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" "google.golang.org/protobuf/proto" - "strings" - "github.com/celestiaorg/celestia-node/nodebuilder/node" ) diff --git a/nodebuilder/p2p/opts.go b/nodebuilder/p2p/opts.go index 6e9bf64eef..9501dfe8e1 100644 --- a/nodebuilder/p2p/opts.go +++ b/nodebuilder/p2p/opts.go @@ -3,9 +3,8 @@ package p2p import ( "encoding/hex" - hst "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/crypto" + hst "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/libs/fxutil" diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index ba32c97b84..1394a5c9e8 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -12,6 +12,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" disc "github.com/celestiaorg/celestia-node/share/availability/discovery" diff --git a/nodebuilder/share/share_test.go b/nodebuilder/share/share_test.go index 4fd6eb9365..388fd9af07 100644 --- a/nodebuilder/share/share_test.go +++ b/nodebuilder/share/share_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" ) diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index 001bdd420f..e9f240073e 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -2,6 +2,7 @@ package state import ( apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/core" diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 36965050d8..07f61e5952 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -5,9 +5,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/celestiaorg/celestia-node/state" - "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/state" ) var _ Module = (*API)(nil) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index ac25106f95..75615938b7 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -10,9 +10,8 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 2f62464e28..8475f62ace 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -16,12 +16,11 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" - "github.com/celestiaorg/celestia-node/share/availability/light" - "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/share/eds" ) /* diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index e9ee88f867..bd061b4e9b 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -11,6 +11,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" ) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 8b35dbaeac..76476f3824 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -7,10 +7,9 @@ import ( ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 7918464a04..def9095d86 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -19,6 +19,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" appshares "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 65cf88f9da..b2fb780bd4 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" ) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 7c3367026a..0ba2a34b52 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -7,11 +7,12 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/rsmt2d" ) func init() { diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 80cb8083e9..6604ea1e17 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -9,10 +9,11 @@ import ( core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/rsmt2d" ) // TestIncorrectBadEncodingFraudProof asserts that BEFP is not generated for the correct data diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index 5f13f06f17..67a6ee54c2 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -7,8 +7,9 @@ import ( "github.com/ipfs/go-blockservice" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share/ipld" ) // ErrByzantine is a thrown when recovered data square is not correct diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 2c3dcb175d..86eaad54a8 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -8,10 +8,11 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/minio/sha256-simd" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/celestia-node/share" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/nmt" ) var log = logging.Logger("share/byzantine") diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go index e98c424597..501f4b40d9 100644 --- a/share/eds/byzantine/share_proof_test.go +++ b/share/eds/byzantine/share_proof_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" ) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 6bbdb8c1f4..68390ea7d6 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -18,10 +18,11 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/rsmt2d" ) var ( diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 182d57205c..4c1985b36e 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -12,8 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" @@ -21,6 +19,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" diff --git a/share/eds/store_test.go b/share/eds/store_test.go index e59f740f3d..6216a0ca20 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -14,10 +14,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" ) func TestEDSStore(t *testing.T) { diff --git a/share/get.go b/share/get.go index 31385a281a..c093ccf6dd 100644 --- a/share/get.go +++ b/share/get.go @@ -7,8 +7,9 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/share/ipld" ) // GetShare fetches and returns the data for leaf `leafIndex` of root `rootCid`. diff --git a/share/getter.go b/share/getter.go index f740bcaf24..16b727bc88 100644 --- a/share/getter.go +++ b/share/getter.go @@ -6,11 +6,11 @@ import ( "github.com/minio/sha256-simd" - "github.com/celestiaorg/celestia-node/share/ipld" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share/ipld" ) // Getter interface provides a set of accessors for shares by the Root. diff --git a/share/getters/ipld.go b/share/getters/ipld.go index d1aecda481..d8004ddb42 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -10,13 +10,13 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" - - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/rsmt2d" ) var _ share.Getter = (*IPLDGetter)(nil) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 6302c243f7..97af7529ac 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -6,14 +6,14 @@ import ( "fmt" "time" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" - - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/rsmt2d" ) var _ share.Getter = (*ShrexGetter)(nil) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 41a39749cf..44a236d38f 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -13,13 +13,13 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" - - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/rsmt2d" ) func TestGetSharesWithProofByNamespace(t *testing.T) { diff --git a/share/getters/store.go b/share/getters/store.go index cc87f0e6f1..9c1fdfc70c 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -9,13 +9,13 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/ipld" - - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/rsmt2d" ) var _ share.Getter = (*StoreGetter)(nil) diff --git a/share/getters/tee.go b/share/getters/tee.go index 08c5e6eb68..41e9073fd4 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -9,12 +9,12 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" - - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/rsmt2d" ) var _ share.Getter = (*TeeGetter)(nil) diff --git a/share/getters/testing.go b/share/getters/testing.go index eca65d9722..040dc6873a 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -6,9 +6,10 @@ import ( "testing" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" ) // TestGetter provides a testing SingleEDSGetter and the root of the EDS it holds. diff --git a/share/getters/utils.go b/share/getters/utils.go index e848e06165..6df830a34c 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -13,12 +13,12 @@ import ( "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" - - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" ) var ( diff --git a/share/p2p/middleware.go b/share/p2p/middleware.go index fa3f0e26ae..355ea7c468 100644 --- a/share/p2p/middleware.go +++ b/share/p2p/middleware.go @@ -4,7 +4,6 @@ import ( "sync/atomic" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/network" ) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index d31b7a2bda..1956704e56 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -9,12 +9,11 @@ import ( "time" "unsafe" - "github.com/libp2p/go-libp2p/p2p/net/conngater" - logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 8b1e4d1f43..21b0ba8256 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -8,14 +8,13 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" - routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" - "github.com/libp2p/go-libp2p/p2p/net/conngater" - dht "github.com/libp2p/go-libp2p-kad-dht" pubsub "github.com/libp2p/go-libp2p-pubsub" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 82c7ba9b63..1eaea62aba 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -12,13 +12,13 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexeds/pb" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - "github.com/celestiaorg/rsmt2d" ) // Client is responsible for requesting EDSs for blocksync over the ShrEx/EDS protocol. diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 376d3472c7..c81fa43619 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -14,11 +14,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p" - - "github.com/celestiaorg/celestia-app/pkg/da" ) func TestExchange_RequestEDS(t *testing.T) { diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 69f48bbbf0..baeaf8fdb1 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -14,13 +14,13 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - "github.com/celestiaorg/nmt/namespace" ) // Client implements client side of shrex/nd protocol to obtain namespaced shares data from remote diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index a808fc4174..4fb56adc51 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -5,7 +5,6 @@ import ( "fmt" logging "github.com/ipfs/go-log/v2" - pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" diff --git a/state/integration_test.go b/state/integration_test.go index 07bf044da6..25b8e00a2d 100644 --- a/state/integration_test.go +++ b/state/integration_test.go @@ -16,13 +16,12 @@ import ( rpcclient "github.com/tendermint/tendermint/rpc/client" "google.golang.org/grpc" - "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/testutil/testfactory" "github.com/celestiaorg/celestia-app/testutil/testnode" blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" ) From dc78b1d33176ffc3f122acadf79e9e234b0a494b Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 3 Mar 2023 16:24:15 +0200 Subject: [PATCH 0433/1008] feat(libs/header/p2p): allow server to return all stored headers that correspond to the requested range (#1807) --- libs/header/p2p/exchange_test.go | 39 ++++++++++++++++++++++++++++++-- libs/header/p2p/server.go | 29 +++++++++++++++++++++--- libs/header/p2p/session.go | 14 ++++++------ 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index e425317ba8..f8cfd2b3be 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -381,6 +381,41 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { assert.NotEqual(t, newPeerScore, prevScore) } +// TestExchange_RequestPartialRange enusres in case of receiving a partial response +// from server, Exchange will re-request remaining headers from another peer +func TestExchange_RequestPartialRange(t *testing.T) { + hosts := createMocknet(t, 3) + exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) + + // create one more server(with more headers in the store) + serverSideEx, err := NewExchangeServer[*test.DummyHeader]( + hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), + WithNetworkID[ServerParameters](networkID), + ) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + t.Cleanup(cancel) + + require.NoError(t, err) + require.NoError(t, serverSideEx.Start(ctx)) + exchg.peerTracker.peerLk.Lock() + prevScoreBefore1 := exchg.peerTracker.trackedPeers[hosts[1].ID()].peerScore + prevScoreBefore2 := 50 + // reducing peerScore of the second server, so our exchange will request host[1] first. + exchg.peerTracker.trackedPeers[hosts[2].ID()] = &peerStat{peerID: hosts[2].ID(), peerScore: 50} + exchg.peerTracker.peerLk.Unlock() + h, err := exchg.GetRangeByHeight(ctx, 1, 8) + require.NotNil(t, h) + require.NoError(t, err) + + exchg.peerTracker.peerLk.Lock() + prevScoreAfter1 := exchg.peerTracker.trackedPeers[hosts[1].ID()].peerScore + prevScoreAfter2 := exchg.peerTracker.trackedPeers[hosts[2].ID()].peerScore + exchg.peerTracker.peerLk.Unlock() + + assert.NotEqual(t, prevScoreBefore1, prevScoreAfter1) + assert.NotEqual(t, prevScoreBefore2, prevScoreAfter2) +} + func createMocknet(t *testing.T, amount int) []libhost.Host { net, err := mocknet.FullMeshConnected(amount) require.NoError(t, err) @@ -424,7 +459,7 @@ type timedOutStore struct { timeout time.Duration } -func (t *timedOutStore) GetRangeByHeight(_ context.Context, _, _ uint64) ([]*test.DummyHeader, error) { +func (t *timedOutStore) HasAt(_ context.Context, _ uint64) bool { time.Sleep(t.timeout + 1) - return []*test.DummyHeader{}, nil + return true } diff --git a/libs/header/p2p/server.go b/libs/header/p2p/server.go index 29a980dd06..a6b7e38b37 100644 --- a/libs/header/p2p/server.go +++ b/libs/header/p2p/server.go @@ -201,10 +201,33 @@ func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { } log.Debugw("server: handling headers request", "from", from, "to", to) + // check that store has the requested height if !serv.store.HasAt(ctx, to-1) { - span.SetStatus(codes.Error, header.ErrNotFound.Error()) - log.Debugw("server: requested headers not stored", "from", from, "to", to) - return nil, header.ErrNotFound + head, err := serv.store.Head(ctx) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + log.Debugw("server: could not get current head", "err", err) + return nil, err + } + + // might be a case when store hasn't synced yet to the requested range + if uint64(head.Height()) < from { + span.SetStatus(codes.Error, header.ErrNotFound.Error()) + log.Debugw("server: requested headers not stored", + "from", from, + "to", to, + "currentHead", + head.Height(), + ) + return nil, header.ErrNotFound + } + + log.Debugw("server: serving partial range", + "prevMaxHeight", to, + "newMaxHeight", uint64(head.Height())+1, + ) + // change `to` height to return a partial range + to = uint64(head.Height()) + 1 } headersByRange, err := serv.store.GetRangeByHeight(ctx, from, to) diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index 983ff02c23..158dc9a7a5 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -181,14 +181,14 @@ func (s *session[H]) doRequest( "requestedAmount", req.Amount, ) - defer func() { - stat.updateStats(size, duration) - }() + // update peer stats + stat.updateStats(size, duration) + responseLn := uint64(len(h)) // ensure that we received the correct amount of headers. - if uint64(len(h)) < req.Amount { - from := uint64(h[len(h)-1].Height()) - amount := req.Amount - from + if responseLn < req.Amount { + from := uint64(h[responseLn-1].Height()) + amount := req.Amount - responseLn select { case <-s.ctx.Done(): @@ -200,7 +200,7 @@ func (s *session[H]) doRequest( } } - // send headers to the channel, update peer stats and return peer to the queue, so it can be + // send headers to the channel, return peer to the queue, so it can be // re-used in case if there are other requests awaiting headers <- h s.queue.push(stat) From a27f8488a4d732b00a9ae2ff8b212040111151c7 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 6 Mar 2023 17:29:27 +0200 Subject: [PATCH 0434/1008] fix(Makefile): exit if an error appears (#1868) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3ee18e3e3f..43bf24a368 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ LDFLAGS="-X 'main.buildTime=$(shell date)' -X 'main.lastCommit=$(shell git rev-p ifeq (${PREFIX},) PREFIX := /usr/local endif - ## help: Get more info on make commands. help: Makefile @echo " Choose a command run in "$(PROJECTNAME)":" @@ -158,7 +157,8 @@ openrpc-gen: lint-imports: @echo "--> Running imports linter" @for file in `find . -type f -name '*.go'`; \ - do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout $$file; \ + do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout $$file \ + || exit 1; \ done; .PHONY: lint-imports From b8d0203a103f42afe78acabda01f5cd86747dd19 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 7 Mar 2023 13:13:27 +0200 Subject: [PATCH 0435/1008] bugfix(libs/header/p2p): fix range validation in session (#1871) --- libs/header/p2p/session.go | 28 ++++++++++++++-------- libs/header/p2p/session_test.go | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index 158dc9a7a5..7898807a82 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -236,26 +236,34 @@ func (s *session[H]) processResponse(responses []*p2p_pb.HeaderResponse) ([]H, e return headers, err } -// validate checks that the received range of headers is valid against the provided header. +// validate checks that the received range of headers is adjacent and is valid against the provided header. func (s *session[H]) validate(headers []H) error { - // if `from` is empty, then additional validation for the header`s range is not needed. + // if `s.from` is empty, then additional validation for the header`s range is not needed. if s.from.IsZero() { return nil } trusted := s.from - // verify that the whole range is valid. - for i := 0; i < len(headers); i++ { - err := trusted.Verify(headers[i]) + // verify that the whole range is valid and adjacent. + for _, untrusted := range headers { + err := trusted.Verify(untrusted) if err != nil { return err } - if trusted.Height()+1 != headers[i].Height() { - // Exchange requires requested ranges to always consist of adjacent headers - return fmt.Errorf("peer sent valid but non-adjacent header") - } - trusted = headers[i] + // extra check for the adjacency should be performed only for the received range, + // because headers are received out of order and `s.from` can't be adjacent to them + if trusted.Height() != s.from.Height() { + if trusted.Height()+1 != untrusted.Height() { + // Exchange requires requested ranges to always consist of adjacent headers + return fmt.Errorf("peer sent valid but non-adjacent header. expected:%d, received:%d", + trusted.Height()+1, + untrusted.Height(), + ) + } + } + // as `untrusted` was verified against previous trusted header, we can assume that it is valid + trusted = untrusted } return nil } diff --git a/libs/header/p2p/session_test.go b/libs/header/p2p/session_test.go index cc6a3667a1..9cd2df3185 100644 --- a/libs/header/p2p/session_test.go +++ b/libs/header/p2p/session_test.go @@ -1,9 +1,15 @@ package p2p import ( + "context" "testing" + "time" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/libs/header/test" ) func Test_PrepareRequests(t *testing.T) { @@ -14,3 +20,39 @@ func Test_PrepareRequests(t *testing.T) { require.Equal(t, requests[0].GetOrigin(), uint64(1)) require.Equal(t, requests[1].GetOrigin(), uint64(6)) } + +// Test_Validate ensures that headers range is adjacent and valid. +func Test_Validate(t *testing.T) { + suite := test.NewTestSuite(t) + head := suite.Head() + ses := newSession( + context.Background(), + nil, + &peerTracker{trackedPeers: make(map[peer.ID]*peerStat)}, + "", time.Second, + withValidation(head), + ) + + headers := suite.GenDummyHeaders(5) + err := ses.validate(headers) + assert.NoError(t, err) +} + +// Test_ValidateFails ensures that non-adjacent range will return an error. +func Test_ValidateFails(t *testing.T) { + suite := test.NewTestSuite(t) + head := suite.Head() + ses := newSession( + context.Background(), + nil, + &peerTracker{trackedPeers: make(map[peer.ID]*peerStat)}, + "", time.Second, + withValidation(head), + ) + + headers := suite.GenDummyHeaders(5) + // break adjacency + headers[2] = headers[4] + err := ses.validate(headers) + assert.Error(t, err) +} From 915b7aefda4f1a85c8710b13599e39fad1e927d8 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 7 Mar 2023 16:11:18 +0200 Subject: [PATCH 0436/1008] misc(github/workflows): add refactoring label and remove improvement (#1872) --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index df6c86eaf1..7e8c4ef79a 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -16,4 +16,4 @@ jobs: with: mode: minimum count: 1 - labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:improvement, kind:feature, kind:dependencies, kind:docs" # yamllint disable-line rule:line-length + labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:refactoring, kind:feature, kind:dependencies, kind:docs" # yamllint disable-line rule:line-length From b7213bfa9e84098834bc869a465cfc4cbb85b7ba Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Tue, 7 Mar 2023 11:14:56 -0500 Subject: [PATCH 0437/1008] Extend label automation to add external label (#1869) --- .github/workflows/issue-label-automation.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/issue-label-automation.yml b/.github/workflows/issue-label-automation.yml index fd2921fc28..9154d58fda 100644 --- a/.github/workflows/issue-label-automation.yml +++ b/.github/workflows/issue-label-automation.yml @@ -8,9 +8,24 @@ jobs: permissions: issues: write steps: + - name: Check for External Contributor + uses: tspascoal/get-user-teams-membership@v2 + id: teamCheck + with: + username: ${{ github.actor }} + team: "celestia-node" + GITHUB_TOKEN: ${{ secrets.PAT_TEAM_CHECK }} + - name: Triage labeling uses: andymckay/labeler@master with: add-labels: "needs:triage" ignore-if-labeled: true repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: External labeling + if: ${{ steps.teamCheck.outputs.isTeamMember == 'false' }} + uses: andymckay/labeler@master + with: + add-labels: "external" + repo-token: ${{ secrets.GITHUB_TOKEN }} From a07d2b8286fc0d24f4b49937876a16b97257b7ab Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Thu, 9 Mar 2023 05:29:49 -0500 Subject: [PATCH 0438/1008] .github: add labels to PRs (#1877) --- .github/workflows/issue-label-automation.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-label-automation.yml b/.github/workflows/issue-label-automation.yml index 9154d58fda..4b24bca99c 100644 --- a/.github/workflows/issue-label-automation.yml +++ b/.github/workflows/issue-label-automation.yml @@ -1,12 +1,16 @@ -name: issue-automation +name: Label Automation on: + # Using pull_request_target for forks since labels are not a security issue + pull_request_target: + types: [opened] issues: types: [opened] jobs: - automate-issues-labels: + automate-labels: runs-on: ubuntu-latest permissions: issues: write + pull-requests: write steps: - name: Check for External Contributor uses: tspascoal/get-user-teams-membership@v2 @@ -16,13 +20,17 @@ jobs: team: "celestia-node" GITHUB_TOKEN: ${{ secrets.PAT_TEAM_CHECK }} + # For issues we want to add a `needs:triage` label if it is unlabeled - name: Triage labeling + if: ${{ github.event_name == 'issues' }} uses: andymckay/labeler@master with: add-labels: "needs:triage" ignore-if-labeled: true repo-token: ${{ secrets.GITHUB_TOKEN }} + # For both issues and PRs we want to add the `external` label if the + # author is not a member of the node team - name: External labeling if: ${{ steps.teamCheck.outputs.isTeamMember == 'false' }} uses: andymckay/labeler@master From 8f1f6e9cbe6c34b5d26b91fff9afd2f4fdc18cb7 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 9 Mar 2023 14:48:26 +0100 Subject: [PATCH 0439/1008] fix(libs/header/sync): resolve head synchronization issue (#1878) Because syncedHead and writeHead inside of Store are not synchronized, we can have a case we set a sync head as an old header. This happens when we receive an old header from the network. This is only fixing PR that has some ugliness on it. The refactoring PR is to follow. --- libs/header/sync/sync_head.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go index 6ff046916c..51edc50f02 100644 --- a/libs/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -31,7 +31,11 @@ func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) { if !pendHead.IsZero() { return pendHead, nil } - // if empty, get subjective head out of the store + // next, check if the syncer holds any known head + if syncHead := s.syncedHead.Load(); syncHead != nil { + return *syncHead, nil + } + // if both pending and syncer head are empty - get subjective head out of the store netHead, err := s.store.Head(ctx) if err != nil { return zero, err @@ -107,6 +111,12 @@ func (s *Syncer[H]) networkHead(ctx context.Context) (H, error) { // incomingNetHead processes new gossiped network headers. func (s *Syncer[H]) incomingNetHead(ctx context.Context, netHead H) pubsub.ValidationResult { + // check if it's an outdated head + // TODO(@Wondertan): This check does should not be here and it's already part of the validation + // pipeline. We have it here because syncedHead is not synchronized with writeHead inside of Store. + if syncHead := s.syncedHead.Load(); syncHead != nil && (*syncHead).Height() >= netHead.Height() { + return pubsub.ValidationIgnore + } // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network // header _, err := s.store.Append(ctx, netHead) From ee6b45402848ddd49e70472d7ca447090963b21e Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 9 Mar 2023 14:51:44 +0100 Subject: [PATCH 0440/1008] fix(modp2p): change the timecache strategy for gossip sub (#1873) We observed that sometimes old headers are received by nodes on the p2p network. This PR attempts to increase the time a gossiped message stays in caches by changing the strategy to LastSeen. This assumes there are duplicates on the network which reenter the cache. --- nodebuilder/p2p/pubsub.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 4fae70566a..3292026ebf 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -8,6 +8,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" + "github.com/libp2p/go-libp2p-pubsub/timecache" hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" @@ -73,6 +74,7 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { scoreThresholds := peerScoreThresholds() opts := []pubsub.Option{ + pubsub.WithSeenMessagesStrategy(timecache.Strategy_LastSeen), pubsub.WithPeerScore(peerScores, scoreThresholds), pubsub.WithPeerExchange(cfg.PeerExchange || cfg.Bootstrapper), pubsub.WithDirectPeers(fpeers), From ffa3093bce06f5809120e4d34036b017e594169a Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 9 Mar 2023 16:27:39 +0200 Subject: [PATCH 0441/1008] refactoring(header/syncer): request verified range of headers (#1749) --- libs/header/local/exchange.go | 2 +- libs/header/p2p/exchange.go | 2 +- libs/header/p2p/session.go | 7 +- libs/header/sync/ranges.go | 15 ---- libs/header/sync/sync.go | 131 ++++++++++++++++++++-------------- libs/header/sync/sync_test.go | 19 +++-- 6 files changed, 95 insertions(+), 81 deletions(-) diff --git a/libs/header/local/exchange.go b/libs/header/local/exchange.go index a2aa0e3eb4..515e94fcf7 100644 --- a/libs/header/local/exchange.go +++ b/libs/header/local/exchange.go @@ -43,7 +43,7 @@ func (l *Exchange[H]) GetRangeByHeight(ctx context.Context, origin, amount uint6 func (l *Exchange[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64, ) ([]H, error) { - return l.store.GetVerifiedRange(ctx, from, uint64(from.Height())+amount) + return l.store.GetVerifiedRange(ctx, from, uint64(from.Height())+amount+1) } func (l *Exchange[H]) Get(ctx context.Context, hash header.Hash) (H, error) { diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index a881747785..4bfddfe183 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -197,7 +197,7 @@ func (ex *Exchange[H]) GetVerifiedRange( ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout, withValidation(from), ) defer session.close() - + // we request the next header height that we don't have: `fromHead`+1 return session.getRangeByHeight(ctx, uint64(from.Height())+1, amount, ex.Params.MaxHeadersPerRequest) } diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index 7898807a82..df9801b887 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -74,7 +74,7 @@ func (s *session[H]) getRangeByHeight( ctx context.Context, from, amount, headersPerPeer uint64, ) ([]H, error) { - log.Debugw("requesting headers", "from", from, "to", from+amount) + log.Debugw("requesting headers", "from", from, "to", from+amount-1) // -1 need to exclude to+1 height requests := prepareRequests(from, amount, headersPerPeer) result := make(chan []H, len(requests)) @@ -104,6 +104,11 @@ LOOP: sort.Slice(headers, func(i, j int) bool { return headers[i].Height() < headers[j].Height() }) + + log.Debugw("received headers range", + "from", headers[0].Height(), + "to", headers[len(headers)-1].Height(), + ) return headers, nil } diff --git a/libs/header/sync/ranges.go b/libs/header/sync/ranges.go index 8a05b728c3..2830e462ff 100644 --- a/libs/header/sync/ranges.go +++ b/libs/header/sync/ranges.go @@ -68,21 +68,6 @@ func (rs *ranges[H]) Add(h H) { } } -// FirstRangeWithin checks if the first range is within a given height span [start:end] -// and returns it. -func (rs *ranges[H]) FirstRangeWithin(start, end uint64) (*headerRange[H], bool) { - r, ok := rs.First() - if !ok { - return nil, false - } - - if r.start >= start && r.start <= end { - return r, true - } - - return nil, false -} - // First provides a first non-empty range, while cleaning up empty ones. func (rs *ranges[H]) First() (*headerRange[H], bool) { rs.lk.Lock() diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 834a50f6ab..c80fefa3ea 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -201,9 +201,12 @@ func (s *Syncer[H]) sync(ctx context.Context) { return // should never happen, but just in case } + from := header.Height() + 1 + log.Infow("syncing headers", - "from", header.Height(), + "from", from, "to", newHead.Height()) + err := s.doSync(ctx, header, newHead) if err != nil { if errors.Is(err, context.Canceled) { @@ -212,40 +215,30 @@ func (s *Syncer[H]) sync(ctx context.Context) { } log.Errorw("syncing headers", - "from", header.Height(), + "from", from, "to", newHead.Height(), "err", err) return } log.Infow("finished syncing", - "from", header.Height(), + "from", from, "to", newHead.Height(), "elapsed time", s.state.End.Sub(s.state.Start)) } // doSync performs actual syncing updating the internal State func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) { - from, to := uint64(fromHead.Height())+1, uint64(toHead.Height()) - s.stateLk.Lock() s.state.ID++ - s.state.FromHeight = from - s.state.ToHeight = to + s.state.FromHeight = uint64(fromHead.Height()) + 1 + s.state.ToHeight = uint64(toHead.Height()) s.state.FromHash = fromHead.Hash() s.state.ToHash = toHead.Hash() s.state.Start = time.Now() s.stateLk.Unlock() - for processed := 0; from < to; from += uint64(processed) { - processed, err = s.processHeaders(ctx, from, to) - if err != nil && processed == 0 { - break - } - if s.metrics != nil { - s.metrics.recordTotalSynced(processed) - } - } + err = s.processHeaders(ctx, fromHead, uint64(toHead.Height())) s.stateLk.Lock() s.state.End = time.Now() @@ -256,50 +249,84 @@ func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) // processHeaders gets and stores headers starting at the given 'from' height up to 'to' height - // [from:to] -func (s *Syncer[H]) processHeaders(ctx context.Context, from, to uint64) (int, error) { - headers, err := s.findHeaders(ctx, from, to) - if err != nil { - return 0, err - } +// processHeaders checks headers in pending cache that apply to the requested range. +// If some headers are missing, it starts requesting them from the network. +func (s *Syncer[H]) processHeaders( + ctx context.Context, + fromHead H, + to uint64, +) (err error) { + for { + headersRange, ok := s.pending.First() + if !ok { + break + } - amount, err := s.store.Append(ctx, headers...) - if err == nil && amount > 0 { - s.syncedHead.Store(&headers[amount-1]) - } - return amount, err -} + headers, amount := headersRange.Before(to) + if amount == 0 { + break + } -// findHeaders gets headers from either remote peers or from local cache of headers received by -// PubSub - [from:to] -func (s *Syncer[H]) findHeaders(ctx context.Context, from, to uint64) ([]H, error) { - amount := to - from + 1 // + 1 to include 'to' height as well - if amount > s.Params.MaxRequestSize { - to = from + s.Params.MaxRequestSize - 1 // `from` is already included in range - amount = s.Params.MaxRequestSize - } + // check that returned range is adjacent to `fromHead` + if fromHead.Height()+1 != headers[0].Height() { + // make an external request + if err = s.requestHeaders(ctx, fromHead, uint64(headers[0].Height()-1)); err != nil { + return err + } + } - out := make([]H, 0, amount) - for from <= to { - // if we have some range cached - use it - r, ok := s.pending.FirstRangeWithin(from, to) - if !ok { - hs, err := s.exchange.GetRangeByHeight(ctx, from, to-from+1) - return append(out, hs...), err + // apply cached headers + if err = s.storeHeaders(ctx, headers); err != nil { + return err } - // first, request everything between from and start of the found range - hs, err := s.exchange.GetRangeByHeight(ctx, from, r.start-from) + // update fromHead for the next iteration + fromHead = headers[len(headers)-1] + } + return s.requestHeaders(ctx, fromHead, to) +} + +// requestHeaders requests headers from the network -> (fromHeader.Height : to]. +func (s *Syncer[H]) requestHeaders( + ctx context.Context, + fromHead H, + to uint64, +) error { + amount := to - uint64(fromHead.Height()) + // start requesting headers until amount remaining will be 0 + for amount > 0 { + size := s.Params.MaxRequestSize + if amount < size { + size = amount + } + headers, err := s.exchange.GetVerifiedRange(ctx, fromHead, size) if err != nil { - return nil, err + return err } - out = append(out, hs...) - from += uint64(len(hs)) + amount -= size - // apply cached range if any - cached, ln := r.Before(to) - out = append(out, cached...) - from += ln + if err := s.storeHeaders(ctx, headers); err != nil { + return err + } + fromHead = headers[len(headers)-1] + } + return nil +} + +// storeHeaders updates store with new headers and updates current syncedHead. +func (s *Syncer[H]) storeHeaders(ctx context.Context, headers []H) error { + // we don't expect any issues in storing right now, as all headers are now verified. + // So, we should return immediately in case an error appears. + stored, err := s.store.Append(ctx, headers...) + if err != nil { + return err + } + if stored > 0 { + s.syncedHead.Store(&headers[stored-1]) } - return out, nil + if s.metrics != nil { + s.metrics.recordTotalSynced(stored) + } + return nil } diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index 44dd0179b9..bfd7480f23 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -255,7 +255,7 @@ func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { // 1. get range of headers from pending; [2;11] // 2. get headers from the remote store; [12;20] // 3. apply last header from pending; - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) suite := test.NewTestSuite(t) @@ -281,16 +281,13 @@ func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { require.NoError(t, err) syncer.pending.Add(suite.GetRandomHeader()) - h, err := syncer.findHeaders(ctx, 2, 21) - require.NoError(t, err) - require.NotNil(t, h) - require.Equal(t, h[0].Height(), int64(2)) - require.Equal(t, h[len(h)-1].Height(), int64(21)) - header := h[0] - for i := 1; i < len(h); i++ { - require.NoError(t, header.Verify(h[i])) - header = h[i] - } + require.NoError(t, err) + err = syncer.processHeaders(ctx, head, 21) + require.NoError(t, err) + + headerPtr := syncer.syncedHead.Load() + require.NotNil(t, headerPtr) + assert.Equal(t, (*headerPtr).Height(), int64(21)) } type exchangeCountingHead struct { From 572efabcad3aad8792488ae2a991d5db5d0664bc Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 9 Mar 2023 19:37:58 +0200 Subject: [PATCH 0442/1008] refactoring!(libs/header): simplify return params in store.Append (#1831) --- libs/header/interface.go | 2 +- libs/header/mocks/store.go | 4 ++-- libs/header/store/init_test.go | 2 +- libs/header/store/store.go | 16 ++++++++-------- libs/header/store/store_test.go | 10 ++++------ libs/header/sync/sync.go | 10 ++++------ libs/header/sync/sync_head.go | 2 +- libs/header/sync/sync_test.go | 18 +++++++++--------- 8 files changed, 30 insertions(+), 34 deletions(-) diff --git a/libs/header/interface.go b/libs/header/interface.go index 96cdb39628..1c348b5a47 100644 --- a/libs/header/interface.go +++ b/libs/header/interface.go @@ -95,7 +95,7 @@ type Store[H Header] interface { // as it applies them contiguously on top of the current head height. // It returns the amount of successfully applied headers, // so caller can understand what given header was invalid, if any. - Append(context.Context, ...H) (int, error) + Append(context.Context, ...H) error } // Getter contains the behavior necessary for a component to retrieve diff --git a/libs/header/mocks/store.go b/libs/header/mocks/store.go index 8d3667d131..8a047ea6b5 100644 --- a/libs/header/mocks/store.go +++ b/libs/header/mocks/store.go @@ -90,7 +90,7 @@ func (m *MockStore[H]) HasAt(_ context.Context, height uint64) bool { return height != 0 && m.HeadHeight >= int64(height) } -func (m *MockStore[H]) Append(ctx context.Context, headers ...H) (int, error) { +func (m *MockStore[H]) Append(ctx context.Context, headers ...H) error { for _, header := range headers { m.Headers[header.Height()] = header // set head @@ -98,5 +98,5 @@ func (m *MockStore[H]) Append(ctx context.Context, headers ...H) (int, error) { m.HeadHeight = header.Height() } } - return len(headers), nil + return nil } diff --git a/libs/header/store/init_test.go b/libs/header/store/init_test.go index 3da167e71d..db80020693 100644 --- a/libs/header/store/init_test.go +++ b/libs/header/store/init_test.go @@ -32,7 +32,7 @@ func TestInitStore_NoReinit(t *testing.T) { err = store.Start(ctx) require.NoError(t, err) - _, err = store.Append(ctx, suite.GenDummyHeaders(10)...) + err = store.Append(ctx, suite.GenDummyHeaders(10)...) require.NoError(t, err) err = store.Stop(ctx) diff --git a/libs/header/store/store.go b/libs/header/store/store.go index 7daad91229..4c64c8724e 100644 --- a/libs/header/store/store.go +++ b/libs/header/store/store.go @@ -287,10 +287,10 @@ func (s *Store[H]) HasAt(_ context.Context, height uint64) bool { return height != uint64(0) && s.Height() >= height } -func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { +func (s *Store[H]) Append(ctx context.Context, headers ...H) error { lh := len(headers) if lh == 0 { - return 0, nil + return nil } var err error @@ -300,7 +300,7 @@ func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { if headPtr == nil { head, err = s.Head(ctx) if err != nil { - return 0, err + return err } } else { head = *headPtr @@ -312,7 +312,7 @@ func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { // currently store requires all headers to be appended sequentially and adjacently // TODO(@Wondertan): Further pruning friendly Store design should reevaluate this requirement if h.Height() != head.Height()+1 { - return 0, &header.ErrNonAdjacent{ + return &header.ErrNonAdjacent{ Head: head.Height(), Attempted: h.Height(), } @@ -332,7 +332,7 @@ func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { // if the first header is invalid, no need to go further if i == 0 { // and simply return - return 0, err + return err } // otherwise, stop the loop and apply headers appeared to be valid break @@ -349,11 +349,11 @@ func (s *Store[H]) Append(ctx context.Context, headers ...H) (int, error) { log.Infow("new head", "height", wh.Height(), "hash", wh.Hash()) // we return an error here after writing, // as there might be an invalid header in between of a given range - return ln, err + return err case <-s.writesDn: - return 0, errStoppedStore + return errStoppedStore case <-ctx.Done(): - return 0, ctx.Err() + return ctx.Err() } } diff --git a/libs/header/store/store_test.go b/libs/header/store/store_test.go index e9aca94f59..89388637ba 100644 --- a/libs/header/store/store_test.go +++ b/libs/header/store/store_test.go @@ -31,9 +31,8 @@ func TestStore(t *testing.T) { assert.EqualValues(t, suite.Head().Hash(), head.Hash()) in := suite.GenDummyHeaders(10) - ln, err := store.Append(ctx, in...) + err = store.Append(ctx, in...) require.NoError(t, err) - assert.Equal(t, 10, ln) out, err := store.GetRangeByHeight(ctx, 2, 12) require.NoError(t, err) @@ -54,9 +53,8 @@ func TestStore(t *testing.T) { assert.False(t, ok) go func() { - ln, err := store.Append(ctx, suite.GenDummyHeaders(1)...) + err := store.Append(ctx, suite.GenDummyHeaders(1)...) require.NoError(t, err) - assert.Equal(t, 1, ln) }() h, err := store.GetByHeight(ctx, 12) @@ -102,10 +100,10 @@ func TestStorePendingCacheMiss(t *testing.T) { err = store.Start(ctx) require.NoError(t, err) - _, err = store.Append(ctx, suite.GenDummyHeaders(100)...) + err = store.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) - _, err = store.Append(ctx, suite.GenDummyHeaders(50)...) + err = store.Append(ctx, suite.GenDummyHeaders(50)...) require.NoError(t, err) _, err = store.GetRangeByHeight(ctx, 1, 101) diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index c80fefa3ea..b82e54eebb 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -317,16 +317,14 @@ func (s *Syncer[H]) requestHeaders( func (s *Syncer[H]) storeHeaders(ctx context.Context, headers []H) error { // we don't expect any issues in storing right now, as all headers are now verified. // So, we should return immediately in case an error appears. - stored, err := s.store.Append(ctx, headers...) - if err != nil { + if err := s.store.Append(ctx, headers...); err != nil { return err } - if stored > 0 { - s.syncedHead.Store(&headers[stored-1]) - } + + s.syncedHead.Store(&headers[len(headers)-1]) if s.metrics != nil { - s.metrics.recordTotalSynced(stored) + s.metrics.recordTotalSynced(len(headers)) } return nil } diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go index 51edc50f02..103ce304c6 100644 --- a/libs/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -119,7 +119,7 @@ func (s *Syncer[H]) incomingNetHead(ctx context.Context, netHead H) pubsub.Valid } // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network // header - _, err := s.store.Append(ctx, netHead) + err := s.store.Append(ctx, netHead) if err == nil { // a happy case where we appended maybe head directly, so accept s.syncedHead.Store(&netHead) diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index bfd7480f23..cc32a69d53 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -23,7 +23,7 @@ func TestSyncSimpleRequestingHead(t *testing.T) { head := suite.Head() remoteStore := store.NewTestStore(ctx, t, head) - _, err := remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) + err := remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) _, err = remoteStore.GetByHeight(ctx, 100) @@ -79,7 +79,7 @@ func TestDoSyncFullRangeFromExternalPeer(t *testing.T) { require.NoError(t, err) require.NoError(t, syncer.Start(ctx)) - _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(10)...) + err = remoteStore.Append(ctx, suite.GenDummyHeaders(10)...) require.NoError(t, err) // give store time to update heightSub index time.Sleep(time.Millisecond * 100) @@ -121,7 +121,7 @@ func TestSyncCatchUp(t *testing.T) { require.NoError(t, err) // 2. chain grows and syncer misses that - _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) + err = remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) require.NoError(t, err) incomingHead := suite.GenDummyHeaders(1)[0] @@ -172,19 +172,19 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { require.NoError(t, err) // miss 1 (helps to test that Syncer properly requests missed Headers from Exchange) - _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(1)...) + err = remoteStore.Append(ctx, suite.GenDummyHeaders(1)...) require.NoError(t, err) range1 := suite.GenDummyHeaders(15) - _, err = remoteStore.Append(ctx, range1...) + err = remoteStore.Append(ctx, range1...) require.NoError(t, err) // miss 2 - _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(3)...) + err = remoteStore.Append(ctx, suite.GenDummyHeaders(3)...) require.NoError(t, err) range2 := suite.GenDummyHeaders(23) - _, err = remoteStore.Append(ctx, range2...) + err = remoteStore.Append(ctx, range2...) require.NoError(t, err) // manually add to pending @@ -275,9 +275,9 @@ func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { for _, h := range range1 { syncer.pending.Add(h) } - _, err = remoteStore.Append(ctx, range1...) + err = remoteStore.Append(ctx, range1...) require.NoError(t, err) - _, err = remoteStore.Append(ctx, suite.GenDummyHeaders(9)...) + err = remoteStore.Append(ctx, suite.GenDummyHeaders(9)...) require.NoError(t, err) syncer.pending.Add(suite.GetRandomHeader()) From 1c71505f75d40997e2c3e7e2375e37b1955a375b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 9 Mar 2023 19:50:46 +0100 Subject: [PATCH 0443/1008] fix(cmd/cel-key): Ensure keyring-backend is actually `test` by default (#1879) `KeysCommand` from the sdk is quite wonky in that it really only sets a keyring backend if the flag was `changed`, so this hack ensures we can properly set a default and that the default is `test`. Resolves #1876 --- cmd/cel-key/main.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/cel-key/main.go b/cmd/cel-key/main.go index fdfb8d0d93..736ea9d7f5 100644 --- a/cmd/cel-key/main.go +++ b/cmd/cel-key/main.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/spf13/cobra" @@ -46,6 +47,14 @@ func init() { return err } + if !cmd.Flag(flags.FlagKeyringBackend).Changed { + err = cmd.Flag(flags.FlagKeyringBackend).Value.Set(keyring.BackendTest) + if err != nil { + return err + } + cmd.Flag(flags.FlagKeyringBackend).Changed = true + } + return ParseDirectoryFlags(cmd) } } From f7aac97bd382afead0ec2725575f0cf106513a83 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 10 Mar 2023 11:47:24 +0100 Subject: [PATCH 0444/1008] fix(das): always set result when worker sampled (#1882) Not a critical bug, but it fixes logging issues where showed range was always one header. --- das/worker.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/das/worker.go b/das/worker.go index 7d99f72d20..3cd61ea08c 100644 --- a/das/worker.go +++ b/das/worker.go @@ -69,13 +69,10 @@ func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- for curr := w.state.From; curr <= w.state.To; curr++ { err := w.sample(ctx, timeout, curr) - if err != nil { - if errors.Is(err, context.Canceled) { - // sampling worker will resume upon restart - break - } - w.setResult(curr, err) - continue + w.setResult(curr, err) + if errors.Is(err, context.Canceled) { + // sampling worker will resume upon restart + break } } From 4a4dd32e4a8fd445253f0e045beeea2db91bdfcf Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 10 Mar 2023 14:12:11 +0100 Subject: [PATCH 0445/1008] fix(header/p2p): return on context to avoid ugly errors on stop (#1883) --- libs/header/p2p/session.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index df9801b887..d1ec607a3a 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -14,7 +14,8 @@ import ( p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" ) -// errEmptyResponse means that server side closes the connection without sending at least 1 response. +// errEmptyResponse means that server side closes the connection without sending at least 1 +// response. var errEmptyResponse = errors.New("empty response") type option[H header.Header] func(*session[H]) @@ -153,7 +154,8 @@ func (s *session[H]) doRequest( r, size, duration, err := sendMessage(ctx, s.host, stat.peerID, s.protocolID, req) if err != nil { - // we should not punish peer at this point and should try to parse responses, despite that error was received. + // we should not punish peer at this point and should try to parse responses, despite that error + // was received. log.Debugw("requesting headers from peer failed", "peer", stat.peerID, "err", err) } @@ -174,6 +176,7 @@ func (s *session[H]) doRequest( select { case <-s.ctx.Done(): + return case s.reqCh <- req: } log.Errorw("processing response", "err", err) @@ -241,7 +244,8 @@ func (s *session[H]) processResponse(responses []*p2p_pb.HeaderResponse) ([]H, e return headers, err } -// validate checks that the received range of headers is adjacent and is valid against the provided header. +// validate checks that the received range of headers is adjacent and is valid against the provided +// header. func (s *session[H]) validate(headers []H) error { // if `s.from` is empty, then additional validation for the header`s range is not needed. if s.from.IsZero() { From 4746eb15046c2bc9ed8a9eefc8551eb87fbcc99c Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Fri, 10 Mar 2023 11:44:24 -0500 Subject: [PATCH 0446/1008] LICENSE: update copywright year and owner (#1887) We don't edit the License file actually, what we wanted was a `NOTICE` file which indicates how the license is applied. This PR adds the minimal `NOTICE` file that was agreed upon offline. --- NOTICE | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000..faf11e3461 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +Celestia Node +Copyright 2021 and onwards Strange Loop Labs AG From e3432b798b2eb22cd194a90215f7c6def15c7c1f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 10 Mar 2023 19:19:31 +0100 Subject: [PATCH 0447/1008] fix(header/sync): fix possible out of bounds panic (#1892) Also renamed Before to Truncate. --- libs/header/sync/ranges.go | 11 ++++++++--- libs/header/sync/ranges_test.go | 12 ++++++++++++ libs/header/sync/sync.go | 4 ++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libs/header/sync/ranges.go b/libs/header/sync/ranges.go index 2830e462ff..5fe4add88d 100644 --- a/libs/header/sync/ranges.go +++ b/libs/header/sync/ranges.go @@ -126,11 +126,15 @@ func (r *headerRange[H]) Head() H { return r.headers[ln-1] } -// Before truncates all the headers before height 'end' - [r.Start:end] -func (r *headerRange[H]) Before(end uint64) ([]H, uint64) { +// Truncate truncates all the headers before height 'end' - [r.Start:end] +func (r *headerRange[H]) Truncate(end uint64) []H { r.lk.Lock() defer r.lk.Unlock() + if r.start > end { + return nil + } + amnt := uint64(len(r.headers)) if r.start+amnt >= end { amnt = end - r.start + 1 // + 1 to include 'end' as well @@ -141,5 +145,6 @@ func (r *headerRange[H]) Before(end uint64) ([]H, uint64) { if len(r.headers) != 0 { r.start = uint64(r.headers[0].Height()) } - return out, amnt + + return out } diff --git a/libs/header/sync/ranges_test.go b/libs/header/sync/ranges_test.go index fea81378ac..4fcaf5285b 100644 --- a/libs/header/sync/ranges_test.go +++ b/libs/header/sync/ranges_test.go @@ -32,3 +32,15 @@ func TestAddParallel(t *testing.T) { last = r.start } } + +func TestRangeTruncate(t *testing.T) { + n := 300 + suite := test.NewTestSuite(t) + headers := suite.GenDummyHeaders(n) + + r := newRange(headers[200]) + r.Append(headers[201:]...) + + truncated := r.Truncate(100) + assert.Nil(t, truncated) +} diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index b82e54eebb..4808edd7ed 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -262,8 +262,8 @@ func (s *Syncer[H]) processHeaders( break } - headers, amount := headersRange.Before(to) - if amount == 0 { + headers := headersRange.Truncate(to) + if len(headers) == 0 { break } From fe69cc2f52b7c66773d5ebdffe5dcc477cbe82dd Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 13 Mar 2023 09:41:01 +0100 Subject: [PATCH 0448/1008] chore(Makefile): shush sort-imports (#1893) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 43bf24a368..488a1e5b13 100644 --- a/Makefile +++ b/Makefile @@ -163,5 +163,5 @@ lint-imports: .PHONY: lint-imports sort-imports: - goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... + @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... .PHONY: sort-imports From c30a08eb0559d22c8ce2151e4573341b7bc92d1f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 13 Mar 2023 11:50:16 +0100 Subject: [PATCH 0449/1008] Shorten labels to match convential prefixes (#1894) --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 7e8c4ef79a..38c9d2820f 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -16,4 +16,4 @@ jobs: with: mode: minimum count: 1 - labels: "kind:bug-fix, kind:miscellaneous, kind:breaking, kind:refactoring, kind:feature, kind:dependencies, kind:docs" # yamllint disable-line rule:line-length + labels: "kind:fix, kind:misc, kind:break!, kind:refactor, kind:feat, kind:deps, kind:docs, kind:ci, kind:chore" # yamllint disable-line rule:line-length From 895750c965e81d689f2f833c23945155c9d2fadf Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 13 Mar 2023 15:18:15 +0200 Subject: [PATCH 0450/1008] refactoring(libs/header): cleanup config params (#1830) --- libs/header/interface.go | 5 + libs/header/p2p/exchange.go | 24 +++-- libs/header/p2p/exchange_test.go | 17 ++- libs/header/p2p/options.go | 154 +++++---------------------- libs/header/p2p/peer_tracker.go | 52 +++++---- libs/header/p2p/peer_tracker_test.go | 6 +- libs/header/p2p/server.go | 8 +- libs/header/sync/options.go | 14 --- libs/header/sync/sync.go | 2 +- libs/header/sync/sync_test.go | 8 +- nodebuilder/header/module.go | 13 +-- nodebuilder/header/module_test.go | 18 +--- 12 files changed, 95 insertions(+), 226 deletions(-) diff --git a/libs/header/interface.go b/libs/header/interface.go index 1c348b5a47..57fe1574ff 100644 --- a/libs/header/interface.go +++ b/libs/header/interface.go @@ -8,6 +8,11 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" ) +const ( + // MaxRangeRequestSize defines the max amount of headers that can be handled/requested at once. + MaxRangeRequestSize uint64 = 512 +) + // Subscriber encompasses the behavior necessary to // subscribe/unsubscribe from new Header events from the // network. diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index 4bfddfe183..a5a115eb50 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -20,6 +20,11 @@ import ( var log = logging.Logger("header/p2p") +// the minimum number of headers of the same height received from trusted peers +// to determine the network head. If all trusted header will return headers with +// non-equal height, then the highest header will be chosen. +const minTrustedHeadResponses = 2 + // Exchange enables sending outbound HeaderRequests to the network as well as // handling inbound HeaderRequests from the network. type Exchange[H header.Header] struct { @@ -60,9 +65,6 @@ func NewExchange[H header.Header]( peerTracker: newPeerTracker( host, connGater, - params.MaxAwaitingTime, - params.DefaultScore, - params.MaxPeerTrackerSize, ), Params: params, }, nil @@ -144,7 +146,7 @@ LOOP: } } - return bestHead[H](result, ex.Params.MinResponses) + return bestHead[H](result) } // GetByHeight performs a request for the Header at the given @@ -175,12 +177,12 @@ func (ex *Exchange[H]) GetRangeByHeight(ctx context.Context, from, amount uint64 if amount == 0 { return make([]H, 0), nil } - if amount > ex.Params.MaxRequestSize { + if amount > header.MaxRangeRequestSize { return nil, header.ErrHeadersLimitExceeded } - session := newSession[H](ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout) + session := newSession[H](ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RangeRequestTimeout) defer session.close() - return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRequest) + return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRangeRequest) } // GetVerifiedRange performs a request for the given range of Headers to the network and @@ -194,11 +196,11 @@ func (ex *Exchange[H]) GetVerifiedRange( return make([]H, 0), nil } session := newSession[H]( - ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RequestTimeout, withValidation(from), + ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RangeRequestTimeout, withValidation(from), ) defer session.close() // we request the next header height that we don't have: `fromHead`+1 - return session.getRangeByHeight(ctx, uint64(from.Height())+1, amount, ex.Params.MaxHeadersPerRequest) + return session.getRangeByHeight(ctx, uint64(from.Height())+1, amount, ex.Params.MaxHeadersPerRangeRequest) } // Get performs a request for the Header by the given hash corresponding @@ -298,7 +300,7 @@ func (ex *Exchange[H]) request( // * should be received at least from 2 peers; // If neither condition is met, then latest Header will be returned (header of the highest // height). -func bestHead[H header.Header](result []H, minResponses int) (H, error) { +func bestHead[H header.Header](result []H) (H, error) { if len(result) == 0 { var zero H return zero, header.ErrNotFound @@ -315,7 +317,7 @@ func bestHead[H header.Header](result []H, minResponses int) (H, error) { // try to find Header with the maximum height that was received at least from 2 peers for _, res := range result { - if counter[res.Hash().String()] >= minResponses { + if counter[res.Hash().String()] >= minTrustedHeadResponses { return res, nil } } diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index f8cfd2b3be..0441ab6825 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -91,8 +91,7 @@ func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { func TestExchange_RequestFullRangeHeaders(t *testing.T) { // create mocknet with 5 peers hosts := createMocknet(t, 5) - totalAmount := 80 - store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), totalAmount) + store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), int(header.MaxRangeRequestSize)) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) // create new exchange @@ -101,7 +100,6 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { WithChainID(networkID), ) require.NoError(t, err) - exchange.Params.MaxHeadersPerRequest = 10 exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) t.Cleanup(exchange.cancel) // amount of servers is len(hosts)-1 because one peer acts as a client @@ -122,13 +120,13 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) t.Cleanup(cancel) // request headers from 1 to totalAmount(80) - headers, err := exchange.GetRangeByHeight(ctx, 1, uint64(totalAmount)) + headers, err := exchange.GetRangeByHeight(ctx, 1, header.MaxRangeRequestSize) require.NoError(t, err) - require.Len(t, headers, 80) + require.Len(t, headers, int(header.MaxRangeRequestSize)) } // TestExchange_RequestHeadersLimitExceeded tests that the Exchange instance will return -// header.ErrHeadersLimitExceeded if the requested range will be move than MaxRequestSize. +// header.ErrHeadersLimitExceeded if the requested range will be move than MaxRangeRequestSize. func TestExchange_RequestHeadersLimitExceeded(t *testing.T) { hosts := createMocknet(t, 2) exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) @@ -213,7 +211,6 @@ func TestExchange_RequestByHash(t *testing.T) { } func Test_bestHead(t *testing.T) { - params := DefaultClientParameters() gen := func() []*test.DummyHeader { suite := test.NewTestSuite(t) res := make([]*test.DummyHeader, 0) @@ -272,7 +269,7 @@ func Test_bestHead(t *testing.T) { } for _, tt := range testCases { res := tt.precondition() - header, err := bestHead(res, params.MinResponses) + header, err := bestHead(res) require.NoError(t, err) require.True(t, header.Height() == tt.expectedHeight) } @@ -358,7 +355,7 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { // create client + server(it does not have needed headers) exchg, _ := createP2PExAndServer(t, host0, host1) - exchg.Params.RequestTimeout = time.Millisecond * 100 + exchg.Params.RangeRequestTimeout = time.Millisecond * 100 // create one more server(with more headers in the store) serverSideEx, err := NewExchangeServer[*test.DummyHeader]( host2, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), @@ -366,7 +363,7 @@ func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { ) require.NoError(t, err) // change store implementation - serverSideEx.store = &timedOutStore{timeout: exchg.Params.RequestTimeout} + serverSideEx.store = &timedOutStore{timeout: exchg.Params.RangeRequestTimeout} require.NoError(t, serverSideEx.Start(context.Background())) t.Cleanup(func() { serverSideEx.Stop(context.Background()) //nolint:errcheck diff --git a/libs/header/p2p/options.go b/libs/header/p2p/options.go index dd4d7a6ccd..15c94d190e 100644 --- a/libs/header/p2p/options.go +++ b/libs/header/p2p/options.go @@ -21,11 +21,9 @@ type ServerParameters struct { WriteDeadline time.Duration // ReadDeadline sets the timeout for reading messages from the stream ReadDeadline time.Duration - // MaxRequestSize defines the max amount of headers that can be handled at once. - MaxRequestSize uint64 - // RequestTimeout defines a timeout after which the session will try to re-request headers + // RangeRequestTimeout defines a timeout after which the session will try to re-request headers // from another peer. - RequestTimeout time.Duration + RangeRequestTimeout time.Duration // networkID is a network that will be used to create a protocol.ID // Is empty by default networkID string @@ -34,10 +32,9 @@ type ServerParameters struct { // DefaultServerParameters returns the default params to configure the store. func DefaultServerParameters() ServerParameters { return ServerParameters{ - WriteDeadline: time.Second * 5, - ReadDeadline: time.Minute, - MaxRequestSize: 512, - RequestTimeout: time.Second * 5, + WriteDeadline: time.Second * 5, + ReadDeadline: time.Minute, + RangeRequestTimeout: time.Second * 5, } } @@ -48,12 +45,9 @@ func (p *ServerParameters) Validate() error { if p.ReadDeadline == 0 { return fmt.Errorf("invalid read time duration: %v", p.ReadDeadline) } - if p.MaxRequestSize == 0 { - return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) - } - if p.RequestTimeout == 0 { + if p.RangeRequestTimeout == 0 { return fmt.Errorf("invalid request timeout for session: "+ - "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) + "%s. %s: %v", greaterThenZero, providedSuffix, p.RangeRequestTimeout) } return nil } @@ -80,28 +74,15 @@ func WithReadDeadline[T ServerParameters](deadline time.Duration) Option[T] { } } -// WithMaxRequestSize is a functional option that configures the -// `MaxRequestSize` parameter. -func WithMaxRequestSize[T parameters](size uint64) Option[T] { +// WithRangeRequestTimeout is a functional option that configures the +// `RangeRequestTimeout` parameter. +func WithRangeRequestTimeout[T parameters](duration time.Duration) Option[T] { return func(p *T) { switch t := any(p).(type) { case *ClientParameters: - t.MaxRequestSize = size + t.RangeRequestTimeout = duration case *ServerParameters: - t.MaxRequestSize = size - } - } -} - -// WithRequestTimeout is a functional option that configures the -// `RequestTimeout` parameter. -func WithRequestTimeout[T parameters](duration time.Duration) Option[T] { - return func(p *T) { - switch t := any(p).(type) { - case *ClientParameters: - t.RequestTimeout = duration - case *ServerParameters: - t.RequestTimeout = duration + t.RangeRequestTimeout = duration } } } @@ -120,26 +101,14 @@ func WithNetworkID[T parameters](networkID string) Option[T] { } // ClientParameters is the set of parameters that must be configured for the exchange. -// TODO: #1667 type ClientParameters struct { - // the target minimum amount of responses with the same chain head - MinResponses int - // MaxRequestSize defines the max amount of headers that can be handled at once. - MaxRequestSize uint64 // TODO: Rename to MaxRangeRequestSize - // MaxHeadersPerRequest defines the max amount of headers that can be requested per 1 request. - MaxHeadersPerRequest uint64 // TODO: Rename to MaxHeadersPerRangeRequest - // MaxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, - // otherwise it will be removed on the next GC cycle. - MaxAwaitingTime time.Duration - // DefaultScore specifies the score for newly connected peers. - DefaultScore float32 - // RequestTimeout defines a timeout after which the session will try to re-request headers + // MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. + MaxHeadersPerRangeRequest uint64 + // RangeRequestTimeout defines a timeout after which the session will try to re-request headers // from another peer. - RequestTimeout time.Duration // TODO: Rename to RangeRequestTimeout + RangeRequestTimeout time.Duration // TrustedPeersRequestTimeout a timeout for any request to a trusted peer. TrustedPeersRequestTimeout time.Duration - // MaxTrackerSize specifies the max amount of peers that can be added to the peerTracker. - MaxPeerTrackerSize int // networkID is a network that will be used to create a protocol.ID networkID string // chainID is an identifier of the chain. @@ -149,14 +118,9 @@ type ClientParameters struct { // DefaultClientParameters returns the default params to configure the store. func DefaultClientParameters() ClientParameters { return ClientParameters{ - MinResponses: 2, - MaxRequestSize: 512, - MaxHeadersPerRequest: 64, - MaxAwaitingTime: time.Hour, - DefaultScore: 1, - RequestTimeout: time.Second * 3, + MaxHeadersPerRangeRequest: 64, + RangeRequestTimeout: time.Second * 8, TrustedPeersRequestTimeout: time.Millisecond * 300, - MaxPeerTrackerSize: 100, } } @@ -166,97 +130,33 @@ const ( ) func (p *ClientParameters) Validate() error { - if p.MinResponses <= 0 { - return fmt.Errorf("invalid MinResponses: %s. %s: %v", - greaterThenZero, providedSuffix, p.MinResponses) - } - if p.MaxRequestSize == 0 { - return fmt.Errorf("invalid MaxRequestSize: %s. %s: %v", greaterThenZero, providedSuffix, p.MaxRequestSize) - } - if p.MaxHeadersPerRequest == 0 { - return fmt.Errorf("invalid MaxHeadersPerRequest: %s. %s: %v", greaterThenZero, providedSuffix, p.MaxHeadersPerRequest) + if p.MaxHeadersPerRangeRequest == 0 { + return fmt.Errorf("invalid MaxHeadersPerRangeRequest:%s. %s: %v", + greaterThenZero, providedSuffix, p.MaxHeadersPerRangeRequest) } - if p.MaxHeadersPerRequest > p.MaxRequestSize { - return fmt.Errorf("MaxHeadersPerRequest should not be more than MaxRequestSize."+ - "MaxHeadersPerRequest: %v, MaxRequestSize: %v", p.MaxHeadersPerRequest, p.MaxRequestSize) - } - if p.MaxAwaitingTime == 0 { - return fmt.Errorf("invalid MaxAwaitingTime for peerTracker: "+ - "%s. %s: %v", greaterThenZero, providedSuffix, p.MaxAwaitingTime) - } - if p.DefaultScore <= 0 { - return fmt.Errorf("invalid DefaultScore: %s. %s: %f", greaterThenZero, providedSuffix, p.DefaultScore) - } - if p.RequestTimeout == 0 { + if p.RangeRequestTimeout == 0 { return fmt.Errorf("invalid request timeout for session: "+ - "%s. %s: %v", greaterThenZero, providedSuffix, p.RequestTimeout) + "%s. %s: %v", greaterThenZero, providedSuffix, p.RangeRequestTimeout) } if p.TrustedPeersRequestTimeout == 0 { return fmt.Errorf("invalid TrustedPeersRequestTimeout: "+ "%s. %s: %v", greaterThenZero, providedSuffix, p.TrustedPeersRequestTimeout) } - if p.MaxPeerTrackerSize <= 0 { - return fmt.Errorf("invalid MaxTrackerSize: %s. %s: %d", greaterThenZero, providedSuffix, p.MaxPeerTrackerSize) - } return nil } -// WithMinResponses is a functional option that configures the -// `MinResponses` parameter. -func WithMinResponses[T ClientParameters](responses int) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ClientParameters: - t.MinResponses = responses - } - } -} - -// WithMaxHeadersPerRequest is a functional option that configures the -// // `MaxRequestSize` parameter. -func WithMaxHeadersPerRequest[T ClientParameters](amount uint64) Option[T] { +// WithMaxHeadersPerRangeRequest is a functional option that configures the +// // `MaxRangeRequestSize` parameter. +func WithMaxHeadersPerRangeRequest[T ClientParameters](amount uint64) Option[T] { return func(p *T) { switch t := any(p).(type) { //nolint:gocritic case *ClientParameters: - t.MaxHeadersPerRequest = amount + t.MaxHeadersPerRangeRequest = amount } } } -// WithMaxAwaitingTime is a functional option that configures the -// `MaxAwaitingTime` parameter. -func WithMaxAwaitingTime[T ClientParameters](duration time.Duration) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ClientParameters: - t.MaxAwaitingTime = duration - } - } -} - -// WithDefaultScore is a functional option that configures the -// `DefaultScore` parameter. -func WithDefaultScore[T ClientParameters](score float32) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ClientParameters: - t.DefaultScore = score - } - } -} - -// WithMaxTrackerSize is a functional option that configures the -// `MaxTrackerSize` parameter. -func WithMaxTrackerSize[T ClientParameters](size int) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ClientParameters: - t.MaxPeerTrackerSize = size - } - } -} - // WithChainID is a functional option that configures the // `chainID` parameter. func WithChainID[T ClientParameters](chainID string) Option[T] { diff --git a/libs/header/p2p/peer_tracker.go b/libs/header/p2p/peer_tracker.go index fddd2360b2..2b70c83acb 100644 --- a/libs/header/p2p/peer_tracker.go +++ b/libs/header/p2p/peer_tracker.go @@ -12,8 +12,20 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" ) -// gcCycle defines the duration after which the peerTracker starts removing peers. -var gcCycle = time.Minute * 30 +const ( + // defaultScore specifies the score for newly connected peers. + defaultScore float32 = 1 + // maxTrackerSize specifies the max amount of peers that can be added to the peerTracker. + maxPeerTrackerSize = 100 +) + +var ( + // maxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, + // otherwise it will be removed on the next GC cycle. + maxAwaitingTime = time.Hour + // gcCycle defines the duration after which the peerTracker starts removing peers. + gcCycle = time.Minute * 30 +) type peerTracker struct { host host.Host @@ -28,14 +40,6 @@ type peerTracker struct { // online until pruneDeadline, it will be removed and its score will be lost. disconnectedPeers map[peer.ID]*peerStat - // maxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, - // otherwise it will be removed on the next GC cycle. - maxAwaitingTime time.Duration - // defaultScore specifies the score for newly connected peers. - defaultScore float32 - // maxPeerTrackerSize specifies the max amount of peers that can be added to the peerTracker. - maxPeerTrackerSize int - ctx context.Context cancel context.CancelFunc // done is used to gracefully stop the peerTracker. @@ -46,22 +50,16 @@ type peerTracker struct { func newPeerTracker( h host.Host, connGater *conngater.BasicConnectionGater, - maxAwaitingTime time.Duration, - defaultScore float32, - maxPeerTrackerSize int, ) *peerTracker { ctx, cancel := context.WithCancel(context.Background()) return &peerTracker{ - host: h, - connGater: connGater, - disconnectedPeers: make(map[peer.ID]*peerStat), - trackedPeers: make(map[peer.ID]*peerStat), - maxAwaitingTime: maxAwaitingTime, - defaultScore: defaultScore, - maxPeerTrackerSize: maxPeerTrackerSize, - ctx: ctx, - cancel: cancel, - done: make(chan struct{}, 2), + host: h, + connGater: connGater, + disconnectedPeers: make(map[peer.ID]*peerStat), + trackedPeers: make(map[peer.ID]*peerStat), + ctx: ctx, + cancel: cancel, + done: make(chan struct{}, 2), } } @@ -118,7 +116,7 @@ func (p *peerTracker) connected(pID peer.ID) { // skip adding the peer to avoid overfilling of the peerTracker with unused peers if: // peerTracker reaches the maxTrackerSize and there are more connected peers // than disconnected peers. - if len(p.trackedPeers)+len(p.disconnectedPeers) > p.maxPeerTrackerSize && + if len(p.trackedPeers)+len(p.disconnectedPeers) > maxPeerTrackerSize && len(p.trackedPeers) > len(p.disconnectedPeers) { return } @@ -127,7 +125,7 @@ func (p *peerTracker) connected(pID peer.ID) { // because libp2p does not emit multiple Connected events per 1 peer stats, ok := p.disconnectedPeers[pID] if !ok { - stats = &peerStat{peerID: pID, peerScore: p.defaultScore} + stats = &peerStat{peerID: pID, peerScore: defaultScore} } else { delete(p.disconnectedPeers, pID) } @@ -141,7 +139,7 @@ func (p *peerTracker) disconnected(pID peer.ID) { if !ok { return } - stats.pruneDeadline = time.Now().Add(p.maxAwaitingTime) + stats.pruneDeadline = time.Now().Add(maxAwaitingTime) p.disconnectedPeers[pID] = stats delete(p.trackedPeers, pID) } @@ -177,7 +175,7 @@ func (p *peerTracker) gc() { } for id, peer := range p.trackedPeers { - if peer.peerScore <= p.defaultScore { + if peer.peerScore <= defaultScore { delete(p.trackedPeers, id) } } diff --git a/libs/header/p2p/peer_tracker_test.go b/libs/header/p2p/peer_tracker_test.go index 0f3175aae6..2645a2419e 100644 --- a/libs/header/p2p/peer_tracker_test.go +++ b/libs/header/p2p/peer_tracker_test.go @@ -18,7 +18,8 @@ func TestPeerTracker_GC(t *testing.T) { gcCycle = time.Millisecond * 200 connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - p := newPeerTracker(h[0], connGater, time.Millisecond*1, 1, 5) + p := newPeerTracker(h[0], connGater) + maxAwaitingTime = time.Millisecond pid1 := peer.ID("peer1") pid2 := peer.ID("peer2") pid3 := peer.ID("peer3") @@ -42,7 +43,8 @@ func TestPeerTracker_BlockPeer(t *testing.T) { h := createMocknet(t, 2) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - p := newPeerTracker(h[0], connGater, time.Millisecond*1, 1, 5) + p := newPeerTracker(h[0], connGater) + maxAwaitingTime = time.Millisecond p.blockPeer(h[1].ID(), errors.New("test")) require.Len(t, connGater.ListBlockedPeers(), 1) require.True(t, connGater.ListBlockedPeers()[0] == h[1].ID()) diff --git a/libs/header/p2p/server.go b/libs/header/p2p/server.go index a6b7e38b37..65c772c75f 100644 --- a/libs/header/p2p/server.go +++ b/libs/header/p2p/server.go @@ -157,7 +157,7 @@ func (serv *ExchangeServer[H]) requestHandler(stream network.Stream) { // if it exists. func (serv *ExchangeServer[H]) handleRequestByHash(hash []byte) ([]H, error) { log.Debugw("server: handling header request", "hash", header.Hash(hash).String()) - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RangeRequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-by-hash", trace.WithAttributes( attribute.String("hash", header.Hash(hash).String()), @@ -186,7 +186,7 @@ func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { return serv.handleHeadRequest() } - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RangeRequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-range", trace.WithAttributes( @@ -194,7 +194,7 @@ func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { attribute.Int64("to", int64(to)))) defer span.End() - if to-from > serv.Params.MaxRequestSize { + if to-from > header.MaxRangeRequestSize { log.Errorw("server: skip request for too many headers.", "amount", to-from) span.SetStatus(codes.Error, header.ErrHeadersLimitExceeded.Error()) return nil, header.ErrHeadersLimitExceeded @@ -250,7 +250,7 @@ func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { // handleHeadRequest returns the latest stored head. func (serv *ExchangeServer[H]) handleHeadRequest() ([]H, error) { log.Debug("server: handling head request") - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RequestTimeout) + ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RangeRequestTimeout) defer cancel() ctx, span := tracer.Start(ctx, "request-head") defer span.End() diff --git a/libs/header/sync/options.go b/libs/header/sync/options.go index b56d13142b..d314003ce3 100644 --- a/libs/header/sync/options.go +++ b/libs/header/sync/options.go @@ -18,8 +18,6 @@ type Parameters struct { // needed to report and punish misbehavior should be less than the unbonding // period. TrustingPeriod time.Duration - // MaxRequestSizeNumber of headers that can be requested at once. - MaxRequestSize uint64 // blockTime provides a reference point for the Syncer to determine // whether its subjective head is outdated. // Keeping it private, we don't want users to independently configure it. @@ -31,7 +29,6 @@ type Parameters struct { func DefaultParameters() Parameters { return Parameters{ TrustingPeriod: 168 * time.Hour, - MaxRequestSize: 512, } } @@ -39,9 +36,6 @@ func (p *Parameters) Validate() error { if p.TrustingPeriod == 0 { return fmt.Errorf("invalid trusting period duration: %v", p.TrustingPeriod) } - if p.MaxRequestSize == 0 { - return fmt.Errorf("invalid max request size: %d", p.MaxRequestSize) - } return nil } @@ -60,11 +54,3 @@ func WithTrustingPeriod(duration time.Duration) Options { p.TrustingPeriod = duration } } - -// WithMaxRequestSize is a functional option that configures the -// `MaxRequestSize` parameter. -func WithMaxRequestSize(amount uint64) Options { - return func(p *Parameters) { - p.MaxRequestSize = amount - } -} diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index 4808edd7ed..d7808592f0 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -295,7 +295,7 @@ func (s *Syncer[H]) requestHeaders( amount := to - uint64(fromHead.Height()) // start requesting headers until amount remaining will be 0 for amount > 0 { - size := s.Params.MaxRequestSize + size := header.MaxRangeRequestSize if amount < size { size = amount } diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index cc32a69d53..ea99ae1782 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -36,7 +36,6 @@ func TestSyncSimpleRequestingHead(t *testing.T) { &test.DummySubscriber{}, WithBlockTime(time.Second*30), WithTrustingPeriod(time.Microsecond), - WithMaxRequestSize(13), ) require.NoError(t, err) err = syncer.Start(ctx) @@ -74,12 +73,11 @@ func TestDoSyncFullRangeFromExternalPeer(t *testing.T) { local.NewExchange(remoteStore), localStore, &test.DummySubscriber{}, - WithMaxRequestSize(10), ) require.NoError(t, err) require.NoError(t, syncer.Start(ctx)) - err = remoteStore.Append(ctx, suite.GenDummyHeaders(10)...) + err = remoteStore.Append(ctx, suite.GenDummyHeaders(int(header.MaxRangeRequestSize))...) require.NoError(t, err) // give store time to update heightSub index time.Sleep(time.Millisecond * 100) @@ -92,10 +90,8 @@ func TestDoSyncFullRangeFromExternalPeer(t *testing.T) { err = syncer.doSync(ctx, localHead, remoteHead) require.NoError(t, err) - // give store time to update heightSub index - time.Sleep(time.Millisecond * 100) - newHead, err := localStore.Head(ctx) + newHead := *syncer.syncedHead.Load() require.NoError(t, err) require.Equal(t, newHead.Height(), remoteHead.Height()) } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index c460e266ae..383d8db800 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -42,8 +42,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return []p2p.Option[p2p.ServerParameters]{ p2p.WithWriteDeadline(cfg.Server.WriteDeadline), p2p.WithReadDeadline(cfg.Server.ReadDeadline), - p2p.WithMaxRequestSize[p2p.ServerParameters](cfg.Server.MaxRequestSize), - p2p.WithRequestTimeout[p2p.ServerParameters](cfg.Server.RequestTimeout), + p2p.WithRangeRequestTimeout[p2p.ServerParameters](cfg.Server.RangeRequestTimeout), p2p.WithNetworkID[p2p.ServerParameters](network.String()), } }), @@ -67,7 +66,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return []sync.Options{ sync.WithBlockTime(modp2p.BlockTime), sync.WithTrustingPeriod(cfg.Syncer.TrustingPeriod), - sync.WithMaxRequestSize(cfg.Syncer.MaxRequestSize), } }), fx.Provide(fx.Annotate( @@ -114,13 +112,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide( func(cfg Config, network modp2p.Network) []p2p.Option[p2p.ClientParameters] { return []p2p.Option[p2p.ClientParameters]{ - p2p.WithMinResponses(cfg.Client.MinResponses), - p2p.WithMaxRequestSize[p2p.ClientParameters](cfg.Client.MaxRequestSize), - p2p.WithMaxHeadersPerRequest(cfg.Client.MaxHeadersPerRequest), - p2p.WithMaxAwaitingTime(cfg.Client.MaxAwaitingTime), - p2p.WithDefaultScore(cfg.Client.DefaultScore), - p2p.WithRequestTimeout[p2p.ClientParameters](cfg.Client.RequestTimeout), - p2p.WithMaxTrackerSize(cfg.Client.MaxPeerTrackerSize), + p2p.WithMaxHeadersPerRangeRequest(cfg.Client.MaxHeadersPerRangeRequest), + p2p.WithRangeRequestTimeout[p2p.ClientParameters](cfg.Client.RangeRequestTimeout), p2p.WithNetworkID[p2p.ClientParameters](network.String()), p2p.WithChainID(network.String()), } diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 15e0604e14..f995a4fb68 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -55,7 +55,6 @@ func TestConstructModule_StoreParams(t *testing.T) { func TestConstructModule_SyncerParams(t *testing.T) { cfg := DefaultConfig(node.Light) cfg.Syncer.TrustingPeriod = time.Hour - cfg.Syncer.MaxRequestSize = 1 var syncer *sync.Syncer[*header.ExtendedHeader] app := fxtest.New(t, fx.Supply(modp2p.Private), @@ -80,7 +79,6 @@ func TestConstructModule_SyncerParams(t *testing.T) { }), ) require.Equal(t, cfg.Syncer.TrustingPeriod, syncer.Params.TrustingPeriod) - require.Equal(t, cfg.Syncer.MaxRequestSize, syncer.Params.MaxRequestSize) require.NoError(t, app.Err()) } @@ -88,9 +86,7 @@ func TestConstructModule_SyncerParams(t *testing.T) { // params are set in store correctly. func TestConstructModule_ExchangeParams(t *testing.T) { cfg := DefaultConfig(node.Light) - cfg.Client.MinResponses = 10 - cfg.Client.MaxRequestSize = 200 - cfg.Client.MaxHeadersPerRequest = 15 + cfg.Client.MaxHeadersPerRangeRequest = 15 var exchange *p2p.Exchange[*header.ExtendedHeader] var exchangeServer *p2p.ExchangeServer[*header.ExtendedHeader] @@ -113,16 +109,10 @@ func TestConstructModule_ExchangeParams(t *testing.T) { }), ) require.NoError(t, app.Err()) - require.Equal(t, exchange.Params.MinResponses, cfg.Client.MinResponses) - require.Equal(t, exchange.Params.MaxRequestSize, cfg.Client.MaxRequestSize) - require.Equal(t, exchange.Params.MaxHeadersPerRequest, cfg.Client.MaxHeadersPerRequest) - require.Equal(t, exchange.Params.MaxAwaitingTime, cfg.Client.MaxAwaitingTime) - require.Equal(t, exchange.Params.DefaultScore, cfg.Client.DefaultScore) - require.Equal(t, exchange.Params.MaxPeerTrackerSize, cfg.Client.MaxPeerTrackerSize) - require.Equal(t, exchange.Params.RequestTimeout, cfg.Client.RequestTimeout) + require.Equal(t, exchange.Params.MaxHeadersPerRangeRequest, cfg.Client.MaxHeadersPerRangeRequest) + require.Equal(t, exchange.Params.RangeRequestTimeout, cfg.Client.RangeRequestTimeout) require.Equal(t, exchangeServer.Params.WriteDeadline, cfg.Server.WriteDeadline) require.Equal(t, exchangeServer.Params.ReadDeadline, cfg.Server.ReadDeadline) - require.Equal(t, exchangeServer.Params.MaxRequestSize, cfg.Server.MaxRequestSize) - require.Equal(t, exchangeServer.Params.RequestTimeout, cfg.Server.RequestTimeout) + require.Equal(t, exchangeServer.Params.RangeRequestTimeout, cfg.Server.RangeRequestTimeout) } From 3f972484a41c12f83cbb2d163c47444df258a3f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 13:45:17 +0000 Subject: [PATCH 0451/1008] chore(deps): bump github.com/ipfs/go-merkledag from 0.9.0 to 0.10.0 (#1867) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 54daeab513..d5e97901c4 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-libipfs v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 - github.com/ipfs/go-merkledag v0.9.0 + github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 github.com/libp2p/go-libp2p v0.25.1 github.com/libp2p/go-libp2p-kad-dht v0.21.0 @@ -199,8 +199,8 @@ require ( github.com/ipfs/go-peertaskqueue v0.8.1 // indirect github.com/ipfs/go-verifcid v0.0.2 // indirect github.com/ipld/go-car/v2 v2.5.1 // indirect - github.com/ipld/go-codec-dagpb v1.5.0 // indirect - github.com/ipld/go-ipld-prime v0.19.0 // indirect + github.com/ipld/go-codec-dagpb v1.6.0 // indirect + github.com/ipld/go-ipld-prime v0.20.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect @@ -241,7 +241,7 @@ require ( github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multicodec v0.7.0 // indirect + github.com/multiformats/go-multicodec v0.8.0 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/onsi/ginkgo/v2 v2.5.1 // indirect diff --git a/go.sum b/go.sum index 42dfe032d1..dbc4b2db6f 100644 --- a/go.sum +++ b/go.sum @@ -430,7 +430,7 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -873,8 +873,8 @@ github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOL github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= -github.com/ipfs/go-merkledag v0.9.0 h1:DFC8qZ96Dz1hMT7dtIpcY524eFFDiEWAF8hNJHWW2pk= -github.com/ipfs/go-merkledag v0.9.0/go.mod h1:bPHqkHt5OZ0p1n3iqPeDiw2jIBkjAytRjS3WSBwjq90= +github.com/ipfs/go-merkledag v0.10.0 h1:IUQhj/kzTZfam4e+LnaEpoiZ9vZF6ldimVlby+6OXL4= +github.com/ipfs/go-merkledag v0.10.0/go.mod h1:zkVav8KiYlmbzUzNM6kENzkdP5+qR7+2mCwxkQ6GIj8= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= @@ -887,13 +887,13 @@ github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZze github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk= github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= -github.com/ipld/go-codec-dagpb v1.5.0 h1:RspDRdsJpLfgCI0ONhTAnbHdySGD4t+LHSPK4X1+R0k= -github.com/ipld/go-codec-dagpb v1.5.0/go.mod h1:0yRIutEFD8o1DGVqw4RSHh+BUTlJA9XWldxaaWR/o4g= +github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= +github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= -github.com/ipld/go-ipld-prime v0.19.0 h1:5axC7rJmPc17Emw6TelxGwnzALk0PdupZ2oj2roDj04= -github.com/ipld/go-ipld-prime v0.19.0/go.mod h1:Q9j3BaVXwaA3o5JUDNvptDDr/x8+F7FG6XJ8WI3ILg4= +github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= +github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= @@ -978,8 +978,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -1359,8 +1359,8 @@ github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyD github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.7.0 h1:rTUjGOwjlhGHbEMbPoSUJowG1spZTVsITRANCjKTUAQ= -github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.8.0 h1:evBmgkbSQux+Ds2IgfhkO38Dl2GDtRW8/Rp6YiSHX/Q= +github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -1571,8 +1571,8 @@ github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRr github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -1750,7 +1750,7 @@ github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= -github.com/warpfork/go-testmark v0.10.0 h1:E86YlUMYfwIacEsQGlnTvjk1IgYkyTGjPhF0RnwTCmw= +github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= From 5f2c5d7d983e7a007c7a6d74068bd201ac45eb84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 13:57:41 +0000 Subject: [PATCH 0452/1008] chore(deps): bump google.golang.org/grpc from 1.52.0 to 1.53.0 (#1864) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index d5e97901c4..71f1795b71 100644 --- a/go.mod +++ b/go.mod @@ -70,15 +70,15 @@ require ( golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 golang.org/x/sync v0.1.0 golang.org/x/text v0.7.0 - google.golang.org/grpc v1.52.0 + google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) require ( - cloud.google.com/go v0.105.0 // indirect - cloud.google.com/go/compute v1.12.1 // indirect - cloud.google.com/go/compute/metadata v0.2.1 // indirect - cloud.google.com/go/iam v0.7.0 // indirect + cloud.google.com/go v0.107.0 // indirect + cloud.google.com/go/compute v1.15.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v0.8.0 // indirect cloud.google.com/go/storage v1.27.0 // indirect cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect @@ -162,7 +162,7 @@ require ( github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect - github.com/googleapis/gax-go/v2 v2.6.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -305,14 +305,14 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.6.0 // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect + golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect golang.org/x/tools v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.102.0 // indirect + google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index dbc4b2db6f..7dfa2ae03b 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -30,14 +30,14 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= @@ -636,8 +636,8 @@ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -2041,8 +2041,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2292,8 +2292,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2352,8 +2352,8 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2384,8 +2384,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From f99fb22e28c53951d46f2083a323d13c15ec1898 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 14:11:45 +0000 Subject: [PATCH 0453/1008] chore(deps): bump github.com/libp2p/go-libp2p-routing-helpers from 0.6.0 to 0.6.1 (#1866) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71f1795b71..10c6444d20 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/libp2p/go-libp2p-kad-dht v0.21.0 github.com/libp2p/go-libp2p-pubsub v0.9.1 github.com/libp2p/go-libp2p-record v0.2.0 - github.com/libp2p/go-libp2p-routing-helpers v0.6.0 + github.com/libp2p/go-libp2p-routing-helpers v0.6.1 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 diff --git a/go.sum b/go.sum index 7dfa2ae03b..08ffd75354 100644 --- a/go.sum +++ b/go.sum @@ -1094,8 +1094,8 @@ github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= -github.com/libp2p/go-libp2p-routing-helpers v0.6.0 h1:Rfyd+wp/cU0PjNjCphGzLYzd7Q51fjOMs5Sjj6zWGT0= -github.com/libp2p/go-libp2p-routing-helpers v0.6.0/go.mod h1:wwK/XSLt6njjO7sRbjhf8w7PGBOfdntMQ2mOQPZ5s/Q= +github.com/libp2p/go-libp2p-routing-helpers v0.6.1 h1:tI3rHOf/FDQsxC2pHBaOZiqPJ0MZYyzGAf4V45xla4U= +github.com/libp2p/go-libp2p-routing-helpers v0.6.1/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= From 11ec5d402c4b1240b29957fcb61c977c1559c935 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 13 Mar 2023 18:02:08 +0100 Subject: [PATCH 0454/1008] fix(share): Fix empty EDS / empty root initialisation (#1897) We did not generate the empty EDS correctly - moving forward, it's best that celestia-app `pkg/da` contains a convenience function for generating empty shares that we can rely on instead of duplicating logic here. I will do it now, but for now we need this solution b/c it's a bug. --- core/listener_test.go | 7 +------ share/empty.go | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/core/listener_test.go b/core/listener_test.go index c38509a75c..c0e6245135 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -52,13 +52,8 @@ func TestListener(t *testing.T) { // ensure headers and dataHash are getting broadcasted to the relevant topics for i := 0; i < 5; i++ { - h, err := subs.NextHeader(ctx) + _, err := subs.NextHeader(ctx) require.NoError(t, err) - - dataHash, err := edsSubs.Next(ctx) - require.NoError(t, err) - - require.Equal(t, h.DataHash.Bytes(), []byte(dataHash)) } err = cl.Stop(ctx) diff --git a/share/empty.go b/share/empty.go index d32e751969..6b5d7bb1e8 100644 --- a/share/empty.go +++ b/share/empty.go @@ -10,6 +10,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/rsmt2d" ) @@ -26,9 +27,14 @@ func init() { if err != nil { panic(fmt.Errorf("failed to create empty EDS: %w", err)) } - emptyEDS = eds + dah := da.NewDataAvailabilityHeader(eds) + minDAH := da.MinDataAvailabilityHeader() + if !bytes.Equal(minDAH.Hash(), dah.Hash()) { + panic(fmt.Sprintf("mismatch in calculated minimum DAH and minimum DAH from celestia-app, "+ + "expected %X, got %X", minDAH.Hash(), dah.Hash())) + } emptyRoot = &dah // precompute Hash, so it's cached internally to avoid potential races @@ -53,19 +59,7 @@ func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { return emptyEDS } -// tail is filler for all tail padded shares -// it is allocated once and used everywhere -var tailPaddingShare = append( - append(make([]byte, 0, appconsts.ShareSize), appconsts.TailPaddingNamespaceID...), - bytes.Repeat([]byte{0}, appconsts.ShareSize-appconsts.NamespaceSize)..., -) - // emptyDataSquare returns the minimum size data square filled with tail padding. func emptyDataSquare() [][]byte { - shares := make([][]byte, appconsts.MinShareCount) - for i := 0; i < appconsts.MinShareCount; i++ { - shares[i] = tailPaddingShare - } - - return shares + return shares.ToBytes(shares.TailPaddingShares(appconsts.MinShareCount)) } From 0038a821500441ed6f5b6aa009eb3d0b3e5f4485 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 14 Mar 2023 20:05:21 +0800 Subject: [PATCH 0455/1008] feat(share/shrex)!: cleanup shrex server side timeouts (#1885) Recent tests on arabica shows that 256 block sampling time could be up to 50s. Increase shrex server side timeouts according to new observed values. --- nodebuilder/share/config.go | 14 +++- nodebuilder/share/module.go | 10 +-- share/getters/shrex.go | 11 +-- share/getters/shrex_test.go | 5 +- share/getters/utils.go | 4 +- share/p2p/params.go | 69 +++++++++++++++++++ share/p2p/shrexeds/client.go | 9 +-- share/p2p/shrexeds/exchange_test.go | 4 +- share/p2p/shrexeds/options.go | 88 ------------------------ share/p2p/shrexeds/params.go | 36 ++++++++++ share/p2p/shrexeds/server.go | 19 ++---- share/p2p/shrexnd/client.go | 19 +++--- share/p2p/shrexnd/exchange_test.go | 4 +- share/p2p/shrexnd/options.go | 102 ---------------------------- share/p2p/shrexnd/params.go | 18 +++++ share/p2p/shrexnd/server.go | 17 ++--- 16 files changed, 181 insertions(+), 248 deletions(-) create mode 100644 share/p2p/params.go delete mode 100644 share/p2p/shrexeds/options.go create mode 100644 share/p2p/shrexeds/params.go delete mode 100644 share/p2p/shrexnd/options.go create mode 100644 share/p2p/shrexnd/params.go diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index aeb37a93dc..0bc3dcfa30 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -4,6 +4,9 @@ import ( "errors" "fmt" "time" + + "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" + "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" ) var ( @@ -21,6 +24,10 @@ type Config struct { // UseShareExchange is a flag toggling the usage of shrex protocols for blocksync. // NOTE: This config variable only has an effect on full and bridge nodes. UseShareExchange bool + // ShrExEDSParams sets shrexeds client and server configuration parameters + ShrExEDSParams *shrexeds.Parameters + // ShrExNDParams sets shrexnd client and server configuration parameters + ShrExNDParams *shrexnd.Parameters } func DefaultConfig() Config { @@ -29,6 +36,8 @@ func DefaultConfig() Config { DiscoveryInterval: time.Second * 30, AdvertiseInterval: time.Second * 30, UseShareExchange: true, + ShrExEDSParams: shrexeds.DefaultParameters(), + ShrExNDParams: shrexnd.DefaultParameters(), } } @@ -37,5 +46,8 @@ func (cfg *Config) Validate() error { if cfg.DiscoveryInterval <= 0 || cfg.AdvertiseInterval <= 0 { return fmt.Errorf("nodebuilder/share: %s", ErrNegativeInterval) } - return nil + if err := cfg.ShrExNDParams.Validate(); err != nil { + return err + } + return cfg.ShrExEDSParams.Validate() } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index d5f0aec065..16228c1518 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -46,7 +46,8 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { - return shrexeds.NewServer(host, store, shrexeds.WithNetworkID(network.String())) + cfg.ShrExEDSParams.WithNetworkID(network.String()) + return shrexeds.NewServer(cfg.ShrExEDSParams, host, store) }, fx.OnStart(func(ctx context.Context, server *shrexeds.Server) error { return server.Start(ctx) @@ -62,7 +63,8 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option getter share.Getter, network modp2p.Network, ) (*shrexnd.Server, error) { - return shrexnd.NewServer(host, store, getter, shrexnd.WithNetworkID(network.String())) + cfg.ShrExNDParams.WithNetworkID(network.String()) + return shrexnd.NewServer(cfg.ShrExNDParams, host, store, getter) }, fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { return server.Start(ctx) @@ -155,12 +157,12 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(fullGetter), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { - return shrexnd.NewClient(host, shrexnd.WithNetworkID(network.String())) + return shrexnd.NewClient(cfg.ShrExNDParams, host) }, ), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - return shrexeds.NewClient(host, shrexeds.WithNetworkID(network.String())) + return shrexeds.NewClient(cfg.ShrExEDSParams, host) }, ), fx.Provide(fx.Annotate( diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 97af7529ac..96ec18503c 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -21,7 +21,7 @@ var _ share.Getter = (*ShrexGetter)(nil) const ( // defaultMinRequestTimeout value is set according to observed time taken by healthy peer to // serve getEDS request for block size 256 - defaultMinRequestTimeout = time.Second * 10 + defaultMinRequestTimeout = time.Minute // should be >= shrexeds server write timeout defaultMinAttemptsCount = 3 ) @@ -62,7 +62,9 @@ func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col } func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + var attempt int for { + attempt++ select { case <-ctx.Done(): return nil, fmt.Errorf("getter/shrex: %w", ctx.Err()) @@ -71,10 +73,9 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) if err != nil { log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) - return nil, fmt.Errorf("getter/shrex: %w", err) } - reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount, sg.minRequestTimeout) + reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) eds, err := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) cancel() switch err { @@ -96,7 +97,9 @@ func (sg *ShrexGetter) GetSharesByNamespace( root *share.Root, id namespace.ID, ) (share.NamespacedShares, error) { + var attempt int for { + attempt++ select { case <-ctx.Done(): return nil, fmt.Errorf("getter/shrex: %w", ctx.Err()) @@ -108,7 +111,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( return nil, fmt.Errorf("getter/shrex: %w", err) } - reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount, sg.minRequestTimeout) + reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) nd, err := sg.ndClient.RequestND(reqCtx, root, id, peer) cancel() switch err { diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 44a236d38f..60bd0c2243 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -44,7 +44,8 @@ func TestGetSharesWithProofByNamespace(t *testing.T) { // create server and register handler srvHost := net.NewTestNode().Host - srv, err := shrexnd.NewServer(srvHost, edsStore, NewIPLDGetter(bServ)) + params := shrexnd.DefaultParameters() + srv, err := shrexnd.NewServer(params, srvHost, edsStore, NewIPLDGetter(bServ)) require.NoError(t, err) err = srv.Start(ctx) require.NoError(t, err) @@ -54,7 +55,7 @@ func TestGetSharesWithProofByNamespace(t *testing.T) { }) // create client and connect it to server - client, err := shrexnd.NewClient(net.NewTestNode().Host) + client, err := shrexnd.NewClient(params, net.NewTestNode().Host) require.NoError(t, err) net.ConnectAll() diff --git a/share/getters/utils.go b/share/getters/utils.go index 6df830a34c..eeaef64375 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -94,14 +94,14 @@ func verifyNIDSize(nID namespace.ID) error { } // ctxWithSplitTimeout will split timeout stored in context by splitFactor and return the result if -// it is greater than minTimeout. minTimeout == 0 will be ignored +// it is greater than minTimeout. minTimeout == 0 will be ignored, splitFactor <= 0 will be ignored func ctxWithSplitTimeout( ctx context.Context, splitFactor int, minTimeout time.Duration, ) (context.Context, context.CancelFunc) { deadline, ok := ctx.Deadline() - if !ok { + if !ok || splitFactor <= 0 { if minTimeout == 0 { return context.WithCancel(ctx) } diff --git a/share/p2p/params.go b/share/p2p/params.go new file mode 100644 index 0000000000..f11179293d --- /dev/null +++ b/share/p2p/params.go @@ -0,0 +1,69 @@ +package p2p + +import ( + "fmt" + "time" + + "github.com/libp2p/go-libp2p/core/protocol" +) + +// Parameters is the set of parameters that must be configured for the shrex/eds protocol. +type Parameters struct { + // ServerReadTimeout sets the timeout for reading messages from the stream. + ServerReadTimeout time.Duration + + // ServerWriteTimeout sets the timeout for writing messages to the stream. + ServerWriteTimeout time.Duration + + // HandleRequestTimeout defines the deadline for handling request. + HandleRequestTimeout time.Duration + + // ConcurrencyLimit is the maximum number of concurrently handled streams + ConcurrencyLimit int + + // networkID is prepended to the protocolID and represents the network the protocol is + // running on. + networkID string +} + +func DefaultParameters() *Parameters { + return &Parameters{ + ServerReadTimeout: 5 * time.Second, + ServerWriteTimeout: time.Minute, // based on max observed sample time for 256 blocks (~50s) + HandleRequestTimeout: time.Minute, + ConcurrencyLimit: 10, + } +} + +const errSuffix = "value should be positive and non-zero" + +func (p *Parameters) Validate() error { + if p.ServerReadTimeout <= 0 { + return fmt.Errorf("invalid stream read timeout: %v, %s", p.ServerReadTimeout, errSuffix) + } + if p.ServerWriteTimeout <= 0 { + return fmt.Errorf("invalid write timeout: %v, %s", p.ServerWriteTimeout, errSuffix) + } + if p.HandleRequestTimeout <= 0 { + return fmt.Errorf("invalid hadnle request timeout: %v, %s", p.HandleRequestTimeout, errSuffix) + } + if p.ConcurrencyLimit <= 0 { + return fmt.Errorf("invalid concurrency limit: %s", errSuffix) + } + return nil +} + +// WithNetworkID sets the value of networkID in params +func (p *Parameters) WithNetworkID(networkID string) { + p.networkID = networkID +} + +// NetworkID returns the value of networkID stored in params +func (p *Parameters) NetworkID() string { + return p.networkID +} + +// ProtocolID creates a protocol ID string according to common format +func ProtocolID(networkID, protocolString string) protocol.ID { + return protocol.ID(fmt.Sprintf("/%s%s", networkID, protocolString)) +} diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 1eaea62aba..4c96265c63 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -28,19 +28,14 @@ type Client struct { } // NewClient creates a new ShrEx/EDS client. -func NewClient(host host.Host, opts ...Option) (*Client, error) { - params := DefaultParameters() - for _, opt := range opts { - opt(params) - } - +func NewClient(params *Parameters, host host.Host) (*Client, error) { if err := params.Validate(); err != nil { return nil, fmt.Errorf("shrex-eds: client creation failed: %w", err) } return &Client{ host: host, - protocolID: protocolID(params.networkID), + protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), }, nil } diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index c81fa43619..3085e1e32e 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -152,9 +152,9 @@ func makeExchange(t *testing.T) (*eds.Store, *Client, *Server) { store := newStore(t) hosts := createMocknet(t, 2) - client, err := NewClient(hosts[0]) + client, err := NewClient(DefaultParameters(), hosts[0]) require.NoError(t, err) - server, err := NewServer(hosts[1], store) + server, err := NewServer(DefaultParameters(), hosts[1], store) require.NoError(t, err) return store, client, server diff --git a/share/p2p/shrexeds/options.go b/share/p2p/shrexeds/options.go deleted file mode 100644 index f3215e4704..0000000000 --- a/share/p2p/shrexeds/options.go +++ /dev/null @@ -1,88 +0,0 @@ -package shrexeds - -import ( - "fmt" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/protocol" -) - -const protocolString = "/shrex/eds/v0.0.1" - -var log = logging.Logger("shrex-eds") - -// Option is the functional option that is applied to the shrex/eds protocol to configure its -// parameters. -type Option func(*Parameters) - -// Parameters is the set of parameters that must be configured for the shrex/eds protocol. -type Parameters struct { - // ReadDeadline sets the timeout for reading messages from the stream. - ReadDeadline time.Duration - - // WriteDeadline sets the timeout for writing messages to the stream. - WriteDeadline time.Duration - - // ReadCARDeadline defines the deadline for reading a CAR from disk. - ReadCARDeadline time.Duration - - // BufferSize defines the size of the buffer used for writing an ODS over the stream. - BufferSize uint64 - - // networkID is prepended to the protocolID and represents the network the protocol is - // running on. - networkID string - - // concurrencyLimit is the maximum number of concurrently handled streams - concurrencyLimit int -} - -func DefaultParameters() *Parameters { - return &Parameters{ - ReadDeadline: time.Minute, - WriteDeadline: time.Second * 30, // based on block time - ReadCARDeadline: time.Minute, - BufferSize: 32 * 1024, - concurrencyLimit: 10, - } -} - -const errSuffix = "value should be positive and non-zero" - -func (p *Parameters) Validate() error { - if p.ReadDeadline <= 0 { - return fmt.Errorf("invalid stream read deadline: %s", errSuffix) - } - if p.WriteDeadline <= 0 { - return fmt.Errorf("invalid write deadline: %s", errSuffix) - } - if p.ReadCARDeadline <= 0 { - return fmt.Errorf("invalid read CAR deadline: %s", errSuffix) - } - if p.BufferSize <= 0 { - return fmt.Errorf("invalid buffer size: %s", errSuffix) - } - if p.concurrencyLimit <= 0 { - return fmt.Errorf("invalid concurrency limit: %s", errSuffix) - } - return nil -} - -// WithNetworkID is a functional option that configures the `networkID` parameter -func WithNetworkID(networkID string) Option { - return func(parameters *Parameters) { - parameters.networkID = networkID - } -} - -// WithConcurrencyLimit is a functional option that configures the `concurrencyLimit` parameter -func WithConcurrencyLimit(concurrencyLimit int) Option { - return func(parameters *Parameters) { - parameters.concurrencyLimit = concurrencyLimit - } -} - -func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("/%s%s", networkID, protocolString)) -} diff --git a/share/p2p/shrexeds/params.go b/share/p2p/shrexeds/params.go new file mode 100644 index 0000000000..c6d8d83b8b --- /dev/null +++ b/share/p2p/shrexeds/params.go @@ -0,0 +1,36 @@ +package shrexeds + +import ( + "fmt" + + logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-node/share/p2p" +) + +const protocolString = "/shrex/eds/v0.0.1" + +var log = logging.Logger("shrex-eds") + +// Parameters is the set of parameters that must be configured for the shrex/eds protocol. +type Parameters struct { + *p2p.Parameters + + // BufferSize defines the size of the buffer used for writing an ODS over the stream. + BufferSize uint64 +} + +func DefaultParameters() *Parameters { + return &Parameters{ + Parameters: p2p.DefaultParameters(), + BufferSize: 32 * 1024, + } +} + +func (p *Parameters) Validate() error { + if p.BufferSize <= 0 { + return fmt.Errorf("invalid buffer size: %v, value should be positive and non-zero", p.BufferSize) + } + + return p.Parameters.Validate() +} diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index 7001f97fd8..d79f3c31c7 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -32,12 +32,7 @@ type Server struct { } // NewServer creates a new ShrEx/EDS server. -func NewServer(host host.Host, store *eds.Store, opts ...Option) (*Server, error) { - params := DefaultParameters() - for _, opt := range opts { - opt(params) - } - +func NewServer(params *Parameters, host host.Host, store *eds.Store) (*Server, error) { if err := params.Validate(); err != nil { return nil, fmt.Errorf("shrex-eds: server creation failed: %w", err) } @@ -45,14 +40,14 @@ func NewServer(host host.Host, store *eds.Store, opts ...Option) (*Server, error return &Server{ host: host, store: store, - protocolID: protocolID(params.networkID), + protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), params: params, }, nil } func (s *Server) Start(context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) - s.host.SetStreamHandler(s.protocolID, p2p.RateLimitMiddleware(s.handleStream, s.params.concurrencyLimit)) + s.host.SetStreamHandler(s.protocolID, p2p.RateLimitMiddleware(s.handleStream, s.params.ConcurrencyLimit)) return nil } @@ -81,7 +76,7 @@ func (s *Server) handleStream(stream network.Stream) { return } - ctx, cancel := context.WithTimeout(s.ctx, s.params.ReadCARDeadline) + ctx, cancel := context.WithTimeout(s.ctx, s.params.HandleRequestTimeout) defer cancel() status := p2p_pb.Status_OK // determine whether the EDS is available in our store @@ -120,7 +115,7 @@ func (s *Server) handleStream(stream network.Stream) { } func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) { - err := stream.SetReadDeadline(time.Now().Add(s.params.ReadDeadline)) + err := stream.SetReadDeadline(time.Now().Add(s.params.ServerReadTimeout)) if err != nil { log.Debug(err) } @@ -139,7 +134,7 @@ func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) } func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error { - err := stream.SetWriteDeadline(time.Now().Add(s.params.WriteDeadline)) + err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { log.Debug(err) } @@ -150,7 +145,7 @@ func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error } func (s *Server) writeODS(edsReader io.ReadCloser, stream network.Stream) error { - err := stream.SetWriteDeadline(time.Now().Add(s.params.WriteDeadline)) + err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { log.Debug(err) } diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index baeaf8fdb1..3a86793416 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -33,19 +33,14 @@ type Client struct { } // NewClient creates a new shrEx/nd client -func NewClient(host host.Host, opts ...Option) (*Client, error) { - params := DefaultParameters() - for _, opt := range opts { - opt(params) - } - +func NewClient(params *Parameters, host host.Host) (*Client, error) { if err := params.Validate(); err != nil { return nil, fmt.Errorf("shrex-nd: client creation failed: %w", err) } return &Client{ host: host, - protocolID: protocolID(params.networkID), + protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), params: params, }, nil } @@ -178,15 +173,17 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) return } - if c.params.readTimeout != 0 { - err := stream.SetReadDeadline(time.Now().Add(c.params.readTimeout)) + // if deadline not set, client read deadline defaults to server write deadline + if c.params.ServerWriteTimeout != 0 { + err := stream.SetReadDeadline(time.Now().Add(c.params.ServerWriteTimeout)) if err != nil { log.Debugf("client-nd: set read deadline: %s", err) } } - if c.params.writeTimeout != 0 { - err := stream.SetWriteDeadline(time.Now().Add(c.params.readTimeout)) + // if deadline not set, client write deadline defaults to server read deadline + if c.params.ServerReadTimeout != 0 { + err := stream.SetWriteDeadline(time.Now().Add(c.params.ServerReadTimeout)) if err != nil { log.Debugf("client-nd: set write deadline: %s", err) } diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 3940474025..b1d09bc721 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -19,9 +19,9 @@ func TestExchange_RequestND(t *testing.T) { net, err := mocknet.FullMeshConnected(2) require.NoError(t, err) - client, err := NewClient(net.Hosts()[0]) + client, err := NewClient(DefaultParameters(), net.Hosts()[0]) require.NoError(t, err) - server, err := NewServer(net.Hosts()[1], nil, nil) + server, err := NewServer(DefaultParameters(), net.Hosts()[1], nil, nil) require.NoError(t, err) require.NoError(t, server.Start(context.Background())) diff --git a/share/p2p/shrexnd/options.go b/share/p2p/shrexnd/options.go deleted file mode 100644 index 4548e9bdfd..0000000000 --- a/share/p2p/shrexnd/options.go +++ /dev/null @@ -1,102 +0,0 @@ -package shrexnd - -import ( - "fmt" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/protocol" -) - -const protocolString = "/shrex/nd/0.0.1" - -var log = logging.Logger("shrex/nd") - -// Option is the functional option that is applied to the shrex/eds protocol to configure its -// parameters. -type Option func(*Parameters) - -// Parameters is the set of parameters that must be configured for the shrex/eds protocol. -type Parameters struct { - // readTimeout sets the timeout for reading messages from the stream. - readTimeout time.Duration - - // writeTimeout sets the timeout for writing messages to the stream. - writeTimeout time.Duration - - // serveTimeout defines the deadline for serving request. - serveTimeout time.Duration - - // networkID is prepended to the protocolID and represents the network the protocol is - // running on. - networkID string - - // concurrencyLimit is the maximum number of concurrently handled streams - concurrencyLimit int -} - -func DefaultParameters() *Parameters { - return &Parameters{ - readTimeout: time.Second * 5, - writeTimeout: time.Second * 10, - serveTimeout: time.Second * 10, - concurrencyLimit: 10, - } -} - -const errSuffix = "value should be positive and non-zero" - -func (p *Parameters) Validate() error { - if p.readTimeout <= 0 { - return fmt.Errorf("invalid stream read timeout: %v, %s", p.readTimeout, errSuffix) - } - if p.writeTimeout <= 0 { - return fmt.Errorf("invalid write timeout: %v, %s", p.writeTimeout, errSuffix) - } - if p.serveTimeout <= 0 { - return fmt.Errorf("invalid serve timeout: %v, %s", p.serveTimeout, errSuffix) - } - if p.concurrencyLimit <= 0 { - return fmt.Errorf("invalid concurrency limit: %v, %s", p.concurrencyLimit, errSuffix) - } - return nil -} - -// WithNetworkID is a functional option that configures the `networkID` parameter -func WithNetworkID(networkID string) Option { - return func(parameters *Parameters) { - parameters.networkID = networkID - } -} - -// WithReadTimeout is a functional option that configures the `readTimeout` parameter -func WithReadTimeout(readTimeout time.Duration) Option { - return func(parameters *Parameters) { - parameters.readTimeout = readTimeout - } -} - -// WithWriteTimeout is a functional option that configures the `writeTimeout` parameter -func WithWriteTimeout(writeTimeout time.Duration) Option { - return func(parameters *Parameters) { - parameters.writeTimeout = writeTimeout - } -} - -// WithServeTimeout is a functional option that configures the `serveTimeout` parameter -func WithServeTimeout(serveTimeout time.Duration) Option { - return func(parameters *Parameters) { - parameters.serveTimeout = serveTimeout - } -} - -// WithConcurrencyLimit is a functional option that configures the `concurrencyLimit` parameter -func WithConcurrencyLimit(concurrencyLimit int) Option { - return func(parameters *Parameters) { - parameters.concurrencyLimit = concurrencyLimit - } -} - -func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("/%s%s", networkID, protocolString)) -} diff --git a/share/p2p/shrexnd/params.go b/share/p2p/shrexnd/params.go new file mode 100644 index 0000000000..8f2c999dfe --- /dev/null +++ b/share/p2p/shrexnd/params.go @@ -0,0 +1,18 @@ +package shrexnd + +import ( + logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-node/share/p2p" +) + +const protocolString = "/shrex/nd/0.0.1" + +var log = logging.Logger("shrex/nd") + +// Parameters is the set of parameters that must be configured for the shrex/eds protocol. +type Parameters = p2p.Parameters + +func DefaultParameters() *Parameters { + return p2p.DefaultParameters() +} diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 224db4faf1..5a3e965481 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -33,12 +33,7 @@ type Server struct { } // NewServer creates new Server -func NewServer(host host.Host, store *eds.Store, getter share.Getter, opts ...Option) (*Server, error) { - params := DefaultParameters() - for _, opt := range opts { - opt(params) - } - +func NewServer(params *Parameters, host host.Host, store *eds.Store, getter share.Getter) (*Server, error) { if err := params.Validate(); err != nil { return nil, fmt.Errorf("shrex-nd: server creation failed: %w", err) } @@ -48,7 +43,7 @@ func NewServer(host host.Host, store *eds.Store, getter share.Getter, opts ...Op store: store, host: host, params: params, - protocolID: protocolID(params.networkID), + protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), } return srv, nil @@ -62,7 +57,7 @@ func (srv *Server) Start(context.Context) error { handler := func(s network.Stream) { srv.handleNamespacedData(ctx, s) } - srv.host.SetStreamHandler(srv.protocolID, p2p.RateLimitMiddleware(handler, srv.params.concurrencyLimit)) + srv.host.SetStreamHandler(srv.protocolID, p2p.RateLimitMiddleware(handler, srv.params.ConcurrencyLimit)) return nil } @@ -74,7 +69,7 @@ func (srv *Server) Stop(context.Context) error { } func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { - err := stream.SetReadDeadline(time.Now().Add(srv.params.readTimeout)) + err := stream.SetReadDeadline(time.Now().Add(srv.params.ServerReadTimeout)) if err != nil { log.Debugf("server: setting read deadline: %s", err) } @@ -100,7 +95,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre return } - ctx, cancel := context.WithTimeout(ctx, srv.params.serveTimeout) + ctx, cancel := context.WithTimeout(ctx, srv.params.HandleRequestTimeout) defer cancel() dah, err := srv.store.GetDAH(ctx, req.RootHash) @@ -173,7 +168,7 @@ func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNa } func (srv *Server) respond(stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { - err := stream.SetWriteDeadline(time.Now().Add(srv.params.writeTimeout)) + err := stream.SetWriteDeadline(time.Now().Add(srv.params.ServerWriteTimeout)) if err != nil { log.Debugf("server: seting write deadline: %s", err) } From 1ff6727dc69ed4fc293155a65b1eb343b5c85011 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 14 Mar 2023 21:26:07 +0800 Subject: [PATCH 0456/1008] feat(share/shrex): collect errors from shrex getter (#1884) Allow shrex getter client to collect and propagate request errors to the caller. Also introduce per error debug logs. --- share/getters/shrex.go | 77 +++++++++++++++++--------- share/getters/utils.go | 14 +++++ share/getters/utils_test.go | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 24 deletions(-) create mode 100644 share/getters/utils_test.go diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 96ec18503c..b72fe1576b 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -6,6 +6,8 @@ import ( "fmt" "time" + "go.uber.org/multierr" + "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -62,33 +64,47 @@ func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col } func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - var attempt int + var ( + attempt int + err error + ) for { attempt++ - select { - case <-ctx.Done(): - return nil, fmt.Errorf("getter/shrex: %w", ctx.Err()) - default: - } - peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) - if err != nil { - log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) + start := time.Now() + peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) + if getErr != nil { + err = multierr.Append(err, getErr) + log.Debugw("couldn't find peer", + "datahash", root.String(), + "err", getErr, + "finished (s)", time.Since(start)) + return nil, fmt.Errorf("getter/shrex: %w", err) } + reqStart := time.Now() reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) - eds, err := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) + eds, getErr := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) cancel() - switch err { + switch getErr { case nil: setStatus(peers.ResultSynced) return eds, nil case context.DeadlineExceeded: - log.Debugw("request exceeded deadline, trying with new peer", "datahash", root.String()) case p2p.ErrInvalidResponse: setStatus(peers.ResultBlacklistPeer) default: setStatus(peers.ResultCooldownPeer) } + + if !ErrorContains(err, getErr) { + err = multierr.Append(err, getErr) + } + log.Debugw("request failed", + "datahash", root.String(), + "peer", peer.String(), + "attempt", attempt, + "err", getErr, + "finished (s)", time.Since(reqStart)) } } @@ -97,33 +113,46 @@ func (sg *ShrexGetter) GetSharesByNamespace( root *share.Root, id namespace.ID, ) (share.NamespacedShares, error) { - var attempt int + var ( + attempt int + err error + ) for { attempt++ - select { - case <-ctx.Done(): - return nil, fmt.Errorf("getter/shrex: %w", ctx.Err()) - default: - } - peer, setStatus, err := sg.peerManager.Peer(ctx, root.Hash()) - if err != nil { - log.Debugw("couldn't find peer", "datahash", root.String(), "err", err) + start := time.Now() + peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) + if getErr != nil { + err = multierr.Append(err, getErr) + log.Debugw("couldn't find peer", + "datahash", root.String(), + "err", getErr, + "finished (s)", time.Since(start)) return nil, fmt.Errorf("getter/shrex: %w", err) } + reqStart := time.Now() reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) - nd, err := sg.ndClient.RequestND(reqCtx, root, id, peer) + nd, getErr := sg.ndClient.RequestND(reqCtx, root, id, peer) cancel() - switch err { + switch getErr { case nil: setStatus(peers.ResultSuccess) return nd, nil case context.DeadlineExceeded: - log.Debugw("request exceeded deadline, trying with new peer", "datahash", root.String()) case p2p.ErrInvalidResponse: setStatus(peers.ResultBlacklistPeer) default: setStatus(peers.ResultCooldownPeer) } + + if !ErrorContains(err, getErr) { + err = multierr.Append(err, getErr) + } + log.Debugw("request failed", + "datahash", root.String(), + "peer", peer.String(), + "attempt", attempt, + "err", getErr, + "finished (s)", time.Since(reqStart)) } } diff --git a/share/getters/utils.go b/share/getters/utils.go index eeaef64375..e7c1a25e8f 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -2,6 +2,7 @@ package getters import ( "context" + "errors" "fmt" "time" @@ -114,3 +115,16 @@ func ctxWithSplitTimeout( } return context.WithTimeout(ctx, minTimeout) } + +// ErrorContains reports whether any error in err's tree matches any error in targets tree. +func ErrorContains(err, target error) bool { + if errors.Is(err, target) || target == nil { + return true + } + + target = errors.Unwrap(target) + if target == nil { + return false + } + return ErrorContains(err, target) +} diff --git a/share/getters/utils_test.go b/share/getters/utils_test.go new file mode 100644 index 0000000000..421d1dcd23 --- /dev/null +++ b/share/getters/utils_test.go @@ -0,0 +1,104 @@ +package getters + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/multierr" +) + +func Test_ErrorContains(t *testing.T) { + err1 := errors.New("1") + err2 := errors.New("2") + + w1 := func(err error) error { + return fmt.Errorf("wrap1: %w", err) + } + w2 := func(err error) error { + return fmt.Errorf("wrap1: %w", err) + } + + type args struct { + err error + target error + } + tests := []struct { + name string + args args + want bool + }{ + {"nil err", + args{ + err: nil, + target: err1, + }, + false, + }, + {"nil target", + args{ + err: err1, + target: nil, + }, + true, + }, + {"errors.Is true", + args{ + err: w1(err1), + target: err1, + }, + true, + }, + {"errors.Is false", + args{ + err: w1(err1), + target: err2, + }, + false, + }, + {"same wrap but different base error", + args{ + err: w1(err1), + target: w1(err2), + }, + false, + }, + {"both wrapped true", + args{ + err: w1(err1), + target: w2(err1), + }, + true, + }, + {"both wrapped false", + args{ + err: w1(err1), + target: w2(err2), + }, + false, + }, + {"multierr first in slice", + args{ + err: multierr.Append(w1(err1), w2(err2)), + target: w2(err1), + }, + true, + }, + {"multierr second in slice", + args{ + err: multierr.Append(w1(err1), w2(err2)), + target: w1(err2), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, + tt.want, + ErrorContains(tt.args.err, tt.args.target), + "ErrorContains(%v, %v)", tt.args.err, tt.args.target) + }) + } +} From 562016470d0f465d7c4f4d2bffcd44372c1172f5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 14 Mar 2023 19:19:39 +0100 Subject: [PATCH 0457/1008] feat(nodebuilder/p2p): Add `blockspacerace` (#1904) Adds `blockspacerace-0` network + genesis hash. A follow-up PR will add the bootstrappers. --- nodebuilder/p2p/bootstrap.go | 5 +++++ nodebuilder/p2p/genesis.go | 7 ++++--- nodebuilder/p2p/network.go | 16 ++++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 96f46bcaa7..d0bdb56ebd 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -39,6 +39,11 @@ var bootstrapList = map[Network][]string{ "/dns4/libra.celestia-devops.dev/tcp/2121/p2p/12D3KooWK5aDotDcLsabBmWDazehQLMsDkRyARm1k7f1zGAXqbt4", "/dns4/norma.celestia-devops.dev/tcp/2121/p2p/12D3KooWHYczJDVNfYVkLcNHPTDKCeiVvRhg8Q9JU3bE3m9eEVyY", }, + BlockspaceRace: { + "/dns4/bootstr-incent-3.celestia.tools/tcp/2121/p2p/12D3KooWNzdKcHagtvvr6qtjcPTAdCN6ZBiBLH8FBHbihxqu4GZx", + "/dns4/bootstr-incent-2.celestia.tools/tcp/2121/p2p/12D3KooWNJZyWeCsrKxKrxsNM1RVL2Edp77svvt7Cosa63TggC9m", + "/dns4/bootstr-incent-1.celestia.tools/tcp/2121/p2p/12D3KooWBtxdBzToQwnS4ySGpph9PtGmmjEyATkgX3PfhAo4xmf7", + }, Private: {}, } diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index dbc43183f0..670699b292 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,8 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "EE310062CBB13CE98CBC7EAD3F6A827F0E4A86043FDEB2DA42048821877FE45C", - Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", - Private: "", + Arabica: "EE310062CBB13CE98CBC7EAD3F6A827F0E4A86043FDEB2DA42048821877FE45C", + Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", + BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", + Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index b184c26871..025ba3bddf 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -15,6 +15,8 @@ const ( Arabica Network = "arabica-6" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha" + // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. + BlockspaceRace Network = "blockspacerace-0" // Private can be used to set up any private network, including local testing setups. Private Network = "private" // BlockTime is a network block time. @@ -50,18 +52,20 @@ func (n Network) String() string { // networksList is a strict list of all known long-standing networks. var networksList = map[Network]struct{}{ - Arabica: {}, - Mocha: {}, - Private: {}, + Arabica: {}, + Mocha: {}, + BlockspaceRace: {}, + Private: {}, } // networkAliases is a strict list of all known long-standing networks // mapped from the string representation of their *alias* (rather than // their actual value) to the Network. var networkAliases = map[string]Network{ - "arabica": Arabica, - "mocha": Mocha, - "private": Private, + "arabica": Arabica, + "mocha": Mocha, + "blockspacerace": BlockspaceRace, + "private": Private, } // listProvidedNetworks provides a string listing all known long-standing networks for things like From d816ca52eda6678c79f80931ba52d82354e21f17 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 15 Mar 2023 14:25:11 +0100 Subject: [PATCH 0458/1008] fix(nodebuilder): Also invoke header store head metrics (#1906) Co-authored-by: Wondertan --- {header => libs/header}/metrics.go | 12 ++---------- libs/header/p2p/metrics.go | 2 +- nodebuilder/header/opts.go | 26 ++++++++++++++++++-------- nodebuilder/settings.go | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) rename {header => libs/header}/metrics.go (74%) diff --git a/header/metrics.go b/libs/header/metrics.go similarity index 74% rename from header/metrics.go rename to libs/header/metrics.go index 61378a6251..6e9dcf37c8 100644 --- a/header/metrics.go +++ b/libs/header/metrics.go @@ -8,15 +8,12 @@ import ( "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/unit" - - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/sync" ) var meter = global.MeterProvider().Meter("header") // WithMetrics enables Otel metrics to monitor head and total amount of synced headers. -func WithMetrics(store libhead.Store[*ExtendedHeader], syncer *sync.Syncer[*ExtendedHeader]) error { +func WithMetrics[H Header](store Store[H]) error { headC, _ := meter.AsyncInt64().Counter( "head", instrument.WithUnit(unit.Dimensionless), @@ -42,13 +39,8 @@ func WithMetrics(store libhead.Store[*ExtendedHeader], syncer *sync.Syncer[*Exte headC.Observe( ctx, head.Height(), - attribute.Int("square_size", len(head.DAH.RowsRoots)), ) }, ) - if err != nil { - return err - } - - return syncer.InitMetrics() + return err } diff --git a/libs/header/p2p/metrics.go b/libs/header/p2p/metrics.go index 20bec81248..f6fe1d69cd 100644 --- a/libs/header/p2p/metrics.go +++ b/libs/header/p2p/metrics.go @@ -18,7 +18,7 @@ var ( meter = global.MeterProvider().Meter("header/p2p") ) -func (ex *Exchange[H]) RegisterMetrics() error { +func (ex *Exchange[H]) InitMetrics() error { responseSize, err := meter. SyncFloat64(). Histogram( diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go index c07720cf60..5663f059cd 100644 --- a/nodebuilder/header/opts.go +++ b/nodebuilder/header/opts.go @@ -1,17 +1,27 @@ package header import ( - header "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header" libhead "github.com/celestiaorg/celestia-node/libs/header" - p2p "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/p2p" + "github.com/celestiaorg/celestia-node/libs/header/sync" ) // WithMetrics provides sets `MetricsEnabled` to true on ClientParameters for the header exchange -func WithMetrics(ex libhead.Exchange[*header.ExtendedHeader]) error { - switch p2pex := any(ex).(type) { - case *p2p.Exchange[*header.ExtendedHeader]: - return p2pex.RegisterMetrics() - default: - return nil +func WithMetrics( + store libhead.Store[*header.ExtendedHeader], + ex libhead.Exchange[*header.ExtendedHeader], + sync *sync.Syncer[*header.ExtendedHeader], +) error { + if p2pex, ok := ex.(*p2p.Exchange[*header.ExtendedHeader]); ok { + if err := p2pex.InitMetrics(); err != nil { + return err + } } + + if err := sync.InitMetrics(); err != nil { + return err + } + + return libhead.WithMetrics[*header.ExtendedHeader](store) } diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index a8b9f05d3f..a41831b155 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -13,7 +13,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" - fraud "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/das" modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" From a4e106410b892ebe778b57ad36e38a5f1dda8a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Wed, 15 Mar 2023 14:54:57 +0100 Subject: [PATCH 0459/1008] fix: Go ldflags inside Dockerfile (#1900) Fixed the ldflags handling in Makefile to fix docker build --- Dockerfile | 2 +- Makefile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 53ed4e96f5..e8510f7da7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,4 +23,4 @@ COPY --from=builder /src/./cel-key / EXPOSE 2121 ENTRYPOINT ["/entrypoint.sh"] -CMD ["celestia"] \ No newline at end of file +CMD ["celestia"] diff --git a/Makefile b/Makefile index 488a1e5b13..c82fc5dff9 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL=/usr/bin/env bash PROJECTNAME=$(shell basename "$(PWD)") -LDFLAGS="-X 'main.buildTime=$(shell date)' -X 'main.lastCommit=$(shell git rev-parse HEAD)' -X 'main.semanticVersion=$(shell git describe --tags --dirty=-dev)'" +LDFLAGS=-ldflags="-X 'main.buildTime=$(shell date)' -X 'main.lastCommit=$(shell git rev-parse HEAD)' -X 'main.semanticVersion=$(shell git describe --tags --dirty=-dev)'" ifeq (${PREFIX},) PREFIX := /usr/local endif @@ -19,7 +19,7 @@ install-hooks: ## build: Build celestia-node binary. build: @echo "--> Building Celestia" - @go build -o build/ -ldflags ${LDFLAGS} ./cmd/celestia + @go build -o build/ ${LDFLAGS} ./cmd/celestia .PHONY: build ## clean: Clean up celestia-node binary. @@ -49,7 +49,7 @@ install: ## go-install: Build and install the celestia-node binary into the GOBIN directory. go-install: @echo "--> Installing Celestia" - @go install -ldflags ${LDFLAGS} ./cmd/celestia + @go install ${LDFLAGS} ./cmd/celestia .PHONY: go-install ## shed: Build cel-shed binary. From 36bc88ab8703d7993dc9d698f1bfa9bab54e7fd7 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 15 Mar 2023 17:54:34 +0100 Subject: [PATCH 0460/1008] deps: update libp2p pubsub (#1910) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 10c6444d20..eac629e41c 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/ipld/go-car v0.6.0 github.com/libp2p/go-libp2p v0.25.1 github.com/libp2p/go-libp2p-kad-dht v0.21.0 - github.com/libp2p/go-libp2p-pubsub v0.9.1 + github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.6.1 github.com/minio/sha256-simd v1.0.0 diff --git a/go.sum b/go.sum index 08ffd75354..8123aa3ede 100644 --- a/go.sum +++ b/go.sum @@ -1087,8 +1087,8 @@ github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuD github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= -github.com/libp2p/go-libp2p-pubsub v0.9.1 h1:A6LBg9BaoLf3NwRz+E974sAxTVcbUZYg95IhK2BZz9g= -github.com/libp2p/go-libp2p-pubsub v0.9.1/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc= +github.com/libp2p/go-libp2p-pubsub v0.9.3 h1:ihcz9oIBMaCK9kcx+yHWm3mLAFBMAUsM4ux42aikDxo= +github.com/libp2p/go-libp2p-pubsub v0.9.3/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= From 840f2c072cf5274a8e66869ef0f8578c3056eccf Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:57:46 +0100 Subject: [PATCH 0461/1008] fix(nodebuilder): Ensure new generated account is recoverable via mnemonic (#1908) Co-authored-by: Hlib Kanunnikov --- nodebuilder/init.go | 13 +++++++++++-- nodebuilder/init_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 57ce4c755b..fc919bef85 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" @@ -16,6 +17,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) +const defaultKeyName = "my_celes_key" + // Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under // 'path'. func Init(cfg Config, path string, tp node.Type) error { @@ -145,8 +148,7 @@ func generateKeys(cfg Config, ksPath string) error { return nil } log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ksPath) - keyInfo, mn, err := ring.NewMnemonic("my_celes_key", keyring.English, "", - "", hd.Secp256k1) + keyInfo, mn, err := generateNewKey(ring) if err != nil { return err } @@ -159,3 +161,10 @@ func generateKeys(cfg Config, ksPath string) error { keyInfo.Name, addr.String(), mn) return nil } + +// generateNewKey generates and returns a new key on the given keyring called +// "my_celes_key". +func generateNewKey(ring keyring.Keyring) (*keyring.Record, string, error) { + return ring.NewMnemonic(defaultKeyName, keyring.English, sdk.GetConfig().GetFullBIP44Path(), + keyring.DefaultBIP39Passphrase, hd.Secp256k1) +} diff --git a/nodebuilder/init_test.go b/nodebuilder/init_test.go index 5ad9dc45c4..e438a191bc 100644 --- a/nodebuilder/init_test.go +++ b/nodebuilder/init_test.go @@ -4,9 +4,15 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -62,3 +68,36 @@ func TestInitErrForLockedDir(t *testing.T) { require.Error(t, Init(*cfg, dir, node)) } } + +// TestInit_generateNewKey tests to ensure new account is generated +// correctly. +func TestInit_generateNewKey(t *testing.T) { + cfg := DefaultConfig(node.Bridge) + + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + ring, err := keyring.New(app.Name, cfg.State.KeyringBackend, t.TempDir(), os.Stdin, encConf.Codec) + require.NoError(t, err) + + originalKey, mn, err := generateNewKey(ring) + require.NoError(t, err) + + // check ring and make sure it generated + stored key + keys, err := ring.List() + require.NoError(t, err) + assert.Equal(t, originalKey, keys[0]) + + // ensure the generated account is actually a celestia account + addr, err := originalKey.GetAddress() + require.NoError(t, err) + assert.Contains(t, addr.String(), "celestia") + + // ensure account is recoverable from mnemonic + ring2, err := keyring.New(app.Name, cfg.State.KeyringBackend, t.TempDir(), os.Stdin, encConf.Codec) + require.NoError(t, err) + duplicateKey, err := ring2.NewAccount("test", mn, keyring.DefaultBIP39Passphrase, sdk.GetConfig().GetFullBIP44Path(), + hd.Secp256k1) + require.NoError(t, err) + got, err := duplicateKey.GetAddress() + require.NoError(t, err) + assert.Equal(t, addr.String(), got.String()) +} From 16d54fdce85c036163249c5baafe977d9d93ee60 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 15 Mar 2023 19:02:17 +0100 Subject: [PATCH 0462/1008] misc(core): Add debug log to `CoreListener` (#1909) --- core/listener.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/listener.go b/core/listener.go index da8e1503bf..237a2aebe9 100644 --- a/core/listener.go +++ b/core/listener.go @@ -80,6 +80,7 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { for { select { case b, ok := <-sub: + log.Debugw("listener: new block from core", "height", b.Height) if !ok { return } From c958fd3b3921ca993489d88530e59fe7f337bf4d Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 15 Mar 2023 19:08:20 +0100 Subject: [PATCH 0463/1008] fix(cmd): repo should be closed on defer (#1911) The repo remains unclosed if an error happens during node instantiation or lifecycle. --- cmd/start.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 81d03439c3..2f7dfad7e4 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/spf13/cobra" flag "github.com/spf13/pflag" + "go.uber.org/multierr" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" @@ -25,7 +26,7 @@ Options passed on start override configuration options only on start and are not Aliases: []string{"run", "daemon"}, Args: cobra.NoArgs, SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) (err error) { ctx := cmd.Context() // override config with all modifiers passed on start @@ -47,6 +48,8 @@ Options passed on start override configuration options only on start and are not if err != nil { return err } + // TODO(@Wondertan): Use join errors instead in go1.21 + defer multierr.AppendInvoke(&err, multierr.Invoke(store.Close)) nd, err := nodebuilder.NewWithConfig(NodeType(ctx), Network(ctx), store, &cfg, NodeOptions(ctx)...) if err != nil { @@ -65,12 +68,7 @@ Options passed on start override configuration options only on start and are not ctx, cancel = signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) defer cancel() - err = nd.Stop(ctx) - if err != nil { - return err - } - - return store.Close() + return nd.Stop(ctx) }, } for _, set := range fsets { From b5f06176210bc0400d906269a8742864921c4f96 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 16 Mar 2023 18:03:58 +0800 Subject: [PATCH 0464/1008] refactor(nodebuilder/das): update default DASer setting (#1912) DASer default settings are based on IPLD only getters measurements. With introduction of blocksync to full nodes as main sampling protocol, value should to be revised. Shrex has much less roundtrips to obtain data, thus it is much better at utilising network bandwidth with fewer workers. Default DASer setting are now updated with relation to node type. --- nodebuilder/config.go | 2 +- nodebuilder/das/config.go | 15 +++++++++++++-- nodebuilder/das/module_test.go | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/nodebuilder/config.go b/nodebuilder/config.go index ef9784461a..eda59c25b7 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -50,7 +50,7 @@ func DefaultConfig(tp node.Type) *Config { case node.Bridge: return commonConfig case node.Light, node.Full: - commonConfig.DASer = das.DefaultConfig() + commonConfig.DASer = das.DefaultConfig(tp) return commonConfig default: panic("node: invalid node type") diff --git a/nodebuilder/das/config.go b/nodebuilder/das/config.go index c76dcb2650..c08de95969 100644 --- a/nodebuilder/das/config.go +++ b/nodebuilder/das/config.go @@ -5,6 +5,7 @@ import ( "time" "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -17,9 +18,19 @@ type Config das.Parameters // but this function will provide more once #1261 is addressed. // // TODO(@derrandz): Address #1261 -func DefaultConfig() Config { +func DefaultConfig(tp node.Type) Config { cfg := das.DefaultParameters() - cfg.SampleTimeout = modp2p.BlockTime * time.Duration(cfg.ConcurrencyLimit) + switch tp { + case node.Light: + cfg.SampleTimeout = modp2p.BlockTime * time.Duration(cfg.ConcurrencyLimit) + case node.Full: + // Default value for DASer concurrency limit is based on dasing using ipld getter. + // Full node will primarily use shrex protocol for sampling, that is much more efficient and can + // fully utilize nodes bandwidth with lower amount of parallel sampling workers + cfg.ConcurrencyLimit = 6 + // Full node uses shrex with fallback to ipld to sample, so need 2x amount of time in worst case scenario + cfg.SampleTimeout = 2 * modp2p.BlockTime * time.Duration(cfg.ConcurrencyLimit) + } return Config(cfg) } diff --git a/nodebuilder/das/module_test.go b/nodebuilder/das/module_test.go index 2883b6adb1..f1f7da6f65 100644 --- a/nodebuilder/das/module_test.go +++ b/nodebuilder/das/module_test.go @@ -19,7 +19,7 @@ func TestConstructModule_DASBridgeStub(t *testing.T) { var mod Module - cfg := DefaultConfig() + cfg := DefaultConfig(node.Bridge) app := fxtest.New(t, ConstructModule(node.Bridge, &cfg), fx.Populate(&mod)). From 08823e483883ae68955b2b511282cbae55db5d16 Mon Sep 17 00:00:00 2001 From: Samuel Enderwitz <18609909+smuu@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:19:12 +0100 Subject: [PATCH 0465/1008] refactor(nodebuilder/p2p)!: Configure bootstrapper from env instead of config (#1881) Introduces the environment variable BOOTSTRAPPER, which starts the node in bootstrapper mode. It also removes the bootstrapper setting from the config. Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- README.md | 30 +++++++++++++++++---------- nodebuilder/p2p/bootstrap.go | 9 ++++++++ nodebuilder/p2p/config.go | 5 ----- nodebuilder/p2p/pubsub.go | 5 +++-- nodebuilder/p2p/routing.go | 2 +- nodebuilder/tests/p2p_test.go | 2 -- nodebuilder/tests/reconstruct_test.go | 1 - 7 files changed, 32 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ab707fe3e8..0b632e21fe 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,21 @@ Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-avai ## Table of Contents - [Celestia Node](#celestia-node) -- [Minimum requirements](#minimum-requirements) -- [System Requirements](#system-requirements) -- [Installation](#installation) -- [API docs](#api-docs) -- [Node types](#node-types) -- [Run a node](#run-a-node) -- [Package-specific documentation](#package-specific-documentation) -- [Code of Conduct](#code-of-conduct) + - [Table of Contents](#table-of-contents) + - [Minimum requirements](#minimum-requirements) + - [System Requirements](#system-requirements) + - [Installation](#installation) + - [API docs](#api-docs) + - [Node types](#node-types) + - [Run a node](#run-a-node) + - [Environment variables](#environment-variables) + - [Package-specific documentation](#package-specific-documentation) + - [Code of Conduct](#code-of-conduct) ## Minimum requirements | Requirement | Notes | -|-------------|----------------| +| ----------- | -------------- | | Go version | 1.19 or higher | ## System Requirements @@ -43,7 +45,7 @@ See the official docs page for system requirements per node type: ## Installation ```sh -git clone https://github.com/celestiaorg/celestia-node.git +git clone https://github.com/celestiaorg/celestia-node.git cd celestia-node make build sudo make install @@ -68,13 +70,19 @@ More information can be found [here](https://github.com/celestiaorg/celestia-nod `` can be `bridge`, `full` or `light`. ```sh -celestia init +celestia init ``` ```sh celestia start ``` +### Environment variables + +| Variable | Explanation | Default value | Required | +| ----------------------- | ----------------------------------- | ------------- | -------- | +| `CELESTIA_BOOTSTRAPPER` | Start the node in bootstrapper mode | `false` | Optional | + ## Package-specific documentation - [Header](./header/doc.go) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index d0bdb56ebd..f03825e9cb 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -1,10 +1,19 @@ package p2p import ( + "os" + "strconv" + "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" ) +const envKeyCelestiaBootstrapper = "CELESTIA_BOOTSTRAPPER" + +func isBootstrapper() bool { + return os.Getenv(envKeyCelestiaBootstrapper) == strconv.FormatBool(true) +} + // BootstrappersFor returns address information of bootstrap peers for a given network. func BootstrappersFor(net Network) (Bootstrappers, error) { bs, err := bootstrappersFor(net) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 64bd094de1..9f01513949 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -21,10 +21,6 @@ type Config struct { // NoAnnounceAddresses - Addresses the P2P subsystem may know about, but that should not be // announced/advertised, as undialable from WAN NoAnnounceAddresses []string - // TODO(@Wondertan): This should be a build-time parameter. See - // https://github.com/celestiaorg/celestia-node/issues/63 - // Bootstrapper is flag telling this node is a bootstrapper. - Bootstrapper bool // MutualPeers are peers which have a bidirectional peering agreement with the configured node. // Connections with those peers are protected from being trimmed, dropped or negatively scored. // NOTE: Any two peers must bidirectionally configure each other on their MutualPeers field. @@ -59,7 +55,6 @@ func DefaultConfig(tp node.Type) Config { "/ip6/::/tcp/2121", }, MutualPeers: []string{}, - Bootstrapper: false, PeerExchange: tp == node.Bridge || tp == node.Full, ConnManager: defaultConnManagerConfig(), RoutingTableRefreshPeriod: defaultRoutingRefreshPeriod, diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 3292026ebf..fd31bdc02a 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -43,7 +43,8 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { return nil, err } - isBootstrapper := cfg.Bootstrapper + isBootstrapper := isBootstrapper() + if isBootstrapper { // Turn off the mesh in bootstrappers as per: // @@ -76,7 +77,7 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { opts := []pubsub.Option{ pubsub.WithSeenMessagesStrategy(timecache.Strategy_LastSeen), pubsub.WithPeerScore(peerScores, scoreThresholds), - pubsub.WithPeerExchange(cfg.PeerExchange || cfg.Bootstrapper), + pubsub.WithPeerExchange(cfg.PeerExchange || isBootstrapper), pubsub.WithDirectPeers(fpeers), pubsub.WithMessageIdFn(hashMsgID), // specifying sub protocol helps to avoid conflicts with diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index c3797d8f3e..f70e5bdf2e 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -28,7 +28,7 @@ func peerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) dht.RoutingTableRefreshPeriod(cfg.RoutingTableRefreshPeriod), } - if cfg.Bootstrapper { + if isBootstrapper() { // override options for bootstrapper opts = append(opts, dht.Mode(dht.ModeServer), // it must accept incoming connections diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 8557725423..4e21e7d0e3 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -91,7 +91,6 @@ Steps: func TestBootstrapNodesFromBridgeNode(t *testing.T) { sw := swamp.NewSwamp(t) cfg := nodebuilder.DefaultConfig(node.Bridge) - cfg.P2P.Bootstrapper = true const defaultTimeInterval = time.Second * 10 setTimeInterval(cfg, defaultTimeInterval) @@ -171,7 +170,6 @@ Steps: func TestRestartNodeDiscovery(t *testing.T) { sw := swamp.NewSwamp(t) cfg := nodebuilder.DefaultConfig(node.Bridge) - cfg.P2P.Bootstrapper = true const defaultTimeInterval = time.Second * 2 const fullNodes = 2 diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 8475f62ace..d533a9aeb4 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -107,7 +107,6 @@ func TestFullReconstructFromLights(t *testing.T) { const defaultTimeInterval = time.Second * 5 cfg := nodebuilder.DefaultConfig(node.Full) - cfg.P2P.Bootstrapper = true setTimeInterval(cfg, defaultTimeInterval) bridge := sw.NewBridgeNode() From 596c16bc9702ba4712467855beaecd432e27b023 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 16 Mar 2023 11:32:57 +0100 Subject: [PATCH 0466/1008] refactor(header/sync): cleanup sync head detection logic (#1889) Since #1682, we have kept the "synchronized head" in the Syncer because of synchronization oversight. However, the fix does not fit nicely into the model of subjective and network head and introduced more confusion and hairiness to the code. This PR fixes the original issue and integrates the solution coherently into the code responsible for keeping and detecting the current head. Additionally, sync_head.go is no more dependent on the adjacency of the Store and is prepared for future Store refactoring and storing of pending headers, instead of caching. Also, this PR does minor code cleanups, including comments and var name fixes. Synced up to the tip 2 times on Arabica. > Anybody needs their syncussy cleaned? --- libs/header/p2p/session.go | 2 +- libs/header/sync/metrics.go | 4 +- libs/header/sync/ranges.go | 38 ++++-- libs/header/sync/ranges_test.go | 6 +- libs/header/sync/sync.go | 142 ++++++++++--------- libs/header/sync/sync_getter.go | 31 +++++ libs/header/sync/sync_getter_test.go | 72 ++++++++++ libs/header/sync/sync_head.go | 196 +++++++++++---------------- libs/header/sync/sync_store.go | 40 ++++++ libs/header/sync/sync_store_test.go | 31 +++++ libs/header/sync/sync_test.go | 127 ++++++++--------- 11 files changed, 409 insertions(+), 280 deletions(-) create mode 100644 libs/header/sync/sync_getter.go create mode 100644 libs/header/sync/sync_getter_test.go create mode 100644 libs/header/sync/sync_store.go create mode 100644 libs/header/sync/sync_store_test.go diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index d1ec607a3a..ee788341d5 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -179,7 +179,7 @@ func (s *session[H]) doRequest( return case s.reqCh <- req: } - log.Errorw("processing response", "err", err) + log.Errorw("processing response", "from", req.GetOrigin(), "to", req.Amount+req.GetOrigin()-1, "err", err) return } diff --git a/libs/header/sync/metrics.go b/libs/header/sync/metrics.go index 99851e9d18..d76823b7b1 100644 --- a/libs/header/sync/metrics.go +++ b/libs/header/sync/metrics.go @@ -39,5 +39,7 @@ func (s *Syncer[H]) InitMetrics() error { // recordTotalSynced records the total amount of synced headers. func (m *metrics) recordTotalSynced(totalSynced int) { - atomic.AddInt64(&m.totalSynced, int64(totalSynced)) + if m != nil { + atomic.AddInt64(&m.totalSynced, int64(totalSynced)) + } } diff --git a/libs/header/sync/ranges.go b/libs/header/sync/ranges.go index 5fe4add88d..86d54541cf 100644 --- a/libs/header/sync/ranges.go +++ b/libs/header/sync/ranges.go @@ -62,9 +62,8 @@ func (rs *ranges[H]) Add(h H) { // otherwise, start a new range rs.ranges = append(rs.ranges, newRange[H](h)) - // it is possible to miss a header or few from PubSub, due to quick disconnects or sleep - // once we start rcving them again we save those in new range - // so 'Syncer.findHeaders' can fetch what was missed + // it is possible to miss a header or few from gossiping subsystem, due to quick disconnects + // or hibernation, so once we start rcving them again we save those in the new range } } @@ -126,13 +125,32 @@ func (r *headerRange[H]) Head() H { return r.headers[ln-1] } -// Truncate truncates all the headers before height 'end' - [r.Start:end] -func (r *headerRange[H]) Truncate(end uint64) []H { +// Get returns headers within the range up to the specified 'end' height. +func (r *headerRange[H]) Get(end uint64) []H { + r.lk.RLock() + defer r.lk.RUnlock() + + amnt := r.rangeAmount(end) + return r.headers[:amnt] +} + +// Remove removes all headers within the range up to the specified 'end' height. +func (r *headerRange[H]) Remove(end uint64) { r.lk.Lock() defer r.lk.Unlock() + amnt := r.rangeAmount(end) + r.headers = r.headers[amnt:] + if len(r.headers) != 0 { + r.start = uint64(r.headers[0].Height()) + } +} + +// rangeAmount returns the number of headers to be removed or returned up to the specified 'end' +// height. +func (r *headerRange[H]) rangeAmount(end uint64) uint64 { if r.start > end { - return nil + return 0 } amnt := uint64(len(r.headers)) @@ -140,11 +158,5 @@ func (r *headerRange[H]) Truncate(end uint64) []H { amnt = end - r.start + 1 // + 1 to include 'end' as well } - out := r.headers[:amnt] - r.headers = r.headers[amnt:] - if len(r.headers) != 0 { - r.start = uint64(r.headers[0].Height()) - } - - return out + return amnt } diff --git a/libs/header/sync/ranges_test.go b/libs/header/sync/ranges_test.go index 4fcaf5285b..ce5b123b78 100644 --- a/libs/header/sync/ranges_test.go +++ b/libs/header/sync/ranges_test.go @@ -33,7 +33,7 @@ func TestAddParallel(t *testing.T) { } } -func TestRangeTruncate(t *testing.T) { +func TestRangeGet(t *testing.T) { n := 300 suite := test.NewTestSuite(t) headers := suite.GenDummyHeaders(n) @@ -41,6 +41,6 @@ func TestRangeTruncate(t *testing.T) { r := newRange(headers[200]) r.Append(headers[201:]...) - truncated := r.Truncate(100) - assert.Nil(t, truncated) + truncated := r.Get(100) + assert.Len(t, truncated, 0) } diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go index d7808592f0..50451545ab 100644 --- a/libs/header/sync/sync.go +++ b/libs/header/sync/sync.go @@ -4,7 +4,6 @@ import ( "context" "errors" "sync" - "sync/atomic" "time" logging "github.com/ipfs/go-log/v2" @@ -16,51 +15,45 @@ var log = logging.Logger("header/sync") // Syncer implements efficient synchronization for headers. // -// Subjective header - the latest known header that is not expired (within trusting period) -// Network header - the latest header received from the network +// Subjective Head - the latest known local valid header and a sync target. +// Network Head - the latest valid network-wide header. Becomes subjective once applied locally. // // There are two main processes running in Syncer: -// 1. Main syncing loop(s.syncLoop) -// - Performs syncing from the subjective(local chain view) header up to the latest known trusted header +// - Main syncing loop(s.syncLoop) +// - Performs syncing from the latest stored header up to the latest known Subjective Head // - Syncs by requesting missing headers from Exchange or -// - By accessing cache of pending and verified headers +// - By accessing cache of pending headers // -// 2. Receives new headers from PubSub subnetwork (s.processIncoming) -// - Usually, a new header is adjacent to the trusted head and if so, it is simply appended to the local store, -// incrementing the subjective height and making it the new latest known trusted header. -// - Or, if it receives a header further in the future, -// verifies against the latest known trusted header -// adds the header to pending cache(making it the latest known trusted header) -// and triggers syncing loop to catch up to that point. +// - Receives every new Network Head from PubSub gossip subnetwork (s.incomingNetworkHead) +// - Validates against the latest known Subjective Head, is so +// - Sets as the new Subjective Head, which +// - if there is a gap between the previous and the new Subjective Head +// - Triggers s.syncLoop and saves the Subjective Head in the pending so s.syncLoop can access it type Syncer[H header.Header] struct { - sub header.Subscriber[H] - exchange header.Exchange[H] - store header.Store[H] + sub header.Subscriber[H] // to subscribe for new Network Heads + store syncStore[H] // to store all the headers to + getter syncGetter[H] // to fetch headers from + metrics *metrics // stateLk protects state which represents the current or latest sync stateLk sync.RWMutex state State + // signals to start syncing triggerSync chan struct{} - // syncedHead is the latest synced header. - syncedHead atomic.Pointer[H] // pending keeps ranges of valid new network headers awaiting to be appended to store pending ranges[H] - // netReqLk ensures only one network head is requested at any moment - netReqLk sync.RWMutex // controls lifecycle for syncLoop ctx context.Context cancel context.CancelFunc Params *Parameters - - metrics *metrics } // NewSyncer creates a new instance of Syncer. func NewSyncer[H header.Header]( - exchange header.Exchange[H], + getter header.Getter[H], store header.Store[H], sub header.Subscriber[H], opts ...Options, @@ -75,8 +68,8 @@ func NewSyncer[H header.Header]( return &Syncer[H]{ sub: sub, - exchange: exchange, - store: store, + store: syncStore[H]{Store: store}, + getter: syncGetter[H]{Getter: getter}, triggerSync: make(chan struct{}, 1), // should be buffered Params: ¶ms, }, nil @@ -87,12 +80,12 @@ func (s *Syncer[H]) Start(ctx context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) // register validator for header subscriptions // syncer does not subscribe itself and syncs headers together with validation - err := s.sub.AddValidator(s.incomingNetHead) + err := s.sub.AddValidator(s.incomingNetworkHead) if err != nil { return err } - // get the latest head and set it as syncing target - _, err = s.networkHead(ctx) + // gets the latest head and kicks off syncing if necessary + _, err = s.Head(ctx) if err != nil { return err } @@ -147,7 +140,15 @@ func (s *Syncer[H]) State() State { s.stateLk.RLock() state := s.state s.stateLk.RUnlock() - state.Height = s.store.Height() + + head, err := s.store.Head(s.ctx) + if err == nil { + state.Height = uint64(head.Height()) + } else if state.Error == nil { + // don't ignore the error if we can show it in the state + state.Error = err + } + return state } @@ -171,43 +172,35 @@ func (s *Syncer[H]) syncLoop() { } } -// sync ensures we are synced from the Store's head up to the new subjective head +// sync ensures we are synced from the Store's head up to the new subjective head. func (s *Syncer[H]) sync(ctx context.Context) { - newHead := s.pending.Head() - if newHead.IsZero() { + subjHead, err := s.subjectiveHead(ctx) + if err != nil { + log.Errorw("getting subjective head", "err", err) return } - headPtr := s.syncedHead.Load() - - var header H - if headPtr == nil { - head, err := s.store.Head(ctx) - if err != nil { - log.Errorw("getting head during sync", "err", err) - return - } - header = head - } else { - header = *headPtr + storeHead, err := s.store.Head(ctx) + if err != nil { + log.Errorw("getting stored head", "err", err) + return } - if header.Height() >= newHead.Height() { + if storeHead.Height() >= subjHead.Height() { log.Warnw("sync attempt to an already synced header", - "synced_height", header.Height(), - "attempted_height", newHead.Height(), + "synced_height", storeHead.Height(), + "attempted_height", subjHead.Height(), ) log.Warn("PLEASE REPORT THIS AS A BUG") return // should never happen, but just in case } - from := header.Height() + 1 - + from := storeHead.Height() + 1 log.Infow("syncing headers", "from", from, - "to", newHead.Height()) + "to", subjHead.Height()) - err := s.doSync(ctx, header, newHead) + err = s.doSync(ctx, storeHead, subjHead) if err != nil { if errors.Is(err, context.Canceled) { // don't log this error as it is normal case of Syncer being stopped @@ -216,18 +209,18 @@ func (s *Syncer[H]) sync(ctx context.Context) { log.Errorw("syncing headers", "from", from, - "to", newHead.Height(), + "to", subjHead.Height(), "err", err) return } - log.Infow("finished syncing", + log.Infow("finished syncing headers", "from", from, - "to", newHead.Height(), + "to", subjHead.Height(), "elapsed time", s.state.End.Sub(s.state.Start)) } -// doSync performs actual syncing updating the internal State +// doSync performs actual syncing updating the internal State. func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) { s.stateLk.Lock() s.state.ID++ @@ -247,9 +240,8 @@ func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) return err } -// processHeaders gets and stores headers starting at the given 'from' height up to 'to' height - -// [from:to] -// processHeaders checks headers in pending cache that apply to the requested range. +// processHeaders fetches and stores asked headers (from:to]. +// Checks headers in pending cache that apply to the requested range. // If some headers are missing, it starts requesting them from the network. func (s *Syncer[H]) processHeaders( ctx context.Context, @@ -262,24 +254,27 @@ func (s *Syncer[H]) processHeaders( break } - headers := headersRange.Truncate(to) + headers := headersRange.Get(to) if len(headers) == 0 { break } - // check that returned range is adjacent to `fromHead` + // check if returned range is not adjacent to `fromHead` if fromHead.Height()+1 != headers[0].Height() { - // make an external request - if err = s.requestHeaders(ctx, fromHead, uint64(headers[0].Height()-1)); err != nil { + // if so - request missing ones + to := uint64(headers[0].Height() - 1) + if err = s.requestHeaders(ctx, fromHead, to); err != nil { return err } } // apply cached headers - if err = s.storeHeaders(ctx, headers); err != nil { + if err = s.storeHeaders(ctx, headers...); err != nil { return err } + // cleanup range only after we stored the headers + headersRange.Remove(to) // update fromHead for the next iteration fromHead = headers[len(headers)-1] } @@ -299,32 +294,31 @@ func (s *Syncer[H]) requestHeaders( if amount < size { size = amount } - headers, err := s.exchange.GetVerifiedRange(ctx, fromHead, size) + + headers, err := s.getter.GetVerifiedRange(ctx, fromHead, size) if err != nil { return err } - amount -= size - if err := s.storeHeaders(ctx, headers); err != nil { + if err := s.storeHeaders(ctx, headers...); err != nil { return err } + + amount -= size // size == len(headers) fromHead = headers[len(headers)-1] } return nil } -// storeHeaders updates store with new headers and updates current syncedHead. -func (s *Syncer[H]) storeHeaders(ctx context.Context, headers []H) error { +// storeHeaders updates store with new headers and updates current syncStore's Head. +func (s *Syncer[H]) storeHeaders(ctx context.Context, headers ...H) error { // we don't expect any issues in storing right now, as all headers are now verified. // So, we should return immediately in case an error appears. - if err := s.store.Append(ctx, headers...); err != nil { + err := s.store.Append(ctx, headers...) + if err != nil { return err } - s.syncedHead.Store(&headers[len(headers)-1]) - - if s.metrics != nil { - s.metrics.recordTotalSynced(len(headers)) - } + s.metrics.recordTotalSynced(len(headers)) return nil } diff --git a/libs/header/sync/sync_getter.go b/libs/header/sync/sync_getter.go new file mode 100644 index 0000000000..ad364e7b48 --- /dev/null +++ b/libs/header/sync/sync_getter.go @@ -0,0 +1,31 @@ +package sync + +import ( + "context" + "sync" + + "github.com/celestiaorg/celestia-node/libs/header" +) + +// syncGetter is a Getter wrapper that ensure only one Head call happens at the time +type syncGetter[H header.Header] struct { + header.Getter[H] + + headLk sync.RWMutex + headErr error + head H +} + +func (se *syncGetter[H]) Head(ctx context.Context) (H, error) { + // the lock construction here ensures only one routine calling Head at a time + // while others wait via Rlock + if !se.headLk.TryLock() { + se.headLk.RLock() + defer se.headLk.RUnlock() + return se.head, se.headErr + } + defer se.headLk.Unlock() + + se.head, se.headErr = se.Getter.Head(ctx) + return se.head, se.headErr +} diff --git a/libs/header/sync/sync_getter_test.go b/libs/header/sync/sync_getter_test.go new file mode 100644 index 0000000000..b89c6c57d7 --- /dev/null +++ b/libs/header/sync/sync_getter_test.go @@ -0,0 +1,72 @@ +package sync + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/test" +) + +func TestSyncGetterHead(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + fex := &fakeGetter[*test.DummyHeader]{} + sex := &syncGetter[*test.DummyHeader]{Getter: fex} + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + h, err := sex.Head(ctx) + if h != nil || err != errFakeHead { + t.Fail() + } + }() + } + wg.Wait() + + assert.EqualValues(t, 1, fex.hits.Load()) +} + +var errFakeHead = fmt.Errorf("head") + +type fakeGetter[H header.Header] struct { + hits atomic.Uint32 +} + +func (f *fakeGetter[H]) Head(ctx context.Context) (h H, err error) { + f.hits.Add(1) + select { + case <-time.After(time.Millisecond * 100): + err = errFakeHead + case <-ctx.Done(): + err = ctx.Err() + } + + return +} + +func (f *fakeGetter[H]) Get(ctx context.Context, hash header.Hash) (H, error) { + panic("implement me") +} + +func (f *fakeGetter[H]) GetByHeight(ctx context.Context, u uint64) (H, error) { + panic("implement me") +} + +func (f *fakeGetter[H]) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) { + panic("implement me") +} + +func (f *fakeGetter[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) { + panic("implement me") +} diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go index 103ce304c6..bdbb181c66 100644 --- a/libs/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -10,71 +10,15 @@ import ( "github.com/celestiaorg/celestia-node/libs/header" ) -// Head returns the Syncer's latest known header. It calls 'networkHead' in order to -// either return or eagerly fetch the most recent header. +// Head returns the Network Head. +// +// Known subjective head is considered network head if it is recent enough(now-timestamp<=blocktime) +// Otherwise, head is requested from a trusted peer and +// set as the new subjective head, assuming that trusted peer is always fully synced. func (s *Syncer[H]) Head(ctx context.Context) (H, error) { - return s.networkHead(ctx) -} - -// subjectiveHead returns the latest known local header that is not expired(within trusting period). -// If the header is expired, it is retrieved from a trusted peer without validation; -// in other words, an automatic subjective initialization is performed. -func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) { - var ( - // pending head is the latest known subjective head Syncer syncs to, so try to get it - // NOTES: - // * Empty when no sync is in progress - // * Pending cannot be expired, guaranteed - pendHead = s.pending.Head() - zero H - ) - if !pendHead.IsZero() { - return pendHead, nil - } - // next, check if the syncer holds any known head - if syncHead := s.syncedHead.Load(); syncHead != nil { - return *syncHead, nil - } - // if both pending and syncer head are empty - get subjective head out of the store - netHead, err := s.store.Head(ctx) - if err != nil { - return zero, err - } - // check if our subjective header is not expired and use it - if !isExpired(netHead, s.Params.TrustingPeriod) { - return netHead, nil - } - log.Infow("subjective header expired", "height", netHead.Height()) - // otherwise, request network head from a trusted peer - netHead, err = s.exchange.Head(ctx) - if err != nil { - return zero, err - } - // and set as the new subjective head without validation, - // or, in other words, do 'automatic subjective initialization' - s.newNetHead(ctx, netHead, true) - switch { - default: - log.Infow("subjective initialization finished", "height", netHead.Height()) - return netHead, nil - case isExpired(netHead, s.Params.TrustingPeriod): - log.Warnw("subjective initialization with an expired header", "height", netHead.Height()) - case !isRecent(netHead, s.Params.blockTime): - log.Warnw("subjective initialization with an old header", "height", netHead.Height()) - } - log.Warn("trusted peer is out of sync") - return netHead, nil -} - -// networkHead returns the latest network header. -// Known subjective head is considered network head if it is recent -// enough(now-timestamp<=blocktime). Otherwise, network header is requested from a trusted peer and -// set as the new subjective head, assuming that trusted peer is always synced. -func (s *Syncer[H]) networkHead(ctx context.Context) (H, error) { - var zero H sbjHead, err := s.subjectiveHead(ctx) if err != nil { - return zero, err + return sbjHead, err } // if subjective header is recent enough (relative to the network's block time) - just use it if isRecent(sbjHead, s.Params.blockTime) { @@ -82,88 +26,110 @@ func (s *Syncer[H]) networkHead(ctx context.Context) (H, error) { } // otherwise, request head from a trusted peer, as we assume it is fully synced // - // the lock construction here ensures only one routine requests at a time - // while others wait via Rlock - if !s.netReqLk.TryLock() { - s.netReqLk.RLock() - defer s.netReqLk.RUnlock() - return s.subjectiveHead(ctx) - } - defer s.netReqLk.Unlock() // TODO(@Wondertan): Here is another potential networking optimization: // * From sbjHead's timestamp and current time predict the time to the next header(TNH) // * If now >= TNH && now <= TNH + (THP) header propagation time // * Wait for header to arrive instead of requesting it // * This way we don't request as we know the new network header arrives exactly - netHead, err := s.exchange.Head(ctx) + netHead, err := s.getter.Head(ctx) if err != nil { - return zero, err + return netHead, err } - // process netHead returned from the trusted peer and validate against the subjective head + // process and validate netHead fetched from trusted peers // NOTE: We could trust the netHead like we do during 'automatic subjective initialization' - // but in this case our subjective head is not expired, so we should verify maybeHead + // but in this case our subjective head is not expired, so we should verify netHead // and only if it is valid, set it as new head - s.newNetHead(ctx, netHead, false) - // maybeHead was either accepted or rejected as the new subjective + s.incomingNetworkHead(ctx, netHead) + // netHead was either accepted or rejected as the new subjective // anyway return most current known subjective head return s.subjectiveHead(ctx) } -// incomingNetHead processes new gossiped network headers. -func (s *Syncer[H]) incomingNetHead(ctx context.Context, netHead H) pubsub.ValidationResult { - // check if it's an outdated head - // TODO(@Wondertan): This check does should not be here and it's already part of the validation - // pipeline. We have it here because syncedHead is not synchronized with writeHead inside of Store. - if syncHead := s.syncedHead.Load(); syncHead != nil && (*syncHead).Height() >= netHead.Height() { - return pubsub.ValidationIgnore +// subjectiveHead returns the latest known local header that is not expired(within trusting period). +// If the header is expired, it is retrieved from a trusted peer without validation; +// in other words, an automatic subjective initialization is performed. +func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) { + // pending head is the latest known subjective head and sync target, so try to get it + // NOTES: + // * Empty when no sync is in progress + // * Pending cannot be expired, guaranteed + pendHead := s.pending.Head() + if !pendHead.IsZero() { + return pendHead, nil + } + // if pending is empty - get the latest stored/synced head + storeHead, err := s.store.Head(ctx) + if err != nil { + return storeHead, err + } + // check if the stored header is not expired and use it + if !isExpired(storeHead, s.Params.TrustingPeriod) { + return storeHead, nil + } + // otherwise, request head from a trusted peer + log.Infow("stored head header expired", "height", storeHead.Height()) + trustHead, err := s.getter.Head(ctx) + if err != nil { + return trustHead, err } - // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network - // header - err := s.store.Append(ctx, netHead) - if err == nil { - // a happy case where we appended maybe head directly, so accept - s.syncedHead.Store(&netHead) - return pubsub.ValidationAccept + // and set it as the new subjective head without validation, + // or, in other words, do 'automatic subjective initialization' + // NOTE: we avoid validation as the head expired to prevent possibility of the Long-Range Attack + s.setSubjectiveHead(ctx, trustHead) + switch { + default: + log.Infow("subjective initialization finished", "height", trustHead.Height()) + return trustHead, nil + case isExpired(trustHead, s.Params.TrustingPeriod): + log.Warnw("subjective initialization with an expired header", "height", trustHead.Height()) + case !isRecent(trustHead, s.Params.blockTime): + log.Warnw("subjective initialization with an old header", "height", trustHead.Height()) } + log.Warn("trusted peer is out of sync") + return trustHead, nil +} + +// setSubjectiveHead takes already validated head and sets it as the new sync target. +func (s *Syncer[H]) setSubjectiveHead(ctx context.Context, netHead H) { + // TODO(@Wondertan): Right now, we can only store adjacent headers, instead we should: + // * Allow storing any valid header here in Store + // * Remove ErrNonAdjacent + // * Remove writeHead from the canonical store implementation + err := s.storeHeaders(ctx, netHead) var nonAdj *header.ErrNonAdjacent - if errors.As(err, &nonAdj) { - // not adjacent, maybe we've missed a few headers or its from the past - log.Debugw("attempted to append gossiped non-adjacent header", "store head", - nonAdj.Head, "attempted", nonAdj.Attempted) - } else { - var verErr *header.VerifyError - if errors.As(err, &verErr) { - return pubsub.ValidationReject - } + if err != nil && !errors.As(err, &nonAdj) { // might be a storage error or something else, but we can still try to continue processing netHead - log.Errorw("appending network header", + log.Errorw("storing new network header", "height", netHead.Height(), "hash", netHead.Hash().String(), "err", err) } - // try as new head - return s.newNetHead(ctx, netHead, false) -} -// newNetHead sets the network header as the new subjective head with preceding validation(per -// request). -func (s *Syncer[H]) newNetHead(ctx context.Context, netHead H, trust bool) pubsub.ValidationResult { - // validate netHead against subjective head - if !trust { - if res := s.validate(ctx, netHead); res != pubsub.ValidationAccept { - // netHead was either ignored or rejected - return res - } + storeHead, err := s.store.Head(ctx) + if err == nil && storeHead.Height() >= netHead.Height() { + // we already synced it up - do nothing + return } // and if valid, set it as new subjective head s.pending.Add(netHead) s.wantSync() log.Infow("new network head", "height", netHead.Height(), "hash", netHead.Hash()) - return pubsub.ValidationAccept } -// validate checks validity of the given header against the subjective head. -func (s *Syncer[H]) validate(ctx context.Context, new H) pubsub.ValidationResult { +// incomingNetworkHead processes new potential network headers. +// If the header valid, sets as new subjective header. +func (s *Syncer[H]) incomingNetworkHead(ctx context.Context, netHead H) pubsub.ValidationResult { + // first of all, check the validity of the netHead + res := s.validateHead(ctx, netHead) + if res == pubsub.ValidationAccept { + // and set it if valid + s.setSubjectiveHead(ctx, netHead) + } + return res +} + +// validateHead checks validity of the given header against the subjective head. +func (s *Syncer[H]) validateHead(ctx context.Context, new H) pubsub.ValidationResult { sbjHead, err := s.subjectiveHead(ctx) if err != nil { log.Errorw("getting subjective head during validation", "err", err) diff --git a/libs/header/sync/sync_store.go b/libs/header/sync/sync_store.go new file mode 100644 index 0000000000..b2e3123900 --- /dev/null +++ b/libs/header/sync/sync_store.go @@ -0,0 +1,40 @@ +package sync + +import ( + "context" + "sync/atomic" + + "github.com/celestiaorg/celestia-node/libs/header" +) + +// syncStore is a Store wrapper that provides synchronization over writes and reads +// for Head of underlying Store. Useful for Stores that do not guarantee synchrony between Append +// and Head method. +type syncStore[H header.Header] struct { + header.Store[H] + + head atomic.Pointer[H] +} + +func (s *syncStore[H]) Head(ctx context.Context) (H, error) { + if headPtr := s.head.Load(); headPtr != nil { + return *headPtr, nil + } + + storeHead, err := s.Store.Head(ctx) + if err != nil { + return storeHead, err + } + + s.head.Store(&storeHead) + return storeHead, nil +} + +func (s *syncStore[H]) Append(ctx context.Context, headers ...H) error { + if err := s.Store.Append(ctx, headers...); err != nil { + return err + } + + s.head.Store(&headers[len(headers)-1]) + return nil +} diff --git a/libs/header/sync/sync_store_test.go b/libs/header/sync/sync_store_test.go new file mode 100644 index 0000000000..f2cbc1bbc8 --- /dev/null +++ b/libs/header/sync/sync_store_test.go @@ -0,0 +1,31 @@ +package sync + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/celestia-node/libs/header/mocks" + "github.com/celestiaorg/celestia-node/libs/header/test" +) + +func TestSyncStore(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ts := test.NewTestSuite(t) + s := mocks.NewStore[*test.DummyHeader](t, ts, 100) + ss := syncStore[*test.DummyHeader]{Store: s} + + h, err := ss.Head(ctx) + assert.NoError(t, err) + assert.Equal(t, ts.Head(), h) + + err = ss.Append(ctx, ts.GetRandomHeader()) + assert.NoError(t, err) + + h, err = ss.Head(ctx) + assert.NoError(t, err) + assert.Equal(t, ts.Head(), h) +} diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go index ea99ae1782..08473bf20e 100644 --- a/libs/header/sync/sync_test.go +++ b/libs/header/sync/sync_test.go @@ -82,16 +82,17 @@ func TestDoSyncFullRangeFromExternalPeer(t *testing.T) { // give store time to update heightSub index time.Sleep(time.Millisecond * 100) - localHead, err := localStore.Head(ctx) + // trigger sync by calling Head + _, err = syncer.Head(ctx) require.NoError(t, err) - remoteHead, err := remoteStore.Head(ctx) - require.NoError(t, err) + // give store time to sync + time.Sleep(time.Millisecond * 100) - err = syncer.doSync(ctx, localHead, remoteHead) + remoteHead, err := remoteStore.Head(ctx) require.NoError(t, err) - newHead := *syncer.syncedHead.Load() + newHead, err := localStore.Head(ctx) require.NoError(t, err) require.Equal(t, newHead.Height(), remoteHead.Height()) } @@ -122,22 +123,21 @@ func TestSyncCatchUp(t *testing.T) { incomingHead := suite.GenDummyHeaders(1)[0] // 3. syncer rcvs header from the future and starts catching-up - res := syncer.incomingNetHead(ctx, incomingHead) + res := syncer.incomingNetworkHead(ctx, incomingHead) assert.Equal(t, pubsub.ValidationAccept, res) - time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing + time.Sleep(time.Millisecond * 100) // needs some to realize it is syncing err = syncer.SyncWait(ctx) require.NoError(t, err) + exp, err := remoteStore.Head(ctx) require.NoError(t, err) // 4. assert syncer caught-up have, err := localStore.Head(ctx) require.NoError(t, err) - headerPtr := syncer.syncedHead.Load() - require.NotNil(t, headerPtr) - head = *headerPtr - assert.Equal(t, head.Height(), incomingHead.Height()) + + assert.Equal(t, have.Height(), incomingHead.Height()) assert.Equal(t, exp.Height()+1, have.Height()) // plus one as we didn't add last header to remoteStore assert.Empty(t, syncer.pending.Head()) @@ -196,9 +196,8 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { _, err = localStore.GetByHeight(ctx, 43) require.NoError(t, err) - headerPtr := syncer.syncedHead.Load() - require.NotNil(t, headerPtr) - lastHead := *headerPtr + lastHead, err := syncer.store.Head(ctx) + require.NoError(t, err) require.Equal(t, lastHead.Height(), int64(43)) exp, err := remoteStore.Head(ctx) require.NoError(t, err) @@ -210,40 +209,6 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { assert.Empty(t, syncer.pending.Head()) // assert all cache from pending is used } -// Test that only one objective header is requested at a time -func TestSyncer_OnlyOneRecentRequest(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - store := store.NewTestStore(ctx, t, suite.Head()) - newHead := suite.GetRandomHeader() - exchange := &exchangeCountingHead{header: newHead} - syncer, err := NewSyncer[*test.DummyHeader](exchange, store, &test.DummySubscriber{}, WithBlockTime(time.Nanosecond)) - require.NoError(t, err) - - res := make(chan *test.DummyHeader) - for i := 0; i < 10; i++ { - go func() { - head, err := syncer.networkHead(ctx) - if err != nil { - panic(err) - } - select { - case res <- head: - case <-ctx.Done(): - return - } - }() - } - - for i := 0; i < 10; i++ { - head := <-res - assert.Equal(t, exchange.header, head) - } - assert.Equal(t, 1, exchange.counter) -} - // TestSyncer_FindHeadersReturnsCorrectRange ensures that `findHeaders` returns // range [from;to] func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { @@ -281,38 +246,54 @@ func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { err = syncer.processHeaders(ctx, head, 21) require.NoError(t, err) - headerPtr := syncer.syncedHead.Load() - require.NotNil(t, headerPtr) - assert.Equal(t, (*headerPtr).Height(), int64(21)) + head, err = syncer.store.Head(ctx) + require.NoError(t, err) + assert.Equal(t, head.Height(), int64(21)) } -type exchangeCountingHead struct { - header *test.DummyHeader - counter int -} +func TestSyncerIncomingDuplicate(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) -func (e *exchangeCountingHead) Head(context.Context) (*test.DummyHeader, error) { - e.counter++ - time.Sleep(time.Millisecond * 100) // simulate requesting something - return e.header, nil -} + suite := test.NewTestSuite(t) + head := suite.Head() -func (e *exchangeCountingHead) Get(ctx context.Context, bytes header.Hash) (*test.DummyHeader, error) { - panic("implement me") -} + remoteStore := store.NewTestStore(ctx, t, head) + localStore := store.NewTestStore(ctx, t, head) + syncer, err := NewSyncer[*test.DummyHeader]( + &delayedGetter[*test.DummyHeader]{Getter: local.NewExchange(remoteStore)}, + localStore, + &test.DummySubscriber{}, + ) + require.NoError(t, err) + err = syncer.Start(ctx) + require.NoError(t, err) -func (e *exchangeCountingHead) GetByHeight(ctx context.Context, u uint64) (*test.DummyHeader, error) { - panic("implement me") + range1 := suite.GenDummyHeaders(10) + err = remoteStore.Append(ctx, range1...) + require.NoError(t, err) + + res := syncer.incomingNetworkHead(ctx, range1[len(range1)-1]) + assert.Equal(t, pubsub.ValidationAccept, res) + + time.Sleep(time.Millisecond * 10) + + res = syncer.incomingNetworkHead(ctx, range1[len(range1)-1]) + assert.Equal(t, pubsub.ValidationIgnore, res) + + err = syncer.SyncWait(ctx) + require.NoError(t, err) } -func (e *exchangeCountingHead) GetRangeByHeight( - c context.Context, - from, amount uint64, -) ([]*test.DummyHeader, error) { - panic("implement me") +type delayedGetter[H header.Header] struct { + header.Getter[H] } -func (e *exchangeCountingHead) GetVerifiedRange(c context.Context, from *test.DummyHeader, amount uint64, -) ([]*test.DummyHeader, error) { - panic("implement me") +func (d *delayedGetter[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) { + select { + case <-time.After(time.Millisecond * 100): + return d.Getter.GetVerifiedRange(ctx, from, amount) + case <-ctx.Done(): + return nil, ctx.Err() + } } From ef8c9e71390718996654d09e4415b441f898d75e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 16 Mar 2023 17:55:59 +0100 Subject: [PATCH 0467/1008] test(nodebuilder): Add test that also checks that empty block is stored (#1895) Small additional check in lifecycle. --- nodebuilder/node_test.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 0f4809495b..e8f8108b31 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -14,12 +14,12 @@ import ( "google.golang.org/protobuf/proto" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share" ) func TestLifecycle(t *testing.T) { var test = []struct { - tp node.Type - coreExpected bool + tp node.Type }{ {tp: node.Bridge}, {tp: node.Full}, @@ -133,3 +133,34 @@ func StartMockOtelCollectorHTTPServer(t *testing.T) (string, func()) { server.EnableHTTP2 = true return server.URL, server.Close } + +func TestEmptyBlockExists(t *testing.T) { + var test = []struct { + tp node.Type + }{ + {tp: node.Bridge}, + {tp: node.Full}, + // technically doesn't need to be tested as a SharesAvailable call to + // light node short circuits on an empty Root + {tp: node.Light}, + } + for i, tt := range test { + t.Run(strconv.Itoa(i), func(t *testing.T) { + node := TestNode(t, tt.tp) + + ctx, cancel := context.WithTimeout(context.Background(), Timeout) + defer cancel() + + err := node.Start(ctx) + require.NoError(t, err) + + // ensure an empty block exists in store + err = node.ShareServ.SharesAvailable(ctx, share.EmptyRoot()) + require.NoError(t, err) + + err = node.Stop(ctx) + require.NoError(t, err) + }) + } + +} From 9c4eb3e5b35570d462f557cc91fd50ad8b5a0805 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 16 Mar 2023 19:36:27 +0200 Subject: [PATCH 0468/1008] refactor(fraud): extract fraud package to libs (#1916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomasz Zdybał --- api/docgen/examples.go | 14 ++++----- das/daser.go | 2 +- das/daser_test.go | 10 +++++-- {fraud => libs/fraud}/gossip_score.go | 0 {fraud => libs/fraud}/helpers.go | 0 {fraud => libs/fraud}/interface.go | 4 +-- {fraud => libs/fraud}/metrics.go | 0 {fraud => libs/fraud}/pb/proof.pb.go | 42 +++++++++++++-------------- {fraud => libs/fraud}/pb/proof.proto | 0 {fraud => libs/fraud}/proof.go | 8 ++--- {fraud => libs/fraud}/registry.go | 0 {fraud => libs/fraud}/requester.go | 2 +- {fraud => libs/fraud}/service.go | 0 {fraud => libs/fraud}/service_test.go | 11 +++---- {fraud => libs/fraud}/store.go | 0 {fraud => libs/fraud}/store_test.go | 0 {fraud => libs/fraud}/subscription.go | 0 {fraud => libs/fraud}/sync.go | 2 +- {fraud => libs/fraud}/testing.go | 10 +++---- nodebuilder/das/constructors.go | 2 +- nodebuilder/das/module.go | 5 ++-- nodebuilder/fraud/constructors.go | 9 ++++-- nodebuilder/fraud/fraud.go | 2 +- nodebuilder/fraud/lifecycle.go | 2 +- nodebuilder/fraud/mocks/api.go | 2 +- nodebuilder/fraud/module.go | 2 +- nodebuilder/fraud/service.go | 2 +- nodebuilder/header/module.go | 5 ++-- nodebuilder/header/module_test.go | 2 +- nodebuilder/p2p/pubsub.go | 2 +- nodebuilder/settings.go | 2 +- nodebuilder/state/module.go | 5 ++-- nodebuilder/tests/fraud_test.go | 10 +++---- share/eds/byzantine/bad_encoding.go | 15 ++++++++-- share/eds/retriever_test.go | 4 +-- 35 files changed, 97 insertions(+), 79 deletions(-) rename {fraud => libs/fraud}/gossip_score.go (100%) rename {fraud => libs/fraud}/helpers.go (100%) rename {fraud => libs/fraud}/interface.go (92%) rename {fraud => libs/fraud}/metrics.go (100%) rename {fraud => libs/fraud}/pb/proof.pb.go (91%) rename {fraud => libs/fraud}/pb/proof.proto (100%) rename {fraud => libs/fraud}/proof.go (94%) rename {fraud => libs/fraud}/registry.go (100%) rename {fraud => libs/fraud}/requester.go (95%) rename {fraud => libs/fraud}/service.go (100%) rename {fraud => libs/fraud}/service_test.go (95%) rename {fraud => libs/fraud}/store.go (100%) rename {fraud => libs/fraud}/store_test.go (100%) rename {fraud => libs/fraud}/subscription.go (100%) rename {fraud => libs/fraud}/sync.go (98%) rename {fraud => libs/fraud}/testing.go (92%) diff --git a/api/docgen/examples.go b/api/docgen/examples.go index b4c9f7e551..d59ae43749 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -7,7 +7,7 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) var ExampleValues = map[reflect.Type]interface{}{ @@ -15,12 +15,12 @@ var ExampleValues = map[reflect.Type]interface{}{ reflect.TypeOf(uint64(42)): uint64(42), reflect.TypeOf(uint32(42)): uint32(42), // reflect.TypeOf(int32(42)): int32(42), - reflect.TypeOf(int64(42)): int64(42), - reflect.TypeOf(byte(7)): byte(7), - reflect.TypeOf(float64(42)): float64(42), - reflect.TypeOf(true): true, - reflect.TypeOf([]byte{}): []byte("byte array"), - reflect.TypeOf(fraud.BadEncoding): fraud.BadEncoding, + reflect.TypeOf(int64(42)): int64(42), + reflect.TypeOf(byte(7)): byte(7), + reflect.TypeOf(float64(42)): float64(42), + reflect.TypeOf(true): true, + reflect.TypeOf([]byte{}): []byte("byte array"), + reflect.TypeOf(byzantine.BadEncoding): byzantine.BadEncoding, } func ExampleValue(t, parent reflect.Type) (interface{}, error) { diff --git a/das/daser.go b/das/daser.go index 97b7f99623..96e81f9604 100644 --- a/das/daser.go +++ b/das/daser.go @@ -9,8 +9,8 @@ import ( "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" diff --git a/das/daser_test.go b/das/daser_test.go index 2dd4389488..bf4dd34ad2 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -16,15 +16,16 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/libs/fraud" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/availability/mocks" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -155,7 +156,10 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) // create fraud service and break one header - f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false, "private") + getter := func(ctx context.Context, height uint64) (libhead.Header, error) { + return mockGet.GetByHeight(ctx, height) + } + f := fraud.NewProofService(ps, net.Hosts()[0], getter, ds, false, "private") require.NoError(t, f.Start(ctx)) mockGet.headers[1], _ = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() @@ -165,7 +169,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { require.NoError(t, err) resultCh := make(chan error) - go fraud.OnProof(newCtx, f, fraud.BadEncoding, + go fraud.OnProof(newCtx, f, byzantine.BadEncoding, func(fraud.Proof) { resultCh <- daser.Stop(newCtx) }) diff --git a/fraud/gossip_score.go b/libs/fraud/gossip_score.go similarity index 100% rename from fraud/gossip_score.go rename to libs/fraud/gossip_score.go diff --git a/fraud/helpers.go b/libs/fraud/helpers.go similarity index 100% rename from fraud/helpers.go rename to libs/fraud/helpers.go diff --git a/fraud/interface.go b/libs/fraud/interface.go similarity index 92% rename from fraud/interface.go rename to libs/fraud/interface.go index 1dd4ffe69a..8df44c2bb7 100644 --- a/fraud/interface.go +++ b/libs/fraud/interface.go @@ -5,13 +5,13 @@ import ( logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) var log = logging.Logger("fraud") // headerFetcher aliases a function that is used to fetch an ExtendedHeader from store by height. -type headerFetcher func(context.Context, uint64) (*header.ExtendedHeader, error) +type headerFetcher func(context.Context, uint64) (header.Header, error) // ProofUnmarshaler aliases a function that parses data to `Proof`. type ProofUnmarshaler func([]byte) (Proof, error) diff --git a/fraud/metrics.go b/libs/fraud/metrics.go similarity index 100% rename from fraud/metrics.go rename to libs/fraud/metrics.go diff --git a/fraud/pb/proof.pb.go b/libs/fraud/pb/proof.pb.go similarity index 91% rename from fraud/pb/proof.pb.go rename to libs/fraud/pb/proof.pb.go index d5e992ab63..2d5b679490 100644 --- a/fraud/pb/proof.pb.go +++ b/libs/fraud/pb/proof.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: fraud/pb/proof.proto +// source: libs/fraud/pb/proof.proto package fraud_pb @@ -30,7 +30,7 @@ func (m *FraudMessageRequest) Reset() { *m = FraudMessageRequest{} } func (m *FraudMessageRequest) String() string { return proto.CompactTextString(m) } func (*FraudMessageRequest) ProtoMessage() {} func (*FraudMessageRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{0} + return fileDescriptor_8ed4b0aa9157349f, []int{0} } func (m *FraudMessageRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -75,7 +75,7 @@ func (m *ProofResponse) Reset() { *m = ProofResponse{} } func (m *ProofResponse) String() string { return proto.CompactTextString(m) } func (*ProofResponse) ProtoMessage() {} func (*ProofResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{1} + return fileDescriptor_8ed4b0aa9157349f, []int{1} } func (m *ProofResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -126,7 +126,7 @@ func (m *FraudMessageResponse) Reset() { *m = FraudMessageResponse{} } func (m *FraudMessageResponse) String() string { return proto.CompactTextString(m) } func (*FraudMessageResponse) ProtoMessage() {} func (*FraudMessageResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_318cb87a8bb2d394, []int{2} + return fileDescriptor_8ed4b0aa9157349f, []int{2} } func (m *FraudMessageResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -168,23 +168,23 @@ func init() { proto.RegisterType((*FraudMessageResponse)(nil), "fraud.pb.FraudMessageResponse") } -func init() { proto.RegisterFile("fraud/pb/proof.proto", fileDescriptor_318cb87a8bb2d394) } - -var fileDescriptor_318cb87a8bb2d394 = []byte{ - // 203 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2b, 0x4a, 0x2c, - 0x4d, 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0x28, 0xca, 0xcf, 0x4f, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0xe2, 0x00, 0x8b, 0xea, 0x15, 0x24, 0x29, 0xb9, 0x72, 0x09, 0xbb, 0x81, 0xd8, 0xbe, 0xa9, - 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x7a, 0x5c, 0x42, - 0x50, 0x66, 0x6a, 0x4a, 0x00, 0x48, 0x63, 0x48, 0x65, 0x41, 0xaa, 0x04, 0xa3, 0x02, 0xb3, 0x06, - 0x67, 0x10, 0x16, 0x19, 0x25, 0x4b, 0x2e, 0x5e, 0x30, 0x27, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, - 0x38, 0x55, 0x48, 0x88, 0x8b, 0x05, 0xaa, 0x85, 0x51, 0x83, 0x33, 0x08, 0xcc, 0x16, 0x12, 0xe1, - 0x62, 0x0d, 0x4b, 0xcc, 0x29, 0x4d, 0x95, 0x60, 0x52, 0x60, 0xd6, 0xe0, 0x09, 0x82, 0x70, 0x94, - 0xdc, 0xb9, 0x44, 0x50, 0x5d, 0x00, 0x35, 0x41, 0x9f, 0x8b, 0x0d, 0x6c, 0x64, 0x31, 0xd8, 0x5a, - 0x6e, 0x23, 0x71, 0x3d, 0x98, 0xa3, 0xf5, 0x50, 0xac, 0x0a, 0x82, 0x2a, 0x73, 0x92, 0x38, 0xf1, - 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, - 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0xb0, 0xaf, 0x8d, 0x01, 0x01, 0x00, - 0x00, 0xff, 0xff, 0xa2, 0xc2, 0x65, 0x25, 0x0d, 0x01, 0x00, 0x00, +func init() { proto.RegisterFile("libs/fraud/pb/proof.proto", fileDescriptor_8ed4b0aa9157349f) } + +var fileDescriptor_8ed4b0aa9157349f = []byte{ + // 208 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0xc9, 0x4c, 0x2a, + 0xd6, 0x4f, 0x2b, 0x4a, 0x2c, 0x4d, 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0x28, 0xca, 0xcf, 0x4f, 0xd3, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x8b, 0xea, 0x15, 0x24, 0x29, 0xb9, 0x72, 0x09, + 0xbb, 0x81, 0xd8, 0xbe, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, + 0x25, 0x42, 0x7a, 0x5c, 0x42, 0x50, 0x66, 0x6a, 0x4a, 0x00, 0x48, 0x63, 0x48, 0x65, 0x41, 0xaa, + 0x04, 0xa3, 0x02, 0xb3, 0x06, 0x67, 0x10, 0x16, 0x19, 0x25, 0x4b, 0x2e, 0x5e, 0x30, 0x27, 0x28, + 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0x48, 0x88, 0x8b, 0x05, 0xaa, 0x85, 0x51, 0x83, 0x33, + 0x08, 0xcc, 0x16, 0x12, 0xe1, 0x62, 0x0d, 0x4b, 0xcc, 0x29, 0x4d, 0x95, 0x60, 0x52, 0x60, 0xd6, + 0xe0, 0x09, 0x82, 0x70, 0x94, 0xdc, 0xb9, 0x44, 0x50, 0x5d, 0x00, 0x35, 0x41, 0x9f, 0x8b, 0x0d, + 0x6c, 0x64, 0x31, 0xd8, 0x5a, 0x6e, 0x23, 0x71, 0x3d, 0x98, 0xa3, 0xf5, 0x50, 0xac, 0x0a, 0x82, + 0x2a, 0x73, 0x92, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, + 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0xb0, + 0xaf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x45, 0x72, 0xa0, 0x12, 0x01, 0x00, 0x00, } func (m *FraudMessageRequest) Marshal() (dAtA []byte, err error) { diff --git a/fraud/pb/proof.proto b/libs/fraud/pb/proof.proto similarity index 100% rename from fraud/pb/proof.proto rename to libs/fraud/pb/proof.proto diff --git a/fraud/proof.go b/libs/fraud/proof.go similarity index 94% rename from fraud/proof.go rename to libs/fraud/proof.go index 6bcfa80f27..c26e1b4e80 100644 --- a/fraud/proof.go +++ b/libs/fraud/proof.go @@ -8,7 +8,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric/global" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" ) var ( @@ -40,10 +40,6 @@ func (pt ProofType) String() string { return string(pt) } -const ( - BadEncoding ProofType = "badencoding" -) - // Proof is a generic interface that will be used for all types of fraud proofs in the network. type Proof interface { // Type returns the exact type of fraud proof. @@ -56,7 +52,7 @@ type Proof interface { // Validate throws an error if some conditions don't pass and thus fraud proof is not valid. // NOTE: header.ExtendedHeader should pass basic validation otherwise it will panic if it's // malformed. - Validate(*header.ExtendedHeader) error + Validate(header.Header) error encoding.BinaryMarshaler encoding.BinaryUnmarshaler diff --git a/fraud/registry.go b/libs/fraud/registry.go similarity index 100% rename from fraud/registry.go rename to libs/fraud/registry.go diff --git a/fraud/requester.go b/libs/fraud/requester.go similarity index 95% rename from fraud/requester.go rename to libs/fraud/requester.go index 18b68a864f..75a1d2d4c4 100644 --- a/fraud/requester.go +++ b/libs/fraud/requester.go @@ -9,7 +9,7 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" - pb "github.com/celestiaorg/celestia-node/fraud/pb" + pb "github.com/celestiaorg/celestia-node/libs/fraud/pb" ) const ( diff --git a/fraud/service.go b/libs/fraud/service.go similarity index 100% rename from fraud/service.go rename to libs/fraud/service.go diff --git a/fraud/service_test.go b/libs/fraud/service_test.go similarity index 95% rename from fraud/service_test.go rename to libs/fraud/service_test.go index a1f405e3d1..245eeaa56c 100644 --- a/fraud/service_test.go +++ b/libs/fraud/service_test.go @@ -15,7 +15,8 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/celestia-node/libs/header/test" ) func TestService_Subscribe(t *testing.T) { @@ -127,8 +128,8 @@ func TestService_ReGossiping(t *testing.T) { pserviceB := NewProofService( psB, net.Hosts()[1], - func(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { - return &header.ExtendedHeader{}, nil + func(ctx context.Context, u uint64) (header.Header, error) { + return &test.DummyHeader{}, nil }, sync.MutexWrap(datastore.NewMapDatastore()), false, @@ -143,8 +144,8 @@ func TestService_ReGossiping(t *testing.T) { pserviceC := NewProofService( psC, net.Hosts()[2], - func(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { - return &header.ExtendedHeader{}, nil + func(ctx context.Context, u uint64) (header.Header, error) { + return &test.DummyHeader{}, nil }, sync.MutexWrap(datastore.NewMapDatastore()), false, diff --git a/fraud/store.go b/libs/fraud/store.go similarity index 100% rename from fraud/store.go rename to libs/fraud/store.go diff --git a/fraud/store_test.go b/libs/fraud/store_test.go similarity index 100% rename from fraud/store_test.go rename to libs/fraud/store_test.go diff --git a/fraud/subscription.go b/libs/fraud/subscription.go similarity index 100% rename from fraud/subscription.go rename to libs/fraud/subscription.go diff --git a/fraud/sync.go b/libs/fraud/sync.go similarity index 98% rename from fraud/sync.go rename to libs/fraud/sync.go index d4ba4ba268..e82e33f5f5 100644 --- a/fraud/sync.go +++ b/libs/fraud/sync.go @@ -15,7 +15,7 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" - pb "github.com/celestiaorg/celestia-node/fraud/pb" + pb "github.com/celestiaorg/celestia-node/libs/fraud/pb" ) // syncFraudProofs encompasses the behavior for fetching fraud proofs from other peers. diff --git a/fraud/testing.go b/libs/fraud/testing.go similarity index 92% rename from fraud/testing.go rename to libs/fraud/testing.go index bfed3eefa8..24cfca0c2b 100644 --- a/fraud/testing.go +++ b/libs/fraud/testing.go @@ -14,8 +14,8 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/libs/header" ) type DummyService struct { @@ -34,7 +34,7 @@ func (d *DummyService) Get(context.Context, ProofType) ([]Proof, error) { } type mockStore struct { - headers map[int64]*header.ExtendedHeader + headers map[int64]header.Header headHeight int64 } @@ -42,7 +42,7 @@ type mockStore struct { // headers. func createStore(t *testing.T, numHeaders int) *mockStore { store := &mockStore{ - headers: make(map[int64]*header.ExtendedHeader), + headers: make(map[int64]header.Header), headHeight: 0, } @@ -59,7 +59,7 @@ func createStore(t *testing.T, numHeaders int) *mockStore { return store } -func (m *mockStore) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { +func (m *mockStore) GetByHeight(_ context.Context, height uint64) (header.Header, error) { return m.headers[int64(height)], nil } @@ -101,7 +101,7 @@ func (m *mockProof) Height() uint64 { return 1 } -func (m *mockProof) Validate(*header.ExtendedHeader) error { +func (m *mockProof) Validate(header.Header) error { if !m.Valid { return errors.New("mockProof: proof is not valid") } diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index 5247a13597..14298b2784 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -7,8 +7,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index dbe13684a6..311ad697aa 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -6,9 +6,10 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) func ConstructModule(tp node.Type, cfg *Config) fx.Option { @@ -43,7 +44,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(fx.Annotate( newDASer, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, das *das.DASer) error { - return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, + return fraudServ.Lifecycle(startCtx, ctx, byzantine.BadEncoding, fservice, das.Start, das.Stop) }), fx.OnStop(func(ctx context.Context, das *das.DASer) error { diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index fc22e6afad..6f62fcb6d0 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -1,13 +1,15 @@ package fraud import ( + "context" + "github.com/ipfs/go-datastore" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -28,7 +30,10 @@ func newFraudService(syncerEnabled bool) func( ds datastore.Batching, network p2p.Network, ) (Module, fraud.Service, error) { - pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, syncerEnabled, network.String()) + getter := func(ctx context.Context, height uint64) (libhead.Header, error) { + return hstore.GetByHeight(ctx, height) + } + pservice := fraud.NewProofService(sub, host, getter, ds, syncerEnabled, network.String()) lc.Append(fx.Hook{ OnStart: pservice.Start, OnStop: pservice.Stop, diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index fd9cb3edd8..7a932a540b 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -3,7 +3,7 @@ package fraud import ( "context" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" ) var _ Module = (*API)(nil) diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go index a94bc61749..f3c74e9c63 100644 --- a/nodebuilder/fraud/lifecycle.go +++ b/nodebuilder/fraud/lifecycle.go @@ -6,7 +6,7 @@ import ( "github.com/ipfs/go-datastore" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" ) // Lifecycle controls the lifecycle of service depending on fraud proofs. diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index cc94a4e794..a57a688e18 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -10,8 +10,8 @@ import ( gomock "github.com/golang/mock/gomock" - fraud "github.com/celestiaorg/celestia-node/fraud" fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index d1eb8011e3..c3c8971c5e 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -4,7 +4,7 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go index ed2e3295aa..6ac1338efa 100644 --- a/nodebuilder/fraud/service.go +++ b/nodebuilder/fraud/service.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" ) var _ Module = (*Service)(nil) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 383d8db800..3f6bf4edfa 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -8,8 +8,8 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" "github.com/celestiaorg/celestia-node/libs/header/store" @@ -17,6 +17,7 @@ import ( modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) var log = logging.Logger("module/header") @@ -75,7 +76,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fservice fraud.Service, syncer *sync.Syncer[*header.ExtendedHeader], ) error { - return modfraud.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, + return modfraud.Lifecycle(startCtx, ctx, byzantine.BadEncoding, fservice, syncer.Start, syncer.Stop) }), fx.OnStop(func(ctx context.Context, syncer *sync.Syncer[*header.ExtendedHeader]) error { diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index f995a4fb68..18d72eaf6a 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -13,8 +13,8 @@ import ( "go.uber.org/fx" "go.uber.org/fx/fxtest" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/header/p2p" "github.com/celestiaorg/celestia-node/libs/header/store" diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index fd31bdc02a..d8d302a5f6 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -15,7 +15,7 @@ import ( "go.uber.org/fx" "golang.org/x/crypto/blake2b" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" headp2p "github.com/celestiaorg/celestia-node/libs/header/p2p" ) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index a41831b155..14d73a9f5d 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -13,7 +13,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/das" modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index b8553715a9..bce96b3f1f 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -6,9 +6,10 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/state" ) @@ -26,7 +27,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(fx.Annotate( coreAccessor, fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, ca *state.CoreAccessor) error { - return fraudServ.Lifecycle(startCtx, ctx, fraud.BadEncoding, fservice, ca.Start, ca.Stop) + return fraudServ.Lifecycle(startCtx, ctx, byzantine.BadEncoding, fservice, ca.Start, ca.Stop) }), fx.OnStop(func(ctx context.Context, ca *state.CoreAccessor) error { return ca.Stop(ctx) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 75615938b7..ac6858d1f9 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -10,12 +10,12 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) /* @@ -67,7 +67,7 @@ func TestFraudProofBroadcasting(t *testing.T) { // subscribe to fraud proof before node starts helps // to prevent flakiness when fraud proof is propagating before subscribing on it - subscr, err := full.FraudServ.Subscribe(ctx, fraud.BadEncoding) + subscr, err := full.FraudServ.Subscribe(ctx, byzantine.BadEncoding) require.NoError(t, err) p := <-subscr @@ -88,7 +88,7 @@ func TestFraudProofBroadcasting(t *testing.T) { full = sw.NewNodeWithStore(node.Full, store) require.Error(t, full.Start(ctx)) - proofs, err := full.FraudServ.Get(ctx, fraud.BadEncoding) + proofs, err := full.FraudServ.Get(ctx, byzantine.BadEncoding) require.NoError(t, err) require.NotNil(t, proofs) require.NoError(t, <-fillDn) @@ -144,7 +144,7 @@ func TestFraudProofSyncing(t *testing.T) { ln := sw.NewNodeWithStore(node.Light, nodebuilder.MockStore(t, lightCfg)) require.NoError(t, full.Start(ctx)) - subsFN, err := full.FraudServ.Subscribe(ctx, fraud.BadEncoding) + subsFN, err := full.FraudServ.Subscribe(ctx, byzantine.BadEncoding) require.NoError(t, err) select { @@ -159,7 +159,7 @@ func TestFraudProofSyncing(t *testing.T) { // internal subscription for the fraud proof is done in order to ensure that light node // receives the BEFP. - subsLN, err := ln.FraudServ.Subscribe(ctx, fraud.BadEncoding) + subsLN, err := ln.FraudServ.Subscribe(ctx, byzantine.BadEncoding) require.NoError(t, err) // ensure that the full and light node are connected to speed up test diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 0ba2a34b52..6bee1ff4f1 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -9,12 +9,17 @@ import ( "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" + libhead "github.com/celestiaorg/celestia-node/libs/header" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" ) +const ( + BadEncoding fraud.ProofType = "badencoding" +) + func init() { fraud.Register(&BadEncodingProof{}) } @@ -52,7 +57,7 @@ func CreateBadEncodingProof( // Type returns type of fraud proof. func (p *BadEncodingProof) Type() fraud.ProofType { - return fraud.BadEncoding + return BadEncoding } // HeaderHash returns block hash. @@ -105,7 +110,11 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { // Validate checks that provided Merkle Proofs correspond to the shares, // rebuilds bad row or col from received shares, computes Merkle Root // and compares it with block's Merkle Root. -func (p *BadEncodingProof) Validate(header *header.ExtendedHeader) error { +func (p *BadEncodingProof) Validate(hdr libhead.Header) error { + header, ok := hdr.(*header.ExtendedHeader) + if !ok { + panic(fmt.Sprintf("invalid header type: expected %T, got %T", header, hdr)) + } if header.Height() != int64(p.BlockHeight) { return errors.New("fraud: incorrect block height") } diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 4c1985b36e..0308bb9910 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -17,9 +17,9 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" @@ -157,7 +157,7 @@ func generateByzantineError( h, err := store.GetByHeight(ctx, 1) require.NoError(t, err) - faultHeader, _ := headertest.CreateFraudExtHeader(t, h, bServ) + faultHeader, _ := headertest.CreateFraudExtHeader(t, h.(*header.ExtendedHeader), bServ) _, err = NewRetriever(bServ).Retrieve(ctx, faultHeader.DAH) return faultHeader, err } From d6d4a893d72118d540b9a2a29b68f8b182fc7feb Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 17 Mar 2023 09:02:15 +0100 Subject: [PATCH 0469/1008] fix(nodebuilder): fsStore check for nil Datastore (#1920) Because the datastore is lazily loaded, we may close the Store and hit segfault --- nodebuilder/store.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 072295d9fe..e34a73905a 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -11,6 +11,7 @@ import ( "github.com/ipfs/go-datastore" dsbadger "github.com/ipfs/go-ds-badger2" "github.com/mitchellh/go-homedir" + "go.uber.org/multierr" "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/keystore" @@ -156,9 +157,12 @@ func (f *fsStore) Datastore() (_ datastore.Batching, err error) { return f.data, nil } -func (f *fsStore) Close() error { - defer f.dirLock.Unlock() //nolint: errcheck - return f.data.Close() +func (f *fsStore) Close() (err error) { + err = multierr.Append(err, f.dirLock.Unlock()) + if f.data != nil { + err = multierr.Append(err, f.data.Close()) + } + return } type fsStore struct { From be77cac636b1e2c08560f86c8430442e64902d99 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 17 Mar 2023 11:47:04 +0100 Subject: [PATCH 0470/1008] fix(header/sync): add drift to recency detection (#1922) Pretty often, when you start a node for the first time, there is a warning complaining about an unsynced trusted peer, while we know that the peer is 100% synced. This warning also means we make an additional Head request for the wrong reason. The fix is introducing a half-block time drift. I've tested it like 20 times and couldn't reproduce the warning. We also don't need to be super precise about head to worry about this additional half. The main goal is to get the head quickly enough after being out of sync for a day or more. --- libs/header/sync/sync_head.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go index bdbb181c66..29ac310a73 100644 --- a/libs/header/sync/sync_head.go +++ b/libs/header/sync/sync_head.go @@ -170,5 +170,5 @@ func isExpired(header header.Header, period time.Duration) bool { // isRecent checks if header is recent against the given blockTime. func isRecent(header header.Header, blockTime time.Duration) bool { - return time.Since(header.Time()) <= blockTime // TODO @renaynay: should we allow for a 5-10 block drift here? + return time.Since(header.Time()) <= blockTime+blockTime/2 // add half block time drift } From 5a385d297d2c4bb9a86b60a86d0b1207955283c4 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 17 Mar 2023 13:21:44 +0200 Subject: [PATCH 0471/1008] refactor(libs/header/p2p): extend logging in session (#1923) --- libs/header/p2p/session.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index ee788341d5..d371b2655f 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -168,18 +168,17 @@ func (s *session[H]) doRequest( s.peerTracker.blockPeer(stat.peerID, err) } - // exclude header.ErrNotFound from being logged as it is a `valid` error - // and peer may not have the range(peer just connected and syncing). - if err != header.ErrNotFound { - log.Errorw("processing headers response failed", "peer", stat.peerID, "err", err) - } - select { case <-s.ctx.Done(): return case s.reqCh <- req: } - log.Errorw("processing response", "from", req.GetOrigin(), "to", req.Amount+req.GetOrigin()-1, "err", err) + log.Errorw("processing response", + "from", req.GetOrigin(), + "to", req.Amount+req.GetOrigin()-1, + "err", err, + "peer", stat.peerID, + ) return } From c8a40bc701bcdc825fa5bb02ab1adae8a7ecacb8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 17 Mar 2023 15:01:15 +0100 Subject: [PATCH 0472/1008] fix(core): Fix log that panics if !ok (#1927) Self explanatory. :( --- core/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/listener.go b/core/listener.go index 237a2aebe9..b359dda949 100644 --- a/core/listener.go +++ b/core/listener.go @@ -80,10 +80,10 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { for { select { case b, ok := <-sub: - log.Debugw("listener: new block from core", "height", b.Height) if !ok { return } + log.Debugw("listener: new block from core", "height", b.Height) syncing, err := cl.fetcher.IsSyncing(ctx) if err != nil { From 5e5173cabd623097eae6463b8ff33da43acbe30b Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 17 Mar 2023 16:57:40 +0200 Subject: [PATCH 0473/1008] bugfix(libs/header/p2p): fix data race in test (#1928) --- libs/header/p2p/exchange_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index 0441ab6825..0adbb28ff5 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -318,7 +318,6 @@ func TestExchange_HandleHeaderWithDifferentChainID(t *testing.T) { hosts := createMocknet(t, 2) exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) exchg.Params.chainID = "test" - require.NoError(t, exchg.Start(context.TODO())) _, err := exchg.Head(context.Background()) require.Error(t, err) From 66fa2be032809286e7a422fd50140ec19967c18f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:00:30 +0100 Subject: [PATCH 0474/1008] chore(nodebuilder): tiny log fix (#1929) --- nodebuilder/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/init.go b/nodebuilder/init.go index fc919bef85..83abed945f 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -133,7 +133,7 @@ func generateKeys(cfg Config, ksPath string) error { if cfg.State.KeyringBackend == keyring.BackendTest { log.Warn("Detected plaintext keyring backend. For elevated security properties, consider using" + - "the `file` keyring backend.") + " the `file` keyring backend.") } ring, err := keyring.New(app.Name, cfg.State.KeyringBackend, ksPath, os.Stdin, encConf.Codec) if err != nil { From b555675e642383ac7f865b2dab683ce043cb8663 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 20 Mar 2023 10:14:40 +0100 Subject: [PATCH 0475/1008] fix: only use gateway flag value when the value is changed (#1936) Closes #1915 --- nodebuilder/gateway/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/gateway/flags.go b/nodebuilder/gateway/flags.go index a104000cf8..cd13e47162 100644 --- a/nodebuilder/gateway/flags.go +++ b/nodebuilder/gateway/flags.go @@ -37,11 +37,11 @@ func Flags() *flag.FlagSet { // ParseFlags parses gateway flags from the given cmd and saves them to the passed config. func ParseFlags(cmd *cobra.Command, cfg *Config) { enabled, err := cmd.Flags().GetBool(enabledFlag) - if err == nil { + if cmd.Flags().Changed(enabledFlag) && err == nil { cfg.Enabled = enabled } addr, port := cmd.Flag(addrFlag), cmd.Flag(portFlag) - if !enabled && (addr.Changed || port.Changed) { + if !cfg.Enabled && (addr.Changed || port.Changed) { log.Warn("custom address or port provided without enabling gateway, setting config values") } addrVal := addr.Value.String() From 3cce82224298d3d2499c185d50c50063919f7585 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 20 Mar 2023 15:06:44 +0200 Subject: [PATCH 0476/1008] chore(libs/header/p2p): change log level in case of ErrNotFound (#1937) --- libs/header/p2p/session.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go index d371b2655f..d344508e58 100644 --- a/libs/header/p2p/session.go +++ b/libs/header/p2p/session.go @@ -161,8 +161,13 @@ func (s *session[H]) doRequest( h, err := s.processResponse(r) if err != nil { + logFn := log.Errorw + switch err { - case header.ErrNotFound, errEmptyResponse: + case header.ErrNotFound: + logFn = log.Debugw + fallthrough + case errEmptyResponse: stat.decreaseScore() default: s.peerTracker.blockPeer(stat.peerID, err) @@ -173,7 +178,7 @@ func (s *session[H]) doRequest( return case s.reqCh <- req: } - log.Errorw("processing response", + logFn("processing response", "from", req.GetOrigin(), "to", req.Amount+req.GetOrigin()-1, "err", err, From 7b3ef04c88e58df094214669632a5a8db7ff5ec3 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 20 Mar 2023 15:13:33 +0200 Subject: [PATCH 0477/1008] fix(libs/header/p2p): shuffle trusted peers before sending a request (#1903) --- libs/header/p2p/exchange.go | 51 ++++++++++++++++++++++---------- libs/header/p2p/exchange_test.go | 2 +- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go index a5a115eb50..289414ea46 100644 --- a/libs/header/p2p/exchange.go +++ b/libs/header/p2p/exchange.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "sort" + "time" logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p/core/host" @@ -34,7 +35,7 @@ type Exchange[H header.Header] struct { protocolID protocol.ID host host.Host - trustedPeers peer.IDSlice + trustedPeers func() peer.IDSlice peerTracker *peerTracker Params ClientParameters @@ -58,23 +59,29 @@ func NewExchange[H header.Header]( return nil, err } - return &Exchange[H]{ - host: host, - protocolID: protocolID(params.networkID), - trustedPeers: peers, + ex := &Exchange[H]{ + host: host, + protocolID: protocolID(params.networkID), peerTracker: newPeerTracker( host, connGater, ), Params: params, - }, nil + } + + ex.trustedPeers = func() peer.IDSlice { + return shufflePeers(peers) + } + return ex, nil } func (ex *Exchange[H]) Start(context.Context) error { ex.ctx, ex.cancel = context.WithCancel(context.Background()) log.Infow("client: starting client", "protocol ID", ex.protocolID) - for _, p := range ex.trustedPeers { + trustedPeers := ex.trustedPeers() + + for _, p := range trustedPeers { // Try to pre-connect to trusted peers. // We don't really care if we succeed at this point // and just need any peers in the peerTracker asap @@ -116,8 +123,9 @@ func (ex *Exchange[H]) Head(ctx context.Context) (H, error) { headerCh = make(chan H) ) + trustedPeers := ex.trustedPeers() // request head from each trusted peer - for _, from := range ex.trustedPeers { + for _, from := range trustedPeers { go func(from peer.ID) { // request ensures that the result slice will have at least one Header headers, err := ex.request(ctx, from, req) @@ -131,9 +139,9 @@ func (ex *Exchange[H]) Head(ctx context.Context) (H, error) { }(from) } - result := make([]H, 0, len(ex.trustedPeers)) + result := make([]H, 0, len(trustedPeers)) LOOP: - for range ex.trustedPeers { + for range trustedPeers { select { case h := <-headerCh: if !h.IsZero() { @@ -232,23 +240,22 @@ func (ex *Exchange[H]) performRequest( return make([]H, 0), nil } + trustedPeers := ex.trustedPeers() // TODO: Move this check to constructor(#1671) - if len(ex.trustedPeers) == 0 { + if len(trustedPeers) == 0 { return nil, fmt.Errorf("no trusted peers") } var reqErr error - for i := 0; i < len(ex.trustedPeers); i++ { - //nolint:gosec // G404: Use of weak random number generator - idx := rand.Intn(len(ex.trustedPeers)) + for _, peer := range trustedPeers { ctx, cancel := context.WithTimeout(ctx, ex.Params.TrustedPeersRequestTimeout) - h, err := ex.request(ctx, ex.trustedPeers[idx], req) + h, err := ex.request(ctx, peer, req) cancel() switch err { default: reqErr = err log.Debugw("requesting header from trustedPeer failed", - "trustedPeer", ex.trustedPeers[idx], "err", err) + "trustedPeer", peer, "err", err) continue case context.Canceled, context.DeadlineExceeded, nil: return h, err @@ -295,6 +302,18 @@ func (ex *Exchange[H]) request( return headers, nil } +// shufflePeers changes the order of trusted peers. +func shufflePeers(peers peer.IDSlice) peer.IDSlice { + tpeers := make(peer.IDSlice, len(peers)) + copy(tpeers, peers) + //nolint:gosec // G404: Use of weak random number generator + rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle( + len(tpeers), + func(i, j int) { tpeers[i], tpeers[j] = tpeers[j], tpeers[i] }, + ) + return tpeers +} + // bestHead chooses Header that matches the conditions: // * should have max height among received; // * should be received at least from 2 peers; diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go index 0adbb28ff5..a6e324801f 100644 --- a/libs/header/p2p/exchange_test.go +++ b/libs/header/p2p/exchange_test.go @@ -95,7 +95,7 @@ func TestExchange_RequestFullRangeHeaders(t *testing.T) { connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) // create new exchange - exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{}, connGater, + exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{hosts[4].ID()}, connGater, WithNetworkID[ClientParameters](networkID), WithChainID(networkID), ) From ab428b854247eb5a609aa6e0313241a6e52050f6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 21 Mar 2023 08:30:42 +0100 Subject: [PATCH 0478/1008] fix(rpc): allow methods with same names in different namespaces (#1944) Previously, the RPC did not allow two methods with the same name, even if they were found in separate modules. This PR gets rid of this requirement --- api/docgen/openrpc.go | 14 ++++++++------ api/rpc_test.go | 33 +++++++++++---------------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/api/docgen/openrpc.go b/api/docgen/openrpc.go index 9c05137952..a5cdef0992 100644 --- a/api/docgen/openrpc.go +++ b/api/docgen/openrpc.go @@ -67,14 +67,12 @@ func ParseCommentsFromNodebuilderModules(moduleNames ...string) Comments { v := &Visitor{make(map[string]ast.Node)} ast.Walk(v, f) - // TODO(@distractedm1nd): An issue with this could be two methods with the same name in different - // modules for mn, node := range v.Methods { filteredComments := cmap.Filter(node).Comments() if len(filteredComments) == 0 { - nodeComments[mn] = "No comment exists yet for this method." + nodeComments[moduleName+mn] = "No comment exists yet for this method." } else { - nodeComments[mn] = filteredComments[0].Text() + nodeComments[moduleName+mn] = filteredComments[0].Text() } } } @@ -140,7 +138,7 @@ func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { appReflector.FnIsMethodEligible = func(m reflect.Method) bool { // methods are only eligible if they were found in the Module interface - _, ok := comments[m.Name] + _, ok := comments[extractPackageNameFromAPIMethod(m)+m.Name] if !ok { return false } @@ -170,7 +168,7 @@ func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { } appReflector.FnGetMethodSummary = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { - if v, ok := comments[m.Name]; ok { + if v, ok := comments[extractPackageNameFromAPIMethod(m)+m.Name]; ok { return v, nil } return "", nil // noComment @@ -232,3 +230,7 @@ func OpenRPCSchemaTypeMapper(ty reflect.Type) *jsonschema.Type { return nil } + +func extractPackageNameFromAPIMethod(m reflect.Method) string { + return strings.TrimSuffix(m.Type.In(0).String()[1:], ".API") +} diff --git a/api/rpc_test.go b/api/rpc_test.go index 855aea4422..f2e088ff1f 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -72,21 +72,19 @@ func TestRPCCallsUnderlyingNode(t *testing.T) { } // api contains all modules that are made available as the node's -// public API surface (except for the `node` module as the node -// module contains a method `Info` that is also contained in the -// p2p module). -type api interface { - fraud.Module - header.Module - statemod.Module - share.Module - das.Module - p2p.Module +// public API surface +type api struct { + Fraud fraud.Module + Header header.Module + State statemod.Module + Share share.Module + DAS das.Module + Node node.Module + P2P p2p.Module } func TestModulesImplementFullAPI(t *testing.T) { api := reflect.TypeOf(new(api)).Elem() - nodeapi := reflect.TypeOf(new(node.Module)).Elem() // TODO @renaynay: explain client := reflect.TypeOf(new(client.Client)).Elem() for i := 0; i < client.NumField(); i++ { module := client.Field(i) @@ -94,22 +92,13 @@ func TestModulesImplementFullAPI(t *testing.T) { case "closer": // the "closers" field is not an actual module continue - case "Node": - // node module contains a duplicate method to the p2p module - // and must be tested separately. - internal, ok := module.Type.FieldByName("Internal") - require.True(t, ok, "module %s's API does not have an Internal field", module.Name) - for j := 0; j < internal.Type.NumField(); j++ { - impl := internal.Type.Field(j) - method, _ := nodeapi.MethodByName(impl.Name) - require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) - } default: internal, ok := module.Type.FieldByName("Internal") require.True(t, ok, "module %s's API does not have an Internal field", module.Name) for j := 0; j < internal.Type.NumField(); j++ { impl := internal.Type.Field(j) - method, _ := api.MethodByName(impl.Name) + field, _ := api.FieldByName(module.Name) + method, _ := field.Type.MethodByName(impl.Name) require.Equal(t, method.Type, impl.Type, "method %s does not match", impl.Name) } } From b4fe04de727a1556520846eb91e19947e41617de Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 22 Mar 2023 09:36:18 +0100 Subject: [PATCH 0479/1008] fix(share): enabling discovery on light nodes (#1950) Since light nodes no longer call a constructor that takes discovery, fx was never starting it. This bug was caught by `TestBootstrapNodesFromBridgeNode` --- nodebuilder/share/module.go | 1 + 1 file changed, 1 insertion(+) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 16228c1518..d87b2a017d 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -30,6 +30,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Supply(*cfg), fx.Error(cfgErr), fx.Options(options...), + fx.Invoke(func(disc *disc.Discovery) {}), fx.Provide(fx.Annotate( discovery(*cfg), fx.OnStart(func(ctx context.Context, d *disc.Discovery) error { From 045c4fea6ee286d723de846c891037f7712e729d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:15:41 +0100 Subject: [PATCH 0480/1008] fix(core): Only assume empty square if reported square size is 1 *and* there are no txs (#1952) A bug was caught on blockspacerace network that caused a data root mismatch between bridge nodes and core nodes because celestia-node incorrectly assumes an empty square if there are no transactions which is not the case as squares can contain only tail-padding even if no transactions land in the block itself. We also will now: * rely on using `data.SquareSize` from the block data for extending shares instead of calculating square size from `len(shares)`, and * panic on data root mismatch **Future considerations:** * we need a fuzzing suite against our block extension mechanism that can generate all kinds of blocks (our integration testing doesn't test for edge cases like this. * we need to audit the rest of the codebase to ensure we perform the same calculations over square size or assume empty square where we shouldn't be. --- core/eds.go | 7 +++---- core/eds_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ core/header_test.go | 6 ++++-- header/header.go | 4 ++-- 4 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 core/eds_test.go diff --git a/core/eds.go b/core/eds.go index 5ffa52ac32..e37fa5d960 100644 --- a/core/eds.go +++ b/core/eds.go @@ -11,7 +11,6 @@ import ( appshares "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" ) @@ -20,15 +19,15 @@ import ( // ExtendedDataSquare (EDS). If there are no transactions in the block, // nil is returned in place of the eds. func extendBlock(data types.Data) (*rsmt2d.ExtendedDataSquare, error) { - if len(data.Txs) == 0 { + if len(data.Txs) == 0 && data.SquareSize == uint64(1) { return nil, nil } shares, err := appshares.Split(data, true) if err != nil { return nil, err } - size := utils.SquareSize(len(shares)) - return da.ExtendShares(size, appshares.ToBytes(shares)) + + return da.ExtendShares(data.SquareSize, appshares.ToBytes(shares)) } // storeEDS will only store extended block if it is not empty and doesn't already exist. diff --git a/core/eds_test.go b/core/eds_test.go new file mode 100644 index 0000000000..d67f5005f9 --- /dev/null +++ b/core/eds_test.go @@ -0,0 +1,43 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/da" + + "github.com/celestiaorg/celestia-node/share" +) + +// TestTrulyEmptySquare ensures that a truly empty square (square size 1 and no +// txs) will be recognized as empty and return nil from `extendBlock` so that +// we do not redundantly store empty EDSes. +func TestTrulyEmptySquare(t *testing.T) { + data := types.Data{ + Txs: []types.Tx{}, + Blobs: []types.Blob{}, + SquareSize: 1, + } + + eds, err := extendBlock(data) + require.NoError(t, err) + assert.Nil(t, eds) +} + +// TestNonEmptySquareWithZeroTxs tests that a non-empty square with no +// transactions or blobs computes the correct data root (not the minimum DAH). +func TestNonEmptySquareWithZeroTxs(t *testing.T) { + data := types.Data{ + Txs: []types.Tx{}, + Blobs: []types.Blob{}, + SquareSize: 16, + } + + eds, err := extendBlock(data) + require.NoError(t, err) + dah := da.NewDataAvailabilityHeader(eds) + assert.NotEqual(t, share.EmptyRoot().Hash(), dah.Hash()) +} diff --git a/core/header_test.go b/core/header_test.go index 4f70963cde..524d9979e3 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -44,6 +44,8 @@ func TestMismatchedDataHash_ComputedRoot(t *testing.T) { header.DataHash = rand.Bytes(32) - err := header.Validate() - assert.ErrorContains(t, err, "mismatch between data hash") + panicFn := func() { + header.Validate() //nolint:errcheck + } + assert.Panics(t, panicFn) } diff --git a/header/header.go b/header/header.go index 3e371e1c70..28da7fc088 100644 --- a/header/header.go +++ b/header/header.go @@ -141,8 +141,8 @@ func (eh *ExtendedHeader) Validate() error { // ensure data root from raw header matches computed root if !bytes.Equal(eh.DAH.Hash(), eh.DataHash) { - return fmt.Errorf("mismatch between data hash commitment from core header and computed data root: "+ - "data hash: %X, computed root: %X", eh.DataHash, eh.DAH.Hash()) + panic(fmt.Sprintf("mismatch between data hash commitment from core header and computed data root "+ + "at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash())) } return eh.DAH.ValidateBasic() From eea18a72891af318fb26abb7264416b679ec2a16 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:46:26 +0800 Subject: [PATCH 0481/1008] feat(core/listener): listen for EventDataSignedBlock (#1948) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/1640 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- core/client_test.go | 6 +++--- core/exchange.go | 11 +++-------- core/fetcher.go | 36 ++++++++++++++++++++-------------- core/fetcher_test.go | 20 ++++++++++--------- core/header_test.go | 2 +- core/listener.go | 25 +++++++++++------------ header/header.go | 6 +++--- header/headertest/testing.go | 8 ++++---- nodebuilder/fraud/mocks/api.go | 2 +- 9 files changed, 58 insertions(+), 58 deletions(-) diff --git a/core/client_test.go b/core/client_test.go index e197dc58e7..7467b3c3a3 100644 --- a/core/client_test.go +++ b/core/client_test.go @@ -24,13 +24,13 @@ func TestRemoteClient_StartBlockSubscription_And_GetBlock(t *testing.T) { t.Cleanup(cancel) client := StartTestNode(t).Client - eventChan, err := client.Subscribe(ctx, newBlockSubscriber, newBlockEventQuery) + eventChan, err := client.Subscribe(ctx, newBlockSubscriber, newDataSignedBlockQuery) require.NoError(t, err) for i := 1; i <= 3; i++ { select { case evt := <-eventChan: - h := evt.Data.(types.EventDataNewBlock).Block.Height + h := evt.Data.(types.EventDataSignedBlock).Header.Height block, err := client.Block(ctx, &h) require.NoError(t, err) require.GreaterOrEqual(t, block.Block.Height, int64(i)) @@ -39,5 +39,5 @@ func TestRemoteClient_StartBlockSubscription_And_GetBlock(t *testing.T) { } } // unsubscribe to event channel - require.NoError(t, client.Unsubscribe(ctx, newBlockSubscriber, newBlockEventQuery)) + require.NoError(t, client.Unsubscribe(ctx, newBlockSubscriber, newDataSignedBlockQuery)) } diff --git a/core/exchange.go b/core/exchange.go index 49e5074c0d..97eaf26185 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -91,7 +91,7 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende return nil, err } // construct extended header - eh, err := ce.construct(ctx, block, comm, vals, eds) + eh, err := ce.construct(ctx, &block.Header, comm, vals, eds) if err != nil { return nil, err } @@ -113,12 +113,7 @@ func (ce *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { } func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64) (*header.ExtendedHeader, error) { - b, err := ce.fetcher.GetBlock(ctx, height) - if err != nil { - return nil, err - } - - comm, vals, err := ce.fetcher.GetBlockInfo(ctx, &b.Height) + b, err := ce.fetcher.GetSignedBlock(ctx, height) if err != nil { return nil, err } @@ -129,7 +124,7 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 return nil, err } // create extended header - eh, err := ce.construct(ctx, b, comm, vals, eds) + eh, err := ce.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { return nil, err } diff --git a/core/fetcher.go b/core/fetcher.go index 758c23289a..a23a6da2f7 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -5,6 +5,7 @@ import ( "fmt" logging "github.com/ipfs/go-log/v2" + coretypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" libhead "github.com/celestiaorg/celestia-node/libs/header" @@ -13,15 +14,15 @@ import ( const newBlockSubscriber = "NewBlock/Events" var ( - log = logging.Logger("core") - newBlockEventQuery = types.QueryForEvent(types.EventNewBlock).String() + log = logging.Logger("core") + newDataSignedBlockQuery = types.QueryForEvent(types.EventSignedBlock).String() ) type BlockFetcher struct { client Client - newBlockCh chan *types.Block - doneCh chan struct{} + signedBlockCh chan types.EventDataSignedBlock + doneCh chan struct{} } // NewBlockFetcher returns a new `BlockFetcher`. @@ -78,6 +79,11 @@ func (f *BlockFetcher) GetBlockByHash(ctx context.Context, hash libhead.Hash) (* return res.Block, nil } +// GetSignedBlock queries Core for a `Block` at the given height. +func (f *BlockFetcher) GetSignedBlock(ctx context.Context, height *int64) (*coretypes.ResultSignedBlock, error) { + return f.client.SignedBlock(ctx, height) +} + // Commit queries Core for a `Commit` from the block at // the given height. func (f *BlockFetcher) Commit(ctx context.Context, height *int64) (*types.Commit, error) { @@ -118,22 +124,22 @@ func (f *BlockFetcher) ValidatorSet(ctx context.Context, height *int64) (*types. // SubscribeNewBlockEvent subscribes to new block events from Core, returning // a new block event channel on success. -func (f *BlockFetcher) SubscribeNewBlockEvent(ctx context.Context) (<-chan *types.Block, error) { +func (f *BlockFetcher) SubscribeNewBlockEvent(ctx context.Context) (<-chan types.EventDataSignedBlock, error) { // start the client if not started yet if !f.client.IsRunning() { return nil, fmt.Errorf("client not running") } - eventChan, err := f.client.Subscribe(ctx, newBlockSubscriber, newBlockEventQuery) + eventChan, err := f.client.Subscribe(ctx, newBlockSubscriber, newDataSignedBlockQuery) if err != nil { return nil, err } // create a wrapper channel for translating ResultEvent to "raw" block - if f.newBlockCh != nil { + if f.signedBlockCh != nil { return nil, fmt.Errorf("new block event channel exists") } - f.newBlockCh = make(chan *types.Block) + f.signedBlockCh = make(chan types.EventDataSignedBlock) f.doneCh = make(chan struct{}) go func() { @@ -145,13 +151,13 @@ func (f *BlockFetcher) SubscribeNewBlockEvent(ctx context.Context) (<-chan *type if !ok { return } - newBlock, ok := newEvent.Data.(types.EventDataNewBlock) + signedBlock, ok := newEvent.Data.(types.EventDataSignedBlock) if !ok { log.Warnf("unexpected event: %v", newEvent) continue } select { - case f.newBlockCh <- newBlock.Block: + case f.signedBlockCh <- signedBlock: case <-f.doneCh: return } @@ -159,12 +165,12 @@ func (f *BlockFetcher) SubscribeNewBlockEvent(ctx context.Context) (<-chan *type } }() - return f.newBlockCh, nil + return f.signedBlockCh, nil } // UnsubscribeNewBlockEvent stops the subscription to new block events from Core. func (f *BlockFetcher) UnsubscribeNewBlockEvent(ctx context.Context) error { - if f.newBlockCh == nil { + if f.signedBlockCh == nil { return fmt.Errorf("no new block event channel found") } if f.doneCh == nil { @@ -174,13 +180,13 @@ func (f *BlockFetcher) UnsubscribeNewBlockEvent(ctx context.Context) error { // send stop signal f.doneCh <- struct{}{} // close out fetcher channels - close(f.newBlockCh) + close(f.signedBlockCh) close(f.doneCh) - f.newBlockCh = nil + f.signedBlockCh = nil f.doneCh = nil }() - return f.client.Unsubscribe(ctx, newBlockSubscriber, newBlockEventQuery) + return f.client.Unsubscribe(ctx, newBlockSubscriber, newDataSignedBlockQuery) } // IsSyncing returns the sync status of the Core connection: true for diff --git a/core/fetcher_test.go b/core/fetcher_test.go index b71f7568e4..de7d1ba05e 100644 --- a/core/fetcher_test.go +++ b/core/fetcher_test.go @@ -24,11 +24,14 @@ func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { for i := 1; i < 3; i++ { select { case newBlockFromChan := <-newBlockChan: - h := newBlockFromChan.Height - block, err := fetcher.GetBlock(ctx, &h) + h := newBlockFromChan.Header.Height + block, err := fetcher.GetSignedBlock(ctx, &h) require.NoError(t, err) - assert.Equal(t, newBlockFromChan, block) - require.GreaterOrEqual(t, block.Height, int64(i)) + assert.Equal(t, newBlockFromChan.Data, block.Data) + assert.Equal(t, newBlockFromChan.Header, block.Header) + assert.Equal(t, newBlockFromChan.Commit, block.Commit) + assert.Equal(t, newBlockFromChan.ValidatorSet, block.ValidatorSet) + require.GreaterOrEqual(t, newBlockFromChan.Header.Height, int64(i)) case <-ctx.Done(): require.NoError(t, ctx.Err()) } @@ -63,18 +66,17 @@ func TestBlockFetcherHeaderValues(t *testing.T) { valSet, err := fetcher.ValidatorSet(ctx, &h) require.NoError(t, err) // get next block - var nextBlock *types.Block + var nextBlock types.EventDataSignedBlock select { case nextBlock = <-newBlockChan: case <-ctx.Done(): require.NoError(t, ctx.Err()) } // compare LastCommit from next block to Commit from first block height - assert.Equal(t, nextBlock.LastCommit.Hash(), commit.Hash()) - assert.Equal(t, nextBlock.LastCommit.Height, commit.Height) - assert.Equal(t, nextBlock.LastCommit.Signatures, commit.Signatures) + assert.Equal(t, nextBlock.Header.LastCommitHash, commit.Hash()) + assert.Equal(t, nextBlock.Header.Height, commit.Height+1) // compare ValidatorSet hash to the ValidatorsHash from first block height hexBytes := valSet.Hash() - assert.Equal(t, nextBlock.ValidatorsHash.Bytes(), hexBytes) + assert.Equal(t, nextBlock.ValidatorSet.Hash(), hexBytes) require.NoError(t, fetcher.UnsubscribeNewBlockEvent(ctx)) } diff --git a/core/header_test.go b/core/header_test.go index 524d9979e3..57f53d3661 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -33,7 +33,7 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { eds, err := extendBlock(b.Data) require.NoError(t, err) - headerExt, err := header.MakeExtendedHeader(ctx, b, comm, val, eds) + headerExt, err := header.MakeExtendedHeader(ctx, &b.Header, comm, val, eds) require.NoError(t, err) assert.Equal(t, header.EmptyDAH(), *headerExt.DAH) diff --git a/core/listener.go b/core/listener.go index b359dda949..78a33f25a1 100644 --- a/core/listener.go +++ b/core/listener.go @@ -75,7 +75,7 @@ func (cl *Listener) Stop(ctx context.Context) error { // listen kicks off a loop, listening for new block events from Core, // generating ExtendedHeaders and broadcasting them to the header-sub // gossipsub network. -func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { +func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSignedBlock) { defer log.Info("listener: listening stopped") for { select { @@ -83,7 +83,7 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { if !ok { return } - log.Debugw("listener: new block from core", "height", b.Height) + log.Debugw("listener: new block from core", "height", b.Header.Height) syncing, err := cl.fetcher.IsSyncing(ctx) if err != nil { @@ -91,12 +91,6 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { return } - comm, vals, err := cl.fetcher.GetBlockInfo(ctx, &b.Height) - if err != nil { - log.Errorw("listener: getting block info", "err", err) - return - } - // extend block data eds, err := extendBlock(b.Data) if err != nil { @@ -104,13 +98,14 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { return } // generate extended header - eh, err := cl.construct(ctx, b, comm, vals, eds) + eh, err := cl.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { log.Errorw("listener: making extended header", "err", err) return } + // attempt to store block data if not empty - err = storeEDS(ctx, eh.DAH.Hash(), eds, cl.store) + err = storeEDS(ctx, b.Header.DataHash.Bytes(), eds, cl.store) if err != nil { log.Errorw("listener: storing EDS", "err", err) return @@ -118,17 +113,19 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan *types.Block) { // notify network of new EDS hash only if core is already synced if !syncing { - err = cl.hashBroadcaster(ctx, eh.DataHash.Bytes()) + err = cl.hashBroadcaster(ctx, b.Header.DataHash.Bytes()) if err != nil { - log.Errorw("listener: broadcasting data hash", "height", eh.Height(), - "hash", eh.Hash(), "err", err) + log.Errorw("listener: broadcasting data hash", + "height", b.Header.Height, + "hash", b.Header.Hash(), "err", err) //TODO: hash or datahash? } } // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) if err != nil { - log.Errorw("listener: broadcasting next header", "height", eh.Height(), + log.Errorw("listener: broadcasting next header", + "height", b.Header.Height, "err", err) } case <-ctx.Done(): diff --git a/header/header.go b/header/header.go index 28da7fc088..1b1a0fd2f0 100644 --- a/header/header.go +++ b/header/header.go @@ -19,7 +19,7 @@ import ( // ConstructFn aliases a function that creates an ExtendedHeader. type ConstructFn = func( context.Context, - *core.Block, + *core.Header, *core.Commit, *core.ValidatorSet, *rsmt2d.ExtendedDataSquare, @@ -70,7 +70,7 @@ var _ libhead.Header = &ExtendedHeader{} // MakeExtendedHeader assembles new ExtendedHeader. func MakeExtendedHeader( ctx context.Context, - b *core.Block, + h *core.Header, comm *core.Commit, vals *core.ValidatorSet, eds *rsmt2d.ExtendedDataSquare, @@ -84,7 +84,7 @@ func MakeExtendedHeader( } eh := &ExtendedHeader{ - RawHeader: b.Header, + RawHeader: *h, DAH: &dah, Commit: comm, ValidatorSet: vals, diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 71ce072762..f446122e73 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -297,14 +297,14 @@ func RandBlockID(t *testing.T) types.BlockID { func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService) header.ConstructFn { log.Warn("Corrupting block...", "height", faultHeight) return func(ctx context.Context, - b *types.Block, + h *types.Header, comm *types.Commit, vals *types.ValidatorSet, eds *rsmt2d.ExtendedDataSquare, ) (*header.ExtendedHeader, error) { - if b.Height == faultHeight { + if h.Height == faultHeight { eh := &header.ExtendedHeader{ - RawHeader: b.Header, + RawHeader: *h, Commit: comm, ValidatorSet: vals, } @@ -315,7 +315,7 @@ func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService } return eh, nil } - return header.MakeExtendedHeader(ctx, b, comm, vals, eds) + return header.MakeExtendedHeader(ctx, h, comm, vals, eds) } } diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index a57a688e18..0561602fd6 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -10,8 +10,8 @@ import ( gomock "github.com/golang/mock/gomock" - fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/libs/fraud" + fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" ) // MockModule is a mock of Module interface. From 96f50b6ff956a49ba81228903fc8475d977819c8 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 22 Mar 2023 12:38:24 +0100 Subject: [PATCH 0482/1008] fix(nodebuilder/p2p): all full and bridge nodes use `dht.ModeServer` (#1954) --- nodebuilder/p2p/routing.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go index f70e5bdf2e..e9eccf1d53 100644 --- a/nodebuilder/p2p/routing.go +++ b/nodebuilder/p2p/routing.go @@ -9,6 +9,8 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/routing" "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // contentRouting constructs nil content routing, @@ -19,7 +21,7 @@ func contentRouting(r routing.PeerRouting) routing.ContentRouting { // peerRouting provides constructor for PeerRouting over DHT. // Basically, this provides a way to discover peer addresses by respecting public keys. -func peerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) { +func peerRouting(cfg Config, tp node.Type, params routingParams) (routing.PeerRouting, error) { opts := []dht.Option{ dht.Mode(dht.ModeAuto), dht.BootstrapPeers(params.Peers...), @@ -29,10 +31,14 @@ func peerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) } if isBootstrapper() { - // override options for bootstrapper opts = append(opts, - dht.Mode(dht.ModeServer), // it must accept incoming connections - dht.BootstrapPeers(), // no bootstrappers for a bootstrapper ¯\_(ツ)_/¯ + dht.BootstrapPeers(), // no bootstrappers for a bootstrapper ¯\_(ツ)_/¯ + ) + } + + if tp == node.Bridge || tp == node.Full { + opts = append(opts, + dht.Mode(dht.ModeServer), ) } From 1541ef8941a59755d04e6a4abb2448fce6543152 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Mar 2023 15:36:45 +0100 Subject: [PATCH 0483/1008] deps: bump libp2p (#1956) --- go.mod | 27 +++++---- go.sum | 170 +++++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 129 insertions(+), 68 deletions(-) diff --git a/go.mod b/go.mod index eac629e41c..ce1caab38e 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.0 - github.com/celestiaorg/go-libp2p-messenger v0.1.0 + github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 github.com/cosmos/cosmos-sdk v0.46.7 @@ -40,7 +40,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 - github.com/libp2p/go-libp2p v0.25.1 + github.com/libp2p/go-libp2p v0.26.3 github.com/libp2p/go-libp2p-kad-dht v0.21.0 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 @@ -66,10 +66,10 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.18.2 go.uber.org/multierr v1.9.0 - golang.org/x/crypto v0.6.0 + golang.org/x/crypto v0.7.0 golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 golang.org/x/sync v0.1.0 - golang.org/x/text v0.7.0 + golang.org/x/text v0.8.0 google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) @@ -260,11 +260,10 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-18 v0.2.0 // indirect - github.com/quic-go/qtls-go1-19 v0.2.0 // indirect - github.com/quic-go/qtls-go1-20 v0.1.0 // indirect - github.com/quic-go/quic-go v0.32.0 // indirect - github.com/quic-go/webtransport-go v0.5.1 // indirect + github.com/quic-go/qtls-go1-19 v0.2.1 // indirect + github.com/quic-go/qtls-go1-20 v0.1.1 // indirect + github.com/quic-go/quic-go v0.33.0 // indirect + github.com/quic-go/webtransport-go v0.5.2 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect @@ -303,12 +302,12 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.6.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/tools v0.3.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 8123aa3ede..bf691ccecb 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SO github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= -github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= -github.com/celestiaorg/go-libp2p-messenger v0.1.0/go.mod h1:XzNksXrH0VxuNRGOnjPL9Ck4UyQlbmMpCYg9YwSBerI= +github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= +github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= @@ -246,6 +246,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -274,6 +275,7 @@ github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSM github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= @@ -285,6 +287,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -433,6 +436,7 @@ github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= @@ -737,7 +741,6 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= @@ -794,7 +797,6 @@ github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13X github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= -github.com/ipfs/go-datastore v0.4.6/go.mod h1:XSipLSc64rFKSFRFGo1ecQl+WhYce3K7frtpHkyPFUc= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= @@ -805,7 +807,7 @@ github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjv github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= -github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= +github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= @@ -955,12 +957,15 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -1006,6 +1011,7 @@ github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0 github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= +github.com/libp2p/go-conn-security-multistream v0.3.0/go.mod h1:EEP47t4fw/bTelVmEzIDqSe69hO/ip52xBEhZMLWAHM= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= @@ -1017,9 +1023,10 @@ github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xS github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= -github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= -github.com/libp2p/go-libp2p v0.25.1 h1:YK+YDCHpYyTvitKWVxa5PfElgIpOONU01X5UcLEwJGA= -github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= +github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= +github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= +github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= +github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= @@ -1030,9 +1037,11 @@ github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfha github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= +github.com/libp2p/go-libp2p-blankhost v0.3.0/go.mod h1:urPC+7U01nCGgJ3ZsV8jdwTp6Ji9ID0dMTvq+aJ+nZU= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= +github.com/libp2p/go-libp2p-circuit v0.6.0/go.mod h1:kB8hY+zCpMeScyvFrKrGicRdid6vNXbunKE4rXATZ0M= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= @@ -1055,12 +1064,15 @@ github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJB github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= -github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= +github.com/libp2p/go-libp2p-core v0.10.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= +github.com/libp2p/go-libp2p-core v0.11.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= +github.com/libp2p/go-libp2p-core v0.12.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= +github.com/libp2p/go-libp2p-core v0.14.0/go.mod h1:tLasfcVdTXnixsLB0QYaT1syJOhsbrhG7q6pGrHtBg8= +github.com/libp2p/go-libp2p-core v0.15.1/go.mod h1:agSaboYM4hzB1cWekgVReqV5M4g5M+2eNNejV+1EEhs= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.21.0 h1:J0Yd22VA+sk0CJRGMgtfHvLVIkZDyJ3AJGiljywIw5U= github.com/libp2p/go-libp2p-kad-dht v0.21.0/go.mod h1:Bhm9diAFmc6qcWAr084bHNL159srVZRKADdp96Qqd1I= github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= @@ -1072,11 +1084,13 @@ github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= +github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5BcKwrrb2VmOYb3M= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= +github.com/libp2p/go-libp2p-nat v0.1.0/go.mod h1:DQzAG+QbDYjN1/C3B6vXucLtz3u9rEonLVPtZVzQqks= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= -github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= +github.com/libp2p/go-libp2p-noise v0.4.0/go.mod h1:BzzY5pyzCYSyJbQy9oD8z5oP2idsafjt4/X42h9DjZU= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= @@ -1085,15 +1099,19 @@ github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRj github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= +github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0= +github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-pubsub v0.9.3 h1:ihcz9oIBMaCK9kcx+yHWm3mLAFBMAUsM4ux42aikDxo= github.com/libp2p/go-libp2p-pubsub v0.9.3/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= -github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= +github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc= +github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= +github.com/libp2p/go-libp2p-quic-transport v0.17.0/go.mod h1:x4pw61P3/GRCcSLypcQJE/Q2+E9f4X+5aRcZLXf20LM= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= +github.com/libp2p/go-libp2p-resource-manager v0.2.1/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= github.com/libp2p/go-libp2p-routing-helpers v0.6.1 h1:tI3rHOf/FDQsxC2pHBaOZiqPJ0MZYyzGAf4V45xla4U= github.com/libp2p/go-libp2p-routing-helpers v0.6.1/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= @@ -1106,7 +1124,9 @@ github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHv github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= -github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= +github.com/libp2p/go-libp2p-swarm v0.8.0/go.mod h1:sOMp6dPuqco0r0GHTzfVheVBh6UEL0L1lXUZ5ot2Fvc= +github.com/libp2p/go-libp2p-swarm v0.10.0/go.mod h1:71ceMcV6Rg/0rIQ97rsZWMzto1l9LnNquef+efcRbmA= +github.com/libp2p/go-libp2p-swarm v0.10.2/go.mod h1:Pdkq0QU5a+qu+oyqIV3bknMsnzk9lnNyKvB9acJ5aZs= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -1115,16 +1135,21 @@ github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eq github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= -github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= +github.com/libp2p/go-libp2p-testing v0.5.0/go.mod h1:QBk8fqIL1XNcno/l3/hhaIEn4aLRijpYOR+zVjjlh+A= +github.com/libp2p/go-libp2p-testing v0.7.0/go.mod h1:OLbdn9DbgdMwv00v+tlp1l3oe2Cl+FAjoWIA2pa0X6E= +github.com/libp2p/go-libp2p-testing v0.9.0/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= +github.com/libp2p/go-libp2p-testing v0.9.2/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= -github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= +github.com/libp2p/go-libp2p-tls v0.3.0/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5oFlg+wo2FxJDY= +github.com/libp2p/go-libp2p-tls v0.4.1/go.mod h1:EKCixHEysLNDlLUoKxv+3f/Lp90O2EXNjTr0UQDnrIw= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= +github.com/libp2p/go-libp2p-transport-upgrader v0.5.0/go.mod h1:Rc+XODlB3yce7dvFV4q/RmyJGsFcCZRkeZMu/Zdg0mo= +github.com/libp2p/go-libp2p-transport-upgrader v0.7.0/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= +github.com/libp2p/go-libp2p-transport-upgrader v0.7.1/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= @@ -1133,6 +1158,9 @@ github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2Ez github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= +github.com/libp2p/go-libp2p-yamux v0.8.0/go.mod h1:yTkPgN2ib8FHyU1ZcVD7aelzyAqXXwEPbyx+aSKm9h8= +github.com/libp2p/go-libp2p-yamux v0.8.1/go.mod h1:rUozF8Jah2dL9LLGyBaBeTQeARdwhefMCTQVQt6QobE= +github.com/libp2p/go-libp2p-yamux v0.9.1/go.mod h1:wRc6wvyxQINFcKe7daL4BeQ02Iyp+wxyC8WCNfngBrA= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= @@ -1142,9 +1170,11 @@ github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3 github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= +github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= +github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= @@ -1155,6 +1185,7 @@ github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdm github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= +github.com/libp2p/go-netroute v0.2.0/go.mod h1:Vio7LTzZ+6hoT4CMZi5/6CpY3Snzh2vgZhWgxMNwlQI= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= @@ -1164,28 +1195,31 @@ github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= +github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= -github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= +github.com/libp2p/go-reuseport-transport v0.1.0/go.mod h1:vev0C0uMkzriDY59yFHD9v+ujJvYmDQVLowvAjEOmfw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= +github.com/libp2p/go-stream-muxer-multistream v0.4.0/go.mod h1:nb+dGViZleRP4XcyHuZSVrJCBl55nRBOMmiSL/dyziw= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= -github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= -github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= +github.com/libp2p/go-tcp-transport v0.4.0/go.mod h1:0y52Rwrn4076xdJYu/51/qJIdxz+EWDAOG2S45sV3VI= +github.com/libp2p/go-tcp-transport v0.5.0/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= +github.com/libp2p/go-tcp-transport v0.5.1/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= -github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= +github.com/libp2p/go-ws-transport v0.6.0/go.mod h1:dXqtI9e2JV9FtF1NOtWVZSKXh5zXvnuwPXfj8GPBbYU= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= @@ -1194,13 +1228,18 @@ github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/go-yamux/v3 v3.0.1/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= +github.com/libp2p/go-yamux/v3 v3.0.2/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= +github.com/libp2p/go-yamux/v3 v3.1.1/go.mod h1:jeLEQgLXqE2YqX1ilAClIfCMDY+0uXQUKmmb/qp0gT4= github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= +github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= -github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= +github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= +github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= +github.com/lucas-clemente/quic-go v0.27.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1220,9 +1259,12 @@ github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2o github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= -github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= -github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= +github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= +github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= +github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= +github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= +github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= @@ -1263,6 +1305,7 @@ github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= @@ -1318,6 +1361,7 @@ github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.0.4/go.mod h1:jNLFzjPZtp3aIARHbJRZIaPuspdH0J6q39uUM5pnABM= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= @@ -1335,6 +1379,8 @@ github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4 github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= +github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= +github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= @@ -1359,6 +1405,7 @@ github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyD github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= +github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.8.0 h1:evBmgkbSQux+Ds2IgfhkO38Dl2GDtRW8/Rp6YiSHX/Q= github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= @@ -1375,6 +1422,8 @@ github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wS github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= +github.com/multiformats/go-multistream v0.3.0/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= +github.com/multiformats/go-multistream v0.3.1/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -1419,6 +1468,7 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -1522,8 +1572,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1542,18 +1592,18 @@ github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U= -github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= -github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk= -github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI= -github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA= -github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= -github.com/quic-go/webtransport-go v0.5.1 h1:1eVb7WDWCRoaeTtFHpFBJ6WDN1bSrPrRoW6tZgSw0Ow= -github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= +github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= +github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= +github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= +github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= +github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= +github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= +github.com/raulk/go-watchdog v1.2.0/go.mod h1:lzSbAl5sh4rtI8tYHU01BWIDzgzqaQLj6RcA1i4mlqI= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1780,6 +1830,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.0 h1:dlMC7aO8Wss1CxBq2I96kZ69Nh1ligzbs8UWOtq/AsA= @@ -1848,12 +1899,15 @@ go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= @@ -1863,8 +1917,8 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -1903,10 +1957,11 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1954,8 +2009,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2021,12 +2077,13 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2159,17 +2216,18 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2179,8 +2237,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2255,12 +2313,14 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -2384,6 +2444,7 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -2399,6 +2460,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From e407142923c14b8ef38122da30fa787d49b3f295 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 22 Mar 2023 16:00:10 +0100 Subject: [PATCH 0484/1008] chore: extract libs/header into go-header (#1947) --- cmd/cel-shed/header.go | 3 +- core/exchange.go | 3 +- core/fetcher.go | 2 +- core/listener.go | 3 +- core/listener_test.go | 3 +- das/coordinator.go | 3 +- das/daser.go | 3 +- das/daser_test.go | 3 +- das/subscriber.go | 3 +- das/worker.go | 3 +- go.mod | 1 + go.sum | 2 + header/header.go | 3 +- header/headertest/testing.go | 4 +- header/headertest/verify_test.go | 2 +- header/verify.go | 2 +- libs/fraud/interface.go | 2 +- libs/fraud/proof.go | 2 +- libs/fraud/service_test.go | 4 +- libs/fraud/testing.go | 3 +- libs/header/doc.go | 10 - libs/header/errors.go | 12 - libs/header/hash.go | 38 -- libs/header/header.go | 31 -- libs/header/interface.go | 131 ----- libs/header/local/exchange.go | 51 -- libs/header/metrics.go | 46 -- libs/header/mocks/store.go | 102 ---- libs/header/p2p/exchange.go | 346 ------------ libs/header/p2p/exchange_test.go | 461 ---------------- libs/header/p2p/gossip_score.go | 32 -- libs/header/p2p/helpers.go | 122 ---- libs/header/p2p/metrics.go | 63 --- libs/header/p2p/options.go | 169 ------ libs/header/p2p/pb/header_request.pb.go | 706 ------------------------ libs/header/p2p/pb/header_request.proto | 22 - libs/header/p2p/peer_stats.go | 149 ----- libs/header/p2p/peer_stats_test.go | 111 ---- libs/header/p2p/peer_tracker.go | 216 -------- libs/header/p2p/peer_tracker_test.go | 51 -- libs/header/p2p/server.go | 271 --------- libs/header/p2p/server_test.go | 32 -- libs/header/p2p/session.go | 306 ---------- libs/header/p2p/session_test.go | 58 -- libs/header/p2p/subscriber.go | 90 --- libs/header/p2p/subscription.go | 54 -- libs/header/p2p/subscription_test.go | 88 --- libs/header/store/batch.go | 108 ---- libs/header/store/height_indexer.go | 58 -- libs/header/store/heightsub.go | 122 ---- libs/header/store/heightsub_test.go | 48 -- libs/header/store/init.go | 24 - libs/header/store/init_test.go | 56 -- libs/header/store/keys.go | 22 - libs/header/store/options.go | 70 --- libs/header/store/store.go | 458 --------------- libs/header/store/store_test.go | 114 ---- libs/header/store/testing.go | 28 - libs/header/sync/metrics.go | 45 -- libs/header/sync/options.go | 56 -- libs/header/sync/ranges.go | 162 ------ libs/header/sync/ranges_test.go | 46 -- libs/header/sync/sync.go | 324 ----------- libs/header/sync/sync_getter.go | 31 -- libs/header/sync/sync_getter_test.go | 72 --- libs/header/sync/sync_head.go | 174 ------ libs/header/sync/sync_store.go | 40 -- libs/header/sync/sync_store_test.go | 31 -- libs/header/sync/sync_test.go | 299 ---------- libs/header/test/testing.go | 269 --------- nodebuilder/core/module.go | 3 +- nodebuilder/das/config.go | 3 +- nodebuilder/das/constructors.go | 3 +- nodebuilder/fraud/constructors.go | 3 +- nodebuilder/header/config.go | 9 +- nodebuilder/header/constructors.go | 9 +- nodebuilder/header/header.go | 5 +- nodebuilder/header/mocks/api.go | 4 +- nodebuilder/header/module.go | 9 +- nodebuilder/header/module_test.go | 9 +- nodebuilder/header/opts.go | 7 +- nodebuilder/header/service.go | 7 +- nodebuilder/p2p/pubsub.go | 3 +- nodebuilder/state/core.go | 2 +- nodebuilder/testing.go | 2 +- nodebuilder/tests/swamp/swamp.go | 2 +- share/eds/byzantine/bad_encoding.go | 2 +- share/p2p/peers/manager.go | 3 +- share/p2p/peers/manager_test.go | 3 +- state/core_access.go | 2 +- 90 files changed, 85 insertions(+), 6484 deletions(-) delete mode 100644 libs/header/doc.go delete mode 100644 libs/header/errors.go delete mode 100644 libs/header/hash.go delete mode 100644 libs/header/header.go delete mode 100644 libs/header/interface.go delete mode 100644 libs/header/local/exchange.go delete mode 100644 libs/header/metrics.go delete mode 100644 libs/header/mocks/store.go delete mode 100644 libs/header/p2p/exchange.go delete mode 100644 libs/header/p2p/exchange_test.go delete mode 100644 libs/header/p2p/gossip_score.go delete mode 100644 libs/header/p2p/helpers.go delete mode 100644 libs/header/p2p/metrics.go delete mode 100644 libs/header/p2p/options.go delete mode 100644 libs/header/p2p/pb/header_request.pb.go delete mode 100644 libs/header/p2p/pb/header_request.proto delete mode 100644 libs/header/p2p/peer_stats.go delete mode 100644 libs/header/p2p/peer_stats_test.go delete mode 100644 libs/header/p2p/peer_tracker.go delete mode 100644 libs/header/p2p/peer_tracker_test.go delete mode 100644 libs/header/p2p/server.go delete mode 100644 libs/header/p2p/server_test.go delete mode 100644 libs/header/p2p/session.go delete mode 100644 libs/header/p2p/session_test.go delete mode 100644 libs/header/p2p/subscriber.go delete mode 100644 libs/header/p2p/subscription.go delete mode 100644 libs/header/p2p/subscription_test.go delete mode 100644 libs/header/store/batch.go delete mode 100644 libs/header/store/height_indexer.go delete mode 100644 libs/header/store/heightsub.go delete mode 100644 libs/header/store/heightsub_test.go delete mode 100644 libs/header/store/init.go delete mode 100644 libs/header/store/init_test.go delete mode 100644 libs/header/store/keys.go delete mode 100644 libs/header/store/options.go delete mode 100644 libs/header/store/store.go delete mode 100644 libs/header/store/store_test.go delete mode 100644 libs/header/store/testing.go delete mode 100644 libs/header/sync/metrics.go delete mode 100644 libs/header/sync/options.go delete mode 100644 libs/header/sync/ranges.go delete mode 100644 libs/header/sync/ranges_test.go delete mode 100644 libs/header/sync/sync.go delete mode 100644 libs/header/sync/sync_getter.go delete mode 100644 libs/header/sync/sync_getter_test.go delete mode 100644 libs/header/sync/sync_head.go delete mode 100644 libs/header/sync/sync_store.go delete mode 100644 libs/header/sync/sync_store_test.go delete mode 100644 libs/header/sync/sync_test.go delete mode 100644 libs/header/test/testing.go diff --git a/cmd/cel-shed/header.go b/cmd/cel-shed/header.go index 1069e7035f..8216f19698 100644 --- a/cmd/cel-shed/header.go +++ b/cmd/cel-shed/header.go @@ -7,8 +7,9 @@ import ( "github.com/spf13/cobra" + "github.com/celestiaorg/go-header/store" + "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/header/store" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) diff --git a/core/exchange.go b/core/exchange.go index 97eaf26185..156742f0d5 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -5,8 +5,9 @@ import ( "context" "fmt" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share/eds" ) diff --git a/core/fetcher.go b/core/fetcher.go index a23a6da2f7..adf5bfa913 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -8,7 +8,7 @@ import ( coretypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" - libhead "github.com/celestiaorg/celestia-node/libs/header" + libhead "github.com/celestiaorg/go-header" ) const newBlockSubscriber = "NewBlock/Events" diff --git a/core/listener.go b/core/listener.go index 78a33f25a1..8f7bdfe8a6 100644 --- a/core/listener.go +++ b/core/listener.go @@ -7,8 +7,9 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/tendermint/tendermint/types" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/core/listener_test.go b/core/listener_test.go index c0e6245135..05f338f013 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -13,8 +13,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/header/p2p" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/das/coordinator.go b/das/coordinator.go index 2aca548364..cd41073fa9 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -5,8 +5,9 @@ import ( "sync" "time" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/das/daser.go b/das/daser.go index 96e81f9604..128fd1a40b 100644 --- a/das/daser.go +++ b/das/daser.go @@ -9,9 +9,10 @@ import ( "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" diff --git a/das/daser_test.go b/das/daser_test.go index bf4dd34ad2..c85e656ed5 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -16,10 +16,11 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" diff --git a/das/subscriber.go b/das/subscriber.go index 54f93a550e..18cc891c90 100644 --- a/das/subscriber.go +++ b/das/subscriber.go @@ -3,8 +3,9 @@ package das import ( "context" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" ) // subscriber subscribes to notifications about new headers in the network to keep diff --git a/das/worker.go b/das/worker.go index 3cd61ea08c..6cd70224be 100644 --- a/das/worker.go +++ b/das/worker.go @@ -9,8 +9,9 @@ import ( "go.uber.org/multierr" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/go.mod b/go.mod index ce1caab38e..48136882ba 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.0 + github.com/celestiaorg/go-header v0.2.0 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index bf691ccecb..9cd716835e 100644 --- a/go.sum +++ b/go.sum @@ -208,6 +208,8 @@ github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SO github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= +github.com/celestiaorg/go-header v0.2.0 h1:UnufpDXQGLpP40SyiwfZLRT7alKLmo3lraPaJtsV8qI= +github.com/celestiaorg/go-header v0.2.0/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= diff --git a/header/header.go b/header/header.go index 1b1a0fd2f0..d1581890b9 100644 --- a/header/header.go +++ b/header/header.go @@ -11,9 +11,8 @@ import ( core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" - - libhead "github.com/celestiaorg/celestia-node/libs/header" ) // ConstructFn aliases a function that creates an ExtendedHeader. diff --git a/header/headertest/testing.go b/header/headertest/testing.go index f446122e73..9e2cbe50ce 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -22,11 +22,11 @@ import ( "golang.org/x/exp/rand" "github.com/celestiaorg/celestia-app/pkg/da" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/test" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/test" "github.com/celestiaorg/celestia-node/share" ) diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go index 4dce036b94..78da779a54 100644 --- a/header/headertest/verify_test.go +++ b/header/headertest/verify_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" tmrand "github.com/tendermint/tendermint/libs/rand" - libhead "github.com/celestiaorg/celestia-node/libs/header" + libhead "github.com/celestiaorg/go-header" ) func TestVerify(t *testing.T) { diff --git a/header/verify.go b/header/verify.go index eebb39c08b..18e7f91ea6 100644 --- a/header/verify.go +++ b/header/verify.go @@ -7,7 +7,7 @@ import ( "github.com/tendermint/tendermint/light" - libhead "github.com/celestiaorg/celestia-node/libs/header" + libhead "github.com/celestiaorg/go-header" ) // Verify validates given untrusted Header against trusted ExtendedHeader. diff --git a/libs/fraud/interface.go b/libs/fraud/interface.go index 8df44c2bb7..1ae8b0995c 100644 --- a/libs/fraud/interface.go +++ b/libs/fraud/interface.go @@ -5,7 +5,7 @@ import ( logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/go-header" ) var log = logging.Logger("fraud") diff --git a/libs/fraud/proof.go b/libs/fraud/proof.go index c26e1b4e80..d8d0a81cfa 100644 --- a/libs/fraud/proof.go +++ b/libs/fraud/proof.go @@ -8,7 +8,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric/global" - "github.com/celestiaorg/celestia-node/libs/header" + "github.com/celestiaorg/go-header" ) var ( diff --git a/libs/fraud/service_test.go b/libs/fraud/service_test.go index 245eeaa56c..2237454d47 100644 --- a/libs/fraud/service_test.go +++ b/libs/fraud/service_test.go @@ -15,8 +15,8 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/test" + "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/test" ) func TestService_Subscribe(t *testing.T) { diff --git a/libs/fraud/testing.go b/libs/fraud/testing.go index 24cfca0c2b..9e027dd45d 100644 --- a/libs/fraud/testing.go +++ b/libs/fraud/testing.go @@ -14,8 +14,9 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" + "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/libs/header" ) type DummyService struct { diff --git a/libs/header/doc.go b/libs/header/doc.go deleted file mode 100644 index a2505a5c8a..0000000000 --- a/libs/header/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -/* -Package header contains all services related to generating, requesting, syncing and storing Headers. - -There are 4 main components in the header package: - 1. p2p.Subscriber listens for new Headers from the P2P network (via the HeaderSub) - 2. p2p.Exchange request Headers from other nodes - 3. Syncer manages syncing of past and recent Headers from either the P2P network - 4. Store manages storing Headers and making them available for access by other dependent services. -*/ -package header diff --git a/libs/header/errors.go b/libs/header/errors.go deleted file mode 100644 index 8a04f6b686..0000000000 --- a/libs/header/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package header - -import "fmt" - -// VerifyError is thrown on during Verify if it fails. -type VerifyError struct { - Reason error -} - -func (vr *VerifyError) Error() string { - return fmt.Sprintf("header: verify: %s", vr.Reason.Error()) -} diff --git a/libs/header/hash.go b/libs/header/hash.go deleted file mode 100644 index 156cd2bb05..0000000000 --- a/libs/header/hash.go +++ /dev/null @@ -1,38 +0,0 @@ -package header - -import ( - "encoding/hex" - "fmt" - "strings" -) - -// Hash represents cryptographic hash and provides basic serialization functions. -type Hash []byte - -// String implements fmt.Stringer interface. -func (h Hash) String() string { - return strings.ToUpper(hex.EncodeToString(h)) -} - -// MarshalJSON serializes Hash into valid JSON. -func (h Hash) MarshalJSON() ([]byte, error) { - s := strings.ToUpper(hex.EncodeToString(h)) - jbz := make([]byte, len(s)+2) - jbz[0] = '"' - copy(jbz[1:], s) - jbz[len(jbz)-1] = '"' - return jbz, nil -} - -// UnmarshalJSON deserializes JSON representation of a Hash into object. -func (h *Hash) UnmarshalJSON(data []byte) error { - if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' { - return fmt.Errorf("invalid hex string: %s", data) - } - bz2, err := hex.DecodeString(string(data[1 : len(data)-1])) - if err != nil { - return err - } - *h = bz2 - return nil -} diff --git a/libs/header/header.go b/libs/header/header.go deleted file mode 100644 index cdf27db423..0000000000 --- a/libs/header/header.go +++ /dev/null @@ -1,31 +0,0 @@ -package header - -import ( - "encoding" - "time" -) - -// Header abstracts all methods required to perform header sync. -type Header interface { - // New creates new instance of a header. - New() Header - // IsZero reports whether Header is a zero value of it's concrete type. - IsZero() bool - // ChainID returns identifier of the chain (ChainID). - ChainID() string - // Hash returns hash of a header. - Hash() Hash - // Height returns the height of a header. - Height() int64 - // LastHeader returns the hash of last header before this header (aka. previous header hash). - LastHeader() Hash - // Time returns time when header was created. - Time() time.Time - // Verify validates given untrusted Header against trusted Header. - Verify(Header) error - // Validate performs basic validation to check for missed/incorrect fields. - Validate() error - - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler -} diff --git a/libs/header/interface.go b/libs/header/interface.go deleted file mode 100644 index 57fe1574ff..0000000000 --- a/libs/header/interface.go +++ /dev/null @@ -1,131 +0,0 @@ -package header - -import ( - "context" - "errors" - "fmt" - - pubsub "github.com/libp2p/go-libp2p-pubsub" -) - -const ( - // MaxRangeRequestSize defines the max amount of headers that can be handled/requested at once. - MaxRangeRequestSize uint64 = 512 -) - -// Subscriber encompasses the behavior necessary to -// subscribe/unsubscribe from new Header events from the -// network. -type Subscriber[H Header] interface { - // Subscribe creates long-living Subscription for validated Headers. - // Multiple Subscriptions can be created. - Subscribe() (Subscription[H], error) - // AddValidator registers a Validator for all Subscriptions. - // Registered Validators screen Headers for their validity - // before they are sent through Subscriptions. - // Multiple validators can be registered. - AddValidator(func(context.Context, H) pubsub.ValidationResult) error -} - -// Subscription can retrieve the next Header from the -// network. -type Subscription[H Header] interface { - // NextHeader returns the newest verified and valid Header - // in the network. - NextHeader(ctx context.Context) (H, error) - // Cancel cancels the subscription. - Cancel() -} - -// Broadcaster broadcasts a Header to the network. -type Broadcaster[H Header] interface { - Broadcast(ctx context.Context, header H, opts ...pubsub.PubOpt) error -} - -// Exchange encompasses the behavior necessary to request Headers -// from the network. -type Exchange[H Header] interface { - Getter[H] -} - -var ( - // ErrNotFound is returned when there is no requested header. - ErrNotFound = errors.New("header: not found") - - // ErrNoHead is returned when Store is empty (does not contain any known header). - ErrNoHead = fmt.Errorf("header/store: no chain head") - - // ErrHeadersLimitExceeded is returned when ExchangeServer receives header request for more - // than maxRequestSize headers. - ErrHeadersLimitExceeded = errors.New("header/p2p: header limit per 1 request exceeded") -) - -// ErrNonAdjacent is returned when Store is appended with a header not adjacent to the stored head. -type ErrNonAdjacent struct { - Head int64 - Attempted int64 -} - -func (ena *ErrNonAdjacent) Error() string { - return fmt.Sprintf("header/store: non-adjacent: head %d, attempted %d", ena.Head, ena.Attempted) -} - -// Store encompasses the behavior necessary to store and retrieve Headers -// from a node's local storage. -type Store[H Header] interface { - // Start starts the store. - Start(context.Context) error - - // Stop stops the store by preventing further writes - // and waiting till the ongoing ones are done. - Stop(context.Context) error - - // Getter encompasses all getter methods for headers. - Getter[H] - - // Init initializes Store with the given head, meaning it is initialized with the genesis header. - Init(context.Context, H) error - - // Height reports current height of the chain head. - Height() uint64 - - // Has checks whether Header is already stored. - Has(context.Context, Hash) (bool, error) - - // HasAt checks whether Header at the given height is already stored. - HasAt(context.Context, uint64) bool - - // Append stores and verifies the given Header(s). - // It requires them to be adjacent and in ascending order, - // as it applies them contiguously on top of the current head height. - // It returns the amount of successfully applied headers, - // so caller can understand what given header was invalid, if any. - Append(context.Context, ...H) error -} - -// Getter contains the behavior necessary for a component to retrieve -// headers that have been processed during header sync. -type Getter[H Header] interface { - Head[H] - - // Get returns the Header corresponding to the given hash. - Get(context.Context, Hash) (H, error) - - // GetByHeight returns the Header corresponding to the given block height. - GetByHeight(context.Context, uint64) (H, error) - - // GetRangeByHeight returns the given range of Headers. - GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) - - // GetVerifiedRange requests the header range from the provided Header and - // verifies that the returned headers are adjacent to each other. - GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) -} - -// Head contains the behavior necessary for a component to retrieve -// the chain head. Note that "chain head" is subjective to the component -// reporting it. -type Head[H Header] interface { - // Head returns the latest known header. - Head(context.Context) (H, error) -} diff --git a/libs/header/local/exchange.go b/libs/header/local/exchange.go deleted file mode 100644 index 515e94fcf7..0000000000 --- a/libs/header/local/exchange.go +++ /dev/null @@ -1,51 +0,0 @@ -package local - -import ( - "context" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// Exchange is a simple Exchange that reads Headers from Store without any networking. -type Exchange[H header.Header] struct { - store header.Store[H] -} - -// NewExchange creates a new local Exchange. -func NewExchange[H header.Header](store header.Store[H]) header.Exchange[H] { - return &Exchange[H]{ - store: store, - } -} - -func (l *Exchange[H]) Start(context.Context) error { - return nil -} - -func (l *Exchange[H]) Stop(context.Context) error { - return nil -} - -func (l *Exchange[H]) Head(ctx context.Context) (H, error) { - return l.store.Head(ctx) -} - -func (l *Exchange[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { - return l.store.GetByHeight(ctx, height) -} - -func (l *Exchange[H]) GetRangeByHeight(ctx context.Context, origin, amount uint64) ([]H, error) { - if amount == 0 { - return nil, nil - } - return l.store.GetRangeByHeight(ctx, origin, origin+amount) -} - -func (l *Exchange[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64, -) ([]H, error) { - return l.store.GetVerifiedRange(ctx, from, uint64(from.Height())+amount+1) -} - -func (l *Exchange[H]) Get(ctx context.Context, hash header.Hash) (H, error) { - return l.store.Get(ctx, hash) -} diff --git a/libs/header/metrics.go b/libs/header/metrics.go deleted file mode 100644 index 6e9dcf37c8..0000000000 --- a/libs/header/metrics.go +++ /dev/null @@ -1,46 +0,0 @@ -package header - -import ( - "context" - "time" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/unit" -) - -var meter = global.MeterProvider().Meter("header") - -// WithMetrics enables Otel metrics to monitor head and total amount of synced headers. -func WithMetrics[H Header](store Store[H]) error { - headC, _ := meter.AsyncInt64().Counter( - "head", - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription("Subjective head of the node"), - ) - - err := meter.RegisterCallback( - []instrument.Asynchronous{ - headC, - }, - func(ctx context.Context) { - // add timeout to limit the time it takes to get the head - // in case there is a deadlock - ctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - - head, err := store.Head(ctx) - if err != nil { - headC.Observe(ctx, 0, attribute.String("err", err.Error())) - return - } - - headC.Observe( - ctx, - head.Height(), - ) - }, - ) - return err -} diff --git a/libs/header/mocks/store.go b/libs/header/mocks/store.go deleted file mode 100644 index 8a047ea6b5..0000000000 --- a/libs/header/mocks/store.go +++ /dev/null @@ -1,102 +0,0 @@ -package mocks - -import ( - "bytes" - "context" - "testing" - - "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -type MockStore[H header.Header] struct { - Headers map[int64]H - HeadHeight int64 -} - -// NewStore creates a mock store and adds several random -// headers -func NewStore[H header.Header](t *testing.T, gen test.Generator[H], numHeaders int) *MockStore[H] { - store := &MockStore[H]{ - Headers: make(map[int64]H), - HeadHeight: 0, - } - - for i := 0; i < numHeaders; i++ { - header := gen.GetRandomHeader() - store.Headers[header.Height()] = header - - if header.Height() > store.HeadHeight { - store.HeadHeight = header.Height() - } - } - return store -} - -func (m *MockStore[H]) Init(context.Context, H) error { return nil } -func (m *MockStore[H]) Start(context.Context) error { return nil } -func (m *MockStore[H]) Stop(context.Context) error { return nil } - -func (m *MockStore[H]) Height() uint64 { - return uint64(m.HeadHeight) -} - -func (m *MockStore[H]) Head(context.Context) (H, error) { - return m.Headers[m.HeadHeight], nil -} - -func (m *MockStore[H]) Get(ctx context.Context, hash header.Hash) (H, error) { - for _, header := range m.Headers { - if bytes.Equal(header.Hash(), hash) { - return header, nil - } - } - var zero H - return zero, header.ErrNotFound -} - -func (m *MockStore[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { - return m.Headers[int64(height)], nil -} - -func (m *MockStore[H]) GetRangeByHeight(ctx context.Context, from, to uint64) ([]H, error) { - headers := make([]H, to-from) - // As the requested range is [from; to), - // check that (to-1) height in request is less than - // the biggest header height in store. - if to-1 > m.Height() { - return nil, header.ErrNotFound - } - for i := range headers { - headers[i] = m.Headers[int64(from)] - from++ - } - return headers, nil -} - -func (m *MockStore[H]) GetVerifiedRange( - ctx context.Context, - h H, - to uint64, -) ([]H, error) { - return m.GetRangeByHeight(ctx, uint64(h.Height())+1, to) -} - -func (m *MockStore[H]) Has(context.Context, header.Hash) (bool, error) { - return false, nil -} - -func (m *MockStore[H]) HasAt(_ context.Context, height uint64) bool { - return height != 0 && m.HeadHeight >= int64(height) -} - -func (m *MockStore[H]) Append(ctx context.Context, headers ...H) error { - for _, header := range headers { - m.Headers[header.Height()] = header - // set head - if header.Height() > m.HeadHeight { - m.HeadHeight = header.Height() - } - } - return nil -} diff --git a/libs/header/p2p/exchange.go b/libs/header/p2p/exchange.go deleted file mode 100644 index 289414ea46..0000000000 --- a/libs/header/p2p/exchange.go +++ /dev/null @@ -1,346 +0,0 @@ -package p2p - -import ( - "bytes" - "context" - "errors" - "fmt" - "math/rand" - "sort" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - "github.com/libp2p/go-libp2p/p2p/net/conngater" - - "github.com/celestiaorg/celestia-node/libs/header" - p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" -) - -var log = logging.Logger("header/p2p") - -// the minimum number of headers of the same height received from trusted peers -// to determine the network head. If all trusted header will return headers with -// non-equal height, then the highest header will be chosen. -const minTrustedHeadResponses = 2 - -// Exchange enables sending outbound HeaderRequests to the network as well as -// handling inbound HeaderRequests from the network. -type Exchange[H header.Header] struct { - ctx context.Context - cancel context.CancelFunc - - protocolID protocol.ID - host host.Host - - trustedPeers func() peer.IDSlice - peerTracker *peerTracker - - Params ClientParameters - - metrics *metrics -} - -func NewExchange[H header.Header]( - host host.Host, - peers peer.IDSlice, - connGater *conngater.BasicConnectionGater, - opts ...Option[ClientParameters], -) (*Exchange[H], error) { - params := DefaultClientParameters() - for _, opt := range opts { - opt(¶ms) - } - - err := params.Validate() - if err != nil { - return nil, err - } - - ex := &Exchange[H]{ - host: host, - protocolID: protocolID(params.networkID), - peerTracker: newPeerTracker( - host, - connGater, - ), - Params: params, - } - - ex.trustedPeers = func() peer.IDSlice { - return shufflePeers(peers) - } - return ex, nil -} - -func (ex *Exchange[H]) Start(context.Context) error { - ex.ctx, ex.cancel = context.WithCancel(context.Background()) - log.Infow("client: starting client", "protocol ID", ex.protocolID) - - trustedPeers := ex.trustedPeers() - - for _, p := range trustedPeers { - // Try to pre-connect to trusted peers. - // We don't really care if we succeed at this point - // and just need any peers in the peerTracker asap - go func(p peer.ID) { - err := ex.host.Connect(ex.ctx, peer.AddrInfo{ID: p}) - if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { - log.Debugw("err connecting to a bootstrap peer", "err", err, "peer", p) - } - }(p) - } - go ex.peerTracker.gc() - go ex.peerTracker.track() - return nil -} - -func (ex *Exchange[H]) Stop(context.Context) error { - // cancel the session if it exists - ex.cancel() - // stop the peerTracker - ex.peerTracker.stop() - return nil -} - -// Head requests the latest Header. Note that the Header must be verified thereafter. -// NOTE: -// It is fine to continue handling head request if the timeout will be reached. -// As we are requesting head from multiple trusted peers, -// we may already have some headers when the timeout will be reached. -func (ex *Exchange[H]) Head(ctx context.Context) (H, error) { - log.Debug("requesting head") - // create request - req := &p2p_pb.HeaderRequest{ - Data: &p2p_pb.HeaderRequest_Origin{Origin: uint64(0)}, - Amount: 1, - } - - var ( - zero H - headerCh = make(chan H) - ) - - trustedPeers := ex.trustedPeers() - // request head from each trusted peer - for _, from := range trustedPeers { - go func(from peer.ID) { - // request ensures that the result slice will have at least one Header - headers, err := ex.request(ctx, from, req) - if err != nil { - log.Errorw("head request to trusted peer failed", "trustedPeer", from, "err", err) - var zero H - headerCh <- zero - return - } - headerCh <- headers[0] - }(from) - } - - result := make([]H, 0, len(trustedPeers)) -LOOP: - for range trustedPeers { - select { - case h := <-headerCh: - if !h.IsZero() { - result = append(result, h) - } - case <-ctx.Done(): - break LOOP - case <-ex.ctx.Done(): - return zero, ctx.Err() - } - } - - return bestHead[H](result) -} - -// GetByHeight performs a request for the Header at the given -// height to the network. Note that the Header must be verified -// thereafter. -func (ex *Exchange[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { - log.Debugw("requesting header", "height", height) - var zero H - // sanity check height - if height == 0 { - return zero, fmt.Errorf("specified request height must be greater than 0") - } - // create request - req := &p2p_pb.HeaderRequest{ - Data: &p2p_pb.HeaderRequest_Origin{Origin: height}, - Amount: 1, - } - headers, err := ex.performRequest(ctx, req) - if err != nil { - return zero, err - } - return headers[0], nil -} - -// GetRangeByHeight performs a request for the given range of Headers -// to the network. Note that the Headers must be verified thereafter. -func (ex *Exchange[H]) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) { - if amount == 0 { - return make([]H, 0), nil - } - if amount > header.MaxRangeRequestSize { - return nil, header.ErrHeadersLimitExceeded - } - session := newSession[H](ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RangeRequestTimeout) - defer session.close() - return session.getRangeByHeight(ctx, from, amount, ex.Params.MaxHeadersPerRangeRequest) -} - -// GetVerifiedRange performs a request for the given range of Headers to the network and -// ensures that returned headers are correct against the passed one. -func (ex *Exchange[H]) GetVerifiedRange( - ctx context.Context, - from H, - amount uint64, -) ([]H, error) { - if amount == 0 { - return make([]H, 0), nil - } - session := newSession[H]( - ex.ctx, ex.host, ex.peerTracker, ex.protocolID, ex.Params.RangeRequestTimeout, withValidation(from), - ) - defer session.close() - // we request the next header height that we don't have: `fromHead`+1 - return session.getRangeByHeight(ctx, uint64(from.Height())+1, amount, ex.Params.MaxHeadersPerRangeRequest) -} - -// Get performs a request for the Header by the given hash corresponding -// to the RawHeader. Note that the Header must be verified thereafter. -func (ex *Exchange[H]) Get(ctx context.Context, hash header.Hash) (H, error) { - log.Debugw("requesting header", "hash", hash.String()) - var zero H - // create request - req := &p2p_pb.HeaderRequest{ - Data: &p2p_pb.HeaderRequest_Hash{Hash: hash}, - Amount: 1, - } - headers, err := ex.performRequest(ctx, req) - if err != nil { - return zero, err - } - - if !bytes.Equal(headers[0].Hash(), hash) { - return zero, fmt.Errorf("incorrect hash in header: expected %x, got %x", hash, headers[0].Hash()) - } - return headers[0], nil -} - -func (ex *Exchange[H]) performRequest( - ctx context.Context, - req *p2p_pb.HeaderRequest, -) ([]H, error) { - if req.Amount == 0 { - return make([]H, 0), nil - } - - trustedPeers := ex.trustedPeers() - // TODO: Move this check to constructor(#1671) - if len(trustedPeers) == 0 { - return nil, fmt.Errorf("no trusted peers") - } - var reqErr error - - for _, peer := range trustedPeers { - ctx, cancel := context.WithTimeout(ctx, ex.Params.TrustedPeersRequestTimeout) - h, err := ex.request(ctx, peer, req) - cancel() - switch err { - default: - reqErr = err - log.Debugw("requesting header from trustedPeer failed", - "trustedPeer", peer, "err", err) - continue - case context.Canceled, context.DeadlineExceeded, nil: - return h, err - } - } - return nil, reqErr -} - -// request sends the HeaderRequest to a remote peer. -func (ex *Exchange[H]) request( - ctx context.Context, - to peer.ID, - req *p2p_pb.HeaderRequest, -) ([]H, error) { - log.Debugw("requesting peer", "peer", to) - responses, size, duration, err := sendMessage(ctx, ex.host, to, ex.protocolID, req) - ex.metrics.observeResponse(ctx, size, duration, err) - if err != nil { - log.Debugw("err sending request", "peer", to, "err", err) - return nil, err - } - - headers := make([]H, 0, len(responses)) - for _, response := range responses { - if err = convertStatusCodeToError(response.StatusCode); err != nil { - return nil, err - } - var empty H - header := empty.New() - err := header.UnmarshalBinary(response.Body) - if err != nil { - return nil, err - } - err = validateChainID(ex.Params.chainID, header.(H).ChainID()) - if err != nil { - return nil, err - } - headers = append(headers, header.(H)) - } - - if len(headers) == 0 { - return nil, header.ErrNotFound - } - return headers, nil -} - -// shufflePeers changes the order of trusted peers. -func shufflePeers(peers peer.IDSlice) peer.IDSlice { - tpeers := make(peer.IDSlice, len(peers)) - copy(tpeers, peers) - //nolint:gosec // G404: Use of weak random number generator - rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle( - len(tpeers), - func(i, j int) { tpeers[i], tpeers[j] = tpeers[j], tpeers[i] }, - ) - return tpeers -} - -// bestHead chooses Header that matches the conditions: -// * should have max height among received; -// * should be received at least from 2 peers; -// If neither condition is met, then latest Header will be returned (header of the highest -// height). -func bestHead[H header.Header](result []H) (H, error) { - if len(result) == 0 { - var zero H - return zero, header.ErrNotFound - } - counter := make(map[string]int) - // go through all of Headers and count the number of headers with a specific hash - for _, res := range result { - counter[res.Hash().String()]++ - } - // sort results in a decreasing order - sort.Slice(result, func(i, j int) bool { - return result[i].Height() > result[j].Height() - }) - - // try to find Header with the maximum height that was received at least from 2 peers - for _, res := range result { - if counter[res.Hash().String()] >= minTrustedHeadResponses { - return res, nil - } - } - log.Debug("could not find latest header received from at least two peers, returning header with the max height") - // otherwise return header with the max height - return result[0], nil -} diff --git a/libs/header/p2p/exchange_test.go b/libs/header/p2p/exchange_test.go deleted file mode 100644 index a6e324801f..0000000000 --- a/libs/header/p2p/exchange_test.go +++ /dev/null @@ -1,461 +0,0 @@ -package p2p - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - libhost "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - blankhost "github.com/libp2p/go-libp2p/p2p/host/blank" - "github.com/libp2p/go-libp2p/p2p/net/conngater" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - swarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - - "github.com/celestiaorg/celestia-node/libs/header" - headerMock "github.com/celestiaorg/celestia-node/libs/header/mocks" - p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -const networkID = "private" - -func TestExchange_RequestHead(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - // perform header request - header, err := exchg.Head(context.Background()) - require.NoError(t, err) - - assert.Equal(t, store.Headers[store.HeadHeight].Height(), header.Height()) - assert.Equal(t, store.Headers[store.HeadHeight].Hash(), header.Hash()) -} - -func TestExchange_RequestHeader(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - // perform expected request - header, err := exchg.GetByHeight(context.Background(), 5) - require.NoError(t, err) - assert.Equal(t, store.Headers[5].Height(), header.Height()) - assert.Equal(t, store.Headers[5].Hash(), header.Hash()) -} - -func TestExchange_RequestHeaders(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - // perform expected request - gotHeaders, err := exchg.GetRangeByHeight(context.Background(), 1, 5) - require.NoError(t, err) - for _, got := range gotHeaders { - assert.Equal(t, store.Headers[got.Height()].Height(), got.Height()) - assert.Equal(t, store.Headers[got.Height()].Hash(), got.Hash()) - } -} - -func TestExchange_RequestVerifiedHeaders(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - // perform expected request - h := store.Headers[1] - _, err := exchg.GetVerifiedRange(context.Background(), h, 3) - require.NoError(t, err) -} - -func TestExchange_RequestVerifiedHeadersFails(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - store.Headers[2] = store.Headers[3] - // perform expected request - h := store.Headers[1] - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500) - t.Cleanup(cancel) - _, err := exchg.GetVerifiedRange(ctx, h, 3) - assert.Error(t, err) - - // ensure that peer was added to the blacklist - peers := exchg.peerTracker.connGater.ListBlockedPeers() - require.Len(t, peers, 1) - require.True(t, hosts[1].ID() == peers[0]) -} - -// TestExchange_RequestFullRangeHeaders requests max amount of headers -// to verify how session will parallelize all requests. -func TestExchange_RequestFullRangeHeaders(t *testing.T) { - // create mocknet with 5 peers - hosts := createMocknet(t, 5) - store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), int(header.MaxRangeRequestSize)) - connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) - require.NoError(t, err) - // create new exchange - exchange, err := NewExchange[*test.DummyHeader](hosts[len(hosts)-1], []peer.ID{hosts[4].ID()}, connGater, - WithNetworkID[ClientParameters](networkID), - WithChainID(networkID), - ) - require.NoError(t, err) - exchange.ctx, exchange.cancel = context.WithCancel(context.Background()) - t.Cleanup(exchange.cancel) - // amount of servers is len(hosts)-1 because one peer acts as a client - servers := make([]*ExchangeServer[*test.DummyHeader], len(hosts)-1) - for index := range servers { - servers[index], err = NewExchangeServer[*test.DummyHeader]( - hosts[index], - store, - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - servers[index].Start(context.Background()) //nolint:errcheck - exchange.peerTracker.peerLk.Lock() - exchange.peerTracker.trackedPeers[hosts[index].ID()] = &peerStat{peerID: hosts[index].ID()} - exchange.peerTracker.peerLk.Unlock() - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - t.Cleanup(cancel) - // request headers from 1 to totalAmount(80) - headers, err := exchange.GetRangeByHeight(ctx, 1, header.MaxRangeRequestSize) - require.NoError(t, err) - require.Len(t, headers, int(header.MaxRangeRequestSize)) -} - -// TestExchange_RequestHeadersLimitExceeded tests that the Exchange instance will return -// header.ErrHeadersLimitExceeded if the requested range will be move than MaxRangeRequestSize. -func TestExchange_RequestHeadersLimitExceeded(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) - _, err := exchg.GetRangeByHeight(context.Background(), 1, 600) - require.Error(t, err) - require.ErrorAs(t, err, &header.ErrHeadersLimitExceeded) -} - -// TestExchange_RequestHeadersFromAnotherPeer tests that the Exchange instance will request range -// from another peer with lower score after receiving header.ErrNotFound -func TestExchange_RequestHeadersFromAnotherPeer(t *testing.T) { - hosts := createMocknet(t, 3) - // create client + server(it does not have needed headers) - exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) - // create one more server(with more headers in the store) - serverSideEx, err := NewExchangeServer[*test.DummyHeader]( - hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - require.NoError(t, serverSideEx.Start(context.Background())) - t.Cleanup(func() { - serverSideEx.Stop(context.Background()) //nolint:errcheck - }) - exchg.peerTracker.peerLk.Lock() - exchg.peerTracker.trackedPeers[hosts[2].ID()] = &peerStat{peerID: hosts[2].ID(), peerScore: 20} - exchg.peerTracker.peerLk.Unlock() - _, err = exchg.GetRangeByHeight(context.Background(), 5, 3) - require.NoError(t, err) - // ensure that peerScore for the second peer is changed - newPeerScore := exchg.peerTracker.trackedPeers[hosts[2].ID()].score() - require.NotEqual(t, 20, newPeerScore) -} - -// TestExchange_RequestByHash tests that the Exchange instance can -// respond to an HeaderRequest for a hash instead of a height. -func TestExchange_RequestByHash(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - net, err := mocknet.FullMeshConnected(2) - require.NoError(t, err) - // get host and peer - host, peer := net.Hosts()[0], net.Hosts()[1] - // create and start the ExchangeServer - store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) - serv, err := NewExchangeServer[*test.DummyHeader]( - host, - store, - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - err = serv.Start(ctx) - require.NoError(t, err) - t.Cleanup(func() { - serv.Stop(context.Background()) //nolint:errcheck - }) - - // start a new stream via Peer to see if Host can handle inbound requests - stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, protocolID(networkID)) - require.NoError(t, err) - // create request for a header at a random height - reqHeight := store.HeadHeight - 2 - req := &p2p_pb.HeaderRequest{ - Data: &p2p_pb.HeaderRequest_Hash{Hash: store.Headers[reqHeight].Hash()}, - Amount: 1, - } - // send request - _, err = serde.Write(stream, req) - require.NoError(t, err) - // read resp - resp := new(p2p_pb.HeaderResponse) - _, err = serde.Read(stream, resp) - require.NoError(t, err) - // compare - var eh test.DummyHeader - err = eh.UnmarshalBinary(resp.Body) - require.NoError(t, err) - - assert.Equal(t, store.Headers[reqHeight].Height(), eh.Height()) - assert.Equal(t, store.Headers[reqHeight].Hash(), eh.Hash()) -} - -func Test_bestHead(t *testing.T) { - gen := func() []*test.DummyHeader { - suite := test.NewTestSuite(t) - res := make([]*test.DummyHeader, 0) - for i := 0; i < 3; i++ { - res = append(res, suite.GetRandomHeader()) - } - return res - } - testCases := []struct { - precondition func() []*test.DummyHeader - expectedHeight int64 - }{ - /* - Height -> Amount - headerHeight[0]=1 -> 1 - headerHeight[1]=2 -> 1 - headerHeight[2]=3 -> 1 - result -> headerHeight[2] - */ - { - precondition: gen, - expectedHeight: 3, - }, - /* - Height -> Amount - headerHeight[0]=1 -> 2 - headerHeight[1]=2 -> 1 - headerHeight[2]=3 -> 1 - result -> headerHeight[0] - */ - { - precondition: func() []*test.DummyHeader { - res := gen() - res = append(res, res[0]) - return res - }, - expectedHeight: 1, - }, - /* - Height -> Amount - headerHeight[0]=1 -> 3 - headerHeight[1]=2 -> 2 - headerHeight[2]=3 -> 1 - result -> headerHeight[1] - */ - { - precondition: func() []*test.DummyHeader { - res := gen() - res = append(res, res[0]) - res = append(res, res[0]) - res = append(res, res[1]) - return res - }, - expectedHeight: 2, - }, - } - for _, tt := range testCases { - res := tt.precondition() - header, err := bestHead(res) - require.NoError(t, err) - require.True(t, header.Height() == tt.expectedHeight) - } -} - -// TestExchange_RequestByHashFails tests that the Exchange instance can -// respond with a StatusCode_NOT_FOUND if it will not have requested header. -func TestExchange_RequestByHashFails(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - net, err := mocknet.FullMeshConnected(2) - require.NoError(t, err) - // get host and peer - host, peer := net.Hosts()[0], net.Hosts()[1] - serv, err := NewExchangeServer[*test.DummyHeader]( - host, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 0), - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - err = serv.Start(ctx) - require.NoError(t, err) - t.Cleanup(func() { - serv.Stop(context.Background()) //nolint:errcheck - }) - - stream, err := peer.NewStream(context.Background(), libhost.InfoFromHost(host).ID, protocolID(networkID)) - require.NoError(t, err) - req := &p2p_pb.HeaderRequest{ - Data: &p2p_pb.HeaderRequest_Hash{Hash: []byte("dummy_hash")}, - Amount: 1, - } - // send request - _, err = serde.Write(stream, req) - require.NoError(t, err) - // read resp - resp := new(p2p_pb.HeaderResponse) - _, err = serde.Read(stream, resp) - require.NoError(t, err) - require.Equal(t, resp.StatusCode, p2p_pb.StatusCode_NOT_FOUND) -} - -// TestExchange_HandleHeaderWithDifferentChainID ensures that headers with different -// chainIDs will not be served and stored. -func TestExchange_HandleHeaderWithDifferentChainID(t *testing.T) { - hosts := createMocknet(t, 2) - exchg, store := createP2PExAndServer(t, hosts[0], hosts[1]) - exchg.Params.chainID = "test" - - _, err := exchg.Head(context.Background()) - require.Error(t, err) - - _, err = exchg.GetByHeight(context.Background(), 1) - require.Error(t, err) - - h, err := store.GetByHeight(context.Background(), 1) - require.NoError(t, err) - _, err = exchg.Get(context.Background(), h.Hash()) - require.Error(t, err) -} - -// TestExchange_RequestHeadersFromAnotherPeer tests that the Exchange instance will request range -// from another peer with lower score after receiving header.ErrNotFound -func TestExchange_RequestHeadersFromAnotherPeerWhenTimeout(t *testing.T) { - // create blankhost because mocknet does not support deadlines - swarm0 := swarm.GenSwarm(t) - host0 := blankhost.NewBlankHost(swarm0) - swarm1 := swarm.GenSwarm(t) - host1 := blankhost.NewBlankHost(swarm1) - swarm2 := swarm.GenSwarm(t) - host2 := blankhost.NewBlankHost(swarm2) - dial := func(a, b network.Network) { - swarm.DivulgeAddresses(b, a) - if _, err := a.DialPeer(context.Background(), b.LocalPeer()); err != nil { - t.Fatalf("Failed to dial: %s", err) - } - } - // dial peers - dial(swarm0, swarm1) - dial(swarm0, swarm2) - dial(swarm1, swarm2) - - // create client + server(it does not have needed headers) - exchg, _ := createP2PExAndServer(t, host0, host1) - exchg.Params.RangeRequestTimeout = time.Millisecond * 100 - // create one more server(with more headers in the store) - serverSideEx, err := NewExchangeServer[*test.DummyHeader]( - host2, headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - // change store implementation - serverSideEx.store = &timedOutStore{timeout: exchg.Params.RangeRequestTimeout} - require.NoError(t, serverSideEx.Start(context.Background())) - t.Cleanup(func() { - serverSideEx.Stop(context.Background()) //nolint:errcheck - }) - prevScore := exchg.peerTracker.trackedPeers[host1.ID()].score() - exchg.peerTracker.peerLk.Lock() - exchg.peerTracker.trackedPeers[host2.ID()] = &peerStat{peerID: host2.ID(), peerScore: 200} - exchg.peerTracker.peerLk.Unlock() - _, err = exchg.GetRangeByHeight(context.Background(), 1, 3) - require.NoError(t, err) - newPeerScore := exchg.peerTracker.trackedPeers[host1.ID()].score() - assert.NotEqual(t, newPeerScore, prevScore) -} - -// TestExchange_RequestPartialRange enusres in case of receiving a partial response -// from server, Exchange will re-request remaining headers from another peer -func TestExchange_RequestPartialRange(t *testing.T) { - hosts := createMocknet(t, 3) - exchg, _ := createP2PExAndServer(t, hosts[0], hosts[1]) - - // create one more server(with more headers in the store) - serverSideEx, err := NewExchangeServer[*test.DummyHeader]( - hosts[2], headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 10), - WithNetworkID[ServerParameters](networkID), - ) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - t.Cleanup(cancel) - - require.NoError(t, err) - require.NoError(t, serverSideEx.Start(ctx)) - exchg.peerTracker.peerLk.Lock() - prevScoreBefore1 := exchg.peerTracker.trackedPeers[hosts[1].ID()].peerScore - prevScoreBefore2 := 50 - // reducing peerScore of the second server, so our exchange will request host[1] first. - exchg.peerTracker.trackedPeers[hosts[2].ID()] = &peerStat{peerID: hosts[2].ID(), peerScore: 50} - exchg.peerTracker.peerLk.Unlock() - h, err := exchg.GetRangeByHeight(ctx, 1, 8) - require.NotNil(t, h) - require.NoError(t, err) - - exchg.peerTracker.peerLk.Lock() - prevScoreAfter1 := exchg.peerTracker.trackedPeers[hosts[1].ID()].peerScore - prevScoreAfter2 := exchg.peerTracker.trackedPeers[hosts[2].ID()].peerScore - exchg.peerTracker.peerLk.Unlock() - - assert.NotEqual(t, prevScoreBefore1, prevScoreAfter1) - assert.NotEqual(t, prevScoreBefore2, prevScoreAfter2) -} - -func createMocknet(t *testing.T, amount int) []libhost.Host { - net, err := mocknet.FullMeshConnected(amount) - require.NoError(t, err) - // get host and peer - return net.Hosts() -} - -// createP2PExAndServer creates a Exchange with 5 headers already in its store. -func createP2PExAndServer( - t *testing.T, - host, tpeer libhost.Host, -) (*Exchange[*test.DummyHeader], *headerMock.MockStore[*test.DummyHeader]) { - store := headerMock.NewStore[*test.DummyHeader](t, test.NewTestSuite(t), 5) - serverSideEx, err := NewExchangeServer[*test.DummyHeader](tpeer, store, - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - err = serverSideEx.Start(context.Background()) - require.NoError(t, err) - connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) - require.NoError(t, err) - ex, err := NewExchange[*test.DummyHeader](host, []peer.ID{tpeer.ID()}, connGater, - WithNetworkID[ClientParameters](networkID), - WithChainID(networkID), - ) - require.NoError(t, err) - require.NoError(t, ex.Start(context.Background())) - time.Sleep(time.Millisecond * 100) // give peerTracker time to add a trusted peer - ex.peerTracker.peerLk.Lock() - ex.peerTracker.trackedPeers[tpeer.ID()] = &peerStat{peerID: tpeer.ID(), peerScore: 100.0} - ex.peerTracker.peerLk.Unlock() - t.Cleanup(func() { - serverSideEx.Stop(context.Background()) //nolint:errcheck - ex.Stop(context.Background()) //nolint:errcheck - }) - return ex, store -} - -type timedOutStore struct { - headerMock.MockStore[*test.DummyHeader] - timeout time.Duration -} - -func (t *timedOutStore) HasAt(_ context.Context, _ uint64) bool { - time.Sleep(t.timeout + 1) - return true -} diff --git a/libs/header/p2p/gossip_score.go b/libs/header/p2p/gossip_score.go deleted file mode 100644 index b00ac02451..0000000000 --- a/libs/header/p2p/gossip_score.go +++ /dev/null @@ -1,32 +0,0 @@ -package p2p - -import ( - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" -) - -// GossibSubScore provides a set of recommended parameters for header GossipSub topic, a.k.a -// HeaderSub. -var GossibSubScore = pubsub.TopicScoreParams{ - // expected > 1 tx/second - TopicWeight: 0.1, // max cap is 5, single invalid message is -100 - - // 1 tick per second, maxes at 1 hour - TimeInMeshWeight: 0.0002778, // ~1/3600 - TimeInMeshQuantum: time.Second, - TimeInMeshCap: 1, - - // deliveries decay after 1 hour, cap at 100 blocks - FirstMessageDeliveriesWeight: 5, // max value is 500 - FirstMessageDeliveriesDecay: pubsub.ScoreParameterDecay(time.Hour), - FirstMessageDeliveriesCap: 100, // 100 blocks in an hour - - // invalid messages decay after 1 hour - InvalidMessageDeliveriesWeight: -1000, - InvalidMessageDeliveriesDecay: pubsub.ScoreParameterDecay(time.Hour), - - // Mesh Delivery Failure is currently turned off for messages - // This is on purpose as the network is still too small, which results in - // asymmetries and potential unmeshing from negative scores. -} diff --git a/libs/header/p2p/helpers.go b/libs/header/p2p/helpers.go deleted file mode 100644 index d703c1db3f..0000000000 --- a/libs/header/p2p/helpers.go +++ /dev/null @@ -1,122 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - "io" - "strings" - "time" - - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - - "github.com/celestiaorg/celestia-node/libs/header" - p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" -) - -func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("/%s/header-ex/v0.0.3", networkID)) -} - -func PubsubTopicID(networkID string) string { - return fmt.Sprintf("/%s/header-sub/v0.0.1", networkID) -} - -func validateChainID(want, have string) error { - if want != "" && !strings.EqualFold(want, have) { - return fmt.Errorf("header with different chainID received.want=%s,have=%s", - want, have, - ) - } - return nil -} - -// sendMessage opens the stream to the given peers and sends HeaderRequest to fetch -// Headers. As a result sendMessage returns HeaderResponse, the size of fetched -// data, the duration of the request and an error. -func sendMessage( - ctx context.Context, - host host.Host, - to peer.ID, - protocol protocol.ID, - req *p2p_pb.HeaderRequest, -) ([]*p2p_pb.HeaderResponse, uint64, uint64, error) { - startTime := time.Now() - stream, err := host.NewStream(ctx, to, protocol) - if err != nil { - return nil, 0, 0, fmt.Errorf("header/p2p: failed to open a new stream: %w", err) - } - - // set stream deadline from the context deadline. - // if it is empty, then we assume that it will - // hang until the server will close the stream by the timeout. - if dl, ok := ctx.Deadline(); ok { - if err = stream.SetDeadline(dl); err != nil { - log.Debugw("error setting deadline: %s", err) - } - } - - // send request - _, err = serde.Write(stream, req) - if err != nil { - stream.Reset() //nolint:errcheck - return nil, 0, 0, fmt.Errorf("header/p2p: failed to write a request: %w", err) - } - - err = stream.CloseWrite() - if err != nil { - return nil, 0, 0, err - } - - headers := make([]*p2p_pb.HeaderResponse, 0) - - var totalRespLn uint64 - for i := 0; i < int(req.Amount); i++ { - resp := new(p2p_pb.HeaderResponse) - respLn, readErr := serde.Read(stream, resp) - if readErr != nil { - err = readErr - break - } - - totalRespLn += uint64(respLn) - headers = append(headers, resp) - } - - duration := time.Since(startTime).Milliseconds() - - // we allow the server side to explicitly close the connection - // if it does not have the requested range. - // In this case, server side will send us a response with ErrNotFound status code inside - // and then will close the stream. - // If the server side will have a part of the requested range, then it will send this part - // and then will close the connection - if err == io.EOF { - err = nil - } - - if err == nil { - if closeErr := stream.Close(); closeErr != nil { - log.Errorw("closing stream", "err", closeErr) - } - } else { - // reset stream in case of an error - stream.Reset() //nolint:errcheck - } - return headers, totalRespLn, uint64(duration), err -} - -// convertStatusCodeToError converts passed status code into an error. -func convertStatusCodeToError(code p2p_pb.StatusCode) error { - switch code { - case p2p_pb.StatusCode_OK: - return nil - case p2p_pb.StatusCode_NOT_FOUND: - return header.ErrNotFound - default: - return fmt.Errorf("unknown status code %d", code) - } -} diff --git a/libs/header/p2p/metrics.go b/libs/header/p2p/metrics.go deleted file mode 100644 index f6fe1d69cd..0000000000 --- a/libs/header/p2p/metrics.go +++ /dev/null @@ -1,63 +0,0 @@ -package p2p - -import ( - "context" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" -) - -type metrics struct { - responseSize syncfloat64.Histogram - responseDuration syncfloat64.Histogram -} - -var ( - meter = global.MeterProvider().Meter("header/p2p") -) - -func (ex *Exchange[H]) InitMetrics() error { - responseSize, err := meter. - SyncFloat64(). - Histogram( - "header_p2p_headers_response_size", - instrument.WithDescription("Size of get headers response in bytes"), - ) - if err != nil { - return err - } - - responseDuration, err := meter. - SyncFloat64(). - Histogram( - "header_p2p_headers_request_duration", - instrument.WithDescription("Duration of get headers request in seconds"), - ) - if err != nil { - return err - } - - ex.metrics = &metrics{ - responseSize: responseSize, - responseDuration: responseDuration, - } - return nil -} - -func (m *metrics) observeResponse(ctx context.Context, size uint64, duration uint64, err error) { - if m == nil { - return - } - m.responseSize.Record( - ctx, - float64(size), - attribute.Bool("failed", err != nil), - ) - m.responseDuration.Record( - ctx, - float64(duration), - attribute.Bool("failed", err != nil), - ) -} diff --git a/libs/header/p2p/options.go b/libs/header/p2p/options.go deleted file mode 100644 index 15c94d190e..0000000000 --- a/libs/header/p2p/options.go +++ /dev/null @@ -1,169 +0,0 @@ -package p2p - -import ( - "fmt" - "time" -) - -// parameters is an interface that encompasses all params needed for -// client and server parameters to protect `optional functions` from this package. -type parameters interface { - ServerParameters | ClientParameters -} - -// Option is the functional option that is applied to the exchange instance -// to configure parameters. -type Option[T parameters] func(*T) - -// ServerParameters is the set of parameters that must be configured for the exchange. -type ServerParameters struct { - // WriteDeadline sets the timeout for sending messages to the stream - WriteDeadline time.Duration - // ReadDeadline sets the timeout for reading messages from the stream - ReadDeadline time.Duration - // RangeRequestTimeout defines a timeout after which the session will try to re-request headers - // from another peer. - RangeRequestTimeout time.Duration - // networkID is a network that will be used to create a protocol.ID - // Is empty by default - networkID string -} - -// DefaultServerParameters returns the default params to configure the store. -func DefaultServerParameters() ServerParameters { - return ServerParameters{ - WriteDeadline: time.Second * 5, - ReadDeadline: time.Minute, - RangeRequestTimeout: time.Second * 5, - } -} - -func (p *ServerParameters) Validate() error { - if p.WriteDeadline == 0 { - return fmt.Errorf("invalid write time duration: %v", p.WriteDeadline) - } - if p.ReadDeadline == 0 { - return fmt.Errorf("invalid read time duration: %v", p.ReadDeadline) - } - if p.RangeRequestTimeout == 0 { - return fmt.Errorf("invalid request timeout for session: "+ - "%s. %s: %v", greaterThenZero, providedSuffix, p.RangeRequestTimeout) - } - return nil -} - -// WithWriteDeadline is a functional option that configures the -// `WriteDeadline` parameter. -func WithWriteDeadline[T ServerParameters](deadline time.Duration) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ServerParameters: - t.WriteDeadline = deadline - } - } -} - -// WithReadDeadline is a functional option that configures the -// `WithReadDeadline` parameter. -func WithReadDeadline[T ServerParameters](deadline time.Duration) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ServerParameters: - t.ReadDeadline = deadline - } - } -} - -// WithRangeRequestTimeout is a functional option that configures the -// `RangeRequestTimeout` parameter. -func WithRangeRequestTimeout[T parameters](duration time.Duration) Option[T] { - return func(p *T) { - switch t := any(p).(type) { - case *ClientParameters: - t.RangeRequestTimeout = duration - case *ServerParameters: - t.RangeRequestTimeout = duration - } - } -} - -// WithNetworkID is a functional option that configures the -// `networkID` parameter. -func WithNetworkID[T parameters](networkID string) Option[T] { - return func(p *T) { - switch t := any(p).(type) { - case *ClientParameters: - t.networkID = networkID - case *ServerParameters: - t.networkID = networkID - } - } -} - -// ClientParameters is the set of parameters that must be configured for the exchange. -type ClientParameters struct { - // MaxHeadersPerRangeRequest defines the max amount of headers that can be requested per 1 request. - MaxHeadersPerRangeRequest uint64 - // RangeRequestTimeout defines a timeout after which the session will try to re-request headers - // from another peer. - RangeRequestTimeout time.Duration - // TrustedPeersRequestTimeout a timeout for any request to a trusted peer. - TrustedPeersRequestTimeout time.Duration - // networkID is a network that will be used to create a protocol.ID - networkID string - // chainID is an identifier of the chain. - chainID string -} - -// DefaultClientParameters returns the default params to configure the store. -func DefaultClientParameters() ClientParameters { - return ClientParameters{ - MaxHeadersPerRangeRequest: 64, - RangeRequestTimeout: time.Second * 8, - TrustedPeersRequestTimeout: time.Millisecond * 300, - } -} - -const ( - greaterThenZero = "should be greater than 0" - providedSuffix = "Provided value" -) - -func (p *ClientParameters) Validate() error { - if p.MaxHeadersPerRangeRequest == 0 { - return fmt.Errorf("invalid MaxHeadersPerRangeRequest:%s. %s: %v", - greaterThenZero, providedSuffix, p.MaxHeadersPerRangeRequest) - } - if p.RangeRequestTimeout == 0 { - return fmt.Errorf("invalid request timeout for session: "+ - "%s. %s: %v", greaterThenZero, providedSuffix, p.RangeRequestTimeout) - } - if p.TrustedPeersRequestTimeout == 0 { - return fmt.Errorf("invalid TrustedPeersRequestTimeout: "+ - "%s. %s: %v", greaterThenZero, providedSuffix, p.TrustedPeersRequestTimeout) - } - return nil -} - -// WithMaxHeadersPerRangeRequest is a functional option that configures the -// // `MaxRangeRequestSize` parameter. -func WithMaxHeadersPerRangeRequest[T ClientParameters](amount uint64) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ClientParameters: - t.MaxHeadersPerRangeRequest = amount - } - - } -} - -// WithChainID is a functional option that configures the -// `chainID` parameter. -func WithChainID[T ClientParameters](chainID string) Option[T] { - return func(p *T) { - switch t := any(p).(type) { //nolint:gocritic - case *ClientParameters: - t.chainID = chainID - } - } -} diff --git a/libs/header/p2p/pb/header_request.pb.go b/libs/header/p2p/pb/header_request.pb.go deleted file mode 100644 index e0b7e4f016..0000000000 --- a/libs/header/p2p/pb/header_request.pb.go +++ /dev/null @@ -1,706 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: libs/header/p2p/pb/header_request.proto - -package p2p_pb - -import ( - fmt "fmt" - proto "github.com/gogo/protobuf/proto" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -type StatusCode int32 - -const ( - StatusCode_INVALID StatusCode = 0 - StatusCode_OK StatusCode = 1 - StatusCode_NOT_FOUND StatusCode = 2 -) - -var StatusCode_name = map[int32]string{ - 0: "INVALID", - 1: "OK", - 2: "NOT_FOUND", -} - -var StatusCode_value = map[string]int32{ - "INVALID": 0, - "OK": 1, - "NOT_FOUND": 2, -} - -func (x StatusCode) String() string { - return proto.EnumName(StatusCode_name, int32(x)) -} - -func (StatusCode) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_43554822dc0b0806, []int{0} -} - -type HeaderRequest struct { - // Types that are valid to be assigned to Data: - // *HeaderRequest_Origin - // *HeaderRequest_Hash - Data isHeaderRequest_Data `protobuf_oneof:"data"` - Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` -} - -func (m *HeaderRequest) Reset() { *m = HeaderRequest{} } -func (m *HeaderRequest) String() string { return proto.CompactTextString(m) } -func (*HeaderRequest) ProtoMessage() {} -func (*HeaderRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_43554822dc0b0806, []int{0} -} -func (m *HeaderRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *HeaderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_HeaderRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *HeaderRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_HeaderRequest.Merge(m, src) -} -func (m *HeaderRequest) XXX_Size() int { - return m.Size() -} -func (m *HeaderRequest) XXX_DiscardUnknown() { - xxx_messageInfo_HeaderRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_HeaderRequest proto.InternalMessageInfo - -type isHeaderRequest_Data interface { - isHeaderRequest_Data() - MarshalTo([]byte) (int, error) - Size() int -} - -type HeaderRequest_Origin struct { - Origin uint64 `protobuf:"varint,1,opt,name=origin,proto3,oneof" json:"origin,omitempty"` -} -type HeaderRequest_Hash struct { - Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3,oneof" json:"hash,omitempty"` -} - -func (*HeaderRequest_Origin) isHeaderRequest_Data() {} -func (*HeaderRequest_Hash) isHeaderRequest_Data() {} - -func (m *HeaderRequest) GetData() isHeaderRequest_Data { - if m != nil { - return m.Data - } - return nil -} - -func (m *HeaderRequest) GetOrigin() uint64 { - if x, ok := m.GetData().(*HeaderRequest_Origin); ok { - return x.Origin - } - return 0 -} - -func (m *HeaderRequest) GetHash() []byte { - if x, ok := m.GetData().(*HeaderRequest_Hash); ok { - return x.Hash - } - return nil -} - -func (m *HeaderRequest) GetAmount() uint64 { - if m != nil { - return m.Amount - } - return 0 -} - -// XXX_OneofWrappers is for the internal use of the proto package. -func (*HeaderRequest) XXX_OneofWrappers() []interface{} { - return []interface{}{ - (*HeaderRequest_Origin)(nil), - (*HeaderRequest_Hash)(nil), - } -} - -type HeaderResponse struct { - Body []byte `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` - StatusCode StatusCode `protobuf:"varint,2,opt,name=statusCode,proto3,enum=p2p.pb.StatusCode" json:"statusCode,omitempty"` -} - -func (m *HeaderResponse) Reset() { *m = HeaderResponse{} } -func (m *HeaderResponse) String() string { return proto.CompactTextString(m) } -func (*HeaderResponse) ProtoMessage() {} -func (*HeaderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_43554822dc0b0806, []int{1} -} -func (m *HeaderResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *HeaderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_HeaderResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *HeaderResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_HeaderResponse.Merge(m, src) -} -func (m *HeaderResponse) XXX_Size() int { - return m.Size() -} -func (m *HeaderResponse) XXX_DiscardUnknown() { - xxx_messageInfo_HeaderResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_HeaderResponse proto.InternalMessageInfo - -func (m *HeaderResponse) GetBody() []byte { - if m != nil { - return m.Body - } - return nil -} - -func (m *HeaderResponse) GetStatusCode() StatusCode { - if m != nil { - return m.StatusCode - } - return StatusCode_INVALID -} - -func init() { - proto.RegisterEnum("p2p.pb.StatusCode", StatusCode_name, StatusCode_value) - proto.RegisterType((*HeaderRequest)(nil), "p2p.pb.HeaderRequest") - proto.RegisterType((*HeaderResponse)(nil), "p2p.pb.HeaderResponse") -} - -func init() { - proto.RegisterFile("libs/header/p2p/pb/header_request.proto", fileDescriptor_43554822dc0b0806) -} - -var fileDescriptor_43554822dc0b0806 = []byte{ - // 271 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0xcd, 0x4a, 0xf3, 0x40, - 0x14, 0x86, 0x67, 0xfa, 0x85, 0xf9, 0xf0, 0xd8, 0x96, 0x32, 0x88, 0x64, 0x35, 0x94, 0x6e, 0x2c, - 0x2e, 0x12, 0x89, 0x57, 0x60, 0x2d, 0x92, 0xa2, 0x24, 0x30, 0xfe, 0xe0, 0x2e, 0x4c, 0xc8, 0x60, - 0x02, 0x9a, 0x19, 0x33, 0x93, 0x85, 0x77, 0xe1, 0x65, 0xb9, 0xec, 0xd2, 0xa5, 0x24, 0x37, 0x22, - 0x4e, 0xe3, 0xcf, 0xee, 0x3c, 0x3c, 0x2f, 0xe7, 0x3d, 0x1c, 0x38, 0x7a, 0xac, 0x72, 0x13, 0x96, - 0x52, 0x14, 0xb2, 0x09, 0x75, 0xa4, 0x43, 0x9d, 0x0f, 0x94, 0x35, 0xf2, 0xb9, 0x95, 0xc6, 0x06, - 0xba, 0x51, 0x56, 0x51, 0xa2, 0x23, 0x1d, 0xe8, 0x7c, 0x91, 0xc1, 0x24, 0x76, 0x9e, 0xef, 0x34, - 0xf5, 0x81, 0xa8, 0xa6, 0x7a, 0xa8, 0x6a, 0x1f, 0xcf, 0xf1, 0xd2, 0x8b, 0x11, 0x1f, 0x98, 0x1e, - 0x80, 0x57, 0x0a, 0x53, 0xfa, 0xa3, 0x39, 0x5e, 0x8e, 0x63, 0xc4, 0x1d, 0xd1, 0x43, 0x20, 0xe2, - 0x49, 0xb5, 0xb5, 0xf5, 0xff, 0x7d, 0xe5, 0xf9, 0x40, 0x2b, 0x02, 0x5e, 0x21, 0xac, 0x58, 0xdc, - 0xc3, 0xf4, 0xbb, 0xc0, 0x68, 0x55, 0x1b, 0x49, 0x29, 0x78, 0xb9, 0x2a, 0x5e, 0xdc, 0xfe, 0x31, - 0x77, 0x33, 0x8d, 0x00, 0x8c, 0x15, 0xb6, 0x35, 0xe7, 0xaa, 0x90, 0xae, 0x61, 0x1a, 0xd1, 0x60, - 0x77, 0x63, 0x70, 0xfd, 0x63, 0xf8, 0x9f, 0xd4, 0xf1, 0x09, 0xc0, 0xaf, 0xa1, 0xfb, 0xf0, 0x7f, - 0x93, 0xdc, 0x9d, 0x5d, 0x6d, 0xd6, 0x33, 0x44, 0x09, 0x8c, 0xd2, 0xcb, 0x19, 0xa6, 0x13, 0xd8, - 0x4b, 0xd2, 0x9b, 0xec, 0x22, 0xbd, 0x4d, 0xd6, 0xb3, 0xd1, 0xca, 0x7f, 0xeb, 0x18, 0xde, 0x76, - 0x0c, 0x7f, 0x74, 0x0c, 0xbf, 0xf6, 0x0c, 0x6d, 0x7b, 0x86, 0xde, 0x7b, 0x86, 0x72, 0xe2, 0xbe, - 0x72, 0xfa, 0x19, 0x00, 0x00, 0xff, 0xff, 0x04, 0xdd, 0x70, 0x54, 0x40, 0x01, 0x00, 0x00, -} - -func (m *HeaderRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HeaderRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HeaderRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Amount != 0 { - i = encodeVarintHeaderRequest(dAtA, i, uint64(m.Amount)) - i-- - dAtA[i] = 0x18 - } - if m.Data != nil { - { - size := m.Data.Size() - i -= size - if _, err := m.Data.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - } - } - return len(dAtA) - i, nil -} - -func (m *HeaderRequest_Origin) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HeaderRequest_Origin) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - i = encodeVarintHeaderRequest(dAtA, i, uint64(m.Origin)) - i-- - dAtA[i] = 0x8 - return len(dAtA) - i, nil -} -func (m *HeaderRequest_Hash) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HeaderRequest_Hash) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - if m.Hash != nil { - i -= len(m.Hash) - copy(dAtA[i:], m.Hash) - i = encodeVarintHeaderRequest(dAtA, i, uint64(len(m.Hash))) - i-- - dAtA[i] = 0x12 - } - return len(dAtA) - i, nil -} -func (m *HeaderResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HeaderResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *HeaderResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.StatusCode != 0 { - i = encodeVarintHeaderRequest(dAtA, i, uint64(m.StatusCode)) - i-- - dAtA[i] = 0x10 - } - if len(m.Body) > 0 { - i -= len(m.Body) - copy(dAtA[i:], m.Body) - i = encodeVarintHeaderRequest(dAtA, i, uint64(len(m.Body))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintHeaderRequest(dAtA []byte, offset int, v uint64) int { - offset -= sovHeaderRequest(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *HeaderRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Data != nil { - n += m.Data.Size() - } - if m.Amount != 0 { - n += 1 + sovHeaderRequest(uint64(m.Amount)) - } - return n -} - -func (m *HeaderRequest_Origin) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - n += 1 + sovHeaderRequest(uint64(m.Origin)) - return n -} -func (m *HeaderRequest_Hash) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Hash != nil { - l = len(m.Hash) - n += 1 + l + sovHeaderRequest(uint64(l)) - } - return n -} -func (m *HeaderResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Body) - if l > 0 { - n += 1 + l + sovHeaderRequest(uint64(l)) - } - if m.StatusCode != 0 { - n += 1 + sovHeaderRequest(uint64(m.StatusCode)) - } - return n -} - -func sovHeaderRequest(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozHeaderRequest(x uint64) (n int) { - return sovHeaderRequest(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *HeaderRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HeaderRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HeaderRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Origin", wireType) - } - var v uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Data = &HeaderRequest_Origin{v} - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHeaderRequest - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHeaderRequest - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := make([]byte, postIndex-iNdEx) - copy(v, dAtA[iNdEx:postIndex]) - m.Data = &HeaderRequest_Hash{v} - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - m.Amount = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Amount |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipHeaderRequest(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthHeaderRequest - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HeaderResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HeaderResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HeaderResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHeaderRequest - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthHeaderRequest - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Body = append(m.Body[:0], dAtA[iNdEx:postIndex]...) - if m.Body == nil { - m.Body = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field StatusCode", wireType) - } - m.StatusCode = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.StatusCode |= StatusCode(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipHeaderRequest(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthHeaderRequest - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipHeaderRequest(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHeaderRequest - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthHeaderRequest - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupHeaderRequest - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthHeaderRequest - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthHeaderRequest = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowHeaderRequest = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupHeaderRequest = fmt.Errorf("proto: unexpected end of group") -) diff --git a/libs/header/p2p/pb/header_request.proto b/libs/header/p2p/pb/header_request.proto deleted file mode 100644 index ee764c7ab1..0000000000 --- a/libs/header/p2p/pb/header_request.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package p2p.pb; - -message HeaderRequest { - oneof data { - uint64 origin = 1; - bytes hash = 2; - } - uint64 amount = 3; -} - -enum StatusCode { - INVALID = 0; - OK = 1; - NOT_FOUND = 2; -}; - -message HeaderResponse { - bytes body = 1; - StatusCode statusCode = 2; -} diff --git a/libs/header/p2p/peer_stats.go b/libs/header/p2p/peer_stats.go deleted file mode 100644 index ced0592996..0000000000 --- a/libs/header/p2p/peer_stats.go +++ /dev/null @@ -1,149 +0,0 @@ -package p2p - -import ( - "container/heap" - "context" - "sync" - "time" - - "github.com/libp2p/go-libp2p/core/peer" -) - -// peerStat represents a peer's average statistics. -type peerStat struct { - sync.RWMutex - peerID peer.ID - // score is the average speed per single request - peerScore float32 - // pruneDeadline specifies when disconnected peer will be removed if - // it does not return online. - pruneDeadline time.Time -} - -// updateStats recalculates peer.score by averaging the last score -// updateStats takes the total amount of bytes that were requested from the peer -// and the total request duration(in milliseconds). The final score is calculated -// by dividing the amount by time, so the result score will represent how many bytes -// were retrieved in 1 millisecond. This value will then be averaged relative to the -// previous peerScore. -func (p *peerStat) updateStats(amount uint64, time uint64) { - p.Lock() - defer p.Unlock() - averageSpeed := float32(amount) - if time != 0 { - averageSpeed /= float32(time) - } - if p.peerScore == 0.0 { - p.peerScore = averageSpeed - return - } - p.peerScore = (p.peerScore + averageSpeed) / 2 -} - -// decreaseScore decreases peerScore by 20% of the peer that failed the request by any reason. -// NOTE: decreasing peerScore in one session will not affect its position in queue in another -// session(as we can have multiple sessions running concurrently). -// TODO(vgonkivs): to figure out the better scoring increments/decrements -func (p *peerStat) decreaseScore() { - p.Lock() - defer p.Unlock() - - p.peerScore -= p.peerScore / 100 * 20 -} - -// score reads a peer's latest score from the queue -func (p *peerStat) score() float32 { - p.RLock() - defer p.RUnlock() - return p.peerScore -} - -// peerStats implements heap.Interface, so we can be sure that we are getting the peer -// with the highest score, each time we call Pop. -type peerStats []*peerStat - -func newPeerStats() peerStats { - ps := make(peerStats, 0) - heap.Init(&ps) - return ps -} - -func (ps peerStats) Len() int { return len(ps) } - -// Less compares two peerScores. -// Less is used by heap.Interface to build the queue in a decreasing order. -func (ps peerStats) Less(i, j int) bool { - return ps[i].score() > ps[j].score() -} - -func (ps peerStats) Swap(i, j int) { - ps[i], ps[j] = ps[j], ps[i] -} - -// Push adds peerStat to the queue. -func (ps *peerStats) Push(x any) { - item := x.(*peerStat) - *ps = append(*ps, item) -} - -// Pop returns the peer with the highest score from the queue. -func (ps *peerStats) Pop() any { - old := *ps - n := len(old) - item := old[n-1] - old[n-1] = nil - *ps = old[:n-1] - return item -} - -// peerQueue wraps peerStats and guards it with the mutex. -type peerQueue struct { - ctx context.Context - - statsLk sync.RWMutex - stats peerStats - - havePeer chan struct{} -} - -func newPeerQueue(ctx context.Context, stats []*peerStat) *peerQueue { - statsCh := make(chan struct{}, len(stats)) - pq := &peerQueue{ - ctx: ctx, - stats: newPeerStats(), - havePeer: statsCh, - } - for _, stat := range stats { - pq.push(stat) - } - return pq -} - -// waitPop pops the peer with the biggest score. -// in case if there are no peer available in current session, it blocks until -// the peer will be pushed in. -func (p *peerQueue) waitPop(ctx context.Context) *peerStat { - // TODO(vgonkivs): implement fallback solution for cases when peer queue is empty. - // As we discussed with @Wondertan there could be 2 possible solutions: - // * use libp2p.Discovery to find new peers outside peerTracker to request headers; - // * implement IWANT/IHAVE messaging system and start requesting ranges from the Peerstore; - select { - case <-ctx.Done(): - return &peerStat{} - case <-p.ctx.Done(): - return &peerStat{} - case <-p.havePeer: - } - p.statsLk.Lock() - defer p.statsLk.Unlock() - return heap.Pop(&p.stats).(*peerStat) -} - -// push adds the peer to the queue. -func (p *peerQueue) push(stat *peerStat) { - p.statsLk.Lock() - heap.Push(&p.stats, stat) - p.statsLk.Unlock() - // notify that the peer is available in the queue, so it can be popped out - p.havePeer <- struct{}{} -} diff --git a/libs/header/p2p/peer_stats_test.go b/libs/header/p2p/peer_stats_test.go deleted file mode 100644 index d5a875d12d..0000000000 --- a/libs/header/p2p/peer_stats_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package p2p - -import ( - "container/heap" - "context" - "testing" - - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/require" -) - -func Test_PeerStatsPush(t *testing.T) { - pQueue := newPeerStats() - pQueue.Push(&peerStat{peerID: "peerID"}) - require.True(t, pQueue.Len() == 1) -} - -func Test_PeerStatsPop(t *testing.T) { - pQueue := newPeerStats() - pQueue.Push(&peerStat{peerID: "peerID"}) - stats := heap.Pop(&pQueue).(*peerStat) - require.Equal(t, stats.peerID, peer.ID("peerID")) -} - -func Test_PeerQueuePopBestPeer(t *testing.T) { - peersStat := peerStats{ - {peerID: "peerID1", peerScore: 1}, - {peerID: "peerID2", peerScore: 2}, - {peerID: "peerID3", peerScore: 4}, - {peerID: "peerID4"}, // score = 0 - } - wantStat := peerStats{ - {peerID: "peerID3", peerScore: 4}, - {peerID: "peerID2", peerScore: 2}, - {peerID: "peerID1", peerScore: 1}, - {peerID: "peerID4"}, // score = 0 - } - - // we do not need timeout/cancel functionality here - pQueue := newPeerQueue(context.Background(), peersStat) - for index := 0; index < pQueue.stats.Len(); index++ { - stats := heap.Pop(&pQueue.stats).(*peerStat) - require.Equal(t, stats, wantStat[index]) - } -} - -func Test_PeerQueueRemovePeer(t *testing.T) { - peersStat := []*peerStat{ - {peerID: "peerID1", peerScore: 1}, - {peerID: "peerID2", peerScore: 2}, - {peerID: "peerID3", peerScore: 4}, - {peerID: "peerID4"}, // score = 0 - } - - // we do not need timeout/cancel functionality here - pQueue := newPeerQueue(context.Background(), peersStat) - - _ = heap.Pop(&pQueue.stats) - stat := heap.Pop(&pQueue.stats).(*peerStat) - require.Equal(t, stat.peerID, peer.ID("peerID2")) -} - -func Test_StatsUpdateStats(t *testing.T) { - // we do not need timeout/cancel functionality here - pQueue := newPeerQueue(context.Background(), []*peerStat{}) - stat := &peerStat{peerID: "peerID", peerScore: 0} - heap.Push(&pQueue.stats, stat) - testCases := []struct { - inputTime uint64 - inputBytes uint64 - resultScore float32 - }{ - // common case, where time and bytes is not equal to 0 - { - inputTime: 16, - inputBytes: 4, - resultScore: 4, - }, - // in case if bytes is equal to 0, - // then the request was failed and previous score will be - // decreased - { - inputTime: 10, - inputBytes: 0, - resultScore: 2, - }, - // testing case with time=0, to ensure that dividing by 0 is handled properly - { - inputTime: 0, - inputBytes: 0, - resultScore: 1, - }, - } - - for _, tt := range testCases { - stat.updateStats(tt.inputBytes, tt.inputTime) - updatedStat := heap.Pop(&pQueue.stats).(*peerStat) - require.Equal(t, updatedStat.score(), stat.score()) - heap.Push(&pQueue.stats, updatedStat) - } -} - -func Test_StatDecreaseScore(t *testing.T) { - pStats := &peerStat{ - peerID: peer.ID("test"), - peerScore: 100, - } - // will decrease score by 20% - pStats.decreaseScore() - require.Equal(t, pStats.score(), float32(80.0)) -} diff --git a/libs/header/p2p/peer_tracker.go b/libs/header/p2p/peer_tracker.go deleted file mode 100644 index 2b70c83acb..0000000000 --- a/libs/header/p2p/peer_tracker.go +++ /dev/null @@ -1,216 +0,0 @@ -package p2p - -import ( - "context" - "sync" - "time" - - "github.com/libp2p/go-libp2p/core/event" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/p2p/net/conngater" -) - -const ( - // defaultScore specifies the score for newly connected peers. - defaultScore float32 = 1 - // maxTrackerSize specifies the max amount of peers that can be added to the peerTracker. - maxPeerTrackerSize = 100 -) - -var ( - // maxAwaitingTime specifies the duration that gives to the disconnected peer to be back online, - // otherwise it will be removed on the next GC cycle. - maxAwaitingTime = time.Hour - // gcCycle defines the duration after which the peerTracker starts removing peers. - gcCycle = time.Minute * 30 -) - -type peerTracker struct { - host host.Host - connGater *conngater.BasicConnectionGater - - peerLk sync.RWMutex - // trackedPeers contains active peers that we can request to. - // we cache the peer once they disconnect, - // so we can guarantee that peerQueue will only contain active peers - trackedPeers map[peer.ID]*peerStat - // disconnectedPeers contains disconnected peers. In case if peer does not return - // online until pruneDeadline, it will be removed and its score will be lost. - disconnectedPeers map[peer.ID]*peerStat - - ctx context.Context - cancel context.CancelFunc - // done is used to gracefully stop the peerTracker. - // It allows to wait until track() and gc() will be stopped. - done chan struct{} -} - -func newPeerTracker( - h host.Host, - connGater *conngater.BasicConnectionGater, -) *peerTracker { - ctx, cancel := context.WithCancel(context.Background()) - return &peerTracker{ - host: h, - connGater: connGater, - disconnectedPeers: make(map[peer.ID]*peerStat), - trackedPeers: make(map[peer.ID]*peerStat), - ctx: ctx, - cancel: cancel, - done: make(chan struct{}, 2), - } -} - -func (p *peerTracker) track() { - defer func() { - p.done <- struct{}{} - }() - - // store peers that have been already connected - for _, c := range p.host.Network().Conns() { - p.connected(c.RemotePeer()) - } - - subs, err := p.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) - if err != nil { - log.Errorw("subscribing to EvtPeerConnectednessChanged", "err", err) - return - } - - for { - select { - case <-p.ctx.Done(): - err = subs.Close() - if err != nil { - log.Errorw("closing subscription", "err", err) - } - return - case subscription := <-subs.Out(): - ev := subscription.(event.EvtPeerConnectednessChanged) - switch ev.Connectedness { - case network.Connected: - p.connected(ev.Peer) - case network.NotConnected: - p.disconnected(ev.Peer) - } - } - } -} - -func (p *peerTracker) connected(pID peer.ID) { - if p.host.ID() == pID { - return - } - - for _, c := range p.host.Network().ConnsToPeer(pID) { - // check if connection is short-termed and skip this peer - if c.Stat().Transient { - return - } - } - - p.peerLk.Lock() - defer p.peerLk.Unlock() - // skip adding the peer to avoid overfilling of the peerTracker with unused peers if: - // peerTracker reaches the maxTrackerSize and there are more connected peers - // than disconnected peers. - if len(p.trackedPeers)+len(p.disconnectedPeers) > maxPeerTrackerSize && - len(p.trackedPeers) > len(p.disconnectedPeers) { - return - } - - // additional check in p.trackedPeers should be done, - // because libp2p does not emit multiple Connected events per 1 peer - stats, ok := p.disconnectedPeers[pID] - if !ok { - stats = &peerStat{peerID: pID, peerScore: defaultScore} - } else { - delete(p.disconnectedPeers, pID) - } - p.trackedPeers[pID] = stats -} - -func (p *peerTracker) disconnected(pID peer.ID) { - p.peerLk.Lock() - defer p.peerLk.Unlock() - stats, ok := p.trackedPeers[pID] - if !ok { - return - } - stats.pruneDeadline = time.Now().Add(maxAwaitingTime) - p.disconnectedPeers[pID] = stats - delete(p.trackedPeers, pID) -} - -func (p *peerTracker) peers() []*peerStat { - p.peerLk.RLock() - defer p.peerLk.RUnlock() - peers := make([]*peerStat, 0, len(p.trackedPeers)) - for _, stat := range p.trackedPeers { - peers = append(peers, stat) - } - return peers -} - -// gc goes through connected and disconnected peers once every gcPeriod -// and removes: -// * disconnected peers which have been disconnected for more than maxAwaitingTime; -// * connected peers whose scores are less than or equal than defaultScore; -func (p *peerTracker) gc() { - ticker := time.NewTicker(gcCycle) - for { - select { - case <-p.ctx.Done(): - p.done <- struct{}{} - return - case <-ticker.C: - p.peerLk.Lock() - now := time.Now() - for id, peer := range p.disconnectedPeers { - if peer.pruneDeadline.Before(now) { - delete(p.disconnectedPeers, id) - } - } - - for id, peer := range p.trackedPeers { - if peer.peerScore <= defaultScore { - delete(p.trackedPeers, id) - } - } - p.peerLk.Unlock() - } - } -} - -// stop waits until all background routines will be finished. -func (p *peerTracker) stop() { - p.cancel() - - for i := 0; i < cap(p.done); i++ { - <-p.done - } -} - -// blockPeer blocks a peer on the networking level and removes it from the local cache. -func (p *peerTracker) blockPeer(pID peer.ID, reason error) { - // add peer to the blacklist, so we can't connect to it in the future. - err := p.connGater.BlockPeer(pID) - if err != nil { - log.Errorw("header/p2p: blocking peer failed", "pID", pID, "err", err) - } - // close connections to peer. - err = p.host.Network().ClosePeer(pID) - if err != nil { - log.Errorw("header/p2p: closing connection with peer failed", "pID", pID, "err", err) - } - - log.Warnw("header/p2p: blocked peer", "pID", pID, "reason", reason) - - p.peerLk.Lock() - defer p.peerLk.Unlock() - // remove peer from cache. - delete(p.trackedPeers, pID) - delete(p.disconnectedPeers, pID) -} diff --git a/libs/header/p2p/peer_tracker_test.go b/libs/header/p2p/peer_tracker_test.go deleted file mode 100644 index 2645a2419e..0000000000 --- a/libs/header/p2p/peer_tracker_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package p2p - -import ( - "errors" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/p2p/net/conngater" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestPeerTracker_GC(t *testing.T) { - h := createMocknet(t, 1) - gcCycle = time.Millisecond * 200 - connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) - require.NoError(t, err) - p := newPeerTracker(h[0], connGater) - maxAwaitingTime = time.Millisecond - pid1 := peer.ID("peer1") - pid2 := peer.ID("peer2") - pid3 := peer.ID("peer3") - pid4 := peer.ID("peer4") - p.trackedPeers[pid1] = &peerStat{peerID: pid1, peerScore: 0.5} - p.trackedPeers[pid2] = &peerStat{peerID: pid2, peerScore: 10} - p.disconnectedPeers[pid3] = &peerStat{peerID: pid3, pruneDeadline: time.Now()} - p.disconnectedPeers[pid4] = &peerStat{peerID: pid4, pruneDeadline: time.Now().Add(time.Minute * 10)} - assert.True(t, len(p.trackedPeers) > 0) - assert.True(t, len(p.disconnectedPeers) > 0) - - go p.track() - go p.gc() - time.Sleep(time.Second * 1) - p.stop() - require.Nil(t, p.trackedPeers[pid1]) - require.Nil(t, p.disconnectedPeers[pid3]) -} - -func TestPeerTracker_BlockPeer(t *testing.T) { - h := createMocknet(t, 2) - connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) - require.NoError(t, err) - p := newPeerTracker(h[0], connGater) - maxAwaitingTime = time.Millisecond - p.blockPeer(h[1].ID(), errors.New("test")) - require.Len(t, connGater.ListBlockedPeers(), 1) - require.True(t, connGater.ListBlockedPeers()[0] == h[1].ID()) -} diff --git a/libs/header/p2p/server.go b/libs/header/p2p/server.go deleted file mode 100644 index 65c772c75f..0000000000 --- a/libs/header/p2p/server.go +++ /dev/null @@ -1,271 +0,0 @@ -package p2p - -import ( - "context" - "errors" - "time" - - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/protocol" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - - "github.com/celestiaorg/celestia-node/libs/header" - p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" -) - -var ( - tracer = otel.Tracer("header/server") -) - -// ExchangeServer represents the server-side component for -// responding to inbound header-related requests. -type ExchangeServer[H header.Header] struct { - protocolID protocol.ID - - host host.Host - store header.Store[H] - - ctx context.Context - cancel context.CancelFunc - - Params ServerParameters -} - -// NewExchangeServer returns a new P2P server that handles inbound -// header-related requests. -func NewExchangeServer[H header.Header]( - host host.Host, - store header.Store[H], - opts ...Option[ServerParameters], -) (*ExchangeServer[H], error) { - params := DefaultServerParameters() - for _, opt := range opts { - opt(¶ms) - } - if err := params.Validate(); err != nil { - return nil, err - } - - return &ExchangeServer[H]{ - protocolID: protocolID(params.networkID), - host: host, - store: store, - Params: params, - }, nil -} - -// Start sets the stream handler for inbound header-related requests. -func (serv *ExchangeServer[H]) Start(context.Context) error { - serv.ctx, serv.cancel = context.WithCancel(context.Background()) - log.Infow("server: listening for inbound header requests", "protocol ID", serv.protocolID) - - serv.host.SetStreamHandler(serv.protocolID, serv.requestHandler) - - return nil -} - -// Stop removes the stream handler for serving header-related requests. -func (serv *ExchangeServer[H]) Stop(context.Context) error { - log.Info("server: stopping server") - serv.cancel() - serv.host.RemoveStreamHandler(serv.protocolID) - return nil -} - -// requestHandler handles inbound HeaderRequests. -func (serv *ExchangeServer[H]) requestHandler(stream network.Stream) { - err := stream.SetReadDeadline(time.Now().Add(serv.Params.ReadDeadline)) - if err != nil { - log.Debugf("error setting deadline: %s", err) - } - // unmarshal request - pbreq := new(p2p_pb.HeaderRequest) - _, err = serde.Read(stream, pbreq) - if err != nil { - log.Errorw("server: reading header request from stream", "err", err) - stream.Reset() //nolint:errcheck - return - } - if err = stream.CloseRead(); err != nil { - log.Error(err) - } - - var headers []H - // retrieve and write Headers - switch pbreq.Data.(type) { - case *p2p_pb.HeaderRequest_Hash: - headers, err = serv.handleRequestByHash(pbreq.GetHash()) - case *p2p_pb.HeaderRequest_Origin: - headers, err = serv.handleRequest(pbreq.GetOrigin(), pbreq.GetOrigin()+pbreq.Amount) - default: - log.Error("server: invalid data type received") - stream.Reset() //nolint:errcheck - return - } - var code p2p_pb.StatusCode - switch err { - case nil: - code = p2p_pb.StatusCode_OK - case header.ErrNotFound: - code = p2p_pb.StatusCode_NOT_FOUND - default: - stream.Reset() //nolint:errcheck - return - } - - // reallocate headers with 1 nil Header if code is not StatusCode_OK - if code != p2p_pb.StatusCode_OK { - headers = make([]H, 1) - } - // write all headers to stream - for _, h := range headers { - if err := stream.SetWriteDeadline(time.Now().Add(serv.Params.ReadDeadline)); err != nil { - log.Debugf("error setting deadline: %s", err) - } - var bin []byte - // if header is not nil, then marshal it to []byte. - // if header is nil, then error was received,so we will set empty []byte to proto. - if !h.IsZero() { - bin, err = h.MarshalBinary() - if err != nil { - log.Errorw("server: marshaling header to proto", "height", h.Height, "err", err) - stream.Reset() //nolint:errcheck - return - } - } - _, err = serde.Write(stream, &p2p_pb.HeaderResponse{Body: bin, StatusCode: code}) - if err != nil { - log.Errorw("server: writing header to stream", "err", err) - stream.Reset() //nolint:errcheck - return - } - } - - err = stream.Close() - if err != nil { - log.Errorw("while closing inbound stream", "err", err) - } -} - -// handleRequestByHash returns the Header at the given hash -// if it exists. -func (serv *ExchangeServer[H]) handleRequestByHash(hash []byte) ([]H, error) { - log.Debugw("server: handling header request", "hash", header.Hash(hash).String()) - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RangeRequestTimeout) - defer cancel() - ctx, span := tracer.Start(ctx, "request-by-hash", trace.WithAttributes( - attribute.String("hash", header.Hash(hash).String()), - )) - defer span.End() - - h, err := serv.store.Get(ctx, hash) - if err != nil { - log.Errorw("server: getting header by hash", "hash", header.Hash(hash).String(), "err", err) - span.SetStatus(codes.Error, err.Error()) - return nil, err - } - - span.AddEvent("fetched-header-from-store", trace.WithAttributes( - attribute.String("hash", header.Hash(hash).String()), - attribute.Int64("height", h.Height())), - ) - span.SetStatus(codes.Ok, "") - return []H{h}, nil -} - -// handleRequest fetches the Header at the given origin and -// writes it to the stream. -func (serv *ExchangeServer[H]) handleRequest(from, to uint64) ([]H, error) { - if from == uint64(0) { - return serv.handleHeadRequest() - } - - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RangeRequestTimeout) - defer cancel() - - ctx, span := tracer.Start(ctx, "request-range", trace.WithAttributes( - attribute.Int64("from", int64(from)), - attribute.Int64("to", int64(to)))) - defer span.End() - - if to-from > header.MaxRangeRequestSize { - log.Errorw("server: skip request for too many headers.", "amount", to-from) - span.SetStatus(codes.Error, header.ErrHeadersLimitExceeded.Error()) - return nil, header.ErrHeadersLimitExceeded - } - - log.Debugw("server: handling headers request", "from", from, "to", to) - // check that store has the requested height - if !serv.store.HasAt(ctx, to-1) { - head, err := serv.store.Head(ctx) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - log.Debugw("server: could not get current head", "err", err) - return nil, err - } - - // might be a case when store hasn't synced yet to the requested range - if uint64(head.Height()) < from { - span.SetStatus(codes.Error, header.ErrNotFound.Error()) - log.Debugw("server: requested headers not stored", - "from", from, - "to", to, - "currentHead", - head.Height(), - ) - return nil, header.ErrNotFound - } - - log.Debugw("server: serving partial range", - "prevMaxHeight", to, - "newMaxHeight", uint64(head.Height())+1, - ) - // change `to` height to return a partial range - to = uint64(head.Height()) + 1 - } - - headersByRange, err := serv.store.GetRangeByHeight(ctx, from, to) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - if errors.Is(err, context.DeadlineExceeded) { - log.Warnw("server: requested headers not found", "from", from, "to", to) - return nil, header.ErrNotFound - } - log.Errorw("server: getting headers", "from", from, "to", to, "err", err) - return nil, err - } - - span.AddEvent("fetched-range-of-headers", trace.WithAttributes( - attribute.Int("amount", len(headersByRange)))) - span.SetStatus(codes.Ok, "") - return headersByRange, nil -} - -// handleHeadRequest returns the latest stored head. -func (serv *ExchangeServer[H]) handleHeadRequest() ([]H, error) { - log.Debug("server: handling head request") - ctx, cancel := context.WithTimeout(serv.ctx, serv.Params.RangeRequestTimeout) - defer cancel() - ctx, span := tracer.Start(ctx, "request-head") - defer span.End() - - head, err := serv.store.Head(ctx) - if err != nil { - log.Errorw("server: getting head", "err", err) - span.SetStatus(codes.Error, err.Error()) - return nil, err - } - - span.AddEvent("fetched-head", trace.WithAttributes( - attribute.String("hash", head.Hash().String()), - attribute.Int64("height", head.Height())), - ) - span.SetStatus(codes.Ok, "") - return []H{head}, nil -} diff --git a/libs/header/p2p/server_test.go b/libs/header/p2p/server_test.go deleted file mode 100644 index cb123b36d5..0000000000 --- a/libs/header/p2p/server_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package p2p - -import ( - "context" - "testing" - - "github.com/ipfs/go-datastore" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header/store" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestExchangeServer_handleRequestTimeout(t *testing.T) { - peer := createMocknet(t, 1) - s, err := store.NewStore[*test.DummyHeader](datastore.NewMapDatastore()) - require.NoError(t, err) - server, err := NewExchangeServer[*test.DummyHeader]( - peer[0], - s, - WithNetworkID[ServerParameters](networkID), - ) - require.NoError(t, err) - err = server.Start(context.Background()) - require.NoError(t, err) - t.Cleanup(func() { - server.Stop(context.Background()) //nolint:errcheck - }) - - _, err = server.handleRequest(1, 200) - require.Error(t, err) -} diff --git a/libs/header/p2p/session.go b/libs/header/p2p/session.go deleted file mode 100644 index d344508e58..0000000000 --- a/libs/header/p2p/session.go +++ /dev/null @@ -1,306 +0,0 @@ -package p2p - -import ( - "context" - "errors" - "fmt" - "sort" - "time" - - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/protocol" - - "github.com/celestiaorg/celestia-node/libs/header" - p2p_pb "github.com/celestiaorg/celestia-node/libs/header/p2p/pb" -) - -// errEmptyResponse means that server side closes the connection without sending at least 1 -// response. -var errEmptyResponse = errors.New("empty response") - -type option[H header.Header] func(*session[H]) - -func withValidation[H header.Header](from H) option[H] { - return func(s *session[H]) { - s.from = from - } -} - -// session aims to divide a range of headers -// into several smaller requests among different peers. -type session[H header.Header] struct { - host host.Host - protocolID protocol.ID - queue *peerQueue - // peerTracker contains discovered peers with records that describes their activity. - peerTracker *peerTracker - - // `from` is set when additional validation for range is needed. - // Otherwise, it will be nil. - from H - requestTimeout time.Duration - - ctx context.Context - cancel context.CancelFunc - reqCh chan *p2p_pb.HeaderRequest -} - -func newSession[H header.Header]( - ctx context.Context, - h host.Host, - peerTracker *peerTracker, - protocolID protocol.ID, - requestTimeout time.Duration, - options ...option[H], -) *session[H] { - ctx, cancel := context.WithCancel(ctx) - ses := &session[H]{ - ctx: ctx, - cancel: cancel, - protocolID: protocolID, - host: h, - queue: newPeerQueue(ctx, peerTracker.peers()), - peerTracker: peerTracker, - requestTimeout: requestTimeout, - } - - for _, opt := range options { - opt(ses) - } - return ses -} - -// getRangeByHeight requests headers from different peers. -func (s *session[H]) getRangeByHeight( - ctx context.Context, - from, amount, headersPerPeer uint64, -) ([]H, error) { - log.Debugw("requesting headers", "from", from, "to", from+amount-1) // -1 need to exclude to+1 height - - requests := prepareRequests(from, amount, headersPerPeer) - result := make(chan []H, len(requests)) - s.reqCh = make(chan *p2p_pb.HeaderRequest, len(requests)) - - go s.handleOutgoingRequests(ctx, result) - for _, req := range requests { - s.reqCh <- req - } - - headers := make([]H, 0, amount) -LOOP: - for { - select { - case <-s.ctx.Done(): - return nil, errors.New("header/p2p: exchange is closed") - case <-ctx.Done(): - return nil, ctx.Err() - case res := <-result: - headers = append(headers, res...) - if uint64(len(headers)) == amount { - break LOOP - } - } - } - - sort.Slice(headers, func(i, j int) bool { - return headers[i].Height() < headers[j].Height() - }) - - log.Debugw("received headers range", - "from", headers[0].Height(), - "to", headers[len(headers)-1].Height(), - ) - return headers, nil -} - -// close stops the session. -func (s *session[H]) close() { - if s.cancel != nil { - s.cancel() - s.cancel = nil - } -} - -// handleOutgoingRequests pops a peer from the queue and sends a prepared request to the peer. -// Will exit via canceled session context or when all request are processed. -func (s *session[H]) handleOutgoingRequests(ctx context.Context, result chan []H) { - for { - select { - case <-ctx.Done(): - return - case <-s.ctx.Done(): - return - case req := <-s.reqCh: - // select peer with the highest score among the available ones for the request - stats := s.queue.waitPop(ctx) - if stats.peerID == "" { - return - } - go s.doRequest(ctx, stats, req, result) - } - } -} - -// doRequest chooses the best peer to fetch headers and sends a request in range of available -// maxRetryAttempts. -func (s *session[H]) doRequest( - ctx context.Context, - stat *peerStat, - req *p2p_pb.HeaderRequest, - headers chan []H, -) { - ctx, cancel := context.WithTimeout(ctx, s.requestTimeout) - defer cancel() - - r, size, duration, err := sendMessage(ctx, s.host, stat.peerID, s.protocolID, req) - if err != nil { - // we should not punish peer at this point and should try to parse responses, despite that error - // was received. - log.Debugw("requesting headers from peer failed", "peer", stat.peerID, "err", err) - } - - h, err := s.processResponse(r) - if err != nil { - logFn := log.Errorw - - switch err { - case header.ErrNotFound: - logFn = log.Debugw - fallthrough - case errEmptyResponse: - stat.decreaseScore() - default: - s.peerTracker.blockPeer(stat.peerID, err) - } - - select { - case <-s.ctx.Done(): - return - case s.reqCh <- req: - } - logFn("processing response", - "from", req.GetOrigin(), - "to", req.Amount+req.GetOrigin()-1, - "err", err, - "peer", stat.peerID, - ) - return - } - - log.Debugw("request headers from peer succeeded", - "peer", stat.peerID, - "receivedAmount", len(h), - "requestedAmount", req.Amount, - ) - - // update peer stats - stat.updateStats(size, duration) - - responseLn := uint64(len(h)) - // ensure that we received the correct amount of headers. - if responseLn < req.Amount { - from := uint64(h[responseLn-1].Height()) - amount := req.Amount - responseLn - - select { - case <-s.ctx.Done(): - return - // create a new request with the remaining headers. - // prepareRequests will return a slice with 1 element at this point - case s.reqCh <- prepareRequests(from+1, amount, req.Amount)[0]: - log.Debugw("sending additional request to get remaining headers") - } - } - - // send headers to the channel, return peer to the queue, so it can be - // re-used in case if there are other requests awaiting - headers <- h - s.queue.push(stat) -} - -// processResponse converts HeaderResponse to Header. -func (s *session[H]) processResponse(responses []*p2p_pb.HeaderResponse) ([]H, error) { - if len(responses) == 0 { - return nil, errEmptyResponse - } - - headers := make([]H, 0) - for _, resp := range responses { - err := convertStatusCodeToError(resp.StatusCode) - if err != nil { - return nil, err - } - - var empty H - header := empty.New() - err = header.UnmarshalBinary(resp.Body) - if err != nil { - return nil, err - } - headers = append(headers, header.(H)) - } - - if len(headers) == 0 { - return nil, header.ErrNotFound - } - - err := s.validate(headers) - return headers, err -} - -// validate checks that the received range of headers is adjacent and is valid against the provided -// header. -func (s *session[H]) validate(headers []H) error { - // if `s.from` is empty, then additional validation for the header`s range is not needed. - if s.from.IsZero() { - return nil - } - - trusted := s.from - // verify that the whole range is valid and adjacent. - for _, untrusted := range headers { - err := trusted.Verify(untrusted) - if err != nil { - return err - } - - // extra check for the adjacency should be performed only for the received range, - // because headers are received out of order and `s.from` can't be adjacent to them - if trusted.Height() != s.from.Height() { - if trusted.Height()+1 != untrusted.Height() { - // Exchange requires requested ranges to always consist of adjacent headers - return fmt.Errorf("peer sent valid but non-adjacent header. expected:%d, received:%d", - trusted.Height()+1, - untrusted.Height(), - ) - } - } - // as `untrusted` was verified against previous trusted header, we can assume that it is valid - trusted = untrusted - } - return nil -} - -// prepareRequests converts incoming range into separate HeaderRequest. -func prepareRequests(from, amount, headersPerPeer uint64) []*p2p_pb.HeaderRequest { - requests := make([]*p2p_pb.HeaderRequest, 0, amount/headersPerPeer) - for amount > uint64(0) { - var requestSize uint64 - request := &p2p_pb.HeaderRequest{ - Data: &p2p_pb.HeaderRequest_Origin{Origin: from}, - } - - if amount < headersPerPeer { - requestSize = amount - amount = 0 - } else { - amount -= headersPerPeer - from += headersPerPeer - requestSize = headersPerPeer - } - - request.Amount = requestSize - requests = append(requests, request) - } - return requests -} diff --git a/libs/header/p2p/session_test.go b/libs/header/p2p/session_test.go deleted file mode 100644 index 9cd2df3185..0000000000 --- a/libs/header/p2p/session_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package p2p - -import ( - "context" - "testing" - "time" - - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func Test_PrepareRequests(t *testing.T) { - // from : 1, amount: 10, headersPerPeer: 5 - // result -> {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}} - requests := prepareRequests(1, 10, 5) - require.Len(t, requests, 2) - require.Equal(t, requests[0].GetOrigin(), uint64(1)) - require.Equal(t, requests[1].GetOrigin(), uint64(6)) -} - -// Test_Validate ensures that headers range is adjacent and valid. -func Test_Validate(t *testing.T) { - suite := test.NewTestSuite(t) - head := suite.Head() - ses := newSession( - context.Background(), - nil, - &peerTracker{trackedPeers: make(map[peer.ID]*peerStat)}, - "", time.Second, - withValidation(head), - ) - - headers := suite.GenDummyHeaders(5) - err := ses.validate(headers) - assert.NoError(t, err) -} - -// Test_ValidateFails ensures that non-adjacent range will return an error. -func Test_ValidateFails(t *testing.T) { - suite := test.NewTestSuite(t) - head := suite.Head() - ses := newSession( - context.Background(), - nil, - &peerTracker{trackedPeers: make(map[peer.ID]*peerStat)}, - "", time.Second, - withValidation(head), - ) - - headers := suite.GenDummyHeaders(5) - // break adjacency - headers[2] = headers[4] - err := ses.validate(headers) - assert.Error(t, err) -} diff --git a/libs/header/p2p/subscriber.go b/libs/header/p2p/subscriber.go deleted file mode 100644 index 1917df94b4..0000000000 --- a/libs/header/p2p/subscriber.go +++ /dev/null @@ -1,90 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// Subscriber manages the lifecycle and relationship of header Module -// with the "header-sub" gossipsub topic. -type Subscriber[H header.Header] struct { - pubsubTopicID string - - pubsub *pubsub.PubSub - topic *pubsub.Topic - msgID pubsub.MsgIdFunction -} - -// NewSubscriber returns a Subscriber that manages the header Module's -// relationship with the "header-sub" gossipsub topic. -func NewSubscriber[H header.Header]( - ps *pubsub.PubSub, - msgID pubsub.MsgIdFunction, - networkID string, -) *Subscriber[H] { - return &Subscriber[H]{ - pubsubTopicID: PubsubTopicID(networkID), - pubsub: ps, - msgID: msgID, - } -} - -// Start starts the Subscriber, registering a topic validator for the "header-sub" -// topic and joining it. -func (p *Subscriber[H]) Start(context.Context) (err error) { - log.Infow("joining topic", "topic ID", p.pubsubTopicID) - p.topic, err = p.pubsub.Join(p.pubsubTopicID, pubsub.WithTopicMessageIdFn(p.msgID)) - return err -} - -// Stop closes the topic and unregisters its validator. -func (p *Subscriber[H]) Stop(context.Context) error { - err := p.pubsub.UnregisterTopicValidator(p.pubsubTopicID) - if err != nil { - log.Warnf("unregistering validator: %s", err) - } - - return p.topic.Close() -} - -// AddValidator applies basic pubsub validator for the topic. -func (p *Subscriber[H]) AddValidator(val func(context.Context, H) pubsub.ValidationResult) error { - pval := func(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - var empty H - maybeHead := empty.New() - err := maybeHead.UnmarshalBinary(msg.Data) - if err != nil { - log.Errorw("unmarshalling header", - "from", p.ShortString(), - "err", err) - return pubsub.ValidationReject - } - msg.ValidatorData = maybeHead - return val(ctx, maybeHead.(H)) - } - return p.pubsub.RegisterTopicValidator(p.pubsubTopicID, pval) -} - -// Subscribe returns a new subscription to the Subscriber's -// topic. -func (p *Subscriber[H]) Subscribe() (header.Subscription[H], error) { - if p.topic == nil { - return nil, fmt.Errorf("header topic is not instantiated, service must be started before subscribing") - } - - return newSubscription[H](p.topic) -} - -// Broadcast broadcasts the given Header to the topic. -func (p *Subscriber[H]) Broadcast(ctx context.Context, header H, opts ...pubsub.PubOpt) error { - bin, err := header.MarshalBinary() - if err != nil { - return err - } - return p.topic.Publish(ctx, bin, opts...) -} diff --git a/libs/header/p2p/subscription.go b/libs/header/p2p/subscription.go deleted file mode 100644 index 0986434fef..0000000000 --- a/libs/header/p2p/subscription.go +++ /dev/null @@ -1,54 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - "reflect" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// subscription handles retrieving Headers from the header pubsub topic. -type subscription[H header.Header] struct { - topic *pubsub.Topic - subscription *pubsub.Subscription -} - -// newSubscription creates a new Header event subscription -// on the given host. -func newSubscription[H header.Header](topic *pubsub.Topic) (*subscription[H], error) { - sub, err := topic.Subscribe() - if err != nil { - return nil, err - } - - return &subscription[H]{ - topic: topic, - subscription: sub, - }, nil -} - -// NextHeader returns the next (latest) verified Header from the network. -func (s *subscription[H]) NextHeader(ctx context.Context) (H, error) { - msg, err := s.subscription.Next(ctx) - if err != nil { - var zero H - return zero, err - } - log.Debugw("received message", "topic", msg.Message.GetTopic(), "sender", msg.ReceivedFrom) - - header, ok := msg.ValidatorData.(H) - if !ok { - panic(fmt.Sprintf("invalid type received %s", reflect.TypeOf(msg.ValidatorData))) - } - - log.Debugw("received new Header", "height", header.Height(), "hash", header.Hash()) - return header, nil -} - -// Cancel cancels the subscription to new Headers from the network. -func (s *subscription[H]) Cancel() { - s.subscription.Cancel() -} diff --git a/libs/header/p2p/subscription_test.go b/libs/header/p2p/subscription_test.go deleted file mode 100644 index c95fe7bf5a..0000000000 --- a/libs/header/p2p/subscription_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package p2p - -import ( - "context" - "testing" - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/event" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -// TestSubscriber tests the header Module's implementation of Subscriber. -func TestSubscriber(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer cancel() - - // create mock network - net, err := mocknet.FullMeshLinked(2) - require.NoError(t, err) - - suite := test.NewTestSuite(t) - - // get mock host and create new gossipsub on it - pubsub1, err := pubsub.NewGossipSub(ctx, net.Hosts()[0], pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - - // create sub-service lifecycles for header service 1 - p2pSub1 := NewSubscriber[*test.DummyHeader](pubsub1, pubsub.DefaultMsgIdFn, networkID) - err = p2pSub1.Start(context.Background()) - require.NoError(t, err) - - // get mock host and create new gossipsub on it - pubsub2, err := pubsub.NewGossipSub(ctx, net.Hosts()[1], - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - - // create sub-service lifecycles for header service 2 - p2pSub2 := NewSubscriber[*test.DummyHeader](pubsub2, pubsub.DefaultMsgIdFn, networkID) - err = p2pSub2.Start(context.Background()) - require.NoError(t, err) - - sub0, err := net.Hosts()[0].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - sub1, err := net.Hosts()[1].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - - err = net.ConnectAllButSelf() - require.NoError(t, err) - - // wait on both peer identification events - for i := 0; i < 2; i++ { - select { - case <-sub0.Out(): - case <-sub1.Out(): - case <-ctx.Done(): - assert.FailNow(t, "timeout waiting for peers to connect") - } - } - - // subscribe - _, err = p2pSub2.Subscribe() - require.NoError(t, err) - - p2pSub1.AddValidator(func(context.Context, *test.DummyHeader) pubsub.ValidationResult { //nolint:errcheck - return pubsub.ValidationAccept - }) - subscription, err := p2pSub1.Subscribe() - require.NoError(t, err) - - expectedHeader := suite.GenDummyHeaders(1)[0] - bin, err := expectedHeader.MarshalBinary() - require.NoError(t, err) - - err = p2pSub2.topic.Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) - require.NoError(t, err) - - // get next Header from network - header, err := subscription.NextHeader(ctx) - require.NoError(t, err) - - assert.Equal(t, expectedHeader.Height(), header.Height()) - assert.Equal(t, expectedHeader.Hash(), header.Hash()) -} diff --git a/libs/header/store/batch.go b/libs/header/store/batch.go deleted file mode 100644 index 89518be679..0000000000 --- a/libs/header/store/batch.go +++ /dev/null @@ -1,108 +0,0 @@ -package store - -import ( - "sync" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// batch keeps an adjacent range of headers and loosely mimics the Store -// interface. NOTE: Can fully implement Store for a use case. -// -// It keeps a mapping 'height -> header' and 'hash -> height' -// unlike the Store which keeps 'hash -> header' and 'height -> hash'. -// The approach simplifies implementation for the batch and -// makes it better optimized for the GetByHeight case which is what we need. -type batch[H header.Header] struct { - lk sync.RWMutex - heights map[string]uint64 - headers []H -} - -// newBatch creates the batch with the given pre-allocated size. -func newBatch[H header.Header](size int) *batch[H] { - return &batch[H]{ - heights: make(map[string]uint64, size), - headers: make([]H, 0, size), - } -} - -// Len gives current length of the batch. -func (b *batch[H]) Len() int { - b.lk.RLock() - defer b.lk.RUnlock() - return len(b.headers) -} - -// GetAll returns a slice of all the headers in the batch. -func (b *batch[H]) GetAll() []H { - b.lk.RLock() - defer b.lk.RUnlock() - return b.headers -} - -// Get returns a header by its hash. -func (b *batch[H]) Get(hash header.Hash) H { - b.lk.RLock() - defer b.lk.RUnlock() - height, ok := b.heights[hash.String()] - if !ok { - var zero H - return zero - } - - return b.getByHeight(height) -} - -// GetByHeight returns a header by its height. -func (b *batch[H]) GetByHeight(height uint64) H { - b.lk.RLock() - defer b.lk.RUnlock() - return b.getByHeight(height) -} - -func (b *batch[H]) getByHeight(height uint64) H { - var ( - ln = uint64(len(b.headers)) - zero H - ) - if ln == 0 { - return zero - } - - head := uint64(b.headers[ln-1].Height()) - base := head - ln - if height > head || height <= base { - return zero - } - - return b.headers[height-base-1] -} - -// Append appends new headers to the batch. -func (b *batch[H]) Append(headers ...H) { - b.lk.Lock() - defer b.lk.Unlock() - for _, h := range headers { - b.headers = append(b.headers, h) - b.heights[h.Hash().String()] = uint64(h.Height()) - } -} - -// Has checks whether header by the hash is present in the batch. -func (b *batch[H]) Has(hash header.Hash) bool { - b.lk.RLock() - defer b.lk.RUnlock() - _, ok := b.heights[hash.String()] - return ok -} - -// Reset cleans references to batched headers. -func (b *batch[H]) Reset() { - b.lk.Lock() - defer b.lk.Unlock() - b.headers = b.headers[:0] - for k := range b.heights { - delete(b.heights, k) - } -} diff --git a/libs/header/store/height_indexer.go b/libs/header/store/height_indexer.go deleted file mode 100644 index fd0b1cb436..0000000000 --- a/libs/header/store/height_indexer.go +++ /dev/null @@ -1,58 +0,0 @@ -package store - -import ( - "context" - - lru "github.com/hashicorp/golang-lru" - "github.com/ipfs/go-datastore" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// TODO(@Wondertan): There should be a more clever way to index heights, than just storing -// HeightToHash pair... heightIndexer simply stores and cashes mappings between header Height and -// Hash. -type heightIndexer[H header.Header] struct { - ds datastore.Batching - cache *lru.ARCCache -} - -// newHeightIndexer creates new heightIndexer. -func newHeightIndexer[H header.Header](ds datastore.Batching, indexCacheSize int) (*heightIndexer[H], error) { - cache, err := lru.NewARC(indexCacheSize) - if err != nil { - return nil, err - } - - return &heightIndexer[H]{ - ds: ds, - cache: cache, - }, nil -} - -// HashByHeight loads a header hash corresponding to the given height. -func (hi *heightIndexer[H]) HashByHeight(ctx context.Context, h uint64) (header.Hash, error) { - if v, ok := hi.cache.Get(h); ok { - return v.(header.Hash), nil - } - - val, err := hi.ds.Get(ctx, heightKey(h)) - if err != nil { - return nil, err - } - - hi.cache.Add(h, header.Hash(val)) - return val, nil -} - -// IndexTo saves mapping between header Height and Hash to the given batch. -func (hi *heightIndexer[H]) IndexTo(ctx context.Context, batch datastore.Batch, headers ...H) error { - for _, h := range headers { - err := batch.Put(ctx, heightKey(uint64(h.Height())), h.Hash()) - if err != nil { - return err - } - } - - return nil -} diff --git a/libs/header/store/heightsub.go b/libs/header/store/heightsub.go deleted file mode 100644 index 55cb63f509..0000000000 --- a/libs/header/store/heightsub.go +++ /dev/null @@ -1,122 +0,0 @@ -package store - -import ( - "context" - "errors" - "sync" - "sync/atomic" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// errElapsedHeight is thrown when a requested height was already provided to heightSub. -var errElapsedHeight = errors.New("elapsed height") - -// heightSub provides a minimalistic mechanism to wait till header for a height becomes available. -type heightSub[H header.Header] struct { - // height refers to the latest locally available header height - // that has been fully verified and inserted into the subjective chain - height uint64 // atomic - heightReqsLk sync.Mutex - heightReqs map[uint64][]chan H -} - -// newHeightSub instantiates new heightSub. -func newHeightSub[H header.Header]() *heightSub[H] { - return &heightSub[H]{ - heightReqs: make(map[uint64][]chan H), - } -} - -// Height reports current height. -func (hs *heightSub[H]) Height() uint64 { - return atomic.LoadUint64(&hs.height) -} - -// SetHeight sets the new head height for heightSub. -func (hs *heightSub[H]) SetHeight(height uint64) { - atomic.StoreUint64(&hs.height, height) -} - -// Sub subscribes for a header of a given height. -// It can return errElapsedHeight, which means a requested header was already provided -// and caller should get it elsewhere. -func (hs *heightSub[H]) Sub(ctx context.Context, height uint64) (H, error) { - var zero H - if hs.Height() >= height { - return zero, errElapsedHeight - } - - hs.heightReqsLk.Lock() - if hs.Height() >= height { - // This is a rare case we have to account for. - // The lock above can park a goroutine long enough for hs.height to change for a requested height, - // leaving the request never fulfilled and the goroutine deadlocked. - hs.heightReqsLk.Unlock() - return zero, errElapsedHeight - } - resp := make(chan H, 1) - hs.heightReqs[height] = append(hs.heightReqs[height], resp) - hs.heightReqsLk.Unlock() - - select { - case resp := <-resp: - return resp, nil - case <-ctx.Done(): - // no need to keep the request, if the op is canceled - hs.heightReqsLk.Lock() - delete(hs.heightReqs, height) - hs.heightReqsLk.Unlock() - return zero, ctx.Err() - } -} - -// Pub processes all the outstanding subscriptions matching the given headers. -// Pub is only safe when called from one goroutine. -// For Pub to work correctly, heightSub has to be initialized with SetHeight -// so that given headers are contiguous to the height on heightSub. -func (hs *heightSub[H]) Pub(headers ...H) { - ln := len(headers) - if ln == 0 { - return - } - - height := hs.Height() - from, to := uint64(headers[0].Height()), uint64(headers[ln-1].Height()) - if height+1 != from { - log.Fatal("PLEASE FILE A BUG REPORT: headers given to the heightSub are in the wrong order") - return - } - hs.SetHeight(to) - - hs.heightReqsLk.Lock() - defer hs.heightReqsLk.Unlock() - - // there is a common case where we Pub only header - // in this case, we shouldn't loop over each heightReqs - // and instead read from the map directly - if ln == 1 { - reqs, ok := hs.heightReqs[from] - if ok { - for _, req := range reqs { - req <- headers[0] // reqs must always be buffered, so this won't block - } - delete(hs.heightReqs, from) - } - return - } - - // instead of looping over each header in 'headers', we can loop over each request - // which will drastically decrease idle iterations, as there will be less requests than headers - for height, reqs := range hs.heightReqs { - // then we look if any of the requests match the given range of headers - if height >= from && height <= to { - // and if so, calculate its position and fulfill requests - h := headers[height-from] - for _, req := range reqs { - req <- h // reqs must always be buffered, so this won't block - } - delete(hs.heightReqs, height) - } - } -} diff --git a/libs/header/store/heightsub_test.go b/libs/header/store/heightsub_test.go deleted file mode 100644 index 990f4714ba..0000000000 --- a/libs/header/store/heightsub_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package store - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestHeightSub(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - hs := newHeightSub[*test.DummyHeader]() - - // assert subscription returns nil for past heights - { - h := test.RandDummyHeader(t) - h.Raw.Height = 100 - hs.SetHeight(99) - hs.Pub(h) - - h, err := hs.Sub(ctx, 10) - assert.ErrorIs(t, err, errElapsedHeight) - assert.Nil(t, h) - } - - // assert actual subscription works - { - go func() { - // fixes flakiness on CI - time.Sleep(time.Millisecond) - - h1 := test.RandDummyHeader(t) - h1.Raw.Height = 101 - h2 := test.RandDummyHeader(t) - h2.Raw.Height = 102 - hs.Pub(h1, h2) - }() - - h, err := hs.Sub(ctx, 101) - assert.NoError(t, err) - assert.NotNil(t, h) - } -} diff --git a/libs/header/store/init.go b/libs/header/store/init.go deleted file mode 100644 index b44eee1cc1..0000000000 --- a/libs/header/store/init.go +++ /dev/null @@ -1,24 +0,0 @@ -package store - -import ( - "context" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// Init ensures a Store is initialized. If it is not already initialized, -// it initializes the Store by requesting the header with the given hash. -func Init[H header.Header](ctx context.Context, store header.Store[H], ex header.Exchange[H], hash header.Hash) error { - _, err := store.Head(ctx) - switch err { - default: - return err - case header.ErrNoHead: - initial, err := ex.Get(ctx, hash) - if err != nil { - return err - } - - return store.Init(ctx, initial) - } -} diff --git a/libs/header/store/init_test.go b/libs/header/store/init_test.go deleted file mode 100644 index db80020693..0000000000 --- a/libs/header/store/init_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package store - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header/local" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestInitStore_NoReinit(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - exchange := local.NewExchange(NewTestStore(ctx, t, head)) - - ds := sync.MutexWrap(datastore.NewMapDatastore()) - store, err := NewStore[*test.DummyHeader](ds) - require.NoError(t, err) - - err = Init[*test.DummyHeader](ctx, store, exchange, head.Hash()) - assert.NoError(t, err) - - err = store.Start(ctx) - require.NoError(t, err) - - err = store.Append(ctx, suite.GenDummyHeaders(10)...) - require.NoError(t, err) - - err = store.Stop(ctx) - require.NoError(t, err) - - reopenedStore, err := NewStore[*test.DummyHeader](ds) - assert.NoError(t, err) - - err = reopenedStore.Start(ctx) - require.NoError(t, err) - - reopenedHead, err := reopenedStore.Head(ctx) - require.NoError(t, err) - - // check that reopened head changed and the store wasn't reinitialized - assert.Equal(t, suite.Head().Height(), reopenedHead.Height()) - assert.NotEqual(t, head.Height(), reopenedHead.Height()) - - err = reopenedStore.Stop(ctx) - require.NoError(t, err) -} diff --git a/libs/header/store/keys.go b/libs/header/store/keys.go deleted file mode 100644 index 087be71a8d..0000000000 --- a/libs/header/store/keys.go +++ /dev/null @@ -1,22 +0,0 @@ -package store - -import ( - "strconv" - - "github.com/ipfs/go-datastore" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -var ( - storePrefix = datastore.NewKey("headers") - headKey = datastore.NewKey("head") -) - -func heightKey(h uint64) datastore.Key { - return datastore.NewKey(strconv.Itoa(int(h))) -} - -func headerKey(h header.Header) datastore.Key { - return datastore.NewKey(h.Hash().String()) -} diff --git a/libs/header/store/options.go b/libs/header/store/options.go deleted file mode 100644 index 0f968f2f4d..0000000000 --- a/libs/header/store/options.go +++ /dev/null @@ -1,70 +0,0 @@ -package store - -import ( - "fmt" -) - -// Option is the functional option that is applied to the store instance -// to configure store parameters. -type Option func(*Parameters) - -// Parameters is the set of parameters that must be configured for the store. -type Parameters struct { - // StoreCacheSize defines the maximum amount of entries in the Header Store cache. - StoreCacheSize int - - // IndexCacheSize defines the maximum amount of entries in the Height to Hash index cache. - IndexCacheSize int - - // WriteBatchSize defines the size of the batched header write. - // Headers are written in batches not to thrash the underlying Datastore with writes. - WriteBatchSize int -} - -// DefaultParameters returns the default params to configure the store. -func DefaultParameters() Parameters { - return Parameters{ - StoreCacheSize: 4096, - IndexCacheSize: 16384, - WriteBatchSize: 2048, - } -} - -const errSuffix = "value should be positive and non-zero" - -func (p *Parameters) Validate() error { - if p.StoreCacheSize <= 0 { - return fmt.Errorf("invalid store cache size:%s", errSuffix) - } - if p.IndexCacheSize <= 0 { - return fmt.Errorf("invalid indexer cache size:%s", errSuffix) - } - if p.WriteBatchSize <= 0 { - return fmt.Errorf("invalid batch size:%s", errSuffix) - } - return nil -} - -// WithStoreCacheSize is a functional option that configures the -// `StoreCacheSize` parameter. -func WithStoreCacheSize(size int) Option { - return func(p *Parameters) { - p.StoreCacheSize = size - } -} - -// WithIndexCacheSize is a functional option that configures the -// `IndexCacheSize` parameter. -func WithIndexCacheSize(size int) Option { - return func(p *Parameters) { - p.IndexCacheSize = size - } -} - -// WithWriteBatchSize is a functional option that configures the -// `WriteBatchSize` parameter. -func WithWriteBatchSize(size int) Option { - return func(p *Parameters) { - p.WriteBatchSize = size - } -} diff --git a/libs/header/store/store.go b/libs/header/store/store.go deleted file mode 100644 index 4c64c8724e..0000000000 --- a/libs/header/store/store.go +++ /dev/null @@ -1,458 +0,0 @@ -package store - -import ( - "context" - "errors" - "fmt" - "sync/atomic" - - lru "github.com/hashicorp/golang-lru" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - logging "github.com/ipfs/go-log/v2" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -var log = logging.Logger("header/store") - -var ( - // errStoppedStore is returned for attempted operations on a stopped store - errStoppedStore = errors.New("stopped store") -) - -// Store implements the Store interface for Headers over Datastore. -type Store[H header.Header] struct { - // header storing - // - // underlying KV store - ds datastore.Batching - // adaptive replacement cache of headers - cache *lru.ARCCache - - // header heights management - // - // maps heights to hashes - heightIndex *heightIndexer[H] - // manages current store read head height (1) and - // allows callers to wait until header for a height is stored (2) - heightSub *heightSub[H] - - // writing to datastore - // - // queue of headers to be written - writes chan []H - // signals when writes are finished - writesDn chan struct{} - // writeHead maintains the current write head - writeHead atomic.Pointer[H] - // pending keeps headers pending to be written in one batch - pending *batch[H] - - Params Parameters -} - -// NewStore constructs a Store over datastore. -// The datastore must have a head there otherwise Start will error. -// For first initialization of Store use NewStoreWithHead. -func NewStore[H header.Header](ds datastore.Batching, opts ...Option) (*Store[H], error) { - return newStore[H](ds, opts...) -} - -// NewStoreWithHead initiates a new Store and forcefully sets a given trusted header as head. -func NewStoreWithHead[H header.Header]( - ctx context.Context, - ds datastore.Batching, - head H, - opts ...Option, -) (*Store[H], error) { - store, err := newStore[H](ds, opts...) - if err != nil { - return nil, err - } - - return store, store.Init(ctx, head) -} - -func newStore[H header.Header](ds datastore.Batching, opts ...Option) (*Store[H], error) { - params := DefaultParameters() - for _, opt := range opts { - opt(¶ms) - } - - if err := params.Validate(); err != nil { - return nil, fmt.Errorf("header/store: store creation failed: %w", err) - } - - cache, err := lru.NewARC(params.StoreCacheSize) - if err != nil { - return nil, fmt.Errorf("failed to create index cache: %w", err) - } - - wrappedStore := namespace.Wrap(ds, storePrefix) - index, err := newHeightIndexer[H](wrappedStore, params.IndexCacheSize) - if err != nil { - return nil, fmt.Errorf("failed to create height indexer: %w", err) - } - - return &Store[H]{ - Params: params, - ds: wrappedStore, - heightSub: newHeightSub[H](), - writes: make(chan []H, 16), - writesDn: make(chan struct{}), - cache: cache, - heightIndex: index, - pending: newBatch[H](params.WriteBatchSize), - }, nil -} - -func (s *Store[H]) Init(ctx context.Context, initial H) error { - // trust the given header as the initial head - err := s.flush(ctx, initial) - if err != nil { - return err - } - - log.Infow("initialized head", "height", initial.Height(), "hash", initial.Hash()) - return nil -} - -func (s *Store[H]) Start(context.Context) error { - go s.flushLoop() - return nil -} - -func (s *Store[H]) Stop(ctx context.Context) error { - select { - case <-s.writesDn: - return errStoppedStore - default: - } - // signal to prevent further writes to Store - s.writes <- nil - select { - case <-s.writesDn: // wait till it is done writing - case <-ctx.Done(): - return ctx.Err() - } - - // cleanup caches - s.cache.Purge() - s.heightIndex.cache.Purge() - return nil -} - -func (s *Store[H]) Height() uint64 { - return s.heightSub.Height() -} - -func (s *Store[H]) Head(ctx context.Context) (H, error) { - head, err := s.GetByHeight(ctx, s.heightSub.Height()) - if err == nil { - return head, nil - } - - var zero H - head, err = s.readHead(ctx) - switch err { - default: - return zero, err - case datastore.ErrNotFound, header.ErrNotFound: - return zero, header.ErrNoHead - case nil: - s.heightSub.SetHeight(uint64(head.Height())) - log.Infow("loaded head", "height", head.Height(), "hash", head.Hash()) - return head, nil - } -} - -func (s *Store[H]) Get(ctx context.Context, hash header.Hash) (H, error) { - var zero H - if v, ok := s.cache.Get(hash.String()); ok { - return v.(H), nil - } - // check if the requested header is not yet written on disk - if h := s.pending.Get(hash); !h.IsZero() { - return h, nil - } - - b, err := s.ds.Get(ctx, datastore.NewKey(hash.String())) - if err != nil { - if err == datastore.ErrNotFound { - return zero, header.ErrNotFound - } - - return zero, err - } - - var empty H - h := empty.New() - err = h.UnmarshalBinary(b) - if err != nil { - return zero, err - } - - s.cache.Add(h.Hash().String(), h) - return h.(H), nil -} - -func (s *Store[H]) GetByHeight(ctx context.Context, height uint64) (H, error) { - var zero H - if height == 0 { - return zero, fmt.Errorf("header/store: height must be bigger than zero") - } - // if the requested 'height' was not yet published - // we subscribe to it - h, err := s.heightSub.Sub(ctx, height) - if err != errElapsedHeight { - return h, err - } - // otherwise, the errElapsedHeight is thrown, - // which means the requested 'height' should be present - // - // check if the requested header is not yet written on disk - if h := s.pending.GetByHeight(height); !h.IsZero() { - return h, nil - } - - hash, err := s.heightIndex.HashByHeight(ctx, height) - if err != nil { - if err == datastore.ErrNotFound { - return zero, header.ErrNotFound - } - - return zero, err - } - - return s.Get(ctx, hash) -} - -func (s *Store[H]) GetRangeByHeight(ctx context.Context, from, to uint64) ([]H, error) { - h, err := s.GetByHeight(ctx, to-1) - if err != nil { - return nil, err - } - - ln := to - from - headers := make([]H, ln) - for i := ln - 1; i > 0; i-- { - headers[i] = h - h, err = s.Get(ctx, h.LastHeader()) - if err != nil { - return nil, err - } - } - headers[0] = h - - return headers, nil -} - -func (s *Store[H]) GetVerifiedRange( - ctx context.Context, - from H, - to uint64, -) ([]H, error) { - if uint64(from.Height()) >= to { - return nil, fmt.Errorf("header/store: invalid range(%d,%d)", from.Height(), to) - } - headers, err := s.GetRangeByHeight(ctx, uint64(from.Height())+1, to) - if err != nil { - return nil, err - } - - for _, h := range headers { - err := from.Verify(h) - if err != nil { - return nil, err - } - from = h - } - return headers, nil -} - -func (s *Store[H]) Has(ctx context.Context, hash header.Hash) (bool, error) { - if ok := s.cache.Contains(hash.String()); ok { - return ok, nil - } - // check if the requested header is not yet written on disk - if ok := s.pending.Has(hash); ok { - return ok, nil - } - - return s.ds.Has(ctx, datastore.NewKey(hash.String())) -} - -func (s *Store[H]) HasAt(_ context.Context, height uint64) bool { - return height != uint64(0) && s.Height() >= height -} - -func (s *Store[H]) Append(ctx context.Context, headers ...H) error { - lh := len(headers) - if lh == 0 { - return nil - } - - var err error - // take current write head to verify headers against - var head H - headPtr := s.writeHead.Load() - if headPtr == nil { - head, err = s.Head(ctx) - if err != nil { - return err - } - } else { - head = *headPtr - } - - // collect valid headers - verified := make([]H, 0, lh) - for i, h := range headers { - // currently store requires all headers to be appended sequentially and adjacently - // TODO(@Wondertan): Further pruning friendly Store design should reevaluate this requirement - if h.Height() != head.Height()+1 { - return &header.ErrNonAdjacent{ - Head: head.Height(), - Attempted: h.Height(), - } - } - - err = head.Verify(h) - if err != nil { - var verErr *header.VerifyError - if errors.As(err, &verErr) { - log.Errorw("invalid header", - "height_of_head", head.Height(), - "hash_of_head", head.Hash(), - "height_of_invalid", h.Height(), - "hash_of_invalid", h.Hash(), - "reason", verErr.Reason) - } - // if the first header is invalid, no need to go further - if i == 0 { - // and simply return - return err - } - // otherwise, stop the loop and apply headers appeared to be valid - break - } - verified, head = append(verified, h), h - } - - // queue headers to be written on disk - select { - case s.writes <- verified: - ln := len(verified) - s.writeHead.Store(&verified[ln-1]) - wh := *s.writeHead.Load() - log.Infow("new head", "height", wh.Height(), "hash", wh.Hash()) - // we return an error here after writing, - // as there might be an invalid header in between of a given range - return err - case <-s.writesDn: - return errStoppedStore - case <-ctx.Done(): - return ctx.Err() - } -} - -// flushLoop performs writing task to the underlying datastore in a separate routine -// This way writes are controlled and manageable from one place allowing -// (1) Appends not to be blocked on long disk IO writes and underlying DB compactions -// (2) Batching header writes -func (s *Store[H]) flushLoop() { - defer close(s.writesDn) - ctx := context.Background() - for headers := range s.writes { - // add headers to the pending and ensure they are accessible - s.pending.Append(headers...) - // and notify waiters if any + increase current read head height - // it is important to do Pub after updating pending - // so pending is consistent with atomic Height counter on the heightSub - s.heightSub.Pub(headers...) - // don't flush and continue if pending batch is not grown enough, - // and Store is not stopping(headers == nil) - if s.pending.Len() < s.Params.WriteBatchSize && headers != nil { - continue - } - - err := s.flush(ctx, s.pending.GetAll()...) - if err != nil { - // TODO(@Wondertan): Should this be a fatal error case with os.Exit? - from, to := uint64(headers[0].Height()), uint64(headers[len(headers)-1].Height()) - log.Errorw("writing header batch", "from", from, "to", to) - continue - } - // reset pending - s.pending.Reset() - - if headers == nil { - // a signal to stop - return - } - } -} - -// flush writes the given batch to datastore. -func (s *Store[H]) flush(ctx context.Context, headers ...H) error { - ln := len(headers) - if ln == 0 { - return nil - } - - batch, err := s.ds.Batch(ctx) - if err != nil { - return err - } - - // collect all the headers in the batch to be written - for _, h := range headers { - b, err := h.MarshalBinary() - if err != nil { - return err - } - - err = batch.Put(ctx, headerKey(h), b) - if err != nil { - return err - } - } - - // marshal and add to batch reference to the new head - b, err := headers[ln-1].Hash().MarshalJSON() - if err != nil { - return err - } - - err = batch.Put(ctx, headKey, b) - if err != nil { - return err - } - - // write height indexes for headers as well - err = s.heightIndex.IndexTo(ctx, batch, headers...) - if err != nil { - return err - } - - // finally, commit the batch on disk - return batch.Commit(ctx) -} - -// readHead loads the head from the datastore. -func (s *Store[H]) readHead(ctx context.Context) (H, error) { - var zero H - b, err := s.ds.Get(ctx, headKey) - if err != nil { - return zero, err - } - - var head header.Hash - err = head.UnmarshalJSON(b) - if err != nil { - return zero, err - } - - return s.Get(ctx, head) -} diff --git a/libs/header/store/store_test.go b/libs/header/store/store_test.go deleted file mode 100644 index 89388637ba..0000000000 --- a/libs/header/store/store_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package store - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestStore(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - - ds := sync.MutexWrap(datastore.NewMapDatastore()) - store, err := NewStoreWithHead(ctx, ds, suite.Head()) - require.NoError(t, err) - - err = store.Start(ctx) - require.NoError(t, err) - - head, err := store.Head(ctx) - require.NoError(t, err) - assert.EqualValues(t, suite.Head().Hash(), head.Hash()) - - in := suite.GenDummyHeaders(10) - err = store.Append(ctx, in...) - require.NoError(t, err) - - out, err := store.GetRangeByHeight(ctx, 2, 12) - require.NoError(t, err) - for i, h := range in { - assert.Equal(t, h.Hash(), out[i].Hash()) - } - - head, err = store.Head(ctx) - require.NoError(t, err) - assert.Equal(t, out[len(out)-1].Hash(), head.Hash()) - - ok, err := store.Has(ctx, in[5].Hash()) - require.NoError(t, err) - assert.True(t, ok) - - ok, err = store.Has(ctx, test.RandBytes(32)) - require.NoError(t, err) - assert.False(t, ok) - - go func() { - err := store.Append(ctx, suite.GenDummyHeaders(1)...) - require.NoError(t, err) - }() - - h, err := store.GetByHeight(ctx, 12) - require.NoError(t, err) - assert.NotNil(t, h) - - err = store.Stop(ctx) - require.NoError(t, err) - - // check that the store can be successfully started after previous stop - // with all data being flushed. - store, err = NewStore[*test.DummyHeader](ds) - require.NoError(t, err) - - err = store.Start(ctx) - require.NoError(t, err) - - head, err = store.Head(ctx) - require.NoError(t, err) - assert.Equal(t, suite.Head().Hash(), head.Hash()) - - out, err = store.GetRangeByHeight(ctx, 1, 13) - require.NoError(t, err) - assert.Len(t, out, 12) - - err = store.Stop(ctx) - require.NoError(t, err) -} - -func TestStorePendingCacheMiss(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - - ds := sync.MutexWrap(datastore.NewMapDatastore()) - - store, err := NewStoreWithHead(ctx, ds, suite.Head(), - WithWriteBatchSize(100), - WithStoreCacheSize(100), - ) - require.NoError(t, err) - - err = store.Start(ctx) - require.NoError(t, err) - err = store.Append(ctx, suite.GenDummyHeaders(100)...) - require.NoError(t, err) - - err = store.Append(ctx, suite.GenDummyHeaders(50)...) - require.NoError(t, err) - - _, err = store.GetRangeByHeight(ctx, 1, 101) - require.NoError(t, err) - - _, err = store.GetRangeByHeight(ctx, 101, 151) - require.NoError(t, err) -} diff --git a/libs/header/store/testing.go b/libs/header/store/testing.go deleted file mode 100644 index a472193bda..0000000000 --- a/libs/header/store/testing.go +++ /dev/null @@ -1,28 +0,0 @@ -package store - -import ( - "context" - "testing" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -// NewTestStore creates initialized and started in memory header Store which is useful for testing. -func NewTestStore(ctx context.Context, t *testing.T, head *test.DummyHeader) header.Store[*test.DummyHeader] { - store, err := NewStoreWithHead(ctx, sync.MutexWrap(datastore.NewMapDatastore()), head) - require.NoError(t, err) - - err = store.Start(ctx) - require.NoError(t, err) - - t.Cleanup(func() { - err := store.Stop(ctx) - require.NoError(t, err) - }) - return store -} diff --git a/libs/header/sync/metrics.go b/libs/header/sync/metrics.go deleted file mode 100644 index d76823b7b1..0000000000 --- a/libs/header/sync/metrics.go +++ /dev/null @@ -1,45 +0,0 @@ -package sync - -import ( - "context" - "sync/atomic" - - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" -) - -var meter = global.MeterProvider().Meter("header/sync") - -type metrics struct { - totalSynced int64 -} - -func (s *Syncer[H]) InitMetrics() error { - s.metrics = &metrics{} - - totalSynced, err := meter. - AsyncFloat64(). - Gauge( - "total_synced_headers", - instrument.WithDescription("total synced headers"), - ) - if err != nil { - return err - } - - return meter.RegisterCallback( - []instrument.Asynchronous{ - totalSynced, - }, - func(ctx context.Context) { - totalSynced.Observe(ctx, float64(atomic.LoadInt64(&s.metrics.totalSynced))) - }, - ) -} - -// recordTotalSynced records the total amount of synced headers. -func (m *metrics) recordTotalSynced(totalSynced int) { - if m != nil { - atomic.AddInt64(&m.totalSynced, int64(totalSynced)) - } -} diff --git a/libs/header/sync/options.go b/libs/header/sync/options.go deleted file mode 100644 index d314003ce3..0000000000 --- a/libs/header/sync/options.go +++ /dev/null @@ -1,56 +0,0 @@ -package sync - -import ( - "fmt" - "time" -) - -type Options func(*Parameters) - -// Parameters is the set of parameters that must be configured for the syncer. -type Parameters struct { - // TrustingPeriod is period through which we can trust a header's validators set. - // - // Should be significantly less than the unbonding period (e.g. unbonding - // period = 3 weeks, trusting period = 2 weeks). - // - // More specifically, trusting period + time needed to check headers + time - // needed to report and punish misbehavior should be less than the unbonding - // period. - TrustingPeriod time.Duration - // blockTime provides a reference point for the Syncer to determine - // whether its subjective head is outdated. - // Keeping it private, we don't want users to independently configure it. - // default value is set to 0 so syncer will constantly request networking head. - blockTime time.Duration -} - -// DefaultParameters returns the default params to configure the syncer. -func DefaultParameters() Parameters { - return Parameters{ - TrustingPeriod: 168 * time.Hour, - } -} - -func (p *Parameters) Validate() error { - if p.TrustingPeriod == 0 { - return fmt.Errorf("invalid trusting period duration: %v", p.TrustingPeriod) - } - return nil -} - -// WithBlockTime is a functional option that configures the -// `blockTime` parameter. -func WithBlockTime(duration time.Duration) Options { - return func(p *Parameters) { - p.blockTime = duration - } -} - -// WithTrustingPeriod is a functional option that configures the -// `TrustingPeriod` parameter. -func WithTrustingPeriod(duration time.Duration) Options { - return func(p *Parameters) { - p.TrustingPeriod = duration - } -} diff --git a/libs/header/sync/ranges.go b/libs/header/sync/ranges.go deleted file mode 100644 index 86d54541cf..0000000000 --- a/libs/header/sync/ranges.go +++ /dev/null @@ -1,162 +0,0 @@ -package sync - -import ( - "sync" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// ranges keeps non-overlapping and non-adjacent header ranges which are used to cache headers (in -// ascending order). This prevents unnecessary / duplicate network requests for additional headers -// during sync. -type ranges[H header.Header] struct { - lk sync.RWMutex - ranges []*headerRange[H] -} - -// Head returns the highest Header in all ranges if any. -func (rs *ranges[H]) Head() H { - rs.lk.RLock() - defer rs.lk.RUnlock() - - return rs.head() -} - -func (rs *ranges[H]) head() H { - ln := len(rs.ranges) - if ln == 0 { - var zero H - return zero - } - - head := rs.ranges[ln-1] - return head.Head() -} - -// Add appends the new Header to existing range or starts a new one. -// It starts a new one if the new Header is not adjacent to any of existing ranges. -func (rs *ranges[H]) Add(h H) { - rs.lk.Lock() - defer rs.lk.Unlock() - - head := rs.head() - - // short-circuit if header is from the past - if !head.IsZero() && head.Height() >= h.Height() { - // TODO(@Wondertan): Technically, we can still apply the header: - // * Headers here are verified, so we can trust them - // * PubSub does not guarantee the ordering of msgs - // * So there might be a case where ordering is broken - // * Even considering the delay(block time) with which new headers are generated - // * But rarely - // Would be still nice to implement - log.Warnf("rcvd headers in wrong order") - return - } - - // if the new header is adjacent to head - if !head.IsZero() && h.Height() == head.Height()+1 { - // append it to the last known range - rs.ranges[len(rs.ranges)-1].Append(h) - } else { - // otherwise, start a new range - rs.ranges = append(rs.ranges, newRange[H](h)) - - // it is possible to miss a header or few from gossiping subsystem, due to quick disconnects - // or hibernation, so once we start rcving them again we save those in the new range - } -} - -// First provides a first non-empty range, while cleaning up empty ones. -func (rs *ranges[H]) First() (*headerRange[H], bool) { - rs.lk.Lock() - defer rs.lk.Unlock() - - for { - if len(rs.ranges) == 0 { - return nil, false - } - - out := rs.ranges[0] - if !out.Empty() { - return out, true - } - - rs.ranges = rs.ranges[1:] - } -} - -type headerRange[H header.Header] struct { - lk sync.RWMutex - headers []H - start uint64 -} - -func newRange[H header.Header](h H) *headerRange[H] { - return &headerRange[H]{ - start: uint64(h.Height()), - headers: []H{h}, - } -} - -// Append appends new headers. -func (r *headerRange[H]) Append(h ...H) { - r.lk.Lock() - r.headers = append(r.headers, h...) - r.lk.Unlock() -} - -// Empty reports if range is empty. -func (r *headerRange[H]) Empty() bool { - r.lk.RLock() - defer r.lk.RUnlock() - return len(r.headers) == 0 -} - -// Head reports the head of range if any. -func (r *headerRange[H]) Head() H { - r.lk.RLock() - defer r.lk.RUnlock() - ln := len(r.headers) - if ln == 0 { - var zero H - return zero - } - return r.headers[ln-1] -} - -// Get returns headers within the range up to the specified 'end' height. -func (r *headerRange[H]) Get(end uint64) []H { - r.lk.RLock() - defer r.lk.RUnlock() - - amnt := r.rangeAmount(end) - return r.headers[:amnt] -} - -// Remove removes all headers within the range up to the specified 'end' height. -func (r *headerRange[H]) Remove(end uint64) { - r.lk.Lock() - defer r.lk.Unlock() - - amnt := r.rangeAmount(end) - r.headers = r.headers[amnt:] - if len(r.headers) != 0 { - r.start = uint64(r.headers[0].Height()) - } -} - -// rangeAmount returns the number of headers to be removed or returned up to the specified 'end' -// height. -func (r *headerRange[H]) rangeAmount(end uint64) uint64 { - if r.start > end { - return 0 - } - - amnt := uint64(len(r.headers)) - if r.start+amnt >= end { - amnt = end - r.start + 1 // + 1 to include 'end' as well - } - - return amnt -} diff --git a/libs/header/sync/ranges_test.go b/libs/header/sync/ranges_test.go deleted file mode 100644 index ce5b123b78..0000000000 --- a/libs/header/sync/ranges_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package sync - -import ( - "sync" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestAddParallel(t *testing.T) { - var pending ranges[*test.DummyHeader] - - n := 500 - suite := test.NewTestSuite(t) - headers := suite.GenDummyHeaders(n) - - wg := &sync.WaitGroup{} - wg.Add(n) - for i := 0; i < n; i++ { - go func(i int) { - pending.Add(headers[i]) - wg.Done() - }(i) - } - wg.Wait() - - last := uint64(0) - for _, r := range pending.ranges { - assert.Greater(t, r.start, last) - last = r.start - } -} - -func TestRangeGet(t *testing.T) { - n := 300 - suite := test.NewTestSuite(t) - headers := suite.GenDummyHeaders(n) - - r := newRange(headers[200]) - r.Append(headers[201:]...) - - truncated := r.Get(100) - assert.Len(t, truncated, 0) -} diff --git a/libs/header/sync/sync.go b/libs/header/sync/sync.go deleted file mode 100644 index 50451545ab..0000000000 --- a/libs/header/sync/sync.go +++ /dev/null @@ -1,324 +0,0 @@ -package sync - -import ( - "context" - "errors" - "sync" - "time" - - logging "github.com/ipfs/go-log/v2" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -var log = logging.Logger("header/sync") - -// Syncer implements efficient synchronization for headers. -// -// Subjective Head - the latest known local valid header and a sync target. -// Network Head - the latest valid network-wide header. Becomes subjective once applied locally. -// -// There are two main processes running in Syncer: -// - Main syncing loop(s.syncLoop) -// - Performs syncing from the latest stored header up to the latest known Subjective Head -// - Syncs by requesting missing headers from Exchange or -// - By accessing cache of pending headers -// -// - Receives every new Network Head from PubSub gossip subnetwork (s.incomingNetworkHead) -// - Validates against the latest known Subjective Head, is so -// - Sets as the new Subjective Head, which -// - if there is a gap between the previous and the new Subjective Head -// - Triggers s.syncLoop and saves the Subjective Head in the pending so s.syncLoop can access it -type Syncer[H header.Header] struct { - sub header.Subscriber[H] // to subscribe for new Network Heads - store syncStore[H] // to store all the headers to - getter syncGetter[H] // to fetch headers from - metrics *metrics - - // stateLk protects state which represents the current or latest sync - stateLk sync.RWMutex - state State - - // signals to start syncing - triggerSync chan struct{} - // pending keeps ranges of valid new network headers awaiting to be appended to store - pending ranges[H] - - // controls lifecycle for syncLoop - ctx context.Context - cancel context.CancelFunc - - Params *Parameters -} - -// NewSyncer creates a new instance of Syncer. -func NewSyncer[H header.Header]( - getter header.Getter[H], - store header.Store[H], - sub header.Subscriber[H], - opts ...Options, -) (*Syncer[H], error) { - params := DefaultParameters() - for _, opt := range opts { - opt(¶ms) - } - if err := params.Validate(); err != nil { - return nil, err - } - - return &Syncer[H]{ - sub: sub, - store: syncStore[H]{Store: store}, - getter: syncGetter[H]{Getter: getter}, - triggerSync: make(chan struct{}, 1), // should be buffered - Params: ¶ms, - }, nil -} - -// Start starts the syncing routine. -func (s *Syncer[H]) Start(ctx context.Context) error { - s.ctx, s.cancel = context.WithCancel(context.Background()) - // register validator for header subscriptions - // syncer does not subscribe itself and syncs headers together with validation - err := s.sub.AddValidator(s.incomingNetworkHead) - if err != nil { - return err - } - // gets the latest head and kicks off syncing if necessary - _, err = s.Head(ctx) - if err != nil { - return err - } - // start syncLoop only if Start is errorless - go s.syncLoop() - return nil -} - -// Stop stops Syncer. -func (s *Syncer[H]) Stop(context.Context) error { - s.cancel() - return nil -} - -// SyncWait blocks until ongoing sync is done. -func (s *Syncer[H]) SyncWait(ctx context.Context) error { - state := s.State() - if state.Finished() { - return nil - } - - // this store method blocks until header is available - _, err := s.store.GetByHeight(ctx, state.ToHeight) - return err -} - -// State collects all the information about a sync. -type State struct { - ID uint64 // incrementing ID of a sync - Height uint64 // height at the moment when State is requested for a sync - FromHeight, ToHeight uint64 // the starting and the ending point of a sync - FromHash, ToHash header.Hash - Start, End time.Time - Error error // the error that might happen within a sync -} - -// Finished returns true if sync is done, false otherwise. -func (s State) Finished() bool { - return s.ToHeight <= s.Height -} - -// Duration returns the duration of the sync. -func (s State) Duration() time.Duration { - return s.End.Sub(s.Start) -} - -// State reports state of the current (if in progress), or last sync (if finished). -// Note that throughout the whole Syncer lifetime there might an initial sync and multiple -// catch-ups. All of them are treated as different syncs with different state IDs and other -// information. -func (s *Syncer[H]) State() State { - s.stateLk.RLock() - state := s.state - s.stateLk.RUnlock() - - head, err := s.store.Head(s.ctx) - if err == nil { - state.Height = uint64(head.Height()) - } else if state.Error == nil { - // don't ignore the error if we can show it in the state - state.Error = err - } - - return state -} - -// wantSync will trigger the syncing loop (non-blocking). -func (s *Syncer[H]) wantSync() { - select { - case s.triggerSync <- struct{}{}: - default: - } -} - -// syncLoop controls syncing process. -func (s *Syncer[H]) syncLoop() { - for { - select { - case <-s.triggerSync: - s.sync(s.ctx) - case <-s.ctx.Done(): - return - } - } -} - -// sync ensures we are synced from the Store's head up to the new subjective head. -func (s *Syncer[H]) sync(ctx context.Context) { - subjHead, err := s.subjectiveHead(ctx) - if err != nil { - log.Errorw("getting subjective head", "err", err) - return - } - - storeHead, err := s.store.Head(ctx) - if err != nil { - log.Errorw("getting stored head", "err", err) - return - } - - if storeHead.Height() >= subjHead.Height() { - log.Warnw("sync attempt to an already synced header", - "synced_height", storeHead.Height(), - "attempted_height", subjHead.Height(), - ) - log.Warn("PLEASE REPORT THIS AS A BUG") - return // should never happen, but just in case - } - - from := storeHead.Height() + 1 - log.Infow("syncing headers", - "from", from, - "to", subjHead.Height()) - - err = s.doSync(ctx, storeHead, subjHead) - if err != nil { - if errors.Is(err, context.Canceled) { - // don't log this error as it is normal case of Syncer being stopped - return - } - - log.Errorw("syncing headers", - "from", from, - "to", subjHead.Height(), - "err", err) - return - } - - log.Infow("finished syncing headers", - "from", from, - "to", subjHead.Height(), - "elapsed time", s.state.End.Sub(s.state.Start)) -} - -// doSync performs actual syncing updating the internal State. -func (s *Syncer[H]) doSync(ctx context.Context, fromHead, toHead H) (err error) { - s.stateLk.Lock() - s.state.ID++ - s.state.FromHeight = uint64(fromHead.Height()) + 1 - s.state.ToHeight = uint64(toHead.Height()) - s.state.FromHash = fromHead.Hash() - s.state.ToHash = toHead.Hash() - s.state.Start = time.Now() - s.stateLk.Unlock() - - err = s.processHeaders(ctx, fromHead, uint64(toHead.Height())) - - s.stateLk.Lock() - s.state.End = time.Now() - s.state.Error = err - s.stateLk.Unlock() - return err -} - -// processHeaders fetches and stores asked headers (from:to]. -// Checks headers in pending cache that apply to the requested range. -// If some headers are missing, it starts requesting them from the network. -func (s *Syncer[H]) processHeaders( - ctx context.Context, - fromHead H, - to uint64, -) (err error) { - for { - headersRange, ok := s.pending.First() - if !ok { - break - } - - headers := headersRange.Get(to) - if len(headers) == 0 { - break - } - - // check if returned range is not adjacent to `fromHead` - if fromHead.Height()+1 != headers[0].Height() { - // if so - request missing ones - to := uint64(headers[0].Height() - 1) - if err = s.requestHeaders(ctx, fromHead, to); err != nil { - return err - } - } - - // apply cached headers - if err = s.storeHeaders(ctx, headers...); err != nil { - return err - } - - // cleanup range only after we stored the headers - headersRange.Remove(to) - // update fromHead for the next iteration - fromHead = headers[len(headers)-1] - } - return s.requestHeaders(ctx, fromHead, to) -} - -// requestHeaders requests headers from the network -> (fromHeader.Height : to]. -func (s *Syncer[H]) requestHeaders( - ctx context.Context, - fromHead H, - to uint64, -) error { - amount := to - uint64(fromHead.Height()) - // start requesting headers until amount remaining will be 0 - for amount > 0 { - size := header.MaxRangeRequestSize - if amount < size { - size = amount - } - - headers, err := s.getter.GetVerifiedRange(ctx, fromHead, size) - if err != nil { - return err - } - - if err := s.storeHeaders(ctx, headers...); err != nil { - return err - } - - amount -= size // size == len(headers) - fromHead = headers[len(headers)-1] - } - return nil -} - -// storeHeaders updates store with new headers and updates current syncStore's Head. -func (s *Syncer[H]) storeHeaders(ctx context.Context, headers ...H) error { - // we don't expect any issues in storing right now, as all headers are now verified. - // So, we should return immediately in case an error appears. - err := s.store.Append(ctx, headers...) - if err != nil { - return err - } - - s.metrics.recordTotalSynced(len(headers)) - return nil -} diff --git a/libs/header/sync/sync_getter.go b/libs/header/sync/sync_getter.go deleted file mode 100644 index ad364e7b48..0000000000 --- a/libs/header/sync/sync_getter.go +++ /dev/null @@ -1,31 +0,0 @@ -package sync - -import ( - "context" - "sync" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// syncGetter is a Getter wrapper that ensure only one Head call happens at the time -type syncGetter[H header.Header] struct { - header.Getter[H] - - headLk sync.RWMutex - headErr error - head H -} - -func (se *syncGetter[H]) Head(ctx context.Context) (H, error) { - // the lock construction here ensures only one routine calling Head at a time - // while others wait via Rlock - if !se.headLk.TryLock() { - se.headLk.RLock() - defer se.headLk.RUnlock() - return se.head, se.headErr - } - defer se.headLk.Unlock() - - se.head, se.headErr = se.Getter.Head(ctx) - return se.head, se.headErr -} diff --git a/libs/header/sync/sync_getter_test.go b/libs/header/sync/sync_getter_test.go deleted file mode 100644 index b89c6c57d7..0000000000 --- a/libs/header/sync/sync_getter_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package sync - -import ( - "context" - "fmt" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestSyncGetterHead(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - fex := &fakeGetter[*test.DummyHeader]{} - sex := &syncGetter[*test.DummyHeader]{Getter: fex} - - var wg sync.WaitGroup - for i := 0; i < 100; i++ { - wg.Add(1) - go func() { - defer wg.Done() - h, err := sex.Head(ctx) - if h != nil || err != errFakeHead { - t.Fail() - } - }() - } - wg.Wait() - - assert.EqualValues(t, 1, fex.hits.Load()) -} - -var errFakeHead = fmt.Errorf("head") - -type fakeGetter[H header.Header] struct { - hits atomic.Uint32 -} - -func (f *fakeGetter[H]) Head(ctx context.Context) (h H, err error) { - f.hits.Add(1) - select { - case <-time.After(time.Millisecond * 100): - err = errFakeHead - case <-ctx.Done(): - err = ctx.Err() - } - - return -} - -func (f *fakeGetter[H]) Get(ctx context.Context, hash header.Hash) (H, error) { - panic("implement me") -} - -func (f *fakeGetter[H]) GetByHeight(ctx context.Context, u uint64) (H, error) { - panic("implement me") -} - -func (f *fakeGetter[H]) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]H, error) { - panic("implement me") -} - -func (f *fakeGetter[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) { - panic("implement me") -} diff --git a/libs/header/sync/sync_head.go b/libs/header/sync/sync_head.go deleted file mode 100644 index 29ac310a73..0000000000 --- a/libs/header/sync/sync_head.go +++ /dev/null @@ -1,174 +0,0 @@ -package sync - -import ( - "context" - "errors" - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// Head returns the Network Head. -// -// Known subjective head is considered network head if it is recent enough(now-timestamp<=blocktime) -// Otherwise, head is requested from a trusted peer and -// set as the new subjective head, assuming that trusted peer is always fully synced. -func (s *Syncer[H]) Head(ctx context.Context) (H, error) { - sbjHead, err := s.subjectiveHead(ctx) - if err != nil { - return sbjHead, err - } - // if subjective header is recent enough (relative to the network's block time) - just use it - if isRecent(sbjHead, s.Params.blockTime) { - return sbjHead, nil - } - // otherwise, request head from a trusted peer, as we assume it is fully synced - // - // TODO(@Wondertan): Here is another potential networking optimization: - // * From sbjHead's timestamp and current time predict the time to the next header(TNH) - // * If now >= TNH && now <= TNH + (THP) header propagation time - // * Wait for header to arrive instead of requesting it - // * This way we don't request as we know the new network header arrives exactly - netHead, err := s.getter.Head(ctx) - if err != nil { - return netHead, err - } - // process and validate netHead fetched from trusted peers - // NOTE: We could trust the netHead like we do during 'automatic subjective initialization' - // but in this case our subjective head is not expired, so we should verify netHead - // and only if it is valid, set it as new head - s.incomingNetworkHead(ctx, netHead) - // netHead was either accepted or rejected as the new subjective - // anyway return most current known subjective head - return s.subjectiveHead(ctx) -} - -// subjectiveHead returns the latest known local header that is not expired(within trusting period). -// If the header is expired, it is retrieved from a trusted peer without validation; -// in other words, an automatic subjective initialization is performed. -func (s *Syncer[H]) subjectiveHead(ctx context.Context) (H, error) { - // pending head is the latest known subjective head and sync target, so try to get it - // NOTES: - // * Empty when no sync is in progress - // * Pending cannot be expired, guaranteed - pendHead := s.pending.Head() - if !pendHead.IsZero() { - return pendHead, nil - } - // if pending is empty - get the latest stored/synced head - storeHead, err := s.store.Head(ctx) - if err != nil { - return storeHead, err - } - // check if the stored header is not expired and use it - if !isExpired(storeHead, s.Params.TrustingPeriod) { - return storeHead, nil - } - // otherwise, request head from a trusted peer - log.Infow("stored head header expired", "height", storeHead.Height()) - trustHead, err := s.getter.Head(ctx) - if err != nil { - return trustHead, err - } - // and set it as the new subjective head without validation, - // or, in other words, do 'automatic subjective initialization' - // NOTE: we avoid validation as the head expired to prevent possibility of the Long-Range Attack - s.setSubjectiveHead(ctx, trustHead) - switch { - default: - log.Infow("subjective initialization finished", "height", trustHead.Height()) - return trustHead, nil - case isExpired(trustHead, s.Params.TrustingPeriod): - log.Warnw("subjective initialization with an expired header", "height", trustHead.Height()) - case !isRecent(trustHead, s.Params.blockTime): - log.Warnw("subjective initialization with an old header", "height", trustHead.Height()) - } - log.Warn("trusted peer is out of sync") - return trustHead, nil -} - -// setSubjectiveHead takes already validated head and sets it as the new sync target. -func (s *Syncer[H]) setSubjectiveHead(ctx context.Context, netHead H) { - // TODO(@Wondertan): Right now, we can only store adjacent headers, instead we should: - // * Allow storing any valid header here in Store - // * Remove ErrNonAdjacent - // * Remove writeHead from the canonical store implementation - err := s.storeHeaders(ctx, netHead) - var nonAdj *header.ErrNonAdjacent - if err != nil && !errors.As(err, &nonAdj) { - // might be a storage error or something else, but we can still try to continue processing netHead - log.Errorw("storing new network header", - "height", netHead.Height(), - "hash", netHead.Hash().String(), - "err", err) - } - - storeHead, err := s.store.Head(ctx) - if err == nil && storeHead.Height() >= netHead.Height() { - // we already synced it up - do nothing - return - } - // and if valid, set it as new subjective head - s.pending.Add(netHead) - s.wantSync() - log.Infow("new network head", "height", netHead.Height(), "hash", netHead.Hash()) -} - -// incomingNetworkHead processes new potential network headers. -// If the header valid, sets as new subjective header. -func (s *Syncer[H]) incomingNetworkHead(ctx context.Context, netHead H) pubsub.ValidationResult { - // first of all, check the validity of the netHead - res := s.validateHead(ctx, netHead) - if res == pubsub.ValidationAccept { - // and set it if valid - s.setSubjectiveHead(ctx, netHead) - } - return res -} - -// validateHead checks validity of the given header against the subjective head. -func (s *Syncer[H]) validateHead(ctx context.Context, new H) pubsub.ValidationResult { - sbjHead, err := s.subjectiveHead(ctx) - if err != nil { - log.Errorw("getting subjective head during validation", "err", err) - return pubsub.ValidationIgnore // local error, so ignore - } - // ignore header if it's from the past - if new.Height() <= sbjHead.Height() { - log.Warnw("received known network header", - "current_height", sbjHead.Height(), - "header_height", new.Height(), - "header_hash", new.Hash()) - return pubsub.ValidationIgnore - } - // perform verification - err = sbjHead.Verify(new) - var verErr *header.VerifyError - if errors.As(err, &verErr) { - log.Errorw("invalid network header", - "height_of_invalid", new.Height(), - "hash_of_invalid", new.Hash(), - "height_of_subjective", sbjHead.Height(), - "hash_of_subjective", sbjHead.Hash(), - "reason", verErr.Reason) - return pubsub.ValidationReject - } - // and accept if the header is good - return pubsub.ValidationAccept -} - -// TODO(@Wondertan): We should request TrustingPeriod from the network's state params or -// listen for network params changes to always have a topical value. - -// isExpired checks if header is expired against trusting period. -func isExpired(header header.Header, period time.Duration) bool { - expirationTime := header.Time().Add(period) - return !expirationTime.After(time.Now()) -} - -// isRecent checks if header is recent against the given blockTime. -func isRecent(header header.Header, blockTime time.Duration) bool { - return time.Since(header.Time()) <= blockTime+blockTime/2 // add half block time drift -} diff --git a/libs/header/sync/sync_store.go b/libs/header/sync/sync_store.go deleted file mode 100644 index b2e3123900..0000000000 --- a/libs/header/sync/sync_store.go +++ /dev/null @@ -1,40 +0,0 @@ -package sync - -import ( - "context" - "sync/atomic" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -// syncStore is a Store wrapper that provides synchronization over writes and reads -// for Head of underlying Store. Useful for Stores that do not guarantee synchrony between Append -// and Head method. -type syncStore[H header.Header] struct { - header.Store[H] - - head atomic.Pointer[H] -} - -func (s *syncStore[H]) Head(ctx context.Context) (H, error) { - if headPtr := s.head.Load(); headPtr != nil { - return *headPtr, nil - } - - storeHead, err := s.Store.Head(ctx) - if err != nil { - return storeHead, err - } - - s.head.Store(&storeHead) - return storeHead, nil -} - -func (s *syncStore[H]) Append(ctx context.Context, headers ...H) error { - if err := s.Store.Append(ctx, headers...); err != nil { - return err - } - - s.head.Store(&headers[len(headers)-1]) - return nil -} diff --git a/libs/header/sync/sync_store_test.go b/libs/header/sync/sync_store_test.go deleted file mode 100644 index f2cbc1bbc8..0000000000 --- a/libs/header/sync/sync_store_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package sync - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/celestiaorg/celestia-node/libs/header/mocks" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestSyncStore(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - ts := test.NewTestSuite(t) - s := mocks.NewStore[*test.DummyHeader](t, ts, 100) - ss := syncStore[*test.DummyHeader]{Store: s} - - h, err := ss.Head(ctx) - assert.NoError(t, err) - assert.Equal(t, ts.Head(), h) - - err = ss.Append(ctx, ts.GetRandomHeader()) - assert.NoError(t, err) - - h, err = ss.Head(ctx) - assert.NoError(t, err) - assert.Equal(t, ts.Head(), h) -} diff --git a/libs/header/sync/sync_test.go b/libs/header/sync/sync_test.go deleted file mode 100644 index 08473bf20e..0000000000 --- a/libs/header/sync/sync_test.go +++ /dev/null @@ -1,299 +0,0 @@ -package sync - -import ( - "context" - "testing" - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/local" - "github.com/celestiaorg/celestia-node/libs/header/store" - "github.com/celestiaorg/celestia-node/libs/header/test" -) - -func TestSyncSimpleRequestingHead(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - - remoteStore := store.NewTestStore(ctx, t, head) - err := remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) - require.NoError(t, err) - - _, err = remoteStore.GetByHeight(ctx, 100) - require.NoError(t, err) - - localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer[*test.DummyHeader]( - local.NewExchange(remoteStore), - localStore, - &test.DummySubscriber{}, - WithBlockTime(time.Second*30), - WithTrustingPeriod(time.Microsecond), - ) - require.NoError(t, err) - err = syncer.Start(ctx) - require.NoError(t, err) - - time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing - err = syncer.SyncWait(ctx) - require.NoError(t, err) - - exp, err := remoteStore.Head(ctx) - require.NoError(t, err) - - have, err := localStore.Head(ctx) - require.NoError(t, err) - assert.Equal(t, exp.Height(), have.Height()) - assert.Empty(t, syncer.pending.Head()) - - state := syncer.State() - assert.Equal(t, uint64(exp.Height()), state.Height) - assert.Equal(t, uint64(2), state.FromHeight) - assert.Equal(t, uint64(exp.Height()), state.ToHeight) - assert.True(t, state.Finished(), state) -} - -func TestDoSyncFullRangeFromExternalPeer(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - - remoteStore := store.NewTestStore(ctx, t, head) - localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer[*test.DummyHeader]( - local.NewExchange(remoteStore), - localStore, - &test.DummySubscriber{}, - ) - require.NoError(t, err) - require.NoError(t, syncer.Start(ctx)) - - err = remoteStore.Append(ctx, suite.GenDummyHeaders(int(header.MaxRangeRequestSize))...) - require.NoError(t, err) - // give store time to update heightSub index - time.Sleep(time.Millisecond * 100) - - // trigger sync by calling Head - _, err = syncer.Head(ctx) - require.NoError(t, err) - - // give store time to sync - time.Sleep(time.Millisecond * 100) - - remoteHead, err := remoteStore.Head(ctx) - require.NoError(t, err) - - newHead, err := localStore.Head(ctx) - require.NoError(t, err) - require.Equal(t, newHead.Height(), remoteHead.Height()) -} - -func TestSyncCatchUp(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - - remoteStore := store.NewTestStore(ctx, t, head) - localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer[*test.DummyHeader]( - local.NewExchange(remoteStore), - localStore, - &test.DummySubscriber{}, - WithTrustingPeriod(time.Minute), - ) - require.NoError(t, err) - // 1. Initial sync - err = syncer.Start(ctx) - require.NoError(t, err) - - // 2. chain grows and syncer misses that - err = remoteStore.Append(ctx, suite.GenDummyHeaders(100)...) - require.NoError(t, err) - - incomingHead := suite.GenDummyHeaders(1)[0] - // 3. syncer rcvs header from the future and starts catching-up - res := syncer.incomingNetworkHead(ctx, incomingHead) - assert.Equal(t, pubsub.ValidationAccept, res) - - time.Sleep(time.Millisecond * 100) // needs some to realize it is syncing - err = syncer.SyncWait(ctx) - require.NoError(t, err) - - exp, err := remoteStore.Head(ctx) - require.NoError(t, err) - - // 4. assert syncer caught-up - have, err := localStore.Head(ctx) - require.NoError(t, err) - - assert.Equal(t, have.Height(), incomingHead.Height()) - assert.Equal(t, exp.Height()+1, have.Height()) // plus one as we didn't add last header to remoteStore - assert.Empty(t, syncer.pending.Head()) - - state := syncer.State() - assert.Equal(t, uint64(exp.Height()+1), state.Height) - assert.Equal(t, uint64(2), state.FromHeight) - assert.Equal(t, uint64(exp.Height()+1), state.ToHeight) - assert.True(t, state.Finished(), state) -} - -func TestSyncPendingRangesWithMisses(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - - remoteStore := store.NewTestStore(ctx, t, head) - localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer[*test.DummyHeader]( - local.NewExchange(remoteStore), - localStore, - &test.DummySubscriber{}, - WithTrustingPeriod(time.Minute), - ) - require.NoError(t, err) - err = syncer.Start(ctx) - require.NoError(t, err) - - // miss 1 (helps to test that Syncer properly requests missed Headers from Exchange) - err = remoteStore.Append(ctx, suite.GenDummyHeaders(1)...) - require.NoError(t, err) - - range1 := suite.GenDummyHeaders(15) - err = remoteStore.Append(ctx, range1...) - require.NoError(t, err) - - // miss 2 - err = remoteStore.Append(ctx, suite.GenDummyHeaders(3)...) - require.NoError(t, err) - - range2 := suite.GenDummyHeaders(23) - err = remoteStore.Append(ctx, range2...) - require.NoError(t, err) - - // manually add to pending - for _, h := range append(range1, range2...) { - syncer.pending.Add(h) - } - - // and fire up a sync - syncer.sync(ctx) - - _, err = remoteStore.GetByHeight(ctx, 43) - require.NoError(t, err) - _, err = localStore.GetByHeight(ctx, 43) - require.NoError(t, err) - - lastHead, err := syncer.store.Head(ctx) - require.NoError(t, err) - require.Equal(t, lastHead.Height(), int64(43)) - exp, err := remoteStore.Head(ctx) - require.NoError(t, err) - - have, err := localStore.Head(ctx) - require.NoError(t, err) - - assert.Equal(t, exp.Height(), have.Height()) - assert.Empty(t, syncer.pending.Head()) // assert all cache from pending is used -} - -// TestSyncer_FindHeadersReturnsCorrectRange ensures that `findHeaders` returns -// range [from;to] -func TestSyncer_FindHeadersReturnsCorrectRange(t *testing.T) { - // Test consists of 3 steps: - // 1. get range of headers from pending; [2;11] - // 2. get headers from the remote store; [12;20] - // 3. apply last header from pending; - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - - remoteStore := store.NewTestStore(ctx, t, head) - localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer[*test.DummyHeader]( - local.NewExchange(remoteStore), - localStore, - &test.DummySubscriber{}, - ) - require.NoError(t, err) - - range1 := suite.GenDummyHeaders(10) - // manually add to pending - for _, h := range range1 { - syncer.pending.Add(h) - } - err = remoteStore.Append(ctx, range1...) - require.NoError(t, err) - err = remoteStore.Append(ctx, suite.GenDummyHeaders(9)...) - require.NoError(t, err) - - syncer.pending.Add(suite.GetRandomHeader()) - require.NoError(t, err) - err = syncer.processHeaders(ctx, head, 21) - require.NoError(t, err) - - head, err = syncer.store.Head(ctx) - require.NoError(t, err) - assert.Equal(t, head.Height(), int64(21)) -} - -func TestSyncerIncomingDuplicate(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - suite := test.NewTestSuite(t) - head := suite.Head() - - remoteStore := store.NewTestStore(ctx, t, head) - localStore := store.NewTestStore(ctx, t, head) - syncer, err := NewSyncer[*test.DummyHeader]( - &delayedGetter[*test.DummyHeader]{Getter: local.NewExchange(remoteStore)}, - localStore, - &test.DummySubscriber{}, - ) - require.NoError(t, err) - err = syncer.Start(ctx) - require.NoError(t, err) - - range1 := suite.GenDummyHeaders(10) - err = remoteStore.Append(ctx, range1...) - require.NoError(t, err) - - res := syncer.incomingNetworkHead(ctx, range1[len(range1)-1]) - assert.Equal(t, pubsub.ValidationAccept, res) - - time.Sleep(time.Millisecond * 10) - - res = syncer.incomingNetworkHead(ctx, range1[len(range1)-1]) - assert.Equal(t, pubsub.ValidationIgnore, res) - - err = syncer.SyncWait(ctx) - require.NoError(t, err) -} - -type delayedGetter[H header.Header] struct { - header.Getter[H] -} - -func (d *delayedGetter[H]) GetVerifiedRange(ctx context.Context, from H, amount uint64) ([]H, error) { - select { - case <-time.After(time.Millisecond * 100): - return d.Getter.GetVerifiedRange(ctx, from, amount) - case <-ctx.Done(): - return nil, ctx.Err() - } -} diff --git a/libs/header/test/testing.go b/libs/header/test/testing.go deleted file mode 100644 index 5f0a3d4cc0..0000000000 --- a/libs/header/test/testing.go +++ /dev/null @@ -1,269 +0,0 @@ -package test - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/binary" - "encoding/gob" - "fmt" - "math" - "testing" - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "golang.org/x/crypto/sha3" - - "github.com/celestiaorg/celestia-node/libs/header" -) - -type Raw struct { - ChainID string - PreviousHash header.Hash - - Height int64 - Time time.Time -} - -type DummyHeader struct { - Raw - - hash header.Hash -} - -func (d *DummyHeader) New() header.Header { - return new(DummyHeader) -} - -func (d *DummyHeader) IsZero() bool { - return d == nil -} - -func (d *DummyHeader) ChainID() string { - return "private" -} - -func (d *DummyHeader) Hash() header.Hash { - if len(d.hash) == 0 { - if err := d.rehash(); err != nil { - panic(err) - } - } - return d.hash -} - -func (d *DummyHeader) rehash() error { - b, err := d.MarshalBinary() - if err != nil { - return err - } - hash := sha3.Sum512(b) - d.hash = hash[:] - return nil -} - -func (d *DummyHeader) Height() int64 { - return d.Raw.Height -} - -func (d *DummyHeader) LastHeader() header.Hash { - return d.Raw.PreviousHash -} - -func (d *DummyHeader) Time() time.Time { - return d.Raw.Time -} - -func (d *DummyHeader) IsRecent(blockTime time.Duration) bool { - return time.Since(d.Time()) <= blockTime -} - -func (d *DummyHeader) IsExpired(period time.Duration) bool { - expirationTime := d.Time().Add(period) - return expirationTime.Before(time.Now()) -} - -func (d *DummyHeader) Verify(header header.Header) error { - epsilon := 10 * time.Second - if header.Time().After(time.Now().Add(epsilon)) { - return fmt.Errorf("header Time too far in the future") - } - - if header.Height() <= d.Height() { - return fmt.Errorf("expected new header Height to be larger than old header Time") - } - - if header.Time().Before(d.Time()) { - return fmt.Errorf("expected new header Time to be after old header Time") - } - - return nil -} - -func (d *DummyHeader) Validate() error { - return nil -} - -func (d *DummyHeader) MarshalBinary() ([]byte, error) { - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(d.Raw) - return buf.Bytes(), err -} - -func (d *DummyHeader) UnmarshalBinary(data []byte) error { - dec := gob.NewDecoder(bytes.NewReader(data)) - err := dec.Decode(&d.Raw) - if err != nil { - return err - } - err = d.rehash() - if err != nil { - return err - } - - return nil -} - -// RandBytes returns slice of n-bytes, or nil in case of error -func RandBytes(n int) []byte { - buf := make([]byte, n) - - c, err := rand.Read(buf) - if err != nil || c != n { - return nil - } - - return buf -} - -func randInt63() int64 { - var buf [8]byte - - _, err := rand.Read(buf[:]) - if err != nil { - return math.MaxInt64 - } - - return int64(binary.BigEndian.Uint64(buf[:]) & math.MaxInt64) -} - -func RandDummyHeader(t *testing.T) *DummyHeader { - t.Helper() - dh, err := randDummyHeader() - if err != nil { - t.Fatal(err) - } - return dh -} - -func randDummyHeader() (*DummyHeader, error) { - dh := &DummyHeader{ - Raw{ - PreviousHash: RandBytes(32), - Height: randInt63(), - Time: time.Now().UTC(), - }, - nil, - } - err := dh.rehash() - return dh, err -} - -func mustRandDummyHeader() *DummyHeader { - dh, err := randDummyHeader() - if err != nil { - panic(err) - } - return dh -} - -// Suite provides everything you need to test chain of Headers. -// If not, please don't hesitate to extend it for your case. -type Suite struct { - t *testing.T - - head *DummyHeader -} - -type Generator[H header.Header] interface { - GetRandomHeader() H -} - -// NewTestSuite setups a new test suite. -func NewTestSuite(t *testing.T) *Suite { - return &Suite{ - t: t, - } -} - -func (s *Suite) genesis() *DummyHeader { - return &DummyHeader{ - hash: nil, - Raw: Raw{ - PreviousHash: nil, - Height: 1, - Time: time.Now().Add(-10 * time.Second).UTC(), - }, - } -} - -func (s *Suite) Head() *DummyHeader { - if s.head == nil { - s.head = s.genesis() - } - return s.head -} - -func (s *Suite) GenDummyHeaders(num int) []*DummyHeader { - headers := make([]*DummyHeader, num) - for i := range headers { - headers[i] = s.GetRandomHeader() - } - return headers -} - -func (s *Suite) GetRandomHeader() *DummyHeader { - if s.head == nil { - s.head = s.genesis() - return s.head - } - - dh := mustRandDummyHeader() - dh.Raw.Height = s.head.Height() + 1 - dh.Raw.PreviousHash = s.head.Hash() - _ = dh.rehash() - s.head = dh - return s.head -} - -type DummySubscriber struct { - Headers []*DummyHeader -} - -func (mhs *DummySubscriber) AddValidator(func(context.Context, *DummyHeader) pubsub.ValidationResult) error { - return nil -} - -func (mhs *DummySubscriber) Subscribe() (header.Subscription[*DummyHeader], error) { - return mhs, nil -} - -func (mhs *DummySubscriber) NextHeader(ctx context.Context) (*DummyHeader, error) { - defer func() { - if len(mhs.Headers) > 1 { - // pop the already-returned header - cp := mhs.Headers - mhs.Headers = cp[1:] - } else { - mhs.Headers = make([]*DummyHeader, 0) - } - }() - if len(mhs.Headers) == 0 { - return nil, context.Canceled - } - return mhs.Headers[0], nil -} - -func (mhs *DummySubscriber) Stop(context.Context) error { return nil } -func (mhs *DummySubscriber) Cancel() {} diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index ecb002890a..243310da6c 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -5,10 +5,11 @@ import ( "go.uber.org/fx" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" diff --git a/nodebuilder/das/config.go b/nodebuilder/das/config.go index c08de95969..eeaa382a41 100644 --- a/nodebuilder/das/config.go +++ b/nodebuilder/das/config.go @@ -28,7 +28,8 @@ func DefaultConfig(tp node.Type) Config { // Full node will primarily use shrex protocol for sampling, that is much more efficient and can // fully utilize nodes bandwidth with lower amount of parallel sampling workers cfg.ConcurrencyLimit = 6 - // Full node uses shrex with fallback to ipld to sample, so need 2x amount of time in worst case scenario + // Full node uses shrex with fallback to ipld to sample, so need 2x amount of time in worst case + // scenario cfg.SampleTimeout = 2 * modp2p.BlockTime * time.Duration(cfg.ConcurrencyLimit) } return Config(cfg) diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index 14298b2784..d298c0447f 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -6,10 +6,11 @@ import ( "github.com/ipfs/go-datastore" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index 6f62fcb6d0..b6891d6d56 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -8,9 +8,10 @@ import ( "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 85a7fa5d78..238fb39afd 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -7,10 +7,11 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" - libhead "github.com/celestiaorg/celestia-node/libs/header" - p2p_exchange "github.com/celestiaorg/celestia-node/libs/header/p2p" - "github.com/celestiaorg/celestia-node/libs/header/store" - "github.com/celestiaorg/celestia-node/libs/header/sync" + libhead "github.com/celestiaorg/go-header" + p2p_exchange "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/go-header/store" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index f24c7d6d62..4650ed77d9 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -9,11 +9,12 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/go-header/store" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/p2p" - "github.com/celestiaorg/celestia-node/libs/header/store" - "github.com/celestiaorg/celestia-node/libs/header/sync" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 36f06b8cf0..ba4a49dc69 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -3,9 +3,10 @@ package header import ( "context" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/sync" ) // Module exposes the functionality needed for querying headers from the network. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index b236367726..6691aff181 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -11,8 +11,8 @@ import ( gomock "github.com/golang/mock/gomock" header "github.com/celestiaorg/celestia-node/header" - header0 "github.com/celestiaorg/celestia-node/libs/header" - sync "github.com/celestiaorg/celestia-node/libs/header/sync" + header0 "github.com/celestiaorg/go-header" + sync "github.com/celestiaorg/go-header/sync" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 3f6bf4edfa..5138305cd4 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -8,12 +8,13 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "go.uber.org/fx" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/go-header/store" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/p2p" - "github.com/celestiaorg/celestia-node/libs/header/store" - "github.com/celestiaorg/celestia-node/libs/header/sync" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 18d72eaf6a..00485b9725 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -13,12 +13,13 @@ import ( "go.uber.org/fx" "go.uber.org/fx/fxtest" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/go-header/store" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/p2p" - "github.com/celestiaorg/celestia-node/libs/header/store" - "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go index 5663f059cd..decedcad85 100644 --- a/nodebuilder/header/opts.go +++ b/nodebuilder/header/opts.go @@ -1,10 +1,11 @@ package header import ( + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/p2p" - "github.com/celestiaorg/celestia-node/libs/header/sync" ) // WithMetrics provides sets `MetricsEnabled` to true on ClientParameters for the header exchange diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 35e4d290c1..28e70fc0ee 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -3,10 +3,11 @@ package header import ( "context" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/go-header/sync" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" - "github.com/celestiaorg/celestia-node/libs/header/p2p" - "github.com/celestiaorg/celestia-node/libs/header/sync" ) // Service represents the header Service that can be started / stopped on a node. diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index d8d302a5f6..6202aa9fef 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -15,8 +15,9 @@ import ( "go.uber.org/fx" "golang.org/x/crypto/blake2b" + headp2p "github.com/celestiaorg/go-header/p2p" + "github.com/celestiaorg/celestia-node/libs/fraud" - headp2p "github.com/celestiaorg/celestia-node/libs/header/p2p" ) func init() { diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index e9f240073e..052b6eef0c 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -2,9 +2,9 @@ package state import ( apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/state" ) diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 78063254f8..8c572415fa 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -9,12 +9,12 @@ import ( "go.uber.org/fx" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/go-header/mocks" "github.com/celestiaorg/celestia-node/core" coreheader "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fxutil" - "github.com/celestiaorg/celestia-node/libs/header/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index ee188af2e1..884b39e6d3 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -18,10 +18,10 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-app/testutil/testnode" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 6bee1ff4f1..288c14d37a 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -7,11 +7,11 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/wrapper" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" - libhead "github.com/celestiaorg/celestia-node/libs/header" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" ) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 1956704e56..f152b7fb3e 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -15,8 +15,9 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/net/conngater" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 21b0ba8256..7f0c9819e6 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -18,8 +18,9 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" diff --git a/state/core_access.go b/state/core_access.go index 596c3ab7eb..1339c530a6 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -23,10 +23,10 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/x/blob" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/header" - libhead "github.com/celestiaorg/celestia-node/libs/header" ) var ( From 83521d12868ac77e0960ff2b12a8c348c3c7d2df Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:33:11 +0800 Subject: [PATCH 0485/1008] refactor/fix(core/fetcher): cleanup core fetcher new block subscriber lifecycle (#1955) ## Overview Cleanup core fetcher new block subscriber lifecycle: - [bug-fix] Run subscription with local context instead of fx.Start context. - Make Signed block subscriber inside core respect passed ctx timeout and cancel. - Log error if underlying subscription is closed unexpectedly - Wait for listener to stop before unsubscribing - General cleanup --- core/fetcher.go | 55 ++++++++++++++++++------------------------------ core/listener.go | 8 +++---- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/core/fetcher.go b/core/fetcher.go index adf5bfa913..c24d6f0fac 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -21,8 +21,8 @@ var ( type BlockFetcher struct { client Client - signedBlockCh chan types.EventDataSignedBlock - doneCh chan struct{} + doneCh chan struct{} + cancel context.CancelFunc } // NewBlockFetcher returns a new `BlockFetcher`. @@ -129,63 +129,50 @@ func (f *BlockFetcher) SubscribeNewBlockEvent(ctx context.Context) (<-chan types if !f.client.IsRunning() { return nil, fmt.Errorf("client not running") } + + ctx, cancel := context.WithCancel(ctx) + f.cancel = cancel + f.doneCh = make(chan struct{}) + eventChan, err := f.client.Subscribe(ctx, newBlockSubscriber, newDataSignedBlockQuery) if err != nil { return nil, err } - // create a wrapper channel for translating ResultEvent to "raw" block - if f.signedBlockCh != nil { - return nil, fmt.Errorf("new block event channel exists") - } - - f.signedBlockCh = make(chan types.EventDataSignedBlock) - f.doneCh = make(chan struct{}) - + signedBlockCh := make(chan types.EventDataSignedBlock) go func() { + defer close(f.doneCh) + defer close(signedBlockCh) for { select { - case <-f.doneCh: + case <-ctx.Done(): return case newEvent, ok := <-eventChan: if !ok { + log.Errorw("fetcher: new blocks subscription channel closed unexpectedly") return } - signedBlock, ok := newEvent.Data.(types.EventDataSignedBlock) - if !ok { - log.Warnf("unexpected event: %v", newEvent) - continue - } + signedBlock := newEvent.Data.(types.EventDataSignedBlock) select { - case f.signedBlockCh <- signedBlock: - case <-f.doneCh: + case signedBlockCh <- signedBlock: + case <-ctx.Done(): return } } } }() - return f.signedBlockCh, nil + return signedBlockCh, nil } // UnsubscribeNewBlockEvent stops the subscription to new block events from Core. func (f *BlockFetcher) UnsubscribeNewBlockEvent(ctx context.Context) error { - if f.signedBlockCh == nil { - return fmt.Errorf("no new block event channel found") + f.cancel() + select { + case <-f.doneCh: + case <-ctx.Done(): + return fmt.Errorf("fetcher: unsubscribe from new block events: %w", ctx.Err()) } - if f.doneCh == nil { - return fmt.Errorf("no stop signal chan found in fetcher") - } - defer func() { - // send stop signal - f.doneCh <- struct{}{} - // close out fetcher channels - close(f.signedBlockCh) - close(f.doneCh) - f.signedBlockCh = nil - f.doneCh = nil - }() - return f.client.Unsubscribe(ctx, newBlockSubscriber, newDataSignedBlockQuery) } diff --git a/core/listener.go b/core/listener.go index 8f7bdfe8a6..01530eb180 100644 --- a/core/listener.go +++ b/core/listener.go @@ -50,19 +50,19 @@ func NewListener( } // Start kicks off the Listener listener loop. -func (cl *Listener) Start(ctx context.Context) error { +func (cl *Listener) Start(context.Context) error { if cl.cancel != nil { return fmt.Errorf("listener: already started") } + ctx, cancel := context.WithCancel(context.Background()) + cl.cancel = cancel + sub, err := cl.fetcher.SubscribeNewBlockEvent(ctx) if err != nil { return err } - - ctx, cancel := context.WithCancel(context.Background()) go cl.listen(ctx, sub) - cl.cancel = cancel return nil } From da2680438d89d40bac6e3c9970813caa352aaf77 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:40:33 +0100 Subject: [PATCH 0486/1008] feat(nodebuilder/header): Add `Subscribe` method implementation (#1943) Blocked on method collision (cc @distractedm1nd ) Resolves https://github.com/celestiaorg/celestia-node/issues/1942 --- docs/adr/adr-009-public-api.md | 2 +- nodebuilder/header/header.go | 16 ++++++++++++---- nodebuilder/header/mocks/api.go | 18 ++++++++++++++++-- nodebuilder/header/service.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 98d7d279b5..0eb3ec3b7f 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -105,7 +105,7 @@ GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) GetVerifiedRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) // Subscribe creates long-living Subscription for newly validated // ExtendedHeaders. Multiple Subscriptions can be created. -Subscribe(context.Context) (Subscription, error) +Subscribe(context.Context) (<-chan *header.ExtendedHeader, error) // SyncState returns the current state of the header Syncer. SyncState(context.Context) (sync.State, error) // SyncWait blocks until the header Syncer is synced to network head. diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index ba4a49dc69..5551df9c8f 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -37,6 +37,9 @@ type Module interface { SyncWait(ctx context.Context) error // NetworkHead provides the Syncer's view of the current network head. NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) + + // Subscribe to recent ExtendedHeaders from the network. + Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, error) } // API is a wrapper around Module for the RPC. @@ -53,10 +56,11 @@ type API struct { *header.ExtendedHeader, uint64, ) ([]*header.ExtendedHeader, error) `perm:"public"` - GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` - SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` - SyncWait func(ctx context.Context) error `perm:"read"` - NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"public"` + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` + SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` + SyncWait func(ctx context.Context) error `perm:"read"` + NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"public"` + Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"public"` } } @@ -91,3 +95,7 @@ func (api *API) SyncWait(ctx context.Context) error { func (api *API) NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) { return api.Internal.NetworkHead(ctx) } + +func (api *API) Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, error) { + return api.Internal.Subscribe(ctx) +} diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 6691aff181..e9d0553581 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -113,6 +112,21 @@ func (mr *MockModuleMockRecorder) NetworkHead(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetworkHead", reflect.TypeOf((*MockModule)(nil).NetworkHead), arg0) } +// Subscribe mocks base method. +func (m *MockModule) Subscribe(arg0 context.Context) (<-chan *header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Subscribe", arg0) + ret0, _ := ret[0].(<-chan *header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Subscribe indicates an expected call of Subscribe. +func (mr *MockModuleMockRecorder) Subscribe(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockModule)(nil).Subscribe), arg0) +} + // SyncState mocks base method. func (m *MockModule) SyncState(arg0 context.Context) (sync.State, error) { m.ctrl.T.Helper() diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 28e70fc0ee..a4c6149f49 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -69,3 +69,31 @@ func (s *Service) SyncWait(ctx context.Context) error { func (s *Service) NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) { return s.syncer.Head(ctx) } + +func (s *Service) Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, error) { + subscription, err := s.sub.Subscribe() + if err != nil { + return nil, err + } + + headerCh := make(chan *header.ExtendedHeader) + go func() { + defer close(headerCh) + for { + h, err := subscription.NextHeader(ctx) + if err != nil { + if err != context.DeadlineExceeded && err != context.Canceled { + log.Errorw("fetching header from subscription", "err", err) + } + return + } + + select { + case <-ctx.Done(): + return + case headerCh <- h: + } + } + }() + return headerCh, nil +} From 258b46552a309c43521335c9ddc453bc4f9fa405 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Mar 2023 11:07:03 +0100 Subject: [PATCH 0487/1008] feat(nodebuilder/node | api): Version the API (#1901) Introduce a version string to the API Resolves #1345 --- api/docgen/openrpc.go | 2 +- nodebuilder/node/admin.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/api/docgen/openrpc.go b/api/docgen/openrpc.go index a5cdef0992..c61e22d543 100644 --- a/api/docgen/openrpc.go +++ b/api/docgen/openrpc.go @@ -18,7 +18,7 @@ import ( ) const ( - APIVersion = "v0.0.1" + APIVersion = "v0.1.0" APIDescription = "The Celestia Node API is the collection of RPC methods that " + "can be used to interact with the services provided by Celestia Data Availability Nodes." APIName = "Celestia Node API" diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index 49d2d77341..d86772e94b 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -6,6 +6,8 @@ import ( "github.com/filecoin-project/go-jsonrpc/auth" logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-node/api/docgen" ) type module struct { @@ -27,9 +29,8 @@ type Info struct { func (m *module) Info(context.Context) (Info, error) { return Info{ - Type: m.tp, - // TODO @renaynay @distractedm1nd: Implement versioning in API and way to extract that into this - // struct + Type: m.tp, + APIVersion: docgen.APIVersion, }, nil } From ba75307f0ea8de33c96a2967a6ac7839a7749f4b Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 24 Mar 2023 14:37:11 +0100 Subject: [PATCH 0488/1008] refactoring(share/ipld)!: rework getLeavesByNamespace (#1870) --- share/get.go | 16 +- share/get_test.go | 52 ++-- share/getter.go | 20 +- share/getters/utils.go | 3 +- share/ipld/get.go | 190 +------------ share/ipld/namespace_data.go | 298 ++++++++++++++++++++ share/ipld/{proof.go => proof_collector.go} | 6 - share/p2p/shrexnd/client.go | 25 +- share/p2p/shrexnd/server.go | 12 +- 9 files changed, 350 insertions(+), 272 deletions(-) create mode 100644 share/ipld/namespace_data.go rename share/ipld/{proof.go => proof_collector.go} (90%) diff --git a/share/get.go b/share/get.go index c093ccf6dd..9ec4c3c2c5 100644 --- a/share/get.go +++ b/share/get.go @@ -7,6 +7,7 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/share/ipld" @@ -50,24 +51,25 @@ func GetSharesByNamespace( root cid.Cid, nID namespace.ID, maxShares int, - proofContainer *ipld.Proof, -) ([]Share, error) { +) ([]Share, *nmt.Proof, error) { ctx, span := tracer.Start(ctx, "get-shares-by-namespace") defer span.End() - leaves, err := ipld.GetLeavesByNamespace(ctx, bGetter, root, nID, maxShares, proofContainer) - if err != nil && leaves == nil { - return nil, err + data := ipld.NewNamespaceData(maxShares, nID, ipld.WithLeaves(), ipld.WithProofs()) + err := data.CollectLeavesByNamespace(ctx, bGetter, root) + if err != nil { + return nil, nil, err } + leaves := data.Leaves() + shares := make([]Share, len(leaves)) for i, leaf := range leaves { if leaf != nil { shares[i] = leafToShare(leaf) } } - - return shares, err + return shares, data.Proof(), err } // leafToShare converts an NMT leaf into a Share. diff --git a/share/get_test.go b/share/get_test.go index 3466a726f1..3913dcf4d7 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -19,7 +19,6 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -168,7 +167,7 @@ func TestGetSharesByNamespace(t *testing.T) { var shares []Share for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) - rowShares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots()), nil) + rowShares, _, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) require.NoError(t, err) shares = append(shares, rowShares...) @@ -182,7 +181,7 @@ func TestGetSharesByNamespace(t *testing.T) { } } -func TestGetLeavesByNamespace_IncompleteData(t *testing.T) { +func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() bServ := mdutils.Bserv() @@ -222,13 +221,15 @@ func TestGetLeavesByNamespace_IncompleteData(t *testing.T) { err = bServ.DeleteBlock(ctx, r.Cid()) require.NoError(t, err) - leaves, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares), nil) + namespaceData := ipld.NewNamespaceData(len(shares), nid, ipld.WithLeaves()) + err = namespaceData.CollectLeavesByNamespace(ctx, bServ, rcid) + leaves := namespaceData.Leaves() assert.Nil(t, leaves[1]) assert.Equal(t, 4, len(leaves)) require.Error(t, err) } -func TestGetLeavesByNamespace_AbsentNamespaceId(t *testing.T) { +func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() bServ := mdutils.Bserv() @@ -279,7 +280,7 @@ func TestGetLeavesByNamespace_AbsentNamespaceId(t *testing.T) { } } -func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T) { +func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() bServ := mdutils.Bserv() @@ -303,11 +304,12 @@ func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) - leaves, err := ipld.GetLeavesByNamespace(ctx, bServ, rcid, nid, len(shares), nil) + data := ipld.NewNamespaceData(len(shares), nid, ipld.WithLeaves()) + err := data.CollectLeavesByNamespace(ctx, bServ, rcid) assert.Nil(t, err) - + leaves := data.Leaves() for _, node := range leaves { - // test that the data returned by getLeavesByNamespace for nid + // test that the data returned by collectLeavesByNamespace for nid // matches the commonNamespaceData that was copied across almost all data assert.Equal(t, commonNamespaceData, node.RawData()[NamespaceSize:]) } @@ -353,10 +355,10 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { var shares []Share for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) - proof := new(ipld.Proof) - rowShares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots()), proof) + rowShares, proof, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) require.NoError(t, err) - if rowShares != nil { + if len(rowShares) > 0 { + require.NotNil(t, proof) // append shares to check integrity later shares = append(shares, rowShares...) @@ -366,23 +368,19 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { leaves = append(leaves, append(sh[:NamespaceSize], sh...)) } - proofNodes := make([][]byte, 0, len(proof.Nodes)) - for _, n := range proof.Nodes { - proofNodes = append(proofNodes, ipld.NamespacedSha256FromCID(n)) - } - - // construct new proof - inclusionProof := nmt.NewInclusionProof( - proof.Start, - proof.End, - proofNodes, - ipld.NMTIgnoreMaxNamespace) + // verify namespace + verified := proof.VerifyNamespace( + sha256.New(), + nID, + leaves, + ipld.NamespacedSha256FromCID(rcid)) + require.True(t, verified) // verify inclusion - verified := inclusionProof.VerifyNamespace( + verified = proof.VerifyInclusion( sha256.New(), nID, - leaves, + rowShares, ipld.NamespacedSha256FromCID(rcid)) require.True(t, verified) } @@ -448,7 +446,9 @@ func assertNoRowContainsNID( // for each row root cid check if the minNID exists for _, rowCID := range rowRootCIDs { - leaves, err := ipld.GetLeavesByNamespace(context.Background(), bServ, rowCID, nID, rowRootCount, nil) + data := ipld.NewNamespaceData(rowRootCount, nID, ipld.WithProofs()) + err := data.CollectLeavesByNamespace(context.Background(), bServ, rowCID) + leaves := data.Leaves() assert.Nil(t, leaves) assert.Nil(t, err) } diff --git a/share/getter.go b/share/getter.go index 16b727bc88..f89c8616f7 100644 --- a/share/getter.go +++ b/share/getter.go @@ -9,8 +9,6 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" - - "github.com/celestiaorg/celestia-node/share/ipld" ) // Getter interface provides a set of accessors for shares by the Root. @@ -44,7 +42,7 @@ func (ns NamespacedShares) Flatten() []Share { // NamespacedRow represents all shares with proofs within a specific namespace of a single EDS row. type NamespacedRow struct { Shares []Share - Proof *ipld.Proof + Proof *nmt.Proof } // Verify validates NamespacedShares by checking every row with nmt inclusion proof. @@ -78,20 +76,8 @@ func (row *NamespacedRow) verify(rowRoot []byte, nID namespace.ID) bool { leaves = append(leaves, append(sh[:NamespaceSize], sh...)) } - proofNodes := make([][]byte, 0, len(row.Proof.Nodes)) - for _, n := range row.Proof.Nodes { - proofNodes = append(proofNodes, ipld.NamespacedSha256FromCID(n)) - } - - // construct new proof - inclusionProof := nmt.NewInclusionProof( - row.Proof.Start, - row.Proof.End, - proofNodes, - ipld.NMTIgnoreMaxNamespace) - - // verify inclusion - return inclusionProof.VerifyNamespace( + // verify namespace + return row.Proof.VerifyNamespace( sha256.New(), nID, leaves, diff --git a/share/getters/utils.go b/share/getters/utils.go index e7c1a25e8f..a82b155241 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -66,8 +66,7 @@ func collectSharesByNamespace( // shadow loop variables, to ensure correct values are captured i, rootCID := i, rootCID errGroup.Go(func() error { - proof := new(ipld.Proof) - row, err := share.GetSharesByNamespace(ctx, bg, rootCID, nID, len(root.RowsRoots), proof) + row, proof, err := share.GetSharesByNamespace(ctx, bg, rootCID, nID, len(root.RowsRoots)) shares[i] = share.NamespacedRow{ Shares: row, Proof: proof, diff --git a/share/ipld/get.go b/share/ipld/get.go index b5981137c3..f3c0f8fe1a 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -2,7 +2,6 @@ package ipld import ( "context" - "fmt" "sync" "sync/atomic" @@ -12,9 +11,6 @@ import ( ipld "github.com/ipfs/go-ipld-format" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" - - "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" ) // NumWorkersLimit sets global limit for workers spawned by GetShares. @@ -170,167 +166,6 @@ func GetLeaves(ctx context.Context, wg.Wait() } -// GetLeavesByNamespace returns leaves and corresponding proof that could be used to verify leaves -// inclusion. It returns as many leaves from the given root with the given namespace.ID as it can -// retrieve. If no shares are found, it returns both data and error as nil. If non-nil -// proofContainer param passed, it will be filled with data required for inclusion verification. A -// non-nil error means that only partial data is returned, because at least one share retrieval -// failed. The following implementation is based on `GetShares`. -func GetLeavesByNamespace( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - nID namespace.ID, - maxShares int, - proofContainer *Proof, -) ([]ipld.Node, error) { - if len(nID) != NamespaceSize { - return nil, fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(nID)) - } - - ctx, span := tracer.Start(ctx, "get-leaves-by-namespace") - defer span.End() - - span.SetAttributes( - attribute.String("namespace", nID.String()), - attribute.String("root", root.String()), - ) - - // we don't know where in the tree the leaves in the namespace are, - // so we keep track of the bounds to return the correct slice - // maxShares acts as a sentinel to know if we find any leaves - bounds := fetchedBounds{int64(maxShares), 0} - - // buffer the jobs to avoid blocking, we only need as many - // queued as the number of shares in the second-to-last layer - jobs := make(chan *job, (maxShares+1)/2) - jobs <- &job{id: root, ctx: ctx} - - var wg chanGroup - wg.jobs = jobs - wg.add(1) - - var ( - singleErr sync.Once - retrievalErr error - ) - - // we overallocate space for leaves since we do not know how many we will find - // on the level above, the length of the Row is passed in as maxShares - leaves := make([]ipld.Node, maxShares) - - // if non-nil proof container provided, collect proofs while traversing the tree and fill put them - // into container after - var collectProofs = proofContainer != nil - var proofs *proofCollector - if collectProofs { - proofs = newProofCollector(maxShares) - } - - for { - var j *job - var ok bool - select { - case j, ok = <-jobs: - case <-ctx.Done(): - return nil, ctx.Err() - } - - if !ok { - // if there were no leaves under the given root in the given namespace, - // both return values are nil. otherwise, the error will also be non-nil. - if bounds.lowest == int64(maxShares) { - return nil, retrievalErr - } - - if collectProofs { - proofContainer.Start = int(bounds.lowest) - proofContainer.End = int(bounds.highest) + 1 - proofContainer.Nodes = proofs.Nodes() - } - - return leaves[bounds.lowest : bounds.highest+1], retrievalErr - } - pool.Submit(func() { - ctx, span := tracer.Start(j.ctx, "process-job") - defer span.End() - defer wg.done() - - span.SetAttributes( - attribute.String("cid", j.id.String()), - attribute.Int("pos", j.sharePos), - ) - - // if an error is likely to be returned or not depends on - // the underlying impl of the blockservice, currently it is not a realistic probability - nd, err := GetNode(ctx, bGetter, j.id) - if err != nil { - singleErr.Do(func() { - retrievalErr = err - }) - log.Errorw("getLeavesWithProofsByNamespace: could not retrieve node", "nID", nID, "pos", j.sharePos, "err", err) - span.SetStatus(codes.Error, err.Error()) - // we still need to update the bounds - bounds.update(int64(j.sharePos)) - return - } - - links := nd.Links() - if len(links) == 0 { - // successfully fetched a leaf belonging to the namespace - span.SetStatus(codes.Ok, "") - leaves[j.sharePos] = nd - // we found a leaf, so we update the bounds - // the update routine is repeated until the atomic swap is successful - bounds.update(int64(j.sharePos)) - return - } - - // this node has links in the namespace, so keep walking - for i, lnk := range links { - newJob := &job{ - id: lnk.Cid, - // sharePos represents potential share position in share slice - sharePos: j.sharePos*2 + i, - // depth represents the number of edges present in path from the root node of a tree to that node - depth: j.depth + 1, - // we pass the context to job so that spans are tracked in a tree - // structure - ctx: ctx, - } - // if the link's nID isn't in range we don't need to create a new job for it, - // but need to collect a proof - jobNid := NamespacedSha256FromCID(newJob.id) - - // proof is on the right side, if the nID is less than min namespace of jobNid - if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) { - if collectProofs { - proofs.addRight(lnk.Cid, newJob.depth) - } - continue - } - - // proof is on the left side, if the nID is bigger than max namespace of jobNid - if !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { - if collectProofs { - proofs.addLeft(lnk.Cid, newJob.depth) - } - continue - } - - // by passing the previous check, we know we will have one more node to process - // note: it is important to increase the counter before sending to the channel - wg.add(1) - select { - case jobs <- newJob: - case <-ctx.Done(): - return - } - } - }) - } -} - // GetProof fetches and returns the leaf's Merkle Proof. // It walks down the IPLD NMT tree until it reaches the leaf and returns collected proof func GetProof( @@ -391,31 +226,8 @@ func (w *chanGroup) done() { } } -type fetchedBounds struct { - lowest int64 - highest int64 -} - -// update checks if the passed index is outside the current bounds, -// and updates the bounds atomically if it extends them. -func (b *fetchedBounds) update(index int64) { - lowest := atomic.LoadInt64(&b.lowest) - // try to write index to the lower bound if appropriate, and retry until the atomic op is successful - // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the - // comparison here - for index < lowest && !atomic.CompareAndSwapInt64(&b.lowest, lowest, index) { - lowest = atomic.LoadInt64(&b.lowest) - } - // we always run both checks because element can be both the lower and higher bound - // for example, if there is only one share in the namespace - highest := atomic.LoadInt64(&b.highest) - for index > highest && !atomic.CompareAndSwapInt64(&b.highest, highest, index) { - highest = atomic.LoadInt64(&b.highest) - } -} - // job represents an encountered node to investigate during the `GetLeaves` -// and `GetLeavesByNamespace` routines. +// and `CollectLeavesByNamespace` routines. type job struct { id cid.Cid sharePos int diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go new file mode 100644 index 0000000000..7e384d0c17 --- /dev/null +++ b/share/ipld/namespace_data.go @@ -0,0 +1,298 @@ +package ipld + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" +) + +// Option is the functional option that is applied to the NamespaceData instance +// to configure data that needs to be stored. +type Option func(*NamespaceData) + +// WithLeaves option specifies that leaves should be collected during retrieval. +func WithLeaves() Option { + return func(data *NamespaceData) { + // we over-allocate space for leaves since we do not know how many we will find + // on the level above, the length of the Row is passed in as maxShares + data.leaves = make([]ipld.Node, data.maxShares) + } +} + +// WithProofs option specifies that proofs should be collected during retrieval. +func WithProofs() Option { + return func(data *NamespaceData) { + data.proofs = newProofCollector(data.maxShares) + } +} + +// NamespaceData stores all leaves under the given namespace with their corresponding proofs. +type NamespaceData struct { + leaves []ipld.Node + proofs *proofCollector + bounds fetchedBounds + maxShares int + nID namespace.ID +} + +func NewNamespaceData(maxShares int, nID namespace.ID, options ...Option) *NamespaceData { + data := &NamespaceData{ + // we don't know where in the tree the leaves in the namespace are, + // so we keep track of the bounds to return the correct slice + // maxShares acts as a sentinel to know if we find any leaves + bounds: fetchedBounds{int64(maxShares), 0}, + maxShares: maxShares, + nID: nID, + } + + for _, opt := range options { + opt(data) + } + return data +} + +func (n *NamespaceData) validate() error { + if len(n.nID) != NamespaceSize { + return fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(n.nID)) + } + + if n.leaves == nil && n.proofs == nil { + return errors.New("share/ipld: empty NamespaceData, nothing specified to retrieve") + } + return nil +} + +func (n *NamespaceData) addLeaf(pos int, nd ipld.Node) { + // bounds will be needed in `Proof` method + n.bounds.update(int64(pos)) + + if n.leaves == nil { + return + } + + if nd != nil { + n.leaves[pos] = nd + } +} + +// noLeaves checks that there are no leaves under the given root in the given namespace. +func (n *NamespaceData) noLeaves() bool { + return n.bounds.lowest == int64(n.maxShares) +} + +type direction int + +const ( + left direction = iota + 1 + right +) + +func (n *NamespaceData) addProof(d direction, cid cid.Cid, depth int) { + if n.proofs == nil { + return + } + + switch d { + case left: + n.proofs.addLeft(cid, depth) + case right: + n.proofs.addRight(cid, depth) + default: + panic(fmt.Sprintf("share/ipld: invalid direction: %d", d)) + } +} + +// Leaves returns retrieved leaves within the bounds in case `WithLeaves` option was passed, +// otherwise nil will be returned. +func (n *NamespaceData) Leaves() []ipld.Node { + if n.leaves == nil || n.noLeaves() { + return nil + } + return n.leaves[n.bounds.lowest : n.bounds.highest+1] +} + +// Proof returns proofs within the bounds in case if `WithProofs` option was passed, +// otherwise nil will be returned. +func (n *NamespaceData) Proof() *nmt.Proof { + if n.proofs == nil { + return nil + } + + // return an empty Proof if leaves are not available + if n.noLeaves() { + return &nmt.Proof{} + } + + nodes := make([][]byte, len(n.proofs.Nodes())) + for i, node := range n.proofs.Nodes() { + nodes[i] = NamespacedSha256FromCID(node) + } + + proof := nmt.NewInclusionProof( + int(n.bounds.lowest), + int(n.bounds.highest)+1, + nodes, + NMTIgnoreMaxNamespace, + ) + return &proof +} + +// CollectLeavesByNamespace collects leaves and corresponding proof that could be used to verify leaves +// inclusion. It returns as many leaves from the given root with the given namespace.ID as it can +// retrieve. If no shares are found, it returns error as nil. A +// non-nil error means that only partial data is returned, because at least one share retrieval +// failed. The following implementation is based on `GetShares`. +func (n *NamespaceData) CollectLeavesByNamespace( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, +) error { + if err := n.validate(); err != nil { + return err + } + + ctx, span := tracer.Start(ctx, "get-leaves-by-namespace") + defer span.End() + + span.SetAttributes( + attribute.String("namespace", n.nID.String()), + attribute.String("root", root.String()), + ) + + // buffer the jobs to avoid blocking, we only need as many + // queued as the number of shares in the second-to-last layer + jobs := make(chan *job, (n.maxShares+1)/2) + jobs <- &job{id: root, ctx: ctx} + + var wg chanGroup + wg.jobs = jobs + wg.add(1) + + var ( + singleErr sync.Once + retrievalErr error + ) + + for { + var j *job + var ok bool + select { + case j, ok = <-jobs: + case <-ctx.Done(): + return ctx.Err() + } + + if !ok { + return retrievalErr + } + pool.Submit(func() { + ctx, span := tracer.Start(j.ctx, "process-job") + defer span.End() + defer wg.done() + + span.SetAttributes( + attribute.String("cid", j.id.String()), + attribute.Int("pos", j.sharePos), + ) + + // if an error is likely to be returned or not depends on + // the underlying impl of the blockservice, currently it is not a realistic probability + nd, err := GetNode(ctx, bGetter, j.id) + if err != nil { + singleErr.Do(func() { + retrievalErr = err + }) + log.Errorw("getLeavesWithProofsByNamespace:could not retrieve node", + "nID", n.nID, + "pos", j.sharePos, + "err", err, + ) + span.SetStatus(codes.Error, err.Error()) + // we still need to update the bounds + n.addLeaf(j.sharePos, nil) + return + } + + links := nd.Links() + if len(links) == 0 { + // successfully fetched a leaf belonging to the namespace + span.SetStatus(codes.Ok, "") + // we found a leaf, so we update the bounds + n.addLeaf(j.sharePos, nd) + return + } + + // this node has links in the namespace, so keep walking + for i, lnk := range links { + newJob := &job{ + id: lnk.Cid, + // sharePos represents potential share position in share slice + sharePos: j.sharePos*2 + i, + // depth represents the number of edges present in path from the root node of a tree to that node + depth: j.depth + 1, + // we pass the context to job so that spans are tracked in a tree + // structure + ctx: ctx, + } + // if the link's nID isn't in range we don't need to create a new job for it, + // but need to collect a proof + jobNid := NamespacedSha256FromCID(newJob.id) + + // proof is on the right side, if the nID is less than min namespace of jobNid + if n.nID.Less(nmt.MinNamespace(jobNid, n.nID.Size())) { + n.addProof(right, lnk.Cid, newJob.depth) + continue + } + + // proof is on the left side, if the nID is bigger than max namespace of jobNid + if !n.nID.LessOrEqual(nmt.MaxNamespace(jobNid, n.nID.Size())) { + n.addProof(left, lnk.Cid, newJob.depth) + continue + } + + // by passing the previous check, we know we will have one more node to process + // note: it is important to increase the counter before sending to the channel + wg.add(1) + select { + case jobs <- newJob: + case <-ctx.Done(): + return + } + } + }) + } +} + +type fetchedBounds struct { + lowest int64 + highest int64 +} + +// update checks if the passed index is outside the current bounds, +// and updates the bounds atomically if it extends them. +func (b *fetchedBounds) update(index int64) { + lowest := atomic.LoadInt64(&b.lowest) + // try to write index to the lower bound if appropriate, and retry until the atomic op is successful + // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the + // comparison here + for index < lowest && !atomic.CompareAndSwapInt64(&b.lowest, lowest, index) { + lowest = atomic.LoadInt64(&b.lowest) + } + // we always run both checks because element can be both the lower and higher bound + // for example, if there is only one share in the namespace + highest := atomic.LoadInt64(&b.highest) + for index > highest && !atomic.CompareAndSwapInt64(&b.highest, highest, index) { + highest = atomic.LoadInt64(&b.highest) + } +} diff --git a/share/ipld/proof.go b/share/ipld/proof_collector.go similarity index 90% rename from share/ipld/proof.go rename to share/ipld/proof_collector.go index 407335ed94..937c8b416e 100644 --- a/share/ipld/proof.go +++ b/share/ipld/proof_collector.go @@ -6,12 +6,6 @@ import ( "github.com/ipfs/go-cid" ) -// Proof contains information required for Leaves inclusion validation. -type Proof struct { - Nodes []cid.Cid - Start, End int -} - // proofCollector collects proof nodes' CIDs for the construction of a shares inclusion validation // nmt.Proof. type proofCollector struct { diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 3a86793416..1264412868 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -8,13 +8,13 @@ import ( "net" "time" - "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/share" @@ -136,22 +136,15 @@ func (c *Client) doRequest( func convertToNamespacedShares(rows []*pb.Row) (share.NamespacedShares, error) { shares := make([]share.NamespacedRow, 0, len(rows)) for _, row := range rows { - var proof *ipld.Proof + var proof *nmt.Proof if row.Proof != nil { - cids := make([]cid.Cid, 0, len(row.Proof.Nodes)) - for _, node := range row.Proof.Nodes { - cid, err := cid.Cast(node) - if err != nil { - return nil, fmt.Errorf("casting proofs node to cid: %w", err) - } - cids = append(cids, cid) - } - - proof = &ipld.Proof{ - Nodes: cids, - Start: int(row.Proof.Start), - End: int(row.Proof.End), - } + tmpProof := nmt.NewInclusionProof( + int(row.Proof.Start), + int(row.Proof.End), + row.Proof.Nodes, + ipld.NMTIgnoreMaxNamespace, + ) + proof = &tmpProof } shares = append(shares, share.NamespacedRow{ diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 5a3e965481..fb2429cf93 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -141,16 +141,10 @@ func (srv *Server) respondInternalError(stream network.Stream) { func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNamespaceResponse { rows := make([]*pb.Row, 0, len(shares)) for _, row := range shares { - // construct proof - nodes := make([][]byte, 0, len(row.Proof.Nodes)) - for _, cid := range row.Proof.Nodes { - nodes = append(nodes, cid.Bytes()) - } - proof := &pb.Proof{ - Start: int64(row.Proof.Start), - End: int64(row.Proof.End), - Nodes: nodes, + Start: int64(row.Proof.Start()), + End: int64(row.Proof.End()), + Nodes: row.Proof.Nodes(), } row := &pb.Row{ From ee66f567e8a48fb16247435b7a4447460b588b12 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 27 Mar 2023 10:12:51 +0200 Subject: [PATCH 0489/1008] perf(header): make HeaderSub msgid to be header hash (#1964) This patch attempts to solve the header duplication issue on the ITN network by making the msg-id always equal to the header hash. As a byproduct, this also simplifies the logic for msg-id and avoids additional serialization rounds. The only way to really check if this fixes #1958 is by updating the whole network. --- header/serde.go | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/header/serde.go b/header/serde.go index 510e46e1fc..ea21177c43 100644 --- a/header/serde.go +++ b/header/serde.go @@ -96,37 +96,21 @@ func ProtoToExtendedHeader(pb *header_pb.ExtendedHeader) (*ExtendedHeader, error } // msgID computes an id for a pubsub message -// TODO(@Wondertan): This cause additional allocations per each recvd message in the topic. Find a -// way to avoid those. +// TODO(@Wondertan): This cause additional allocations per each recvd message in the topic +// +// Find a way to avoid those. func MsgID(pmsg *pb.Message) string { mID := func(data []byte) string { hash := blake2b.Sum256(data) return string(hash[:]) } - h, err := UnmarshalExtendedHeader(pmsg.Data) - if err != nil { + h, _ := UnmarshalExtendedHeader(pmsg.Data) + if h == nil || h.RawHeader.ValidateBasic() != nil { // There is nothing we can do about the error, and it will be anyway caught during validation. // We also *have* to return some ID for the msg, so give the hash of even faulty msg return mID(pmsg.Data) } - // IMPORTANT NOTE: - // Due to the nature of the Tendermint consensus, validators don't necessarily collect commit - // signatures from the entire validator set, but only the minimum required amount of them (>2/3 of - // voting power). In addition, signatures are collected asynchronously. Therefore, each validator - // may have a different set of signatures that pass the minimum required voting power threshold, - // causing nondeterminism in the header message gossiped over the network. Subsequently, this - // causes message duplicates as each Bridge Node, connected to a personal validator, sends the - // validator's own view of commits of effectively the same header. - // To solve the problem above, we exclude nondeterministic value from message id calculation - h.Commit.Signatures = nil - - data, err := MarshalExtendedHeader(h) - if err != nil { - // See the note under unmarshalling step - return mID(pmsg.Data) - } - - return mID(data) + return h.Commit.BlockID.String() } From 359f3a61bebcb0b8f0a774bbbb1ff8ddabc27846 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Mar 2023 10:30:01 +0200 Subject: [PATCH 0490/1008] chore(.github/workflows): Bump lint version (#1966) --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index c92f820447..20214acbaf 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -25,7 +25,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3.4.0 with: - version: v1.49.0 + version: v1.50.1 go_mod_tidy_check: name: Go Mod Tidy Check From 305e77f3cef646e8d6af678bed06b7d3ea3c3df2 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 27 Mar 2023 12:07:20 +0200 Subject: [PATCH 0491/1008] chore(header): get back the important notice about msg id (#1967) --- header/serde.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/header/serde.go b/header/serde.go index ea21177c43..f4763e3b3b 100644 --- a/header/serde.go +++ b/header/serde.go @@ -112,5 +112,16 @@ func MsgID(pmsg *pb.Message) string { return mID(pmsg.Data) } + // IMPORTANT NOTE: + // Due to the nature of the Tendermint consensus, validators don't necessarily collect commit + // signatures from the entire validator set, but only the minimum required amount of them (>2/3 of + // voting power). In addition, signatures are collected asynchronously. Therefore, each validator + // may have a different set of signatures that pass the minimum required voting power threshold, + // causing nondeterminism in the header message gossiped over the network. Subsequently, this + // causes message duplicates as each Bridge Node, connected to a personal validator, sends the + // validator's own view of commits of effectively the same header. + // + // To solve the nondeterminism problem above, we don't compute msg id on message body and take + // the actual header hash as an id. return h.Commit.BlockID.String() } From d2773358564aceeab03d1ccff4066a848ba1cf38 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 27 Mar 2023 12:11:58 +0200 Subject: [PATCH 0492/1008] perf(modshare): increase the PeersLimit default (#1968) We expect 750 LN for 200 FN(including BN) and 750/200 ~= 4 And we add plus one because the balancing is randomized and not even --- nodebuilder/share/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 0bc3dcfa30..07276fb2ef 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -32,7 +32,7 @@ type Config struct { func DefaultConfig() Config { return Config{ - PeersLimit: 3, + PeersLimit: 5, DiscoveryInterval: time.Second * 30, AdvertiseInterval: time.Second * 30, UseShareExchange: true, From 0f00701a09921a158a7b973861284c1d1ea4a9f1 Mon Sep 17 00:00:00 2001 From: Samuel Enderwitz <18609909+smuu@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:26:50 +0200 Subject: [PATCH 0493/1008] feat: copy container security improvements from celestia-app (#1913) This is part of: celestiaorg/test-infra#160. Features: Configure Dependabot to see the Dockerfile Use alpine as the parent image Remove platform-specific build instructions. This is solved by a shared workflow Use a non-root user named celestia (UID/GID = 10001). More information on that here Change to default folder /home/celestia Let the code read CELESTIA_HOME environment variable It was tested locally and in the robusta Kubernetes cluster. References: celestia-app PR: docker: Images Security celestia-app#1473 --- .github/dependabot.yml | 7 +++++++ Dockerfile | 46 +++++++++++++++++++++++++++++++++--------- cmd/flags_node.go | 11 +++++++--- docker/entrypoint.sh | 14 ++++++++++--- 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9010c315be..b04d52dee4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,3 +19,10 @@ updates: - renaynay labels: - kind:dependencies + - package-ecosystem: docker + directory: "/" + schedule: + interval: weekly + day: monday + time: "11:00" + open-pull-requests-limit: 10 diff --git a/Dockerfile b/Dockerfile index e8510f7da7..3a7aad8a2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,52 @@ -FROM --platform=$BUILDPLATFORM golang:1.19 as builder +FROM docker.io/golang:1.19.7-alpine3.17 as builder + +# hadolint ignore=DL3018 +RUN apk update && apk add --no-cache \ + bash \ + gcc \ + git \ + make \ + musl-dev + WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . -ARG TARGETOS TARGETARCH -ENV GOOS=$TARGETOS -ENV GOARCH=$TARGETARCH RUN make build && make cel-key -FROM ubuntu:20.04 +FROM docker.io/alpine:3.17.2 + +# Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 +ARG UID=10001 +ARG USER_NAME=celestia + +ENV CELESTIA_HOME=/home/${USER_NAME} + # Default node type can be overwritten in deployment manifest ENV NODE_TYPE bridge ENV P2P_NETWORK mocha -COPY docker/entrypoint.sh / +# hadolint ignore=DL3018 +RUN apk update && apk add --no-cache \ + bash \ + # Creates a user with $UID and $GID=$UID + && adduser ${USER_NAME} \ + -D \ + -g "celestia" \ + -h ${CELESTIA_HOME} \ + -s /sbin/nologin \ + -u ${UID} # Copy in the binary -COPY --from=builder /src/build/celestia / -COPY --from=builder /src/./cel-key / +COPY --from=builder /src/build/celestia /bin/celestia +COPY --from=builder /src/./cel-key /bin/cel-key + +COPY docker/entrypoint.sh /home/celestia/entrypoint.sh + +USER ${USER_NAME} EXPOSE 2121 -ENTRYPOINT ["/entrypoint.sh"] -CMD ["celestia"] +ENTRYPOINT [ "/bin/bash", "/home/celestia/entrypoint.sh" ] +CMD [ "celestia" ] diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 1dcdc46a27..8c73a06169 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -78,9 +78,14 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network // DefaultNodeStorePath constructs the default node store path using the given // node type and network. func DefaultNodeStorePath(tp string, network string) (string, error) { - home, err := os.UserHomeDir() - if err != nil { - return "", err + home := os.Getenv("CELESTIA_HOME") + + if home == "" { + var err error + home, err = os.UserHomeDir() + if err != nil { + return "", err + } } return fmt.Sprintf( "%s/.celestia-%s-%s", diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index f5fcb4c74a..b9f9e77e77 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,11 +1,19 @@ #!/bin/bash -set -e +set -e if [ "$1" = 'celestia' ]; then - ./celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" + echo "Initializing Celestia Node with command:" + echo "celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}"" + echo "" - exec ./"$@" "--" + celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" + + echo "" fi +echo "Starting Celestia Node with command:" +echo "$@" +echo "" + exec "$@" From d400fbad72c0aa4e277adb077ba123f0f297ccae Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:50:45 +0200 Subject: [PATCH 0494/1008] chore(core): Add logging (#1961) Add more logs --- core/exchange.go | 27 ++++++++++++++------------- core/listener.go | 9 +++++---- header/header.go | 19 ++++++++++++------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/core/exchange.go b/core/exchange.go index 156742f0d5..c49e2f0248 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -67,7 +67,8 @@ func (ce *Exchange) GetVerifiedRange( for _, h := range headers { err := from.Verify(h) if err != nil { - return nil, err + return nil, fmt.Errorf("verifying next header against last verified height: %d: %w", + from.Height(), err) } from = h } @@ -78,32 +79,32 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende log.Debugw("requesting header", "hash", hash.String()) block, err := ce.fetcher.GetBlockByHash(ctx, hash) if err != nil { - return nil, err + return nil, fmt.Errorf("fetching block by hash %s: %w", hash.String(), err) } comm, vals, err := ce.fetcher.GetBlockInfo(ctx, &block.Height) if err != nil { - return nil, err + return nil, fmt.Errorf("fetching block info for height %d: %w", &block.Height, err) } // extend block data eds, err := extendBlock(block.Data) if err != nil { - return nil, err + return nil, fmt.Errorf("extending block data for height %d: %w", &block.Height, err) } // construct extended header eh, err := ce.construct(ctx, &block.Header, comm, vals, eds) if err != nil { - return nil, err + return nil, fmt.Errorf("constructing extended header for height %d: %w", &block.Height, err) } // verify hashes match if !bytes.Equal(hash, eh.Hash()) { - return nil, fmt.Errorf("incorrect hash in header: expected %x, got %x", hash, eh.Hash()) + return nil, fmt.Errorf("incorrect hash in header at height %d: expected %x, got %x", + &block.Height, hash, eh.Hash()) } err = storeEDS(ctx, eh.DAH.Hash(), eds, ce.store) if err != nil { - log.Errorw("storing EDS to eds.Store", "err", err) - return nil, err + return nil, fmt.Errorf("storing EDS to eds.Store for height %d: %w", &block.Height, err) } return eh, nil } @@ -116,23 +117,23 @@ func (ce *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64) (*header.ExtendedHeader, error) { b, err := ce.fetcher.GetSignedBlock(ctx, height) if err != nil { - return nil, err + return nil, fmt.Errorf("fetching signed block at height %d from core: %w", *height, err) } + log.Debugw("fetched signed block from core", "height", b.Header.Height) // extend block data eds, err := extendBlock(b.Data) if err != nil { - return nil, err + return nil, fmt.Errorf("extending block data for height %d: %w", b.Header.Height, err) } // create extended header eh, err := ce.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { - return nil, err + return nil, fmt.Errorf("constructing extended header for height %d: %w", b.Header.Height, err) } err = storeEDS(ctx, eh.DAH.Hash(), eds, ce.store) if err != nil { - log.Errorw("storing EDS to eds.Store", "err", err) - return nil, err + return nil, fmt.Errorf("storing EDS to eds.Store for block height %d: %w", b.Header.Height, err) } return eh, nil } diff --git a/core/listener.go b/core/listener.go index 01530eb180..8d160b5db3 100644 --- a/core/listener.go +++ b/core/listener.go @@ -88,27 +88,28 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSigned syncing, err := cl.fetcher.IsSyncing(ctx) if err != nil { - log.Errorw("listener: getting sync state", "err", err) + log.Errorw("listener: getting sync state", "height", b.Header.Height, "err", err) return } // extend block data eds, err := extendBlock(b.Data) if err != nil { - log.Errorw("listener: extending block data", "err", err) + log.Errorw("listener: extending block data", "height", b.Header.Height, "err", err) return } // generate extended header eh, err := cl.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { - log.Errorw("listener: making extended header", "err", err) + log.Errorw("listener: making extended header", "height", b.Header.Height, + "err", err) return } // attempt to store block data if not empty err = storeEDS(ctx, b.Header.DataHash.Bytes(), eds, cl.store) if err != nil { - log.Errorw("listener: storing EDS", "err", err) + log.Errorw("listener: storing EDS", "height", b.Header.Height, "err", err) return } diff --git a/header/header.go b/header/header.go index d1581890b9..ff1f58dd2c 100644 --- a/header/header.go +++ b/header/header.go @@ -114,28 +114,29 @@ func (eh *ExtendedHeader) Equals(header *ExtendedHeader) bool { func (eh *ExtendedHeader) Validate() error { err := eh.RawHeader.ValidateBasic() if err != nil { - return err + return fmt.Errorf("ValidateBasic error on RawHeader at height %d: %w", eh.Height(), err) } err = eh.Commit.ValidateBasic() if err != nil { - return err + return fmt.Errorf("ValidateBasic error on Commit at height %d: %w", eh.Height(), err) + } err = eh.ValidatorSet.ValidateBasic() if err != nil { - return err + return fmt.Errorf("ValidateBasic error on ValidatorSet at height %d: %w", eh.Height(), err) } // make sure the validator set is consistent with the header if valSetHash := eh.ValidatorSet.Hash(); !bytes.Equal(eh.ValidatorsHash, valSetHash) { - return fmt.Errorf("expected validator hash of header to match validator set hash (%X != %X)", - eh.ValidatorsHash, valSetHash, + return fmt.Errorf("expected validator hash of header to match validator set hash (%X != %X) at height %d", + eh.ValidatorsHash, valSetHash, eh.Height(), ) } if err := eh.ValidatorSet.VerifyCommitLight(eh.ChainID(), eh.Commit.BlockID, eh.Height(), eh.Commit); err != nil { - return err + return fmt.Errorf("VerifyCommitLight error at height %d: %w", eh.Height(), err) } // ensure data root from raw header matches computed root @@ -144,7 +145,11 @@ func (eh *ExtendedHeader) Validate() error { "at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash())) } - return eh.DAH.ValidateBasic() + err = eh.DAH.ValidateBasic() + if err != nil { + return fmt.Errorf("ValidateBasic error on DAH at height %d: %w", eh.RawHeader.Height, err) + } + return nil } // MarshalBinary marshals ExtendedHeader to binary. From 9e744c995d299a6a1d23b0f9e627336ba189d53b Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:20:02 +0800 Subject: [PATCH 0495/1008] feat(core/listener): add resubscribe to core listener (#1972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core listener will resubscribe in case of underlying sub close or no new blocks in timeout window (2x blocktime) Resolves https://github.com/celestiaorg/celestia-node/issues/1930 Git dif is messed up somehow. I’ve just extracted all message handling logic into separate function `handleNewSignedBlock` without modifying it. --------- Co-authored-by: Hlib Kanunnikov --- core/listener.go | 136 +++++++++++++++++++++----------- core/listener_test.go | 3 +- nodebuilder/core/module.go | 3 +- nodebuilder/header/mocks/api.go | 3 +- share/ipld/namespace_data.go | 6 +- 5 files changed, 100 insertions(+), 51 deletions(-) diff --git a/core/listener.go b/core/listener.go index 8d160b5db3..2ccba24874 100644 --- a/core/listener.go +++ b/core/listener.go @@ -2,7 +2,9 @@ package core import ( "context" + "errors" "fmt" + "time" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/tendermint/tendermint/types" @@ -30,6 +32,8 @@ type Listener struct { headerBroadcaster libhead.Broadcaster[*header.ExtendedHeader] hashBroadcaster shrexsub.BroadcastFn + listenerTimeout time.Duration + cancel context.CancelFunc } @@ -39,6 +43,7 @@ func NewListener( hashBroadcaster shrexsub.BroadcastFn, construct header.ConstructFn, store *eds.Store, + blocktime time.Duration, ) *Listener { return &Listener{ fetcher: fetcher, @@ -46,6 +51,7 @@ func NewListener( hashBroadcaster: hashBroadcaster, construct: construct, store: store, + listenerTimeout: 2 * blocktime, } } @@ -62,76 +68,116 @@ func (cl *Listener) Start(context.Context) error { if err != nil { return err } - go cl.listen(ctx, sub) + go cl.runSubscriber(ctx, sub) return nil } // Stop stops the listener loop. -func (cl *Listener) Stop(ctx context.Context) error { +func (cl *Listener) Stop(context.Context) error { cl.cancel() cl.cancel = nil - return cl.fetcher.UnsubscribeNewBlockEvent(ctx) + return nil +} + +// runSubscriber runs a subscriber to receive event data of new signed blocks. It will attempt to +// resubscribe in case error happens during listening of subscription +func (cl *Listener) runSubscriber(ctx context.Context, sub <-chan types.EventDataSignedBlock) { + for { + err := cl.listen(ctx, sub) + if ctx.Err() != nil { + // listener stopped because external context was canceled + return + } + log.Warnw("listener: subscriber error, resubscribing...", "err", err) + + err = cl.fetcher.UnsubscribeNewBlockEvent(ctx) + if err != nil { + log.Errorw("listener: unsubscribe error", "err", err) + return + } + + sub, err = cl.fetcher.SubscribeNewBlockEvent(ctx) + if err != nil { + log.Errorw("listener: resubscribe error", "err", err) + return + } + } } // listen kicks off a loop, listening for new block events from Core, // generating ExtendedHeaders and broadcasting them to the header-sub // gossipsub network. -func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSignedBlock) { +func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSignedBlock) error { defer log.Info("listener: listening stopped") + timeout := time.NewTimer(cl.listenerTimeout) + defer timeout.Stop() for { select { case b, ok := <-sub: if !ok { - return - } - log.Debugw("listener: new block from core", "height", b.Header.Height) - - syncing, err := cl.fetcher.IsSyncing(ctx) - if err != nil { - log.Errorw("listener: getting sync state", "height", b.Header.Height, "err", err) - return + return errors.New("underlying subscription was closed") } - // extend block data - eds, err := extendBlock(b.Data) - if err != nil { - log.Errorw("listener: extending block data", "height", b.Header.Height, "err", err) - return - } - // generate extended header - eh, err := cl.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) + log.Debugw("listener: new block from core", "height", b.Header.Height) + err := cl.handleNewSignedBlock(ctx, b) if err != nil { - log.Errorw("listener: making extended header", "height", b.Header.Height, + log.Errorw("listener: handling new block msg", + "height", b.Header.Height, + "hash", b.Header.Hash().String(), "err", err) - return } - // attempt to store block data if not empty - err = storeEDS(ctx, b.Header.DataHash.Bytes(), eds, cl.store) - if err != nil { - log.Errorw("listener: storing EDS", "height", b.Header.Height, "err", err) - return + if !timeout.Stop() { + <-timeout.C } + timeout.Reset(cl.listenerTimeout) + case <-timeout.C: + return errors.New("underlying subscription is stuck") + case <-ctx.Done(): + return ctx.Err() + } + } +} - // notify network of new EDS hash only if core is already synced - if !syncing { - err = cl.hashBroadcaster(ctx, b.Header.DataHash.Bytes()) - if err != nil { - log.Errorw("listener: broadcasting data hash", - "height", b.Header.Height, - "hash", b.Header.Hash(), "err", err) //TODO: hash or datahash? - } - } +func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataSignedBlock) error { + // extend block data + eds, err := extendBlock(b.Data) + if err != nil { + return fmt.Errorf("extending block data: %w", err) + } + // generate extended header + eh, err := cl.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) + if err != nil { + return fmt.Errorf("making extended header: %w", err) + } - // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers - err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) - if err != nil { - log.Errorw("listener: broadcasting next header", - "height", b.Header.Height, - "err", err) - } - case <-ctx.Done(): - return + // attempt to store block data if not empty + err = storeEDS(ctx, b.Header.DataHash.Bytes(), eds, cl.store) + if err != nil { + return fmt.Errorf("storing EDS: %w", err) + } + + syncing, err := cl.fetcher.IsSyncing(ctx) + if err != nil { + return fmt.Errorf("getting sync state: %w", err) + } + + // notify network of new EDS hash only if core is already synced + if !syncing { + err = cl.hashBroadcaster(ctx, b.Header.DataHash.Bytes()) + if err != nil && !errors.Is(err, context.Canceled) { + log.Errorw("listener: broadcasting data hash", + "height", b.Header.Height, + "hash", b.Header.Hash(), "err", err) //TODO: hash or datahash? } } + + // broadcast new ExtendedHeader, but if core is still syncing, notify only local subscribers + err = cl.headerBroadcaster.Broadcast(ctx, eh, pubsub.WithLocalPublication(syncing)) + if err != nil && !errors.Is(err, context.Canceled) { + log.Errorw("listener: broadcasting next header", + "height", b.Header.Height, + "err", err) + } + return nil } diff --git a/core/listener_test.go b/core/listener_test.go index 05f338f013..29ea52c152 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -16,6 +16,7 @@ import ( "github.com/celestiaorg/go-header/p2p" "github.com/celestiaorg/celestia-node/header" + nodep2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -167,7 +168,7 @@ func createListener( require.NoError(t, p2pSub.Stop(ctx)) }) - return NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, store) + return NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, store, nodep2p.BlockTime) } func createEdsPubSub(ctx context.Context, t *testing.T) *shrexsub.PubSub { diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index 243310da6c..02863eae7e 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -43,7 +44,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option construct header.ConstructFn, store *eds.Store, ) *core.Listener { - return core.NewListener(bcast, fetcher, pubsub.Broadcast, construct, store) + return core.NewListener(bcast, fetcher, pubsub.Broadcast, construct, store, p2p.BlockTime) }, fx.OnStart(func(ctx context.Context, listener *core.Listener) error { return listener.Start(ctx) diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index e9d0553581..538169c6be 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index 7e384d0c17..9698ef990d 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -148,9 +148,9 @@ func (n *NamespaceData) Proof() *nmt.Proof { return &proof } -// CollectLeavesByNamespace collects leaves and corresponding proof that could be used to verify leaves -// inclusion. It returns as many leaves from the given root with the given namespace.ID as it can -// retrieve. If no shares are found, it returns error as nil. A +// CollectLeavesByNamespace collects leaves and corresponding proof that could be used to verify +// leaves inclusion. It returns as many leaves from the given root with the given namespace.ID as +// it can retrieve. If no shares are found, it returns error as nil. A // non-nil error means that only partial data is returned, because at least one share retrieval // failed. The following implementation is based on `GetShares`. func (n *NamespaceData) CollectLeavesByNamespace( From c31d6414d6f4968bc41a7ee1c815d527a44de83f Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:58:59 +0800 Subject: [PATCH 0496/1008] feat(nodebuilder/share): enable shrex for light nodes (#1856) ## Overview Enables shrex for lights nodes. Do not log unsupported operations on light nodes (getter.GetShare) Depends on https://github.com/celestiaorg/celestia-node/pull/1855 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- nodebuilder/share/constructors.go | 16 ++++- nodebuilder/share/module.go | 107 +++++++++++++++--------------- share/getters/cascade.go | 8 ++- share/getters/shrex.go | 3 +- share/getters/utils.go | 2 + 5 files changed, 75 insertions(+), 61 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 1394a5c9e8..a8d45f6a40 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -61,14 +61,28 @@ func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { return err } +func lightGetter( + shrexGetter *getters.ShrexGetter, + ipldGetter *getters.IPLDGetter, + cfg Config, +) share.Getter { + var cascade []share.Getter + if cfg.UseShareExchange { + cascade = append(cascade, shrexGetter) + } + cascade = append(cascade, ipldGetter) + return getters.NewCascadeGetter(cascade) +} + func fullGetter( store *eds.Store, + storeGetter *getters.StoreGetter, shrexGetter *getters.ShrexGetter, ipldGetter *getters.IPLDGetter, cfg Config, ) share.Getter { var cascade []share.Getter - cascade = append(cascade, getters.NewStoreGetter(store)) + cascade = append(cascade, storeGetter) if cfg.UseShareExchange { cascade = append(cascade, shrexGetter) } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index d87b2a017d..c99a2d5c9d 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -30,6 +30,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Supply(*cfg), fx.Error(cfgErr), fx.Options(options...), + fx.Provide(newModule), fx.Invoke(func(disc *disc.Discovery) {}), fx.Provide(fx.Annotate( discovery(*cfg), @@ -40,10 +41,15 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return d.Stop(ctx) }), )), - fx.Provide(newModule), + fx.Provide( + func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { + return shrexsub.NewPubSub(ctx, h, network.String()) + }, + ), ) bridgeAndFullComponents := fx.Options( + fx.Provide(getters.NewStoreGetter), fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { @@ -61,7 +67,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option func( host host.Host, store *eds.Store, - getter share.Getter, + getter *getters.StoreGetter, network modp2p.Network, ) (*shrexnd.Server, error) { cfg.ShrExNDParams.WithNetworkID(network.String()) @@ -83,7 +89,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option if err != nil { return err } - return ensureEmptyCARExists(ctx, store) }), fx.OnStop(func(ctx context.Context, store *eds.Store) error { @@ -105,49 +110,41 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { return shrexSub.Broadcast }), + ) + + shrexGetterComponents := fx.Options( + fx.Provide(peers.NewManager), fx.Provide( - func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { - return shrexsub.NewPubSub( - ctx, - h, - network.String(), - ) + func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { + cfg.ShrExNDParams.WithNetworkID(network.String()) + return shrexnd.NewClient(cfg.ShrExNDParams, host) + }, + ), + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { + cfg.ShrExEDSParams.WithNetworkID(network.String()) + return shrexeds.NewClient(cfg.ShrExEDSParams, host) }, ), + fx.Provide(fx.Annotate( + getters.NewShrexGetter, + fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Stop(ctx) + }), + )), ) switch tp { - case node.Light: - return fx.Module( - "share", - baseComponents, - fx.Invoke(share.EnsureEmptySquareExists), - fxutil.ProvideAs(getters.NewIPLDGetter, new(share.Getter)), - // shrexsub broadcaster stub for daser - fx.Provide(func() shrexsub.BroadcastFn { - return func(context.Context, share.DataHash) error { - return nil - } - }), - fx.Provide(fx.Annotate(light.NewShareAvailability)), - // cacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a cacheAvailability but the constructor returns a share.Availability - fx.Provide(cacheAvailability), - ) case node.Bridge: return fx.Module( "share", baseComponents, bridgeAndFullComponents, - fx.Provide(func(store *eds.Store) share.Getter { - return getters.NewStoreGetter(store) - }), - fx.Invoke(func(lc fx.Lifecycle, sub *shrexsub.PubSub) error { - lc.Append(fx.Hook{ - OnStart: sub.Start, - OnStop: sub.Stop, - }) - return nil + fxutil.ProvideAs(func(getter *getters.StoreGetter) share.Getter { + return getter }), ) case node.Full: @@ -155,28 +152,28 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option "share", baseComponents, bridgeAndFullComponents, + shrexGetterComponents, + fx.Provide(getters.NewIPLDGetter), fx.Provide(fullGetter), - fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { - return shrexnd.NewClient(cfg.ShrExNDParams, host) - }, - ), - fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - return shrexeds.NewClient(cfg.ShrExEDSParams, host) - }, - ), - fx.Provide(fx.Annotate( - getters.NewShrexGetter, - fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Stop(ctx) - }), - )), - fx.Provide(peers.NewManager), + ) + case node.Light: + return fx.Module( + "share", + baseComponents, + shrexGetterComponents, + fx.Invoke(share.EnsureEmptySquareExists), fx.Provide(getters.NewIPLDGetter), + fx.Provide(lightGetter), + // shrexsub broadcaster stub for daser + fx.Provide(func() shrexsub.BroadcastFn { + return func(context.Context, share.DataHash) error { + return nil + } + }), + fx.Provide(light.NewShareAvailability), + // cacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a cacheAvailability but the constructor returns a share.Availability + fx.Provide(cacheAvailability), ) default: panic("invalid node type") diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 061d973147..ef616996b9 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -126,9 +126,11 @@ func cascadeGetters[V any]( return val, nil } - // TODO(@Wondertan): migrate to errors.Join once Go1.20 is out! - err = multierr.Append(err, getErr) - span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) + if !errors.Is(getErr, errOperationNotSupported) { + // TODO(@Wondertan): migrate to errors.Join once Go1.20 is out! + err = multierr.Append(err, getErr) + span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) + } } return zero, err } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index b72fe1576b..b8264abcfa 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -2,7 +2,6 @@ package getters import ( "context" - "errors" "fmt" "time" @@ -60,7 +59,7 @@ func (sg *ShrexGetter) Stop(ctx context.Context) error { } func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { - return nil, errors.New("getter/shrex: GetShare is not supported") + return nil, fmt.Errorf("getter/shrex: GetShare %w", errOperationNotSupported) } func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { diff --git a/share/getters/utils.go b/share/getters/utils.go index a82b155241..88804ead84 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -25,6 +25,8 @@ import ( var ( tracer = otel.Tracer("share/getters") log = logging.Logger("share/getters") + + errOperationNotSupported = errors.New("operation is not supported") ) // filterRootsByNamespace returns the row roots from the given share.Root that contain the passed From 09cf0043b199f2a329b71b3895ab32c2b42cece3 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 28 Mar 2023 00:11:45 +0800 Subject: [PATCH 0497/1008] fix(shrex/peer-manager)!: hash validation on subjective node start (#1945) Resolves https://github.com/celestiaorg/celestia-node/issues/1917 --- core/listener.go | 5 +- core/listener_test.go | 6 +- das/coordinator_test.go | 3 +- das/worker.go | 5 +- nodebuilder/share/module.go | 2 +- share/p2p/peers/manager.go | 119 +++++--- share/p2p/peers/manager_test.go | 135 +++++++-- share/p2p/shrexsub/pb/notification.pb.go | 354 +++++++++++++++++++++++ share/p2p/shrexsub/pb/notification.proto | 9 + share/p2p/shrexsub/pubsub.go | 58 +++- share/p2p/shrexsub/pubsub_test.go | 22 +- share/p2p/shrexsub/subscription.go | 17 +- 12 files changed, 643 insertions(+), 92 deletions(-) create mode 100644 share/p2p/shrexsub/pb/notification.pb.go create mode 100644 share/p2p/shrexsub/pb/notification.proto diff --git a/core/listener.go b/core/listener.go index 2ccba24874..82b758a7fc 100644 --- a/core/listener.go +++ b/core/listener.go @@ -164,7 +164,10 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS // notify network of new EDS hash only if core is already synced if !syncing { - err = cl.hashBroadcaster(ctx, b.Header.DataHash.Bytes()) + err = cl.hashBroadcaster(ctx, shrexsub.Notification{ + DataHash: eh.DataHash.Bytes(), + Height: uint64(eh.Height()), + }) if err != nil && !errors.Is(err, context.Canceled) { log.Errorw("listener: broadcasting data hash", "height", b.Header.Height, diff --git a/core/listener_test.go b/core/listener_test.go index 29ea52c152..7d4b12310a 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -101,14 +101,14 @@ func TestListenerWithNonEmptyBlocks(t *testing.T) { for i := 0; i < 16; i++ { _, err := cctx.FillBlock(16, cfg.Accounts, flags.BroadcastBlock) require.NoError(t, err) - hash, err := sub.Next(ctx) + msg, err := sub.Next(ctx) require.NoError(t, err) - if bytes.Equal(empty.Hash(), hash) { + if bytes.Equal(empty.Hash(), msg.DataHash) { continue } - has, err := store.Has(ctx, hash) + has, err := store.Has(ctx, msg.DataHash) require.NoError(t, err) require.True(t, has) } diff --git a/das/coordinator_test.go b/das/coordinator_test.go index e567b6b829..af57c40ca1 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -12,7 +12,6 @@ import ( "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -574,7 +573,7 @@ func defaultTestParams() testParams { func newBroadcastMock(callLimit int) shrexsub.BroadcastFn { var m sync.Mutex - return func(ctx context.Context, hash share.DataHash) error { + return func(ctx context.Context, hash shrexsub.Notification) error { m.Lock() defer m.Unlock() if callLimit == 0 { diff --git a/das/worker.go b/das/worker.go index 6cd70224be..4db618ba90 100644 --- a/das/worker.go +++ b/das/worker.go @@ -133,7 +133,10 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 // notify network about availability of new block data (note: only full nodes can notify) if w.state.isRecentHeader { - err = w.broadcast(ctx, h.DataHash.Bytes()) + err = w.broadcast(ctx, shrexsub.Notification{ + DataHash: h.DataHash.Bytes(), + Height: uint64(h.Height()), + }) if err != nil { log.Warn("failed to broadcast availability message", "height", h.Height(), "hash", h.Hash(), "err", err) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index c99a2d5c9d..cdb7bdaa39 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -166,7 +166,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(lightGetter), // shrexsub broadcaster stub for daser fx.Provide(func() shrexsub.BroadcastFn { - return func(context.Context, share.DataHash) error { + return func(context.Context, shrexsub.Notification) error { return nil } }), diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index f152b7fb3e..d3501b329d 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -49,7 +49,10 @@ type Manager struct { connGater *conngater.BasicConnectionGater // pools collecting peers from shrexSub - pools map[string]*syncPool + pools map[string]*syncPool + // messages from shrex.Sub with height below initialHeight will be ignored, since we don't need to + // track peers for those headers + initialHeight atomic.Uint64 poolValidationTimeout time.Duration peerCooldownTime time.Duration gcInterval time.Duration @@ -75,8 +78,13 @@ type syncPool struct { // isValidatedDataHash indicates if datahash was validated by receiving corresponding extended // header from headerSub isValidatedDataHash atomic.Bool - isSynced atomic.Bool - createdAt time.Time + // headerHeight is the height of header corresponding to syncpool + headerHeight atomic.Uint64 + // isSynced will be true if DoneFunc was called with ResultSynced. It indicates that given datahash + // was synced and peer-manager no longer need to keep peers for it + isSynced atomic.Bool + // createdAt is the syncPool creation time + createdAt time.Time } func NewManager( @@ -111,8 +119,8 @@ func NewManager( discovery.WithOnPeersUpdate( func(peerID peer.ID, isAdded bool) { if isAdded { - if s.peerIsBlacklisted(peerID) { - log.Debugw("got blacklisted from discovery", "peer", peerID) + if s.isBlacklistedPeer(peerID) { + log.Debugw("got blacklisted peer from discovery", "peer", peerID) return } log.Debugw("added to full nodes", "peer", peerID) @@ -131,14 +139,14 @@ func (m *Manager) Start(startCtx context.Context) error { ctx, cancel := context.WithCancel(context.Background()) m.cancel = cancel - err := m.shrexSub.Start(startCtx) + err := m.shrexSub.AddValidator(m.validate) if err != nil { - return fmt.Errorf("starting shrexsub: %w", err) + return fmt.Errorf("registering validator: %w", err) } - err = m.shrexSub.AddValidator(m.validate) + err = m.shrexSub.Start(startCtx) if err != nil { - return fmt.Errorf("registering validator: %w", err) + return fmt.Errorf("starting shrexsub: %w", err) } headerSub, err := m.headerSub.Subscribe() @@ -171,16 +179,13 @@ func (m *Manager) Stop(ctx context.Context) error { func (m *Manager) Peer( ctx context.Context, datahash share.DataHash, ) (peer.ID, DoneFunc, error) { - p := m.getOrCreatePool(datahash.String()) - if p.markValidated() { - log.Debugw("marked validated", "datahash", datahash.String()) - } + p := m.validatedPool(datahash.String()) // first, check if a peer is available for the given datahash peerID, ok := p.tryGet() if ok { // some pools could still have blacklisted peers in storage - if m.peerIsBlacklisted(peerID) { + if m.isBlacklistedPeer(peerID) { log.Debugw("removing blacklisted peer from pool", "hash", datahash.String(), "peer", peerID.String()) p.remove(peerID) @@ -244,39 +249,54 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri log.Errorw("get next header from sub", "err", err) continue } + m.validatedPool(h.DataHash.String()) - if m.getOrCreatePool(h.DataHash.String()).markValidated() { - log.Debugw("marked validated", "datahash", h.DataHash.String()) + // store first header for validation purposes + if m.initialHeight.CompareAndSwap(0, uint64(h.Height())) { + log.Debugw("stored initial height", "height", h.Height()) } } } -// Validate will collect peer.ID into corresponding peer pool -func (m *Manager) validate(ctx context.Context, peerID peer.ID, hash share.DataHash) pubsub.ValidationResult { - if hash.IsEmptyRoot() { - // we don't send empty EDS data hashes, but If someone sent it to us - do hard reject - return pubsub.ValidationReject - } - +// validate will collect peer.ID into corresponding peer pool +func (m *Manager) validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { // messages broadcast from self should bypass the validation with Accept if peerID == m.host.ID() { - log.Debugw("received datahash from self", "datahash", hash.String()) + log.Debugw("received datahash from self", "datahash", msg.DataHash.String()) return pubsub.ValidationAccept } // punish peer for sending invalid hash if it has misbehaved in the past - if m.hashIsBlacklisted(hash) { - log.Debugw("received blacklisted hash, reject validation", "peer", peerID, "datahash", hash.String()) + if m.isBlacklistedHash(msg.DataHash) { + log.Debugw("received blacklisted hash, reject validation", "peer", peerID, "datahash", msg.DataHash.String()) + return pubsub.ValidationReject + } + + if m.isBlacklistedPeer(peerID) { + log.Debugw("received message from blacklisted peer, reject validation", + "peer", peerID, + "datahash", msg.DataHash.String()) return pubsub.ValidationReject } - if m.peerIsBlacklisted(peerID) { - log.Debugw("received message from blacklisted peer, reject validation", "peer", peerID, "datahash", hash.String()) + if msg.Height == 0 { + log.Debugw("received message with 0 height", "peer", peerID) return pubsub.ValidationReject } - m.getOrCreatePool(hash.String()).add(peerID) - log.Debugw("got hash from shrex-sub", "peer", peerID, "datahash", hash.String()) + if msg.Height < m.initialHeight.Load() { + // we can use peers from discovery for headers before the first one from headerSub + // if we allow pool creation for those headers, there is chance the pool will not be validated in + // time and will be false-positively trigger blacklisting of hash and all peers that sent msgs for + // that hash + log.Debugw("received message for past header", "peer", peerID, "datahash", msg.DataHash.String()) + return pubsub.ValidationIgnore + } + + p := m.getOrCreatePool(msg.DataHash.String()) + p.headerHeight.Store(msg.Height) + p.add(peerID) + log.Debugw("got hash from shrex-sub", "peer", peerID, "datahash", msg.DataHash.String()) return pubsub.ValidationIgnore } @@ -313,47 +333,64 @@ func (m *Manager) blacklistPeers(peerIDs ...peer.ID) { } } -func (m *Manager) peerIsBlacklisted(peerID peer.ID) bool { +func (m *Manager) isBlacklistedPeer(peerID peer.ID) bool { return !m.connGater.InterceptPeerDial(peerID) } -func (m *Manager) hashIsBlacklisted(hash share.DataHash) bool { +func (m *Manager) isBlacklistedHash(hash share.DataHash) bool { m.lock.Lock() defer m.lock.Unlock() return m.blacklistedHashes[hash.String()] } +func (m *Manager) validatedPool(hashStr string) *syncPool { + p := m.getOrCreatePool(hashStr) + if p.isValidatedDataHash.CompareAndSwap(false, true) { + log.Debugw("pool marked validated", "datahash", hashStr) + } + return p +} + func (m *Manager) GC(ctx context.Context) { ticker := time.NewTicker(m.gcInterval) defer ticker.Stop() var blacklist []peer.ID for { - blacklist = m.cleanUp() - if len(blacklist) > 0 { - m.blacklistPeers(blacklist...) - } - select { case <-ticker.C: case <-ctx.Done(): return } + + blacklist = m.cleanUp() + if len(blacklist) > 0 { + m.blacklistPeers(blacklist...) + } } } func (m *Manager) cleanUp() []peer.ID { + if m.initialHeight.Load() == 0 { + // can't blacklist peers until initialHeight is set + return nil + } + m.lock.Lock() defer m.lock.Unlock() addToBlackList := make(map[peer.ID]struct{}) for h, p := range m.pools { - if time.Since(p.createdAt) > m.poolValidationTimeout && !p.isValidatedDataHash.Load() { + if !p.isValidatedDataHash.Load() && time.Since(p.createdAt) > m.poolValidationTimeout { + delete(m.pools, h) + if p.headerHeight.Load() < m.initialHeight.Load() { + // outdated pools could still be valid even if not validated, no need to blacklist + continue + } log.Debug("blacklisting datahash with all corresponding peers", "datahash", h, "peer_list", p.peersList) // blacklist hash - delete(m.pools, h) m.blacklistedHashes[h] = true // blacklist peers @@ -377,10 +414,6 @@ func (p *syncPool) markSynced() { atomic.StorePointer(old, unsafe.Pointer(newPool(time.Second))) } -func (p *syncPool) markValidated() bool { - return p.isValidatedDataHash.CompareAndSwap(false, true) -} - func (p *syncPool) add(peers ...peer.ID) { if !p.isSynced.Load() { p.pool.add(peers...) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 7f0c9819e6..38bed888a8 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -28,7 +28,7 @@ import ( // TODO: add broadcast to tests func TestManager(t *testing.T) { - t.Run("validate datahash by headerSub", func(t *testing.T) { + t.Run("validate pool by headerSub", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) t.Cleanup(cancel) @@ -49,7 +49,7 @@ func TestManager(t *testing.T) { stopManager(t, manager) }) - t.Run("validate datahash by shrex.Getter", func(t *testing.T) { + t.Run("validate pool by shrex.Getter", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -60,8 +60,8 @@ func TestManager(t *testing.T) { manager, err := testManager(ctx, headerSub) require.NoError(t, err) - peerID := peer.ID("peer1") - result := manager.validate(ctx, peerID, h.DataHash.Bytes()) + peerID, msg := peer.ID("peer1"), newShrexSubMsg(h) + result := manager.validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) @@ -89,22 +89,24 @@ func TestManager(t *testing.T) { manager, err := testManager(ctx, headerSub) require.NoError(t, err) - result := manager.validate(ctx, manager.host.ID(), h.DataHash.Bytes()) + // own messages should be be accepted + msg := newShrexSubMsg(h) + result := manager.validate(ctx, manager.host.ID(), msg) require.Equal(t, pubsub.ValidationAccept, result) + // normal messages should be ignored peerID := peer.ID("peer1") - result = manager.validate(ctx, peerID, h.DataHash.Bytes()) + result = manager.validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) + // mark peer as misbehaved to blacklist it pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) require.NoError(t, err) require.Equal(t, peerID, pID) - - // mark peer as misbehaved tp blacklist it done(ResultBlacklistPeer) - // misbehaved should be Rejected - result = manager.validate(ctx, pID, h.DataHash.Bytes()) + // new messages from misbehaved peer should be Rejected + result = manager.validate(ctx, pID, msg) require.Equal(t, pubsub.ValidationReject, result) stopManager(t, manager) @@ -121,15 +123,36 @@ func TestManager(t *testing.T) { // start test manager manager, err := testManager(ctx, headerSub) require.NoError(t, err) + require.NoError(t, headerSub.wait(ctx, 1)) - peerID := peer.ID("peer1") - manager.validate(ctx, peerID, h.DataHash.Bytes()) // set syncTimeout to 0 to allow cleanup to find outdated datahash manager.poolValidationTimeout = 0 + // create unvalidated pool + peerID := peer.ID("peer1") + msg := shrexsub.Notification{ + DataHash: share.DataHash("datahash1"), + Height: 2, + } + manager.validate(ctx, peerID, msg) + + // create validated pool + validDataHash := share.DataHash("datahash2") + manager.fullNodes.add("full") // add FN to unblock Peer call + manager.Peer(ctx, validDataHash) //nolint:errcheck + + // trigger cleanup blacklisted := manager.cleanUp() require.Contains(t, blacklisted, peerID) - require.True(t, manager.hashIsBlacklisted(h.DataHash.Bytes())) + + // messages with blacklisted hash should be rejected right away + peerID2 := peer.ID("peer2") + result := manager.validate(ctx, peerID2, msg) + require.Equal(t, pubsub.ValidationReject, result) + + // check blacklisted pools + require.True(t, manager.isBlacklistedHash(msg.DataHash)) + require.False(t, manager.isBlacklistedHash(validDataHash)) }) t.Run("no peers from shrex.Sub, get from discovery", func(t *testing.T) { @@ -210,8 +233,8 @@ func TestManager(t *testing.T) { manager, err := testManager(ctx, headerSub) require.NoError(t, err) - peerID := peer.ID("peer1") - result := manager.validate(ctx, peerID, h.DataHash.Bytes()) + peerID, msg := peer.ID("peer1"), newShrexSubMsg(h) + result := manager.validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) @@ -225,10 +248,74 @@ func TestManager(t *testing.T) { require.True(t, pool.isSynced.Load()) // add peer on synced pool should be noop - result = manager.validate(ctx, "peer2", h.DataHash.Bytes()) + result = manager.validate(ctx, "peer2", msg) require.Equal(t, pubsub.ValidationIgnore, result) require.Len(t, pool.peersList, 0) }) + + t.Run("shrexSub sends a message lower than first headerSub header height, msg first", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + h := testHeader() + h.RawHeader.Height = 100 + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + // unlock headerSub to read first header + require.NoError(t, headerSub.wait(ctx, 1)) + // pool will be created for first headerSub header datahash + require.Len(t, manager.pools, 1) + + // create shrexSub msg with height lower than first header from headerSub + msg := shrexsub.Notification{ + DataHash: share.DataHash("datahash"), + Height: uint64(h.Height() - 1), + } + result := manager.validate(ctx, "peer", msg) + require.Equal(t, pubsub.ValidationIgnore, result) + + // amount of pools should not change + require.Len(t, manager.pools, 1) + }) + + t.Run("shrexSub sends a message lower than first headerSub header height, headerSub first", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + h := testHeader() + h.RawHeader.Height = 100 + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + // create shrexSub msg with height lower than first header from headerSub + msg := shrexsub.Notification{ + DataHash: share.DataHash("datahash"), + Height: uint64(h.Height() - 1), + } + result := manager.validate(ctx, "peer", msg) + require.Equal(t, pubsub.ValidationIgnore, result) + + // unlock header sub after message validator + require.NoError(t, headerSub.wait(ctx, 1)) + // pool will be created for first headerSub header datahash + require.Len(t, manager.pools, 2) + + // trigger cleanup and check that no peers or hashes were blacklisted + manager.poolValidationTimeout = 0 + blacklisted := manager.cleanUp() + require.Len(t, blacklisted, 0) + require.Len(t, manager.blacklistedHashes, 0) + + // outdated pool should be removed + require.Len(t, manager.pools, 1) + }) } func TestIntegration(t *testing.T) { @@ -261,7 +348,10 @@ func TestIntegration(t *testing.T) { // broadcast from BN peerHash := share.DataHash("peer1") - require.NoError(t, bnPubSub.Broadcast(ctx, peerHash)) + require.NoError(t, bnPubSub.Broadcast(ctx, shrexsub.Notification{ + DataHash: peerHash, + Height: 1, + })) // FN should get message peerID, _, err := fnPeerManager.Peer(ctx, peerHash) @@ -394,7 +484,9 @@ func stopManager(t *testing.T, m *Manager) { func testHeader() *header.ExtendedHeader { return &header.ExtendedHeader{ - RawHeader: header.RawHeader{}, + RawHeader: header.RawHeader{ + Height: 1, + }, } } @@ -459,3 +551,10 @@ func (s *subLock) NextHeader(ctx context.Context) (*header.ExtendedHeader, error func (s *subLock) Cancel() { } + +func newShrexSubMsg(h *header.ExtendedHeader) shrexsub.Notification { + return shrexsub.Notification{ + DataHash: h.DataHash.Bytes(), + Height: uint64(h.Height()), + } +} diff --git a/share/p2p/shrexsub/pb/notification.pb.go b/share/p2p/shrexsub/pb/notification.pb.go new file mode 100644 index 0000000000..e154dc62b7 --- /dev/null +++ b/share/p2p/shrexsub/pb/notification.pb.go @@ -0,0 +1,354 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: share/p2p/shrexsub/pb/notification.proto + +package share_p2p_shrex_sub + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type RecentEDSNotification struct { + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + DataHash []byte `protobuf:"bytes,2,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` +} + +func (m *RecentEDSNotification) Reset() { *m = RecentEDSNotification{} } +func (m *RecentEDSNotification) String() string { return proto.CompactTextString(m) } +func (*RecentEDSNotification) ProtoMessage() {} +func (*RecentEDSNotification) Descriptor() ([]byte, []int) { + return fileDescriptor_1a6ade914b560e62, []int{0} +} +func (m *RecentEDSNotification) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RecentEDSNotification) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RecentEDSNotification.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RecentEDSNotification) XXX_Merge(src proto.Message) { + xxx_messageInfo_RecentEDSNotification.Merge(m, src) +} +func (m *RecentEDSNotification) XXX_Size() int { + return m.Size() +} +func (m *RecentEDSNotification) XXX_DiscardUnknown() { + xxx_messageInfo_RecentEDSNotification.DiscardUnknown(m) +} + +var xxx_messageInfo_RecentEDSNotification proto.InternalMessageInfo + +func (m *RecentEDSNotification) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *RecentEDSNotification) GetDataHash() []byte { + if m != nil { + return m.DataHash + } + return nil +} + +func init() { + proto.RegisterType((*RecentEDSNotification)(nil), "share.p2p.shrex.sub.RecentEDSNotification") +} + +func init() { + proto.RegisterFile("share/p2p/shrexsub/pb/notification.proto", fileDescriptor_1a6ade914b560e62) +} + +var fileDescriptor_1a6ade914b560e62 = []byte{ + // 176 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x28, 0xce, 0x48, 0x2c, + 0x4a, 0xd5, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0xce, 0x28, 0x4a, 0xad, 0x28, 0x2e, 0x4d, 0xd2, 0x2f, + 0x48, 0xd2, 0xcf, 0xcb, 0x2f, 0xc9, 0x4c, 0xcb, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, + 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x06, 0xab, 0xd4, 0x2b, 0x30, 0x2a, 0xd0, 0x03, 0xab, 0xd4, + 0x2b, 0x2e, 0x4d, 0x52, 0xf2, 0xe1, 0x12, 0x0d, 0x4a, 0x4d, 0x4e, 0xcd, 0x2b, 0x71, 0x75, 0x09, + 0xf6, 0x43, 0xd2, 0x23, 0x24, 0xc6, 0xc5, 0x96, 0x91, 0x9a, 0x99, 0x9e, 0x51, 0x22, 0xc1, 0xa8, + 0xc0, 0xa8, 0xc1, 0x12, 0x04, 0xe5, 0x09, 0x49, 0x73, 0x71, 0xa6, 0x24, 0x96, 0x24, 0xc6, 0x67, + 0x24, 0x16, 0x67, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xf0, 0x04, 0x71, 0x80, 0x04, 0x3c, 0x12, 0x8b, + 0x33, 0x9c, 0x24, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, + 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0xec, + 0x06, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x99, 0x16, 0xea, 0xc6, 0xaf, 0x00, 0x00, 0x00, +} + +func (m *RecentEDSNotification) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RecentEDSNotification) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RecentEDSNotification) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DataHash) > 0 { + i -= len(m.DataHash) + copy(dAtA[i:], m.DataHash) + i = encodeVarintNotification(dAtA, i, uint64(len(m.DataHash))) + i-- + dAtA[i] = 0x12 + } + if m.Height != 0 { + i = encodeVarintNotification(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintNotification(dAtA []byte, offset int, v uint64) int { + offset -= sovNotification(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *RecentEDSNotification) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovNotification(uint64(m.Height)) + } + l = len(m.DataHash) + if l > 0 { + n += 1 + l + sovNotification(uint64(l)) + } + return n +} + +func sovNotification(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozNotification(x uint64) (n int) { + return sovNotification(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RecentEDSNotification) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNotification + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RecentEDSNotification: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RecentEDSNotification: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNotification + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNotification + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNotification + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthNotification + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataHash = append(m.DataHash[:0], dAtA[iNdEx:postIndex]...) + if m.DataHash == nil { + m.DataHash = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNotification(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthNotification + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipNotification(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNotification + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNotification + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNotification + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthNotification + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupNotification + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthNotification + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthNotification = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowNotification = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupNotification = fmt.Errorf("proto: unexpected end of group") +) diff --git a/share/p2p/shrexsub/pb/notification.proto b/share/p2p/shrexsub/pb/notification.proto new file mode 100644 index 0000000000..d96cf3369e --- /dev/null +++ b/share/p2p/shrexsub/pb/notification.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package share.p2p.shrex.sub; + +message RecentEDSNotification { + uint64 height = 1; + bytes data_hash = 2; +} + diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 4fb56adc51..39ffb144c5 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -10,22 +10,29 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/celestiaorg/celestia-node/share" + pb "github.com/celestiaorg/celestia-node/share/p2p/shrexsub/pb" ) var log = logging.Logger("shrex-sub") // pubsubTopic hardcodes the name of the EDS floodsub topic with the provided networkID. func pubsubTopicID(networkID string) string { - return fmt.Sprintf("%s/eds-sub/v0.0.1", networkID) + return fmt.Sprintf("%s/eds-sub/v0.1.0", networkID) } -// Validator is an injectable func and governs EDS notification or DataHash validity. +// ValidatorFn is an injectable func and governs EDS notification msg validity. // It receives the notification and sender peer and expects the validation result. -// Validator is allowed to be blocking for an indefinite time or until the context is canceled. -type Validator func(context.Context, peer.ID, share.DataHash) pubsub.ValidationResult +// ValidatorFn is allowed to be blocking for an indefinite time or until the context is canceled. +type ValidatorFn func(context.Context, peer.ID, Notification) pubsub.ValidationResult // BroadcastFn aliases the function that broadcasts the DataHash. -type BroadcastFn func(context.Context, share.DataHash) error +type BroadcastFn func(context.Context, Notification) error + +// Notification is the format of message sent by Broadcaster +type Notification struct { + DataHash share.DataHash + Height uint64 +} // PubSub manages receiving and propagating the EDS from/to the network // over "eds-sub" subscription. @@ -80,29 +87,52 @@ func (s *PubSub) Stop(context.Context) error { return s.topic.Close() } -// AddValidator registers given Validator for EDS notifications (DataHash). +// AddValidator registers given ValidatorFn for EDS notifications. // Any amount of Validators can be registered. -func (s *PubSub) AddValidator(validate Validator) error { - return s.pubSub.RegisterTopicValidator(s.pubsubTopic, - func(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - return validate(ctx, p, msg.Data) - }) +func (s *PubSub) AddValidator(v ValidatorFn) error { + return s.pubSub.RegisterTopicValidator(s.pubsubTopic, v.validate) +} + +func (v ValidatorFn) validate(ctx context.Context, p peer.ID, msg *pubsub.Message) pubsub.ValidationResult { + var pbmsg pb.RecentEDSNotification + if err := pbmsg.Unmarshal(msg.Data); err != nil { + log.Debugw("validator: unmarshal error", "err", err) + return pubsub.ValidationReject + } + + n := Notification{ + DataHash: pbmsg.DataHash, + Height: pbmsg.Height, + } + if n.DataHash.IsEmptyRoot() { + // we don't send empty EDS data hashes, but If someone sent it to us - do hard reject + return pubsub.ValidationReject + } + return v(ctx, p, n) } // Subscribe provides a new Subscription for EDS notifications. func (s *PubSub) Subscribe() (*Subscription, error) { if s.topic == nil { - return nil, fmt.Errorf("shrex-push: topic is not started") + return nil, fmt.Errorf("shrex-sub: topic is not started") } return newSubscription(s.topic) } // Broadcast sends the EDS notification (DataHash) to every connected peer. -func (s *PubSub) Broadcast(ctx context.Context, data share.DataHash) error { - if data.IsEmptyRoot() { +func (s *PubSub) Broadcast(ctx context.Context, notification Notification) error { + if notification.DataHash.IsEmptyRoot() { // no need to broadcast datahash of an empty block EDS return nil } + msg := pb.RecentEDSNotification{ + Height: notification.Height, + DataHash: notification.DataHash, + } + data, err := msg.Marshal() + if err != nil { + return fmt.Errorf("shrex-sub: marshal notification, %w", err) + } return s.topic.Publish(ctx, data) } diff --git a/share/p2p/shrexsub/pubsub_test.go b/share/p2p/shrexsub/pubsub_test.go index e766c23837..4198b64379 100644 --- a/share/p2p/shrexsub/pubsub_test.go +++ b/share/p2p/shrexsub/pubsub_test.go @@ -9,7 +9,7 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/share" + pb "github.com/celestiaorg/celestia-node/share/p2p/shrexsub/pb" ) func TestPubSub(t *testing.T) { @@ -30,11 +30,23 @@ func TestPubSub(t *testing.T) { subs, err := pSub2.Subscribe() require.NoError(t, err) - var edsHash share.DataHash = []byte("data") - err = pSub1.topic.Publish(ctx, edsHash, pubsub.WithReadiness(pubsub.MinTopicSize(1))) + notification := Notification{ + DataHash: []byte("data"), + Height: 1, + } + + msg := pb.RecentEDSNotification{ + Height: notification.Height, + DataHash: notification.DataHash, + } + + data, err := msg.Marshal() + require.NoError(t, err) + + err = pSub1.topic.Publish(ctx, data, pubsub.WithReadiness(pubsub.MinTopicSize(1))) require.NoError(t, err) - data, err := subs.Next(ctx) + got, err := subs.Next(ctx) require.NoError(t, err) - require.Equal(t, data, edsHash) + require.Equal(t, notification, got) } diff --git a/share/p2p/shrexsub/subscription.go b/share/p2p/shrexsub/subscription.go index 1e6794a33e..32a3e65e51 100644 --- a/share/p2p/shrexsub/subscription.go +++ b/share/p2p/shrexsub/subscription.go @@ -2,10 +2,11 @@ package shrexsub import ( "context" + "fmt" pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/celestiaorg/celestia-node/share" + pb "github.com/celestiaorg/celestia-node/share/p2p/shrexsub/pb" ) // Subscription is a wrapper over pubsub.Subscription that handles @@ -25,15 +26,23 @@ func newSubscription(t *pubsub.Topic) (*Subscription, error) { // Next blocks the caller until any new EDS DataHash notification arrives. // Returns only notifications which successfully pass validation. -func (subs *Subscription) Next(ctx context.Context) (share.DataHash, error) { +func (subs *Subscription) Next(ctx context.Context) (Notification, error) { msg, err := subs.subscription.Next(ctx) if err != nil { log.Errorw("listening for the next eds hash", "err", err) - return nil, err + return Notification{}, err } log.Debugw("received message", "topic", msg.Message.GetTopic(), "sender", msg.ReceivedFrom) - return msg.Data, nil + var pbmsg pb.RecentEDSNotification + if err := pbmsg.Unmarshal(msg.Data); err != nil { + log.Debugw("unmarshal error", "err", err) + return Notification{}, fmt.Errorf("shrex-sub: unmarshal notification, %w", err) + } + return Notification{ + DataHash: pbmsg.DataHash, + Height: pbmsg.Height, + }, nil } // Cancel stops the subscription. From 167ca92b9a9647fa6893c5948fbb20ccee9f5bec Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 28 Mar 2023 00:51:03 +0800 Subject: [PATCH 0498/1008] feat(core/exchange): parallelise GetRangeByHeight request (#1962) ## Overview 4x faster syncing for BN Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- core/exchange.go | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/core/exchange.go b/core/exchange.go index c49e2f0248..b64b2a1dca 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -4,6 +4,9 @@ import ( "bytes" "context" "fmt" + "time" + + "golang.org/x/sync/errgroup" libhead "github.com/celestiaorg/go-header" @@ -11,6 +14,8 @@ import ( "github.com/celestiaorg/celestia-node/share/eds" ) +const concurrencyLimit = 4 + type Exchange struct { fetcher *BlockFetcher store *eds.Store @@ -42,15 +47,27 @@ func (ce *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( log.Debugw("requesting headers", "from", from, "to", from+amount) headers := make([]*header.ExtendedHeader, amount) - for i := range headers { - extHeader, err := ce.GetByHeight(ctx, from+uint64(i)) - if err != nil { - return nil, err - } - headers[i] = extHeader + start := time.Now() + errGroup, ctx := errgroup.WithContext(ctx) + errGroup.SetLimit(concurrencyLimit) + for i := range headers { + i := i + errGroup.Go(func() error { + extHeader, err := ce.GetByHeight(ctx, from+uint64(i)) + if err != nil { + return err + } + + headers[i] = extHeader + return nil + }) } + if err := errGroup.Wait(); err != nil { + return nil, err + } + log.Debugw("received headers", "from", from, "to", from+amount, "after", time.Since(start)) return headers, nil } From 5421474731bd9589cbc48049304dc527834faaa2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Mar 2023 22:44:08 +0200 Subject: [PATCH 0499/1008] fix(nodebuilder/share): Invoke pubsub for shrex (#1976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 😭 --- nodebuilder/share/module.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index cdb7bdaa39..2fae0e8ac6 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -146,6 +146,13 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fxutil.ProvideAs(func(getter *getters.StoreGetter) share.Getter { return getter }), + fx.Invoke(func(lc fx.Lifecycle, sub *shrexsub.PubSub) error { + lc.Append(fx.Hook{ + OnStart: sub.Start, + OnStop: sub.Stop, + }) + return nil + }), ) case node.Full: return fx.Module( From 7314485e97523e4b18c354ff6f99bd33c066251f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 28 Mar 2023 13:40:54 +0200 Subject: [PATCH 0500/1008] deps: bump go-header (#1980) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 48136882ba..ef34cda210 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.0 - github.com/celestiaorg/go-header v0.2.0 + github.com/celestiaorg/go-header v0.2.2 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index 9cd716835e..1f0f4d2f8b 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SO github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= -github.com/celestiaorg/go-header v0.2.0 h1:UnufpDXQGLpP40SyiwfZLRT7alKLmo3lraPaJtsV8qI= -github.com/celestiaorg/go-header v0.2.0/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= +github.com/celestiaorg/go-header v0.2.2 h1:zyb9bOQFkrpDMZ02w39r3TxXZC4dEkPW9O1P1U9oaWM= +github.com/celestiaorg/go-header v0.2.2/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= From 5acb9a1e5aaa3a29c7f938799e5b0bb20fe9f696 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 28 Mar 2023 19:55:09 +0800 Subject: [PATCH 0501/1008] fix(shrex/peer-manager): Temporarily disable blacklisting in peer manager (#1979) Temporarily disable blacklisting in peer manager until all related issues are fixed --- share/p2p/peers/manager.go | 7 +++++++ share/p2p/peers/manager_test.go | 1 + share/p2p/peers/options.go | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index d3501b329d..7ab6181f54 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -56,6 +56,8 @@ type Manager struct { poolValidationTimeout time.Duration peerCooldownTime time.Duration gcInterval time.Duration + enableBlackListing bool + // fullNodes collects full nodes peer.ID found via discovery fullNodes *pool @@ -106,6 +108,7 @@ func NewManager( poolValidationTimeout: params.ValidationTimeout, peerCooldownTime: params.PeerCooldown, gcInterval: params.GcInterval, + enableBlackListing: params.EnableBlackListing, blacklistedHashes: make(map[string]bool), done: make(chan struct{}), } @@ -318,6 +321,10 @@ func (m *Manager) getOrCreatePool(datahash string) *syncPool { func (m *Manager) blacklistPeers(peerIDs ...peer.ID) { log.Debugw("blacklisting peers", "peers", peerIDs) + + if !m.enableBlackListing { + return + } for _, peerID := range peerIDs { m.fullNodes.remove(peerID) // add peer to the blacklist, so we can't connect to it in the future. diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 38bed888a8..d4f2872935 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -103,6 +103,7 @@ func TestManager(t *testing.T) { pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) require.NoError(t, err) require.Equal(t, peerID, pID) + manager.enableBlackListing = true done(ResultBlacklistPeer) // new messages from misbehaved peer should be Rejected diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index 4dea72e472..4b13a27bbc 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -20,6 +20,9 @@ type Parameters struct { // GcInterval is the interval at which the manager will garbage collect unvalidated pools. GcInterval time.Duration + + // EnableBlackListing turns on blacklisting for misbehaved peers + EnableBlackListing bool } // Validate validates the values in Parameters @@ -51,6 +54,8 @@ func DefaultParameters() Parameters { // the new block before we ask them again. PeerCooldown: 3 * time.Second, GcInterval: time.Second * 30, + // blacklisting is off by default //TODO(@walldiss): enable blacklisting once all related issues are resolved + EnableBlackListing: false, } } @@ -74,3 +79,10 @@ func WithGcInterval(interval time.Duration) Option { manager.gcInterval = interval } } + +// WithEnabledBlacklisting turns on blacklisting of misbehaved peers. +func WithEnabledBlacklisting() Option { + return func(manager *Manager) { + manager.enableBlackListing = true + } +} From be0a7ef0789be6a89da898a198808e415f8ee529 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 28 Mar 2023 20:05:33 +0800 Subject: [PATCH 0502/1008] fix(shrex/peer-manager): put on peer cooldown for peers from discovery (#1981) --- share/p2p/peers/manager.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 7ab6181f54..6c147cbd8c 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -196,7 +196,7 @@ func (m *Manager) Peer( } log.Debugw("returning shrex-sub peer", "hash", datahash.String(), "peer", peerID.String()) - return peerID, m.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID, false), nil } // if no peer for datahash is currently available, try to use full node @@ -204,23 +204,23 @@ func (m *Manager) Peer( peerID, ok = m.fullNodes.tryGet() if ok { log.Debugw("got peer from full nodes discovery pool", "peer", peerID, "datahash", datahash.String()) - return peerID, m.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID, true), nil } // no peers are available right now, wait for the first one select { case peerID = <-p.next(ctx): log.Debugw("got peer from shrexSub pool after wait", "peer", peerID, "datahash", datahash.String()) - return peerID, m.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID, false), nil case peerID = <-m.fullNodes.next(ctx): log.Debugw("got peer from discovery pool after wait", "peer", peerID, "datahash", datahash.String()) - return peerID, m.doneFunc(datahash, peerID), nil + return peerID, m.doneFunc(datahash, peerID, true), nil case <-ctx.Done(): return "", nil, ctx.Err() } } -func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { +func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, fromFull bool) DoneFunc { return func(result result) { log.Debugw("set peer status", "peer", peerID, @@ -232,6 +232,9 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID) DoneFunc { m.getOrCreatePool(datahash.String()).markSynced() case ResultCooldownPeer: m.getOrCreatePool(datahash.String()).putOnCooldown(peerID) + if fromFull { + m.fullNodes.putOnCooldown(peerID) + } case ResultBlacklistPeer: m.blacklistPeers(peerID) } From ef582655342c73384a66314972428b152227e428 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 28 Mar 2023 17:46:22 +0200 Subject: [PATCH 0503/1008] deps: bump go-header (#1982) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ef34cda210..ec28951155 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.0 - github.com/celestiaorg/go-header v0.2.2 + github.com/celestiaorg/go-header v0.2.3 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.14.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index 1f0f4d2f8b..fd518a88ad 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SO github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= -github.com/celestiaorg/go-header v0.2.2 h1:zyb9bOQFkrpDMZ02w39r3TxXZC4dEkPW9O1P1U9oaWM= -github.com/celestiaorg/go-header v0.2.2/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= +github.com/celestiaorg/go-header v0.2.3 h1:41r60OtAeexWC3J3eTELgWfzcdKR2taFlfcJ/2IHZD4= +github.com/celestiaorg/go-header v0.2.3/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= From 7aea507916d6418e99d09257ea134d69b2e037fd Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 29 Mar 2023 21:49:40 +0800 Subject: [PATCH 0504/1008] fix(core/exchange): handle nil height pointer in log (#1984) fix for panic in log --- core/exchange.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/exchange.go b/core/exchange.go index b64b2a1dca..70ca47c56f 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -134,6 +134,9 @@ func (ce *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64) (*header.ExtendedHeader, error) { b, err := ce.fetcher.GetSignedBlock(ctx, height) if err != nil { + if height == nil { + return nil, fmt.Errorf("fetching signed block for head from core: %w", err) + } return nil, fmt.Errorf("fetching signed block at height %d from core: %w", *height, err) } log.Debugw("fetched signed block from core", "height", b.Header.Height) From 350526decfb4f9afbd39bed5d6a491ba9737e513 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 30 Mar 2023 14:58:10 +0200 Subject: [PATCH 0505/1008] fix(modp2p): Peers to return number of connected peers rather than known (#1987) --- nodebuilder/p2p/p2p.go | 4 ++-- nodebuilder/p2p/p2p_test.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nodebuilder/p2p/p2p.go b/nodebuilder/p2p/p2p.go index d146bb63dd..a418eaf4d8 100644 --- a/nodebuilder/p2p/p2p.go +++ b/nodebuilder/p2p/p2p.go @@ -26,7 +26,7 @@ var _ Module = (*API)(nil) type Module interface { // Info returns address information about the host. Info(context.Context) (peer.AddrInfo, error) - // Peers returns all peer IDs used across all inner stores. + // Peers returns connected peers. Peers(context.Context) ([]peer.ID, error) // PeerInfo returns a small slice of information Peerstore has on the // given peer. @@ -110,7 +110,7 @@ func (m *module) Info(context.Context) (peer.AddrInfo, error) { } func (m *module) Peers(context.Context) ([]peer.ID, error) { - return m.host.Peerstore().Peers(), nil + return m.host.Network().Peers(), nil } func (m *module) PeerInfo(_ context.Context, id peer.ID) (peer.AddrInfo, error) { diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index 48941886ee..eaee8429f8 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -12,7 +12,6 @@ import ( libhost "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/metrics" "github.com/libp2p/go-libp2p/core/network" - libpeer "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" @@ -34,7 +33,7 @@ func TestP2PModule_Host(t *testing.T) { // test all methods on `manager.host` peers, err := mgr.Peers(ctx) require.NoError(t, err) - assert.Equal(t, []libpeer.ID(host.Peerstore().Peers()), peers) + assert.Equal(t, host.Network().Peers(), peers) peerInfo, err := mgr.PeerInfo(ctx, peer.ID()) require.NoError(t, err) From 55a2bf75f47f6a4dd5a1d2bd36ca5011142293b8 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 31 Mar 2023 00:28:56 +0800 Subject: [PATCH 0506/1008] chore(.github/workflows): Bump lint version (#1970) --- .github/workflows/go-ci.yml | 2 +- api/gateway/server_test.go | 2 +- cmd/celestia/util.go | 2 +- das/daser_test.go | 4 ++-- das/metrics.go | 2 +- header/header.go | 2 +- header/headertest/testing.go | 11 +++++------ nodebuilder/header/flags.go | 6 +----- nodebuilder/node/admin.go | 4 ++-- nodebuilder/node_test.go | 4 ++-- share/availability/test/corrupt_data.go | 12 ++++++------ share/eds/adapters_test.go | 10 +++++----- share/getters/shrex.go | 2 +- share/getters/testing.go | 5 +---- share/ipld/nmt.go | 6 +++--- share/ipld/test_helpers.go | 2 +- share/p2p/peers/manager.go | 1 + share/p2p/peers/manager_test.go | 2 +- share/test_helpers.go | 4 ++-- state/core_access.go | 2 +- 20 files changed, 39 insertions(+), 46 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 20214acbaf..90f4c2b333 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -25,7 +25,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3.4.0 with: - version: v1.50.1 + version: v1.52.2 go_mod_tidy_check: name: Go Mod Tidy Check diff --git a/api/gateway/server_test.go b/api/gateway/server_test.go index 857b8beef2..e98d7a6091 100644 --- a/api/gateway/server_test.go +++ b/api/gateway/server_test.go @@ -81,7 +81,7 @@ func TestServer_contextLeakProtection(t *testing.T) { type ping struct{} -func (p ping) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (p ping) ServeHTTP(w http.ResponseWriter, _ *http.Request) { //nolint:errcheck w.Write([]byte("pong")) } diff --git a/cmd/celestia/util.go b/cmd/celestia/util.go index 9c7025fae9..a38860d1f7 100644 --- a/cmd/celestia/util.go +++ b/cmd/celestia/util.go @@ -13,7 +13,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, args []string) error { +func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) error { var ( ctx = cmd.Context() err error diff --git a/das/daser_test.go b/das/daser_test.go index c85e656ed5..75ade111be 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -345,7 +345,7 @@ func newBenchGetter() benchGetterStub { DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}} } -func (m benchGetterStub) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { +func (m benchGetterStub) GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) { return m.header, nil } @@ -362,7 +362,7 @@ func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.Exten DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}, nil } -func (m getterStub) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { +func (m getterStub) GetRangeByHeight(context.Context, uint64, uint64) ([]*header.ExtendedHeader, error) { return nil, nil } diff --git a/das/metrics.go b/das/metrics.go index 2a4be8fe84..0fca084732 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -134,7 +134,7 @@ func (m *metrics) observeSample( h *header.ExtendedHeader, sampleTime time.Duration, err error, - isRecentHeader bool, + _ bool, ) { if m == nil { return diff --git a/header/header.go b/header/header.go index ff1f58dd2c..ee757f6ab4 100644 --- a/header/header.go +++ b/header/header.go @@ -68,7 +68,7 @@ var _ libhead.Header = &ExtendedHeader{} // MakeExtendedHeader assembles new ExtendedHeader. func MakeExtendedHeader( - ctx context.Context, + _ context.Context, h *core.Header, comm *core.Commit, vals *core.ValidatorSet, diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 9e2cbe50ce..3ec26fc5db 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -248,7 +248,6 @@ func RandValidator(randPower bool, minPower int64) (*types.Validator, types.Priv privVal := types.NewMockPV() votePower := minPower if randPower { - //nolint:gosec // G404: Use of weak random number generator votePower += int64(rand.Uint32()) } pubKey, err := privVal.GetPubKey() @@ -264,7 +263,7 @@ func RandRawHeader(t *testing.T) *header.RawHeader { return &header.RawHeader{ Version: version.Consensus{Block: 11, App: 1}, ChainID: "test", - Height: mrand.Int63(), + Height: mrand.Int63(), //nolint:gosec Time: time.Now(), LastBlockID: RandBlockID(t), LastCommitHash: tmrand.Bytes(32), @@ -280,7 +279,7 @@ func RandRawHeader(t *testing.T) *header.RawHeader { } // RandBlockID provides a BlockID fixture. -func RandBlockID(t *testing.T) types.BlockID { +func RandBlockID(*testing.T) types.BlockID { bid := types.BlockID{ Hash: make([]byte, 32), PartSetHeader: types.PartSetHeader{ @@ -288,8 +287,8 @@ func RandBlockID(t *testing.T) types.BlockID { Hash: make([]byte, 32), }, } - mrand.Read(bid.Hash) - mrand.Read(bid.PartSetHeader.Hash) + mrand.Read(bid.Hash) //nolint:gosec + mrand.Read(bid.PartSetHeader.Hash) //nolint:gosec return bid } @@ -347,7 +346,7 @@ func (mhs *DummySubscriber) Subscribe() (libhead.Subscription[*header.ExtendedHe return mhs, nil } -func (mhs *DummySubscriber) NextHeader(ctx context.Context) (*header.ExtendedHeader, error) { +func (mhs *DummySubscriber) NextHeader(context.Context) (*header.ExtendedHeader, error) { defer func() { if len(mhs.Headers) > 1 { // pop the already-returned header diff --git a/nodebuilder/header/flags.go b/nodebuilder/header/flags.go index 26e7c105ed..aa4cb093a1 100644 --- a/nodebuilder/header/flags.go +++ b/nodebuilder/header/flags.go @@ -29,11 +29,7 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) error { if err := ParseTrustedHashFlags(cmd, cfg); err != nil { return err } - if err := ParseTrustedPeerFlags(cmd, cfg); err != nil { - return err - } - - return nil + return ParseTrustedPeerFlags(cmd, cfg) } // TrustedPeersFlags returns a set of flags. diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index d86772e94b..9277d64a20 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -38,10 +38,10 @@ func (m *module) LogLevelSet(_ context.Context, name, level string) error { return logging.SetLogLevel(name, level) } -func (m *module) AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) { +func (m *module) AuthVerify(context.Context, string) ([]auth.Permission, error) { return []auth.Permission{}, fmt.Errorf("not implemented") } -func (m *module) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) { +func (m *module) AuthNew(context.Context, []auth.Permission) ([]byte, error) { return nil, fmt.Errorf("not implemented") } diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index e8f8108b31..aa22b0fcc7 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -55,8 +55,8 @@ func TestLifecycle(t *testing.T) { } func TestLifecycle_WithMetrics(t *testing.T) { - url, close := StartMockOtelCollectorHTTPServer(t) - defer close() + url, stop := StartMockOtelCollectorHTTPServer(t) + defer stop() otelCollectorURL := strings.ReplaceAll(url, "http://", "") diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index db6d262d09..c9615cd1d4 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -55,7 +55,7 @@ type FraudulentBlockstore struct { Attacking bool } -func (fb FraudulentBlockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { +func (fb FraudulentBlockstore) Has(context.Context, cid.Cid) (bool, error) { return false, nil } @@ -89,8 +89,8 @@ func (fb FraudulentBlockstore) Put(ctx context.Context, block blocks.Block) erro // create data that doesn't match the CID with arbitrary lengths between 1 and // len(block.RawData())*2 - corrupted := make([]byte, 1+mrand.Int()%(len(block.RawData())*2-1)) - mrand.Read(corrupted) + corrupted := make([]byte, 1+mrand.Int()%(len(block.RawData())*2-1)) //nolint:gosec + mrand.Read(corrupted) //nolint:gosec return fb.Datastore.Put(ctx, ds.NewKey("corrupt"+block.Cid().String()), corrupted) } @@ -104,15 +104,15 @@ func (fb FraudulentBlockstore) PutMany(ctx context.Context, blocks []blocks.Bloc return nil } -func (fb FraudulentBlockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { +func (fb FraudulentBlockstore) DeleteBlock(context.Context, cid.Cid) error { panic("implement me") } -func (fb FraudulentBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { +func (fb FraudulentBlockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) { panic("implement me") } -func (fb FraudulentBlockstore) HashOnRead(enabled bool) { +func (fb FraudulentBlockstore) HashOnRead(bool) { panic("implement me") } diff --git a/share/eds/adapters_test.go b/share/eds/adapters_test.go index 31744aa23e..1b40c9db14 100644 --- a/share/eds/adapters_test.go +++ b/share/eds/adapters_test.go @@ -114,11 +114,11 @@ type rbsMock struct { failed map[cid.Cid]struct{} } -func (r rbsMock) Has(ctx context.Context, cid cid.Cid) (bool, error) { +func (r rbsMock) Has(context.Context, cid.Cid) (bool, error) { panic("implement me") } -func (r rbsMock) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { +func (r rbsMock) Get(_ context.Context, cid cid.Cid) (blocks.Block, error) { // return error for failed items if _, ok := r.failed[cid]; ok { return nil, errors.New("not found") @@ -127,15 +127,15 @@ func (r rbsMock) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { return blocks.NewBlockWithCid(nil, cid) } -func (r rbsMock) GetSize(ctx context.Context, cid cid.Cid) (int, error) { +func (r rbsMock) GetSize(context.Context, cid.Cid) (int, error) { panic("implement me") } -func (r rbsMock) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { +func (r rbsMock) AllKeysChan(context.Context) (<-chan cid.Cid, error) { panic("implement me") } -func (r rbsMock) HashOnRead(enabled bool) { +func (r rbsMock) HashOnRead(bool) { panic("implement me") } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index b8264abcfa..13b58d2771 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -58,7 +58,7 @@ func (sg *ShrexGetter) Stop(ctx context.Context) error { return sg.peerManager.Stop(ctx) } -func (sg *ShrexGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { +func (sg *ShrexGetter) GetShare(context.Context, *share.Root, int, int) (share.Share, error) { return nil, fmt.Errorf("getter/shrex: GetShare %w", errOperationNotSupported) } diff --git a/share/getters/testing.go b/share/getters/testing.go index 040dc6873a..a90b937a51 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -46,10 +46,7 @@ func (seg *SingleEDSGetter) GetEDS(_ context.Context, root *share.Root) (*rsmt2d } // GetSharesByNamespace returns NamespacedShares from a kept EDS if the correct root is given. -func (seg *SingleEDSGetter) GetSharesByNamespace( - ctx context.Context, - root *share.Root, - id namespace.ID, +func (seg *SingleEDSGetter) GetSharesByNamespace(context.Context, *share.Root, namespace.ID, ) (share.NamespacedShares, error) { panic("SingleEDSGetter: GetSharesByNamespace is not implemented") } diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index e3b14cb8c5..854d74587a 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -119,15 +119,15 @@ func (n nmtNode) Links() []*ipld.Link { } } -func (n nmtNode) Resolve(path []string) (interface{}, []string, error) { +func (n nmtNode) Resolve([]string) (interface{}, []string, error) { panic("method not implemented") } -func (n nmtNode) Tree(path string, depth int) []string { +func (n nmtNode) Tree(string, int) []string { panic("method not implemented") } -func (n nmtNode) ResolveLink(path []string) (*ipld.Link, []string, error) { +func (n nmtNode) ResolveLink([]string) (*ipld.Link, []string, error) { panic("method not implemented") } diff --git a/share/ipld/test_helpers.go b/share/ipld/test_helpers.go index 2f16d1529d..8e1707ed89 100644 --- a/share/ipld/test_helpers.go +++ b/share/ipld/test_helpers.go @@ -10,7 +10,7 @@ import ( func RandNamespacedCID(t *testing.T) cid.Cid { raw := make([]byte, NmtHashSize) - _, err := mrand.Read(raw) + _, err := mrand.Read(raw) //nolint:gosec require.NoError(t, err) id, err := CidFromNamespacedSha256(raw) require.NoError(t, err) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 6c147cbd8c..9dc7c05ec8 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -266,6 +266,7 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri // validate will collect peer.ID into corresponding peer pool func (m *Manager) validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { + // messages broadcast from self should bypass the validation with Accept if peerID == m.host.ID() { log.Debugw("received datahash from self", "datahash", msg.DataHash.String()) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index d4f2872935..18c0a82672 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -532,7 +532,7 @@ func (s *subLock) Subscribe() (libhead.Subscription[*header.ExtendedHeader], err return s, nil } -func (s *subLock) AddValidator(f func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult) error { +func (s *subLock) AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult) error { panic("implement me") } diff --git a/share/test_helpers.go b/share/test_helpers.go index 1475ff099b..6700ebd9ef 100644 --- a/share/test_helpers.go +++ b/share/test_helpers.go @@ -53,14 +53,14 @@ func RandShares(t require.TestingT, total int) []Share { shares := make([]Share, total) for i := range shares { nid := make([]byte, Size) - _, err := mrand.Read(nid[:NamespaceSize]) + _, err := mrand.Read(nid[:NamespaceSize]) //nolint:gosec require.NoError(t, err) shares[i] = nid } sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) for i := range shares { - _, err := mrand.Read(shares[i][NamespaceSize:]) + _, err := mrand.Read(shares[i][NamespaceSize:]) //nolint:gosec require.NoError(t, err) } diff --git a/state/core_access.go b/state/core_access.go index 1339c530a6..aed7db3df3 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -180,7 +180,7 @@ func (ca *CoreAccessor) SubmitPayForBlob( return response, err } -func (ca *CoreAccessor) AccountAddress(ctx context.Context) (Address, error) { +func (ca *CoreAccessor) AccountAddress(context.Context) (Address, error) { addr, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err From 52f1af451a256837170739f47549a002975ce2c0 Mon Sep 17 00:00:00 2001 From: Samuel Enderwitz <18609909+smuu@users.noreply.github.com> Date: Thu, 30 Mar 2023 22:56:30 +0200 Subject: [PATCH 0507/1008] fix: changing entrypoint path to /opt (#1988) --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3a7aad8a2b..b92f0d9b48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ RUN apk update && apk add --no-cache \ # Creates a user with $UID and $GID=$UID && adduser ${USER_NAME} \ -D \ - -g "celestia" \ + -g ${USER_NAME} \ -h ${CELESTIA_HOME} \ -s /sbin/nologin \ -u ${UID} @@ -42,11 +42,11 @@ RUN apk update && apk add --no-cache \ COPY --from=builder /src/build/celestia /bin/celestia COPY --from=builder /src/./cel-key /bin/cel-key -COPY docker/entrypoint.sh /home/celestia/entrypoint.sh +COPY --chown=${USER_NAME}:${USER_NAME} docker/entrypoint.sh /opt/entrypoint.sh USER ${USER_NAME} EXPOSE 2121 -ENTRYPOINT [ "/bin/bash", "/home/celestia/entrypoint.sh" ] +ENTRYPOINT [ "/bin/bash", "/opt/entrypoint.sh" ] CMD [ "celestia" ] From 39a21631fd939cb79361dd1e64f8e6b6e5e1ea3b Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 31 Mar 2023 10:32:36 +0200 Subject: [PATCH 0508/1008] fix!(gateway): removing undocumented gateway writes (#1991) This PR removes undocumented, unauthenticated gateway endpoints. Soon, docs will exist for taking these actions over the RPC (probably with the CLI). **Removing:** ``` /transfer /delegate /begin_unbonding /cancel_unbond /begin_redelegate ``` --- api/gateway/endpoints.go | 5 - api/gateway/state.go | 212 --------------------------------------- 2 files changed, 217 deletions(-) diff --git a/api/gateway/endpoints.go b/api/gateway/endpoints.go index c923bb8f5d..dfcb96bd06 100644 --- a/api/gateway/endpoints.go +++ b/api/gateway/endpoints.go @@ -12,11 +12,6 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { http.MethodGet) rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) rpc.RegisterHandlerFunc(submitPFBEndpoint, h.handleSubmitPFB, http.MethodPost) - rpc.RegisterHandlerFunc(transferEndpoint, h.handleTransfer, http.MethodPost) - rpc.RegisterHandlerFunc(delegationEndpoint, h.handleDelegation, http.MethodPost) - rpc.RegisterHandlerFunc(undelegationEndpoint, h.handleUndelegation, http.MethodPost) - rpc.RegisterHandlerFunc(cancelUnbondingEndpoint, h.handleCancelUnbonding, http.MethodPost) - rpc.RegisterHandlerFunc(beginRedelegationEndpoint, h.handleRedelegation, http.MethodPost) // staking queries rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryDelegationEndpoint, addrKey), h.handleQueryDelegation, diff --git a/api/gateway/state.go b/api/gateway/state.go index 3ccaf92e1c..00a318c5ae 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -16,11 +16,6 @@ const ( balanceEndpoint = "/balance" submitTxEndpoint = "/submit_tx" submitPFBEndpoint = "/submit_pfb" - transferEndpoint = "/transfer" - delegationEndpoint = "/delegate" - undelegationEndpoint = "/begin_unbonding" - cancelUnbondingEndpoint = "/cancel_unbond" - beginRedelegationEndpoint = "/begin_redelegate" queryDelegationEndpoint = "/query_delegation" queryUnbondingEndpoint = "/query_unbonding" queryRedelegationsEndpoint = "/query_redelegations" @@ -47,48 +42,6 @@ type submitPFBRequest struct { GasLimit uint64 `json:"gas_limit"` } -type transferRequest struct { - To string `json:"to"` - Amount int64 `json:"amount"` - Fee int64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` -} - -// delegationRequest represents a request for both delegation -// and for beginning and canceling undelegation -type delegationRequest struct { - To string `json:"to"` - Amount int64 `json:"amount"` - Fee int64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` -} - -// redelegationRequest represents a request for redelegation -type redelegationRequest struct { - From string `json:"from"` - To string `json:"to"` - Amount int64 `json:"amount"` - Fee int64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` -} - -// unbondRequest represents a request to begin unbonding -type unbondRequest struct { - From string `json:"from"` - Amount int64 `json:"amount"` - Fee int64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` -} - -// cancelUnbondRequest represents a request to cancel unbonding -type cancelUnbondRequest struct { - From string `json:"from"` - Amount int64 `json:"amount"` - Height int64 `json:"height"` - Fee int64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` -} - // queryRedelegationsRequest represents a request to query redelegations type queryRedelegationsRequest struct { From string `json:"from"` @@ -201,171 +154,6 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } } -func (h *Handler) handleTransfer(w http.ResponseWriter, r *http.Request) { - var req transferRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, transferEndpoint, err) - return - } - addr, err := types.AccAddressFromBech32(req.To) - if err != nil { - // first check if it is a validator address and can be converted - valAddr, err := types.ValAddressFromBech32(req.To) - if err != nil { - writeError(w, http.StatusBadRequest, transferEndpoint, ErrInvalidAddressFormat) - return - } - addr = valAddr.Bytes() - } - amount := types.NewInt(req.Amount) - fee := types.NewInt(req.Fee) - - txResp, err := h.state.Transfer(r.Context(), addr, amount, fee, req.GasLimit) - if err != nil { - writeError(w, http.StatusInternalServerError, transferEndpoint, err) - return - } - resp, err := json.Marshal(txResp) - if err != nil { - writeError(w, http.StatusInternalServerError, transferEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", transferEndpoint, "err", err) - } -} - -func (h *Handler) handleDelegation(w http.ResponseWriter, r *http.Request) { - var req delegationRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, delegationEndpoint, err) - return - } - addr, err := types.ValAddressFromBech32(req.To) - if err != nil { - writeError(w, http.StatusBadRequest, delegationEndpoint, err) - return - } - amount := types.NewInt(req.Amount) - fee := types.NewInt(req.Fee) - - txResp, err := h.state.Delegate(r.Context(), addr, amount, fee, req.GasLimit) - if err != nil { - writeError(w, http.StatusInternalServerError, delegationEndpoint, err) - return - } - resp, err := json.Marshal(txResp) - if err != nil { - writeError(w, http.StatusInternalServerError, delegationEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", delegationEndpoint, "err", err) - } -} - -func (h *Handler) handleUndelegation(w http.ResponseWriter, r *http.Request) { - var req unbondRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, undelegationEndpoint, err) - return - } - addr, err := types.ValAddressFromBech32(req.From) - if err != nil { - writeError(w, http.StatusBadRequest, undelegationEndpoint, err) - return - } - amount := types.NewInt(req.Amount) - fee := types.NewInt(req.Fee) - - txResp, err := h.state.Undelegate(r.Context(), addr, amount, fee, req.GasLimit) - if err != nil { - writeError(w, http.StatusInternalServerError, undelegationEndpoint, err) - return - } - resp, err := json.Marshal(txResp) - if err != nil { - writeError(w, http.StatusInternalServerError, undelegationEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", undelegationEndpoint, "err", err) - } -} - -func (h *Handler) handleCancelUnbonding(w http.ResponseWriter, r *http.Request) { - var req cancelUnbondRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, err) - return - } - addr, err := types.ValAddressFromBech32(req.From) - if err != nil { - writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, err) - return - } - amount := types.NewInt(req.Amount) - height := types.NewInt(req.Height) - fee := types.NewInt(req.Fee) - txResp, err := h.state.CancelUnbondingDelegation(r.Context(), addr, amount, height, fee, req.GasLimit) - if err != nil { - writeError(w, http.StatusInternalServerError, cancelUnbondingEndpoint, err) - return - } - resp, err := json.Marshal(txResp) - if err != nil { - writeError(w, http.StatusInternalServerError, cancelUnbondingEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", cancelUnbondingEndpoint, "err", err) - } -} - -func (h *Handler) handleRedelegation(w http.ResponseWriter, r *http.Request) { - var req redelegationRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) - return - } - srcAddr, err := types.ValAddressFromBech32(req.From) - if err != nil { - writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) - return - } - dstAddr, err := types.ValAddressFromBech32(req.To) - if err != nil { - writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) - return - } - amount := types.NewInt(req.Amount) - fee := types.NewInt(req.Fee) - - txResp, err := h.state.BeginRedelegate(r.Context(), srcAddr, dstAddr, amount, fee, req.GasLimit) - if err != nil { - writeError(w, http.StatusInternalServerError, beginRedelegationEndpoint, err) - return - } - resp, err := json.Marshal(txResp) - if err != nil { - writeError(w, http.StatusInternalServerError, beginRedelegationEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", beginRedelegationEndpoint, "err", err) - } -} - func (h *Handler) handleQueryDelegation(w http.ResponseWriter, r *http.Request) { // read and parse request vars := mux.Vars(r) From f18b1ab2da4fc732ba55147a609d0eb26df3ac54 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 31 Mar 2023 10:41:12 +0200 Subject: [PATCH 0509/1008] fix(edsstore): close files after Put and Get (#1994) Self explanatory --- share/eds/store.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/eds/store.go b/share/eds/store.go index 46a416df52..993257bb22 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -173,6 +173,7 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext if err != nil { return err } + defer f.Close() err = WriteEDS(ctx, square, f) if err != nil { @@ -367,6 +368,7 @@ func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.Exten if err != nil { return nil, fmt.Errorf("failed to get CAR file: %w", err) } + defer f.Close() eds, err = ReadEDS(ctx, f, root) if err != nil { return nil, fmt.Errorf("failed to read EDS from CAR file: %w", err) From 6aacc7b0a7dc2ae913c15297791a64c57903fc1f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:07:17 +0200 Subject: [PATCH 0510/1008] (api/docgen): Example types (#1921) Replaces #1408, closes #1358 --------- Co-authored-by: Ryan Co-authored-by: Ryan --- api/docgen/exampledata/extendedHeader.json | 77 ++++++++ .../exampledata/resourceManagerStats.json | 88 +++++++++ api/docgen/exampledata/samplingStats.json | 90 +++++++++ api/docgen/exampledata/txResponse.json | 187 ++++++++++++++++++ api/docgen/examples.go | 131 +++++++++++- api/docgen/openrpc.go | 28 ++- api/rpc/client/client.go | 26 +-- cmd/docgen/{docgen.go => openrpc.go} | 8 +- nodebuilder/node/admin.go | 6 +- 9 files changed, 600 insertions(+), 41 deletions(-) create mode 100644 api/docgen/exampledata/extendedHeader.json create mode 100644 api/docgen/exampledata/resourceManagerStats.json create mode 100644 api/docgen/exampledata/samplingStats.json create mode 100644 api/docgen/exampledata/txResponse.json rename cmd/docgen/{docgen.go => openrpc.go} (86%) diff --git a/api/docgen/exampledata/extendedHeader.json b/api/docgen/exampledata/extendedHeader.json new file mode 100644 index 0000000000..5da16246c1 --- /dev/null +++ b/api/docgen/exampledata/extendedHeader.json @@ -0,0 +1,77 @@ +{ + "header": { + "version": { + "block": "11" + }, + "chain_id": "arabica-6", + "height": "67374", + "time": "2023-02-25T12:10:28.067566292Z", + "last_block_id": { + "hash": "47A2C7758760988500B2F043D3903BBBF1C8B383CA33CF7056AA45E22055663E", + "parts": { + "total": 1, + "hash": "33B012F244E27672169DD3D62CDBC92DA9486E410A5530F41FE6A890D8E2EE42" + } + }, + "last_commit_hash": "888D47F5E9473501C99F2B6136B6B9FFBC9D1CD2F54002BCD5DF002FFEF0A83D", + "data_hash": "257760461993F8F197B421EC7435F3C36C3734923E3DA9A42DC73B05F07B3D08", + "validators_hash": "883A0C92B8D976312B249C1397E73CF2981A9EB715717CBEE3800B8380C22C1D", + "next_validators_hash": "883A0C92B8D976312B249C1397E73CF2981A9EB715717CBEE3800B8380C22C1D", + "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", + "app_hash": "1FC70854A185737C7FD720FCCE9167876EE4B9ABE23DB1EBB8C552D3E3978435", + "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "evidence_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + "proposer_address": "57DC09D28388DBF977CFC30EF50BE8B644CCC1FA" + }, + "validator_set": { + "validators": [ + { + "address": "57DC09D28388DBF977CFC30EF50BE8B644CCC1FA", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "aoB4xU9//HAqOP9ciyp0+PTdZxt/UGKgZOabU6JxW8o=" + }, + "voting_power": "5000000000", + "proposer_priority": "0" + } + ], + "proposer": { + "address": "57DC09D28388DBF977CFC30EF50BE8B644CCC1FA", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "aoB4xU9//HAqOP9ciyp0+PTdZxt/UGKgZOabU6JxW8o=" + }, + "voting_power": "5000000000", + "proposer_priority": "0" + } + }, + "commit": { + "height": 67374, + "round": 0, + "block_id": { + "hash": "A7F6B1CF33313121539206754A73FDC22ADA48C4AA8C4BB4F707ED2E089E59D3", + "parts": { + "total": 1, + "hash": "6634FE1E1DDDCB9914ACE81F146013986F5FDA03A8F1C16DC5ECA0D9B0E08FBC" + } + }, + "signatures": [ + { + "block_id_flag": 2, + "validator_address": "57DC09D28388DBF977CFC30EF50BE8B644CCC1FA", + "timestamp": "2023-02-25T12:10:38.130121476Z", + "signature": "HyR/uRIUNc5GNqQteZyrVjJM47SI9sRAgrLsNqJDls3AzbvHUfN4zzWyw0afyEvNm98Bm2GIoJoZC5D8oQvdBA==" + } + ] + }, + "dah": { + "row_roots": [ + "//////////7//////////ql+/VFmJ8PWE9BcjrTDLrY/hzVeGdzFCpfEhiXDXZmt", + "/////////////////////zHeGnUtPJn8QyPpePSYl4qRVrcUvG2fwptyoA85Myik" + ], + "column_roots": [ + "//////////7//////////ql+/VFmJ8PWE9BcjrTDLrY/hzVeGdzFCpfEhiXDXZmt", + "/////////////////////zHeGnUtPJn8QyPpePSYl4qRVrcUvG2fwptyoA85Myik" + ] + } +} \ No newline at end of file diff --git a/api/docgen/exampledata/resourceManagerStats.json b/api/docgen/exampledata/resourceManagerStats.json new file mode 100644 index 0000000000..2b85465871 --- /dev/null +++ b/api/docgen/exampledata/resourceManagerStats.json @@ -0,0 +1,88 @@ +{ + "System": { + "NumStreamsInbound": 4, + "NumStreamsOutbound": 13, + "NumConnsInbound": 0, + "NumConnsOutbound": 13, + "NumFD": 7, + "Memory": 4456448 + }, + "Transient": { + "NumStreamsInbound": 0, + "NumStreamsOutbound": 0, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + }, + "Services": { + "libp2p.autonat": { + "NumStreamsInbound": 0, + "NumStreamsOutbound": 0, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + }, + "libp2p.identify": { + "NumStreamsInbound": 0, + "NumStreamsOutbound": 0, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + } + }, + "Protocols": { + "/celestia/arabica-3/ipfs/bitswap/1.2.0": { + "NumStreamsInbound": 0, + "NumStreamsOutbound": 4, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + }, + "/celestia/arabica-3/kad/1.0.0": { + "NumStreamsInbound": 0, + "NumStreamsOutbound": 4, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + }, + "/floodsub/1.0.0": { + "NumStreamsInbound": 2, + "NumStreamsOutbound": 0, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + }, + "/ipfs/id/1.0.0": { + "NumStreamsInbound": 0, + "NumStreamsOutbound": 1, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + }, + "/meshsub/1.1.0": { + "NumStreamsInbound": 2, + "NumStreamsOutbound": 4, + "NumConnsInbound": 0, + "NumConnsOutbound": 0, + "NumFD": 0, + "Memory": 0 + } + }, + "Peers": { + "12D3KooWPRb5h3g9MH7sx9qfbSQZG5cXv1a2Qs3o4aW5YmmzPq82": { + "NumStreamsInbound": 1, + "NumStreamsOutbound": 3, + "NumConnsInbound": 0, + "NumConnsOutbound": 3, + "NumFD": 3, + "Memory": 1048576 + } + } +} diff --git a/api/docgen/exampledata/samplingStats.json b/api/docgen/exampledata/samplingStats.json new file mode 100644 index 0000000000..77bf5203e1 --- /dev/null +++ b/api/docgen/exampledata/samplingStats.json @@ -0,0 +1,90 @@ +{ + "head_of_sampled_chain": 27499, + "head_of_catchup": 29101, + "network_head_height": 30483, + "workers": [ + { + "current": 28806, + "from": 28802, + "to": 28901 + }, + { + "current": 28906, + "from": 28902, + "to": 29001 + }, + { + "current": 27794, + "from": 27702, + "to": 27801 + }, + { + "current": 28191, + "from": 28102, + "to": 28201 + }, + { + "current": 28420, + "from": 28402, + "to": 28501 + }, + { + "current": 28334, + "from": 28302, + "to": 28401 + }, + { + "current": 27691, + "from": 27602, + "to": 27701 + }, + { + "current": 27889, + "from": 27802, + "to": 27901 + }, + { + "current": 27990, + "from": 27902, + "to": 28001 + }, + { + "current": 28293, + "from": 28202, + "to": 28301 + }, + { + "current": 28092, + "from": 28002, + "to": 28101 + }, + { + "current": 29004, + "from": 29002, + "to": 29101 + }, + { + "current": 28708, + "from": 28702, + "to": 28801 + }, + { + "current": 28513, + "from": 28502, + "to": 28601 + }, + { + "current": 27500, + "from": 27402, + "to": 27501 + }, + { + "current": 28615, + "from": 28602, + "to": 28701 + } + ], + "concurrency": 16, + "catch_up_done": false, + "is_running": true +} \ No newline at end of file diff --git a/api/docgen/exampledata/txResponse.json b/api/docgen/exampledata/txResponse.json new file mode 100644 index 0000000000..c03731097f --- /dev/null +++ b/api/docgen/exampledata/txResponse.json @@ -0,0 +1,187 @@ +{ + "height": 30497, + "txhash": "05D9016060072AA71B007A6CFB1B895623192D6616D513017964C3BFCD047282", + "data": "12260A242F636F736D6F732E62616E6B2E763162657461312E4D736753656E64526573706F6E7365", + "raw_log": "[{\"msg_index\":0,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"celestia12les8l8gzsacjjxwum9wdy7me8x9xajqch4gyw\"},{\"key\":\"amount\",\"value\":\"30utia\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h\"},{\"key\":\"amount\",\"value\":\"30utia\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"celestia12les8l8gzsacjjxwum9wdy7me8x9xajqch4gyw\"},{\"key\":\"sender\",\"value\":\"celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h\"},{\"key\":\"amount\",\"value\":\"30utia\"}]}]}]", + "logs": [ + { + "msg_index": 0, + "events": [ + { + "type": "coin_received", + "attributes": [ + { + "key": "receiver", + "value": "celestia12les8l8gzsacjjxwum9wdy7me8x9xajqch4gyw" + }, + { + "key": "amount", + "value": "30utia" + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "spender", + "value": "celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h" + }, + { + "key": "amount", + "value": "30utia" + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "action", + "value": "/cosmos.bank.v1beta1.MsgSend" + }, + { + "key": "sender", + "value": "celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h" + }, + { + "key": "module", + "value": "bank" + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "recipient", + "value": "celestia12les8l8gzsacjjxwum9wdy7me8x9xajqch4gyw" + }, + { + "key": "sender", + "value": "celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h" + }, + { + "key": "amount", + "value": "30utia" + } + ] + } + ] + } + ], + "gas_wanted": 10000000, + "gas_used": 69085, + "events": [ + { + "type": "tx", + "attributes": [ + { + "key": "ZmVl", + "value": null, + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "YWNjX3NlcQ==", + "value": "Y2VsZXN0aWExMzc3azVhbjNmOTR2Nnd5YWNldTBjZjRucTZnazJqdHBjNDZnN2gvMA==", + "index": true + } + ] + }, + { + "type": "tx", + "attributes": [ + { + "key": "c2lnbmF0dXJl", + "value": "R3NlVjhGNThFNGphR05LU0NicDBvNmRILytKK3BNQjNvUmtoNVpKNE8rVjdvNVVYQkJNNXpmNkdiYnN6OW9Takc1OUZkSHJRYzFvVVVBbnRBZW1wV0E9PQ==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==", + "index": true + } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { + "key": "c3BlbmRlcg==", + "value": "Y2VsZXN0aWExMzc3azVhbjNmOTR2Nnd5YWNldTBjZjRucTZnazJqdHBjNDZnN2g=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzB1dGlh", + "index": true + } + ] + }, + { + "type": "coin_received", + "attributes": [ + { + "key": "cmVjZWl2ZXI=", + "value": "Y2VsZXN0aWExMmxlczhsOGd6c2Fjamp4d3VtOXdkeTdtZTh4OXhhanFjaDRneXc=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzB1dGlh", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "Y2VsZXN0aWExMmxlczhsOGd6c2Fjamp4d3VtOXdkeTdtZTh4OXhhanFjaDRneXc=", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "Y2VsZXN0aWExMzc3azVhbjNmOTR2Nnd5YWNldTBjZjRucTZnazJqdHBjNDZnN2g=", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MzB1dGlh", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "Y2VsZXN0aWExMzc3azVhbjNmOTR2Nnd5YWNldTBjZjRucTZnazJqdHBjNDZnN2g=", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + } + ] +} \ No newline at end of file diff --git a/api/docgen/examples.go b/api/docgen/examples.go index d59ae43749..215d59dd2d 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -1,26 +1,137 @@ package docgen import ( + _ "embed" + "encoding/json" "fmt" "reflect" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" + "github.com/multiformats/go-multiaddr" "golang.org/x/text/cases" "golang.org/x/text/language" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" + "github.com/celestiaorg/celestia-node/state" ) +//go:embed "exampledata/extendedHeader.json" +var exampleExtendedHeader string + +//go:embed "exampledata/samplingStats.json" +var exampleSamplingStats string + +//go:embed "exampledata/txResponse.json" +var exampleTxResponse string + +//go:embed "exampledata/resourceManagerStats.json" +var exampleResourceMngrStats string + var ExampleValues = map[reflect.Type]interface{}{ - reflect.TypeOf(""): "string value", - reflect.TypeOf(uint64(42)): uint64(42), - reflect.TypeOf(uint32(42)): uint32(42), - // reflect.TypeOf(int32(42)): int32(42), - reflect.TypeOf(int64(42)): int64(42), - reflect.TypeOf(byte(7)): byte(7), - reflect.TypeOf(float64(42)): float64(42), - reflect.TypeOf(true): true, - reflect.TypeOf([]byte{}): []byte("byte array"), - reflect.TypeOf(byzantine.BadEncoding): byzantine.BadEncoding, + reflect.TypeOf(""): "string value", + reflect.TypeOf(uint64(42)): uint64(42), + reflect.TypeOf(uint32(42)): uint32(42), + reflect.TypeOf(int32(42)): int32(42), + reflect.TypeOf(int64(42)): int64(42), + reflect.TypeOf(42): 42, + reflect.TypeOf(byte(7)): byte(7), + reflect.TypeOf(float64(42)): float64(42), + reflect.TypeOf(true): true, + reflect.TypeOf([]byte{}): []byte("byte array"), + reflect.TypeOf(node.Full): node.Full, + reflect.TypeOf(auth.Permission("admin")): auth.Permission("admin"), + reflect.TypeOf(byzantine.BadEncoding): byzantine.BadEncoding, + reflect.TypeOf((*fraud.Proof)(nil)).Elem(): byzantine.CreateBadEncodingProof( + []byte("bad encoding proof"), + 42, + &byzantine.ErrByzantine{ + Index: 0, + Axis: rsmt2d.Axis(0), + Shares: []*byzantine.ShareWithProof{}, + }, + ), + reflect.TypeOf((*error)(nil)).Elem(): fmt.Errorf("error"), +} + +func init() { + addToExampleValues(share.EmptyExtendedDataSquare()) + addr, err := sdk.AccAddressFromBech32("celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h") + if err != nil { + panic(err) + } + addToExampleValues(addr) + ExampleValues[reflect.TypeOf((*sdk.Address)(nil)).Elem()] = addr + + valAddr, err := sdk.ValAddressFromBech32("celestiavaloper1q3v5cugc8cdpud87u4zwy0a74uxkk6u4q4gx4p") + if err != nil { + panic(err) + } + addToExampleValues(valAddr) + + var txResponse *state.TxResponse + err = json.Unmarshal([]byte(exampleTxResponse), &txResponse) + if err != nil { + panic(err) + } + + var samplingStats das.SamplingStats + err = json.Unmarshal([]byte(exampleSamplingStats), &samplingStats) + if err != nil { + panic(err) + } + + var extendedHeader *header.ExtendedHeader + err = json.Unmarshal([]byte(exampleExtendedHeader), &extendedHeader) + if err != nil { + panic(err) + } + + var resourceMngrStats rcmgr.ResourceManagerStat + err = json.Unmarshal([]byte(exampleResourceMngrStats), &resourceMngrStats) + if err != nil { + panic(err) + } + + addToExampleValues(txResponse) + addToExampleValues(samplingStats) + addToExampleValues(extendedHeader) + addToExampleValues(resourceMngrStats) + + mathInt, _ := math.NewIntFromString("42") + addToExampleValues(mathInt) + + addToExampleValues(network.Connected) + addToExampleValues(network.ReachabilityPrivate) + + pID := protocol.ID("/celestia/mocha/ipfs/bitswap") + addToExampleValues(pID) + + peerID := peer.ID("12D3KooWPRb5h3g9MH7sx9qfbSQZG5cXv1a2Qs3o4aW5YmmzPq82") + addToExampleValues(peerID) + + ma, _ := multiaddr.NewMultiaddr("/ip6/::1/udp/2121/quic-v1") + addrInfo := peer.AddrInfo{ + ID: peerID, + Addrs: []multiaddr.Multiaddr{ma}, + } + addToExampleValues(addrInfo) +} + +func addToExampleValues(v interface{}) { + ExampleValues[reflect.TypeOf(v)] = v } func ExampleValue(t, parent reflect.Type) (interface{}, error) { diff --git a/api/docgen/openrpc.go b/api/docgen/openrpc.go index c61e22d543..737b491a89 100644 --- a/api/docgen/openrpc.go +++ b/api/docgen/openrpc.go @@ -15,10 +15,12 @@ import ( "github.com/alecthomas/jsonschema" go_openrpc_reflect "github.com/etclabscore/go-openrpc-reflect" meta_schema "github.com/open-rpc/meta-schema" + + "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) const ( - APIVersion = "v0.1.0" APIDescription = "The Celestia Node API is the collection of RPC methods that " + "can be used to interact with the services provided by Celestia Data Availability Nodes." APIName = "Celestia Node API" @@ -52,9 +54,10 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { type Comments = map[string]string -func ParseCommentsFromNodebuilderModules(moduleNames ...string) Comments { +func ParseCommentsFromNodebuilderModules(moduleNames ...string) (Comments, Comments) { fset := token.NewFileSet() nodeComments := make(Comments) + permComments := make(Comments) for _, moduleName := range moduleNames { fileName := fmt.Sprintf("nodebuilder/%s/%s.go", moduleName, moduleName) f, err := parser.ParseFile(fset, fileName, nil, parser.AllErrors|parser.ParseComments) @@ -75,11 +78,19 @@ func ParseCommentsFromNodebuilderModules(moduleNames ...string) Comments { nodeComments[moduleName+mn] = filteredComments[0].Text() } } + + module := reflect.TypeOf(client.Modules[moduleName]).Elem() + var meth reflect.StructField + for i := 0; i < module.NumField(); i++ { + meth = module.Field(i) + perms := meth.Tag.Get("perm") + permComments[meth.Name] = perms + } } - return nodeComments + return nodeComments, permComments } -func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { +func NewOpenRPCDocument(comments Comments, permissions Comments) *go_openrpc_reflect.Document { d := &go_openrpc_reflect.Document{} d.WithMeta(&go_openrpc_reflect.MetaT{ @@ -93,7 +104,7 @@ func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { title := APIName info.Title = (*meta_schema.InfoObjectProperties)(&title) - version := APIVersion + version := node.APIVersion info.Version = (*meta_schema.InfoObjectVersion)(&version) description := APIDescription @@ -113,10 +124,6 @@ func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { appReflector := &go_openrpc_reflect.EthereumReflectorT{} - appReflector.FnSchemaTypeMap = func() func(ty reflect.Type) *jsonschema.Type { - return OpenRPCSchemaTypeMapper - } - appReflector.FnGetMethodExternalDocs = func( r reflect.Value, m reflect.Method, @@ -155,6 +162,9 @@ func NewOpenRPCDocument(comments Comments) *go_openrpc_reflect.Document { // remove the default implementation from the method descriptions appReflector.FnGetMethodDescription = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + if v, ok := permissions[m.Name]; ok { + return "Auth level: " + v, nil + } return "", nil // noComment } diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 04fe84c8f0..783d3f53cd 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -17,6 +17,18 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) +// TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 +var client Client +var Modules = map[string]interface{}{ + "share": &client.Share.Internal, + "state": &client.State.Internal, + "header": &client.Header.Internal, + "fraud": &client.Fraud.Internal, + "das": &client.DAS.Internal, + "p2p": &client.P2P.Internal, + "node": &client.Node.Internal, +} + type Client struct { Fraud fraud.API Header header.API @@ -64,20 +76,8 @@ func NewClient(ctx context.Context, addr string, token string) (*Client, error) } func newClient(ctx context.Context, addr string, authHeader http.Header) (*Client, error) { - var client Client var multiCloser multiClientCloser - - // TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 - var modules = map[string]interface{}{ - "share": &client.Share.Internal, - "state": &client.State.Internal, - "header": &client.Header.Internal, - "fraud": &client.Fraud.Internal, - "das": &client.DAS.Internal, - "p2p": &client.P2P.Internal, - "node": &client.Node.Internal, - } - for name, module := range modules { + for name, module := range Modules { closer, err := jsonrpc.NewClient(ctx, addr, name, module, authHeader) if err != nil { return nil, err diff --git a/cmd/docgen/docgen.go b/cmd/docgen/openrpc.go similarity index 86% rename from cmd/docgen/docgen.go rename to cmd/docgen/openrpc.go index 2367ee8b90..cd5b57a82c 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/openrpc.go @@ -3,7 +3,6 @@ package main import ( "context" "encoding/json" - "log" "os" "github.com/spf13/cobra" @@ -17,10 +16,10 @@ var rootCmd = &cobra.Command{ Short: "docgen generates the openrpc documentation for Celestia Node packages", RunE: func(cmd *cobra.Command, moduleNames []string) error { // 1. Open the respective nodebuilder/X/service.go files for AST parsing - nodeComments := docgen.ParseCommentsFromNodebuilderModules(moduleNames...) + nodeComments, permComments := docgen.ParseCommentsFromNodebuilderModules(moduleNames...) // 2. Create an OpenRPC document from the map of comments + hardcoded metadata - doc := docgen.NewOpenRPCDocument(nodeComments) + doc := docgen.NewOpenRPCDocument(nodeComments, permComments) // 3. Register the client wrapper interface on the document for moduleName, module := range nodebuilder.PackageToAPI { @@ -35,9 +34,6 @@ var rootCmd = &cobra.Command{ // 5. Print to Stdout jsonOut, err := json.MarshalIndent(d, "", " ") - if err != nil { - log.Fatalln(err) - } if err != nil { return err } diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index 9277d64a20..4607c4d33f 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -6,10 +6,10 @@ import ( "github.com/filecoin-project/go-jsonrpc/auth" logging "github.com/ipfs/go-log/v2" - - "github.com/celestiaorg/celestia-node/api/docgen" ) +const APIVersion = "v0.1.0" + type module struct { tp Type } @@ -30,7 +30,7 @@ type Info struct { func (m *module) Info(context.Context) (Info, error) { return Info{ Type: m.tp, - APIVersion: docgen.APIVersion, + APIVersion: APIVersion, }, nil } From a9b39ad6ad337b26d7a24d10451b5af79e8b023c Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 31 Mar 2023 15:28:36 +0200 Subject: [PATCH 0511/1008] deps: upgrading to celestia-app v0.12.2 (#1996) ## Overview Upgrades celestia-app to v0.12.2 ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- core/testing.go | 1 + go.mod | 4 ++-- go.sum | 8 ++++---- share/eds/eds.go | 12 ++++++++++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/core/testing.go b/core/testing.go index 202cf9f0a1..8b483b40a2 100644 --- a/core/testing.go +++ b/core/testing.go @@ -78,6 +78,7 @@ func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { cfg.SuppressLogs, state, kr, + "private", ) require.NoError(t, err) diff --git a/go.mod b/go.mod index ec28951155..52b3c4a833 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 - github.com/celestiaorg/celestia-app v0.12.0 + github.com/celestiaorg/celestia-app v0.12.2 github.com/celestiaorg/go-header v0.2.3 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/nmt v0.14.0 + github.com/celestiaorg/nmt v0.15.0 github.com/celestiaorg/rsmt2d v0.8.0 github.com/cosmos/cosmos-sdk v0.46.7 github.com/cosmos/cosmos-sdk/api v0.1.0 diff --git a/go.sum b/go.sum index fd518a88ad..cd4563a293 100644 --- a/go.sum +++ b/go.sum @@ -200,8 +200,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.0 h1:s7Xkd3+NKZIbMd3++d1l76ak7+rFJWqspud7P/cFaWg= -github.com/celestiaorg/celestia-app v0.12.0/go.mod h1:j/QeQxYeOesDyVRLQOPcG4fVyJhxOmf1k9Hc7oEkWiA= +github.com/celestiaorg/celestia-app v0.12.2 h1:Mlxzz2SS+uvE4RnbSuXAh4MagiJBW7GnhDa+8kVbzKE= +github.com/celestiaorg/celestia-app v0.12.2/go.mod h1:lKhL1Oxk4Z29M+GQ25luTHBgwSvgiT4puPeBrjdsgXc= github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/xBFIHj93llcw9ZQfNxyVRlI= github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= @@ -216,8 +216,8 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.14.0 h1:ET1PXBm8f3KHCAB7+sRVMTmvejkpQp6HAQsGLjIRtcY= -github.com/celestiaorg/nmt v0.14.0/go.mod h1:b+pwd9cGTSSYLZnUIQSJl07pusJdFeEvCVsVfSRH9gA= +github.com/celestiaorg/nmt v0.15.0 h1:ID9QlMIeP6WK/iiGcfnYLu2qqVIq0UYe/dc3TVPt6EA= +github.com/celestiaorg/nmt v0.15.0/go.mod h1:GfwIvQPhUakn1modWxJ+rv8dUjJzuXg5H+MLFM1o7nY= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.8.0 h1:ZUxTCELZCM9zMGKNF3cT+rUqMddXMeiuyleSJPZ3Wn4= diff --git a/share/eds/eds.go b/share/eds/eds.go index faa64f7e15..b183731a18 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -135,7 +135,11 @@ func (w *writingSession) writeHeader() error { func (w *writingSession) writeQuadrants() error { shares := quadrantOrder(w.eds) for _, share := range shares { - cid, err := ipld.CidFromNamespacedSha256(w.hasher.HashLeaf(share)) + leaf, err := w.hasher.HashLeaf(share) + if err != nil { + return fmt.Errorf("hashing share: %w", err) + } + cid, err := ipld.CidFromNamespacedSha256(leaf) if err != nil { return fmt.Errorf("getting cid from share: %w", err) } @@ -164,7 +168,11 @@ func (w *writingSession) writeProofs(ctx context.Context) error { node := block.RawData() left, right := node[:ipld.NmtHashSize], node[ipld.NmtHashSize:] - cid, err := ipld.CidFromNamespacedSha256(w.hasher.HashNode(left, right)) + hash, err := w.hasher.HashNode(left, right) + if err != nil { + return fmt.Errorf("hashing node: %w", err) + } + cid, err := ipld.CidFromNamespacedSha256(hash) if err != nil { return fmt.Errorf("getting cid: %w", err) } From 2718b1dfb7ee4fbcc8614601dc7d58019bfb1437 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 31 Mar 2023 17:12:54 +0200 Subject: [PATCH 0512/1008] feat(cmd): RPC over CLI (#1974) Based on #3047. Beginning of the CLI. Adds CLI over RPC. Format: `celestia rpc [module] [method] [...args]` `--auth TOKEN` flag sets the auth header (otherwise it reads it from the env, if not found, doesn't set auth) `--url URL` sets request url, default localhost:26658 Most methods are easy to call. The args for `SubmitPayForBlob` are parsed specially, to improve UX. Other methods can be given special argument parsing rules for improved UX. These special rules: - The namespace ID can be encoded as either hex or base64 - The blob can be hex (`0x...`), base64 (`"..."`), or a normal string which will be encoded to base64 (`'"Hello There!"'`) - `fee` doesn't need to be passed as a string as it normally would need to be, it is passed the same was as gasLim examples of valid commands: ``` celestia rpc state SubmitPayForBlob 0x1874e642f5dde589 '"Hello there!!"' 2000 100000 celestia rpc node Info celestia rpc share GetShare '{"row_roots":["//////////7//////////ql+/VFmJ8PWE9BcjrTDLrY/hzVeGdzFCpfEhiXDXZmt","/////////////////////zHeGnUtPJn8QyPpePSYl4qRVrcUvG2fwptyoA85Myik"],"column_roots":["//////////7//////////ql+/VFmJ8PWE9BcjrTDLrY/hzVeGdzFCpfEhiXDXZmt","/////////////////////zHeGnUtPJn8QyPpePSYl4qRVrcUvG2fwptyoA85Myik"]}' 1 1 celestia rpc header GetByHeight 5 ``` --- cmd/celestia/main.go | 2 +- cmd/celestia/rpc.go | 284 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 cmd/celestia/rpc.go diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index 660e6e6c31..bf156936d7 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -43,6 +43,6 @@ var rootCmd = &cobra.Command{ `, Args: cobra.NoArgs, CompletionOptions: cobra.CompletionOptions{ - DisableDefaultCmd: true, + DisableDefaultCmd: false, }, } diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go new file mode 100644 index 0000000000..61274924e5 --- /dev/null +++ b/cmd/celestia/rpc.go @@ -0,0 +1,284 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "reflect" + "strconv" + "strings" + + "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/state" +) + +const ( + authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" +) + +var requestURL string +var authTokenFlag string + +type jsonRPCRequest struct { + ID int64 `json:"id"` + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params []interface{} `json:"params"` +} + +func init() { + rpcCmd.PersistentFlags().StringVar( + &requestURL, + "url", + "http://localhost:26658", + "Request URL", + ) + rpcCmd.PersistentFlags().StringVar( + &authTokenFlag, + "auth", + "", + "Authorization token (if not provided, the "+authEnvKey+" environment variable will be used)", + ) + rootCmd.AddCommand(rpcCmd) +} + +var rpcCmd = &cobra.Command{ + Use: "rpc [namespace] [method] [params...]", + Short: "Send JSON-RPC request", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + modules := client.Modules + if len(args) == 0 { + // get keys from modules (map[string]interface{}) + var keys []string + for k := range modules { + keys = append(keys, k) + } + return keys, cobra.ShellCompDirectiveNoFileComp + } else if len(args) == 1 { + // get methods from module + module := modules[args[0]] + methods := reflect.VisibleFields(reflect.TypeOf(module).Elem()) + var methodNames []string + for _, m := range methods { + methodNames = append(methodNames, m.Name) + } + return methodNames, cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveNoFileComp + }, + Run: func(cmd *cobra.Command, args []string) { + namespace := args[0] + method := args[1] + params := parseParams(method, args[2:]) + + sendJSONRPCRequest(namespace, method, params) + }, +} + +func parseParams(method string, params []string) []interface{} { + parsedParams := make([]interface{}, len(params)) + + switch method { + case "SubmitPayForBlob": + // 1. NamespaceID + if strings.HasPrefix(params[0], "0x") { + decoded, err := hex.DecodeString(params[0][2:]) + if err != nil { + panic("Error decoding namespace ID: hex string could not be decoded.") + } + parsedParams[0] = decoded + } else { + // otherwise, it's just a base64 string + parsedParams[0] = params[0] + } + // 2. Blob + switch { + case strings.HasPrefix(params[1], "0x"): + decoded, err := hex.DecodeString(params[1][2:]) + if err != nil { + panic("Error decoding blob: hex string could not be decoded.") + } + parsedParams[0] = decoded + case strings.HasPrefix(params[1], "\""): + // user input an utf string that needs to be encoded to base64 + parsedParams[1] = base64.StdEncoding.EncodeToString([]byte(params[1])) + default: + // otherwise, we assume the user has already encoded their input to base64 + parsedParams[1] = params[1] + } + // 3. Fee (state.Int is a string) + parsedParams[2] = params[2] + // 4. GasLimit (uint64) + num, err := strconv.ParseUint(params[3], 10, 64) + if err != nil { + panic("Error parsing gas limit: uint64 could not be parsed.") + } + parsedParams[3] = num + return parsedParams + case "QueryDelegation", "QueryUnbonding", "BalanceForAddress": + var err error + parsedParams[0], err = parseAddressFromString(params[0]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + return parsedParams + case "QueryRedelegations": + var err error + parsedParams[0], err = parseAddressFromString(params[0]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + parsedParams[1], err = parseAddressFromString(params[1]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + return parsedParams + case "Transfer", "Delegate", "Undelegate": + // 1. Address + var err error + parsedParams[0], err = parseAddressFromString(params[0]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + // 2. Amount + Fee + parsedParams[1] = params[1] + parsedParams[2] = params[2] + // 3. GasLimit (uint64) + num, err := strconv.ParseUint(params[3], 10, 64) + if err != nil { + panic("Error parsing gas limit: uint64 could not be parsed.") + } + parsedParams[3] = num + return parsedParams + case "CancelUnbondingDelegation": + // 1. Validator Address + var err error + parsedParams[0], err = parseAddressFromString(params[0]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + // 2. Amount + Height + Fee + parsedParams[1] = params[1] + parsedParams[2] = params[2] + parsedParams[3] = params[3] + // 4. GasLimit (uint64) + num, err := strconv.ParseUint(params[4], 10, 64) + if err != nil { + panic("Error parsing gas limit: uint64 could not be parsed.") + } + parsedParams[4] = num + case "BeginRedelegate": + // 1. Source Validator Address + var err error + parsedParams[0], err = parseAddressFromString(params[0]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + // 2. Destination Validator Address + parsedParams[1], err = parseAddressFromString(params[1]) + if err != nil { + panic(fmt.Errorf("error parsing address: %w", err)) + } + // 2. Amount + Fee + parsedParams[2] = params[2] + parsedParams[3] = params[3] + // 4. GasLimit (uint64) + num, err := strconv.ParseUint(params[4], 10, 64) + if err != nil { + panic("Error parsing gas limit: uint64 could not be parsed.") + } + parsedParams[4] = num + default: + } + + for i, param := range params { + if param[0] == '{' || param[0] == '[' { + var raw json.RawMessage + if err := json.Unmarshal([]byte(param), &raw); err == nil { + parsedParams[i] = raw + } else { + parsedParams[i] = param + } + } else { + // try to parse arguments as numbers before adding them as strings + num, err := strconv.ParseInt(param, 10, 64) + if err == nil { + parsedParams[i] = num + continue + } + parsedParams[i] = param + } + } + + return parsedParams +} + +func sendJSONRPCRequest(namespace, method string, params []interface{}) { + url := requestURL + request := jsonRPCRequest{ + ID: 1, + JSONRPC: "2.0", + Method: fmt.Sprintf("%s.%s", namespace, method), + Params: params, + } + + requestBody, err := json.Marshal(request) + if err != nil { + log.Fatalf("Error marshaling JSON-RPC request: %v", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody)) + if err != nil { + log.Fatalf("Error creating JSON-RPC request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + + authToken := authTokenFlag + if authToken == "" { + authToken = os.Getenv(authEnvKey) + } + if authToken != "" { + req.Header.Set("Authorization", "Bearer "+authToken) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatalf("Error sending JSON-RPC request: %v", err) + } + defer resp.Body.Close() + + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error reading response body: %v", err) //nolint:gocritic + } + + fmt.Println(string(responseBody)) +} + +func parseAddressFromString(addrStr string) (state.Address, error) { + var addr state.AccAddress + addr, err := types.AccAddressFromBech32(addrStr) + if err != nil { + // first check if it is a validator address and can be converted + valAddr, err := types.ValAddressFromBech32(addrStr) + if err != nil { + return nil, errors.New("address must be a valid account or validator address ") + } + return valAddr, nil + } + + return addr, nil +} From 22802ced4ead0b930904a726105e65de41b63198 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 3 Apr 2023 12:12:31 +0200 Subject: [PATCH 0513/1008] fix(eds/share): replacing fsmount with filemount registration (#2001) --- share/eds/store.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/share/eds/store.go b/share/eds/store.go index 993257bb22..bb03d0ca3c 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -66,7 +66,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } r := mount.NewRegistry() - err = r.Register("fs", &mount.FSMount{FS: os.DirFS(basepath + blocksPath)}) + err = r.Register("fs", &mount.FileMount{Path: basepath + blocksPath}) if err != nil { return nil, fmt.Errorf("failed to register FS mount on the registry: %w", err) } @@ -181,9 +181,8 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext } ch := make(chan dagstore.ShardResult, 1) - err = s.dgstr.RegisterShard(ctx, shard.KeyFromString(key), &mount.FSMount{ - FS: os.DirFS(s.basepath + blocksPath), - Path: key, + err = s.dgstr.RegisterShard(ctx, shard.KeyFromString(key), &mount.FileMount{ + Path: s.basepath + blocksPath + key, }, ch, dagstore.RegisterOpts{}) if err != nil { return fmt.Errorf("failed to initiate shard registration: %w", err) From f5f5efb4d81b48ca8208d3bdd11da09622c61c0d Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Mon, 3 Apr 2023 17:58:07 +0000 Subject: [PATCH 0514/1008] feat(p2p): add native libp2p debug metrics (#1795) ## Overview This PR introduces libp2p metrics. The metrics that are introduced in this PR are plentiful, and it's sufficient to list the libp2p components they cover: 1. Resource Manager 2. AutoNat 3. Identify 4. EventBus 5. Swarm (_check the following [link](https://github.com/libp2p/go-libp2p/tree/master/dashboards/resource-manager)_) ## Usage Run your node with flag: ``` $ celestia light start ... --metrics --metrics.endpoint ... --p2p.metrics ``` ## Results Dashboards: Screen Shot 2023-02-28 at 00 29 58 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- cmd/flags_misc.go | 20 +++++++ go.mod | 2 +- nodebuilder/p2p/config.go | 5 +- nodebuilder/p2p/metrics.go | 116 +++++++++++++++++++++++++++++++++++++ nodebuilder/p2p/module.go | 13 +++-- nodebuilder/settings.go | 8 +++ 6 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 nodebuilder/p2p/metrics.go diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 2fdf66988b..9db83e8547 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -31,6 +31,7 @@ var ( metricsFlag = "metrics" metricsEndpointFlag = "metrics.endpoint" metricsTlS = "metrics.tls" + p2pMetrics = "p2p.metrics" ) // MiscFlags gives a set of hardcoded miscellaneous flags. @@ -92,6 +93,12 @@ and their lower-case forms`, "Enable TLS connection to OTLP metric backend", ) + flags.Bool( + p2pMetrics, + false, + "Enable libp2p metrics", + ) + return flags } @@ -201,5 +208,18 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx))) } + ok, err = cmd.Flags().GetBool(p2pMetrics) + if err != nil { + panic(err) + } + + if ok { + if metricsEnabled, _ := cmd.Flags().GetBool(metricsFlag); !metricsEnabled { + log.Error("--p2p.metrics used without --metrics being enabled") + } else { + ctx = WithNodeOptions(ctx, nodebuilder.WithP2PMetrics()) + } + } + return ctx, err } diff --git a/go.mod b/go.mod index 52b3c4a833..950206d05e 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 + github.com/prometheus/client_golang v1.14.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 @@ -256,7 +257,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 9f01513949..284f63e2a0 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -32,6 +32,8 @@ type Config struct { ConnManager connManagerConfig RoutingTableRefreshPeriod time.Duration + // Libp2p Metrics Configuration + Metrics MetricsConfig // Allowlist for IPColocation PubSub parameter, a list of string CIDRs IPColocationWhitelist []string } @@ -58,6 +60,7 @@ func DefaultConfig(tp node.Type) Config { PeerExchange: tp == node.Bridge || tp == node.Full, ConnManager: defaultConnManagerConfig(), RoutingTableRefreshPeriod: defaultRoutingRefreshPeriod, + Metrics: DefaultMetricsConfig(), } } @@ -79,5 +82,5 @@ func (cfg *Config) Validate() error { cfg.RoutingTableRefreshPeriod = defaultRoutingRefreshPeriod log.Warnf("routingTableRefreshPeriod is not valid. restoring to default value: %d", cfg.RoutingTableRefreshPeriod) } - return nil + return cfg.Metrics.Validate() } diff --git a/nodebuilder/p2p/metrics.go b/nodebuilder/p2p/metrics.go new file mode 100644 index 0000000000..2a9f61aa9f --- /dev/null +++ b/nodebuilder/p2p/metrics.go @@ -0,0 +1,116 @@ +package p2p + +import ( + "context" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/network" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" + rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs" + ma "github.com/multiformats/go-multiaddr" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +const promAgentEndpoint = "/metrics" + +type MetricsConfig struct { + // Prometheus Agent http configuration + PrometheusAgentPort string +} + +// DefaultMetricsConfig returns default configuration for P2P subsystem. +func DefaultMetricsConfig() MetricsConfig { + return MetricsConfig{ + PrometheusAgentPort: "8890", + } +} + +func (cfg *MetricsConfig) Validate() error { + if cfg.PrometheusAgentPort == "" { + return fmt.Errorf("p2p metrics: prometheus agent port cannot be empty") + } + + if _, err := strconv.Atoi(cfg.PrometheusAgentPort); err != nil { + return fmt.Errorf("p2p metrics: prometheus agent port must be a number") + } + + return nil +} + +// WithMetrics option sets up native libp2p metrics up +func WithMetrics(lifecycle fx.Lifecycle, cfg Config) error { + rcmgrObs.MustRegisterWith(prometheus.DefaultRegisterer) + + reg := prometheus.DefaultRegisterer + registry := reg.(*prometheus.Registry) + + mux := http.NewServeMux() + handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{Registry: reg}) + mux.Handle(promAgentEndpoint, handler) + + promHTTPServer := &http.Server{ + Addr: fmt.Sprintf(":%s", cfg.Metrics.PrometheusAgentPort), + Handler: mux, + ReadHeaderTimeout: 10 * time.Second, + } + + lifecycle.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + go func() { + if err := promHTTPServer.ListenAndServe(); err != nil { + log.Error("Error starting Prometheus metrics exporter http server") + panic(err) + } + }() + + log.Info("Prometheus agent started on :%s/%s", cfg.Metrics.PrometheusAgentPort, promAgentEndpoint) + return nil + }, + OnStop: func(ctx context.Context) error { + return promHTTPServer.Shutdown(ctx) + }, + }) + return nil +} + +func WithMonitoredResourceManager(nodeType node.Type, allowlist []ma.Multiaddr) (network.ResourceManager, error) { + str, err := rcmgrObs.NewStatsTraceReporter() + if err != nil { + return nil, err + } + + var monitoredRcmgr network.ResourceManager + + switch nodeType { + case node.Full, node.Bridge: + monitoredRcmgr, err = rcmgr.NewResourceManager( + rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits), + rcmgr.WithTraceReporter(str), + ) + + case node.Light: + limits := rcmgr.DefaultLimits + libp2p.SetDefaultServiceLimits(&limits) + + monitoredRcmgr, err = rcmgr.NewResourceManager( + rcmgr.NewFixedLimiter(limits.AutoScale()), + rcmgr.WithAllowlistedMultiaddrs(allowlist), + rcmgr.WithTraceReporter(str), + ) + default: + panic("invalid node type") + } + if err != nil { + return nil, err + } + + return monitoredRcmgr, err +} diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index ed7ffbbee4..0b4fc3379f 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -58,10 +58,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "p2p", baseComponents, fx.Provide(blockstoreFromDatastore), - fx.Provide(func(ctx context.Context, bootstrappers Bootstrappers) (network.ResourceManager, error) { - limits := rcmgr.DefaultLimits - libp2p.SetDefaultServiceLimits(&limits) - + fx.Provide(func(ctx context.Context, bootstrappers Bootstrappers) (allowlist []ma.Multiaddr, err error) { mutual, err := cfg.mutualPeers() if err != nil { return nil, err @@ -69,7 +66,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { // TODO(@Wondertan): We should resolve their addresses only once, but currently // we resolve it here and libp2p stuck does that as well internally - allowlist := make([]ma.Multiaddr, 0, len(bootstrappers)+len(mutual)) + allowlist = make([]ma.Multiaddr, 0, len(bootstrappers)+len(mutual)) for _, b := range bootstrappers { for _, baddr := range b.Addrs { resolved, err := madns.DefaultResolver.Resolve(ctx, baddr) @@ -91,6 +88,12 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { } } + return allowlist, nil + }), + fx.Provide(func(ctx context.Context, allowlist []ma.Multiaddr) (network.ResourceManager, error) { + limits := rcmgr.DefaultLimits + libp2p.SetDefaultServiceLimits(&limits) + return rcmgr.NewResourceManager( rcmgr.NewFixedLimiter(limits.AutoScale()), rcmgr.WithAllowlistedMultiaddrs(allowlist), diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 14d73a9f5d..41716fb711 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -63,6 +63,14 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti return opts } +// WithP2PMetrics option enables native libp2p metrisc for node +func WithP2PMetrics() fx.Option { + return fx.Options( + fx.Decorate(p2p.WithMonitoredResourceManager), + fx.Invoke(p2p.WithMetrics), + ) +} + // initializeMetrics initializes the global meter provider. func initializeMetrics( ctx context.Context, From 2706e46776f171a14c121c95384f72b5f6734e0d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:57:27 +0200 Subject: [PATCH 0515/1008] ci(.github): Fix kind label for dependabot (#1969) Co-authored-by: Hlib Kanunnikov --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b04d52dee4..96228eea15 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,7 +18,7 @@ updates: - Wondertan - renaynay labels: - - kind:dependencies + - kind:deps - package-ecosystem: docker directory: "/" schedule: From e14995e60650b51a461f4aaecae26feefc9c801f Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 4 Apr 2023 16:21:40 +0200 Subject: [PATCH 0516/1008] feat: pyroscope integration (#2013) Integrates [pyroscope](https://github.com/grafana/pyroscope) to the node. Adds three flags: `--pyroscope` `--pyroscope.tracing` `--pyroscope.endpoint ` (default `http://localhost:4040`) --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- cmd/flags_misc.go | 58 ++++++++++++++++++++++++++++++++++++++++- go.mod | 3 +++ go.sum | 9 +++++++ nodebuilder/settings.go | 26 ++++++++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 9db83e8547..fdecb4448d 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -8,6 +8,7 @@ import ( "strings" logging "github.com/ipfs/go-log/v2" + otelpyroscope "github.com/pyroscope-io/otel-profiling-go" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "go.opentelemetry.io/otel" @@ -16,6 +17,7 @@ import ( "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.11.0" + "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" @@ -32,6 +34,9 @@ var ( metricsEndpointFlag = "metrics.endpoint" metricsTlS = "metrics.tls" p2pMetrics = "p2p.metrics" + pyroscopeFlag = "pyroscope" + pyroscopeTracing = "pyroscope.tracing" + pyroscopeEndpoint = "pyroscope.endpoint" ) // MiscFlags gives a set of hardcoded miscellaneous flags. @@ -99,6 +104,24 @@ and their lower-case forms`, "Enable libp2p metrics", ) + flags.Bool( + pyroscopeFlag, + false, + "Enables Pyroscope profiling", + ) + + flags.Bool( + pyroscopeTracing, + false, + "Enables Pyroscope tracing integration. Depends on --tracing", + ) + + flags.String( + pyroscopeEndpoint, + "http://localhost:4040", + "Sets HTTP endpoint for Pyroscope profiles to be exported to. Depends on '--pyroscope'", + ) + return flags } @@ -155,12 +178,27 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e }() } + ok, err = cmd.Flags().GetBool(pyroscopeFlag) + if err != nil { + panic(err) + } + + if ok { + ctx = WithNodeOptions(ctx, + nodebuilder.WithPyroscope( + cmd.Flag(pyroscopeEndpoint).Value.String(), + NodeType(ctx), + ), + ) + } + ok, err = cmd.Flags().GetBool(tracingFlag) if err != nil { panic(err) } if ok { + var tp trace.TracerProvider opts := []otlptracehttp.Option{ otlptracehttp.WithCompression(otlptracehttp.GzipCompression), otlptracehttp.WithEndpoint(cmd.Flag(tracingEndpointFlag).Value.String()), @@ -176,7 +214,8 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e return ctx, err } - tp := tracesdk.NewTracerProvider( + tp = tracesdk.NewTracerProvider( + tracesdk.WithSampler(tracesdk.AlwaysSample()), // Always be sure to batch in production. tracesdk.WithBatcher(exp), // Record information about this application in a Resource. @@ -186,6 +225,23 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey )), ) + + ok, err = cmd.Flags().GetBool(pyroscopeTracing) + if err != nil { + panic(err) + } + if ok { + tp = otelpyroscope.NewTracerProvider( + tp, + otelpyroscope.WithAppName("celestia.da-node"), + otelpyroscope.WithPyroscopeURL(cmd.Flag(pyroscopeEndpoint).Value.String()), + otelpyroscope.WithRootSpanOnly(false), + otelpyroscope.WithAddSpanName(true), + otelpyroscope.WithProfileURL(true), + otelpyroscope.WithProfileBaselineURL(true), + ) + } + otel.SetTracerProvider(tp) } diff --git a/go.mod b/go.mod index 950206d05e..9f8561bbac 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,8 @@ require ( github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/prometheus/client_golang v1.14.0 + github.com/pyroscope-io/client v0.7.0 + github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 @@ -260,6 +262,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect + github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.1 // indirect github.com/quic-go/qtls-go1-20 v0.1.1 // indirect diff --git a/go.sum b/go.sum index cd4563a293..bbc5882603 100644 --- a/go.sum +++ b/go.sum @@ -599,6 +599,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -1592,6 +1593,12 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pyroscope-io/client v0.7.0 h1:LWuuqPQ1oa6x7BnmUOuo/aGwdX85QGhWZUBYWWW3zdk= +github.com/pyroscope-io/client v0.7.0/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= +github.com/pyroscope-io/godeltaprof v0.1.0 h1:UBqtjt0yZi4jTxqZmLAs34XG6ycS3vUTlhEUSq4NHLE= +github.com/pyroscope-io/godeltaprof v0.1.0/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= +github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKAvs5drr0511Ki8ic= +github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= @@ -1859,6 +1866,7 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y= go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= @@ -1881,6 +1889,7 @@ go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rq go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 41716fb711..d103f42060 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -6,6 +6,7 @@ import ( "time" "github.com/libp2p/go-libp2p/core/peer" + "github.com/pyroscope-io/client/pyroscope" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/sdk/metric" @@ -33,6 +34,31 @@ func WithBootstrappers(peers p2p.Bootstrappers) fx.Option { return fx.Replace(peers) } +// WithPyroscope enables pyroscope profiling for the node. +func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { + return fx.Options( + fx.Invoke(func(peerID peer.ID) error { + _, err := pyroscope.Start(pyroscope.Config{ + ApplicationName: "celestia.da-node", + ServerAddress: endpoint, + Tags: map[string]string{ + "type": nodeType.String(), + "peerId": peerID.String(), + }, + Logger: nil, + ProfileTypes: []pyroscope.ProfileType{ + pyroscope.ProfileCPU, + pyroscope.ProfileAllocObjects, + pyroscope.ProfileAllocSpace, + pyroscope.ProfileInuseObjects, + pyroscope.ProfileInuseSpace, + }, + }) + return err + }), + ) +} + // WithMetrics enables metrics exporting for the node. func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Option { baseComponents := fx.Options( From d97e634e85f901e4d94659412d3b227a65b29dff Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 4 Apr 2023 17:24:12 +0200 Subject: [PATCH 0517/1008] fix(share/eds): use cached accessors in `GetCAR` and `GetDAH` (#2000) Closes #1514 . This PR enables accessor cache usage for GetCAR and GetDAH. This will allow shrexeds and shrexnd servers to only require opening the underlying file once (until removed from cache). If many peers request the EDS at once, the server previously needed to open the file each time. Will still require a dependency bump of our dagstore fork for the new test to not fail - so draft until then. Related: https://github.com/celestiaorg/dagstore/pull/2 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- go.mod | 2 +- go.sum | 4 ++-- share/eds/accessor_cache.go | 19 ++++++++++++++---- share/eds/store.go | 24 ++++++++++++----------- share/eds/store_test.go | 37 ++++++++++++++++++++++++++++++++++++ share/p2p/shrexeds/server.go | 6 +++--- 6 files changed, 71 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 9f8561bbac..55537aafae 100644 --- a/go.mod +++ b/go.mod @@ -327,7 +327,7 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 - github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 + github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index bbc5882603..13117ba45b 100644 --- a/go.sum +++ b/go.sum @@ -206,8 +206,8 @@ github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/ github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= -github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659 h1:f3205vw3GYBtMiNoS+qB6IuHSs50Iwqsm9lNIikLTCk= -github.com/celestiaorg/dagstore v0.0.0-20221014072825-395797efb659/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= +github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136 h1:LBvY3NDA18fcS72pBAEd2pENoUpz1iV4cCXBN2Zrj/I= +github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-header v0.2.3 h1:41r60OtAeexWC3J3eTELgWfzcdKR2taFlfcJ/2IHZD4= github.com/celestiaorg/go-header v0.2.3/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= diff --git a/share/eds/accessor_cache.go b/share/eds/accessor_cache.go index 047ce49104..eac79b946f 100644 --- a/share/eds/accessor_cache.go +++ b/share/eds/accessor_cache.go @@ -63,6 +63,10 @@ func (bc *blockstoreCache) Get(shardContainingCid shard.Key) (*accessorWithBlock lk.Lock() defer lk.Unlock() + return bc.unsafeGet(shardContainingCid) +} + +func (bc *blockstoreCache) unsafeGet(shardContainingCid shard.Key) (*accessorWithBlockstore, error) { // We've already ensured that the given shard has the cid/multihash we are looking for. val, ok := bc.cache.Get(shardContainingCid) if !ok { @@ -83,16 +87,23 @@ func (bc *blockstoreCache) Get(shardContainingCid shard.Key) (*accessorWithBlock func (bc *blockstoreCache) Add( shardContainingCid shard.Key, accessor *dagstore.ShardAccessor, +) (*accessorWithBlockstore, error) { + lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)] + lk.Lock() + defer lk.Unlock() + + return bc.unsafeAdd(shardContainingCid, accessor) +} + +func (bc *blockstoreCache) unsafeAdd( + shardContainingCid shard.Key, + accessor *dagstore.ShardAccessor, ) (*accessorWithBlockstore, error) { blockStore, err := accessor.Blockstore() if err != nil { return nil, fmt.Errorf("failed to get blockstore from accessor: %w", err) } - lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)] - lk.Lock() - defer lk.Unlock() - newAccessor := &accessorWithBlockstore{ bs: blockStore, sa: accessor, diff --git a/share/eds/store.go b/share/eds/store.go index bb03d0ca3c..257cff2b65 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -204,17 +204,18 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext // The Reader strictly reads the CAR header and first quadrant (1/4) of the EDS, omitting all the // NMT Merkle proofs. Integrity of the store data is not verified. // -// Caller must Close returned reader after reading. -func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) { +// The shard is cached in the Store, so subsequent calls to GetCAR with the same root will use the same reader. +// The cache is responsible for closing the underlying reader. +func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { ctx, span := tracer.Start(ctx, "store/get-car", trace.WithAttributes(attribute.String("root", root.String()))) defer span.End() key := root.String() - accessor, err := s.getAccessor(ctx, shard.KeyFromString(key)) + accessor, err := s.getCachedAccessor(ctx, shard.KeyFromString(key)) if err != nil { return nil, fmt.Errorf("failed to get accessor: %w", err) } - return accessor, nil + return accessor.sa.Reader(), nil } // Blockstore returns an IPFS blockstore providing access to individual shares/nodes of all EDS @@ -247,13 +248,12 @@ func (s *Store) GetDAH(ctx context.Context, root share.DataHash) (*share.Root, e defer span.End() key := shard.KeyFromString(root.String()) - accessor, err := s.getAccessor(ctx, key) + accessor, err := s.getCachedAccessor(ctx, key) if err != nil { return nil, fmt.Errorf("eds/store: failed to get accessor: %w", err) } - defer accessor.Close() - carHeader, err := carv1.ReadHeader(bufio.NewReader(accessor)) + carHeader, err := carv1.ReadHeader(bufio.NewReader(accessor.sa.Reader())) if err != nil { return nil, fmt.Errorf("eds/store: failed to read car header: %w", err) } @@ -297,8 +297,11 @@ func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*dagstore.Shard } func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessorWithBlockstore, error) { - // try to fetch from cache - accessor, err := s.cache.Get(key) + lk := &s.cache.stripedLocks[shardKeyToStriped(key)] + lk.Lock() + defer lk.Unlock() + + accessor, err := s.cache.unsafeGet(key) if err != nil && err != errCacheMiss { log.Errorf("unexpected error while reading key from bs cache %s: %s", key, err) } @@ -311,7 +314,7 @@ func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessor if err != nil { return nil, err } - return s.cache.Add(key, shardAccessor) + return s.cache.unsafeAdd(key, shardAccessor) } // Remove removes EDS from Store by the given share.Root hash and cleans up all @@ -367,7 +370,6 @@ func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.Exten if err != nil { return nil, fmt.Errorf("failed to get CAR file: %w", err) } - defer f.Close() eds, err = ReadEDS(ctx, f, root) if err != nil { return nil, fmt.Errorf("failed to read EDS from CAR file: %w", err) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 6216a0ca20..e30e66f848 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -206,6 +206,43 @@ func Test_BlockstoreCache(t *testing.T) { assert.NoError(t, err, errCacheMiss) } +// Test_CachedAccessor verifies that the reader represented by a cached accessor can be read from +// multiple times, without exhausting the underlying reader. +func Test_CachedAccessor(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + shardKey := shard.KeyFromString(dah.String()) + // adds to cache + cachedAccessor, err := edsStore.getCachedAccessor(ctx, shardKey) + assert.NoError(t, err) + + // first read + carReader, err := car.NewCarReader(cachedAccessor.sa.Reader()) + assert.NoError(t, err) + firstBlock, err := carReader.Next() + assert.NoError(t, err) + + // second read + cachedAccessor, err = edsStore.getCachedAccessor(ctx, shardKey) + assert.NoError(t, err) + carReader, err = car.NewCarReader(cachedAccessor.sa.Reader()) + assert.NoError(t, err) + secondBlock, err := carReader.Next() + assert.NoError(t, err) + + assert.Equal(t, firstBlock, secondBlock) +} + func newStore(t *testing.T) (*Store, error) { t.Helper() diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index d79f3c31c7..ccc841df64 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -80,11 +80,11 @@ func (s *Server) handleStream(stream network.Stream) { defer cancel() status := p2p_pb.Status_OK // determine whether the EDS is available in our store + // we do not close the reader, so that other requests will not need to re-open the file. + // closing is handled by the LRU cache. edsReader, err := s.store.GetCAR(ctx, hash) if err != nil { status = p2p_pb.Status_NOT_FOUND - } else { - defer edsReader.Close() } // inform the client of our status @@ -144,7 +144,7 @@ func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error return err } -func (s *Server) writeODS(edsReader io.ReadCloser, stream network.Stream) error { +func (s *Server) writeODS(edsReader io.Reader, stream network.Stream) error { err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { log.Debug(err) From f480a8f95c342f1e38e44307dd8c111f15b146a0 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 4 Apr 2023 20:02:46 +0200 Subject: [PATCH 0518/1008] fix: updating broken link in README.md (#2022) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b632e21fe..4324ee49c0 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ For more information on setting up a node and the hardware requirements needed, ## API docs -Celestia-node public API is documented [here](https://docs.celestia.org/developers/node-api/). +Celestia-node public API is documented [here](https://docs.celestia.org/category/node-api/). ## Node types From 8e36597a82f3b6574669d57160844078b8bdb82d Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 5 Apr 2023 10:01:44 +0200 Subject: [PATCH 0519/1008] fix: enabling badger gc (#2020) While profiling with pyroscope, I noticed that while `badger/v2.(*DB).doWrites` stays constant for days, `badger/v2/y.(*WaterMark)` grows consistently over days. `Watermark` helps in managing the memory usage and ensuring that certain thresholds are not exceeded, allowing the system to perform optimizations, such as compaction. Badger's garbage collection (GC) primarily targets the value log to free up space on disk, not in memory. The value log GC reclaims space by removing stale and obsolete data from the value log files, which are stored on disk. BUT: invoking the RunValueLogGC() method can have an _indirect_ impact on memory usage: value log GC occurs helps stabilize the growth of the WaterMark by preventing an excessive number of small value log files from accumulating. This can, in turn, lead to more efficient memory usage, as the LSM tree components (MemTable and SSTables) do not need to track as many small value log files. --- nodebuilder/store.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nodebuilder/store.go b/nodebuilder/store.go index e34a73905a..16c10fc828 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -144,10 +144,6 @@ func (f *fsStore) Datastore() (_ datastore.Batching, err error) { // Bigger values constantly takes more RAM // TODO(@Wondertan): Make configurable with more conservative defaults for Light Node opts.MaxTableSize = 64 << 20 - // Remove GC as long as we don't have pruning of data to be GCed. - // Currently, we only append data on disk without removing. - // TODO(@Wondertan): Find good enough default, once pruning is shipped. - opts.GcInterval = 0 f.data, err = dsbadger.NewDatastore(dataPath(f.path), &opts) if err != nil { From b05d6523679823e6cf6c8672232def943a410cca Mon Sep 17 00:00:00 2001 From: Samuel Enderwitz <18609909+smuu@users.noreply.github.com> Date: Wed, 5 Apr 2023 14:04:45 +0200 Subject: [PATCH 0520/1008] feat: pin version of common workflows and actions (#2005) --- .github/workflows/ci_release.yml | 6 +++--- .github/workflows/docker-build-publish.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 3f8ea9de24..fa51c0499f 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -24,7 +24,7 @@ on: jobs: # Dockerfile Linting hadolint: - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@main # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.1.0 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: celestiaorg/.github/.github/actions/yamllint@main + - uses: celestiaorg/.github/.github/actions/yamllint@v0.1.0 markdown-lint: name: Markdown Lint @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@main + uses: celestiaorg/.github/.github/actions/version-release@v0.1.0 with: github-token: ${{secrets.GITHUB_TOKEN}} version-bump: ${{inputs.version}} diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index a0f10a9932..1ae52d54a7 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -17,6 +17,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@main # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.1.0 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From 9ee6b339db3b2a2d4e6b940d22dbb0ab354f279b Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 5 Apr 2023 23:52:55 +0800 Subject: [PATCH 0521/1008] chore(deps): Upgrade setup-go (#2017) --- .github/workflows/go-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 90f4c2b333..fdf37da696 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} @@ -34,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} @@ -51,7 +51,7 @@ jobs: - uses: actions/checkout@v3 - name: set up go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} @@ -69,7 +69,7 @@ jobs: - uses: actions/checkout@v3 - name: set up go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} @@ -84,7 +84,7 @@ jobs: - uses: actions/checkout@v3 - name: set up go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} From f7ce6863968e66390dad0d83cb091d6a4267c16d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 6 Apr 2023 18:57:31 +0800 Subject: [PATCH 0522/1008] chore(deps): Upgrading to go 1.20 (#2015) --- .github/workflows/go-ci.yml | 2 +- Dockerfile | 2 +- README.md | 4 ++-- cmd/celestia/main.go | 4 ---- cmd/start.go | 7 ++++--- das/state_test.go | 5 ++--- das/worker.go | 4 +--- go.mod | 6 +++--- header/headertest/testing.go | 8 ++++---- nodebuilder/p2p/p2p_test.go | 2 +- nodebuilder/store.go | 5 ++--- nodebuilder/tests/swamp/swamp.go | 4 ++-- share/availability/full/availability_test.go | 7 ------- share/availability/light/availability_test.go | 10 ++-------- share/availability/test/corrupt_data.go | 3 ++- share/eds/adapters_test.go | 7 ++++--- share/eds/retriever_test.go | 6 ------ share/get_test.go | 6 +++--- share/getters/cascade.go | 4 +--- share/getters/cascade_test.go | 4 ++-- share/getters/shrex.go | 11 +++++------ share/getters/shrex_test.go | 6 +++--- share/getters/utils_test.go | 5 ++--- share/ipld/nmt_test.go | 6 +++--- share/ipld/test_helpers.go | 4 ++-- share/p2p/peers/options.go | 3 ++- share/test_helpers.go | 6 +++--- 27 files changed, 57 insertions(+), 84 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index fdf37da696..5f9fe88a06 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -4,7 +4,7 @@ on: workflow_call: env: - GO_VERSION: 1.19 + GO_VERSION: '1.20' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/Dockerfile b/Dockerfile index b92f0d9b48..ef34be4198 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.19.7-alpine3.17 as builder +FROM docker.io/golang:1.20-alpine3.17 as builder # hadolint ignore=DL3018 RUN apk update && apk add --no-cache \ diff --git a/README.md b/README.md index 4324ee49c0..76c722275d 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-avai ## Minimum requirements | Requirement | Notes | -| ----------- | -------------- | -| Go version | 1.19 or higher | +| ----------- |----------------| +| Go version | 1.20 or higher | ## System Requirements diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index bf156936d7..24c9423100 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -2,9 +2,7 @@ package main import ( "context" - "math/rand" "os" - "time" "github.com/spf13/cobra" ) @@ -27,8 +25,6 @@ func main() { } func run() error { - rand.Seed(time.Now().Unix()) - return rootCmd.ExecuteContext(context.Background()) } diff --git a/cmd/start.go b/cmd/start.go index 2f7dfad7e4..d46b553e20 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -1,6 +1,7 @@ package cmd import ( + "errors" "os" "os/signal" "path/filepath" @@ -9,7 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "go.uber.org/multierr" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" @@ -48,8 +48,9 @@ Options passed on start override configuration options only on start and are not if err != nil { return err } - // TODO(@Wondertan): Use join errors instead in go1.21 - defer multierr.AppendInvoke(&err, multierr.Invoke(store.Close)) + defer func() { + err = errors.Join(err, store.Close()) + }() nd, err := nodebuilder.NewWithConfig(NodeType(ctx), Network(ctx), store, &cfg, NodeOptions(ctx)...) if err != nil { diff --git a/das/state_test.go b/das/state_test.go index da326cc6e8..8dc7873d30 100644 --- a/das/state_test.go +++ b/das/state_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.uber.org/multierr" ) func Test_coordinatorStats(t *testing.T) { @@ -38,7 +37,7 @@ func Test_coordinatorStats(t *testing.T) { }, Curr: 15, failed: []uint64{12, 13}, - Err: multierr.Append(errors.New("12: failed"), errors.New("13: failed")), + Err: errors.Join(errors.New("12: failed"), errors.New("13: failed")), } }, }, @@ -63,7 +62,7 @@ func Test_coordinatorStats(t *testing.T) { Curr: 15, From: 11, To: 20, - ErrMsg: "12: failed; 13: failed", + ErrMsg: "12: failed\n13: failed", }, }, Concurrency: 2, diff --git a/das/worker.go b/das/worker.go index 4db618ba90..258ea539e8 100644 --- a/das/worker.go +++ b/das/worker.go @@ -7,8 +7,6 @@ import ( "sync" "time" - "go.uber.org/multierr" - libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/header" @@ -179,7 +177,7 @@ func (w *worker) setResult(curr uint64, err error) { defer w.lock.Unlock() if err != nil { w.state.failed = append(w.state.failed, curr) - w.state.Err = multierr.Append(w.state.Err, fmt.Errorf("height: %v, err: %w", curr, err)) + w.state.Err = errors.Join(w.state.Err, fmt.Errorf("height: %v, err: %w", curr, err)) } w.state.Curr = curr } diff --git a/go.mod b/go.mod index 55537aafae..ccf6fdc1b7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/celestiaorg/celestia-node -go 1.19 +go 1.20 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch @@ -69,9 +69,7 @@ require ( go.opentelemetry.io/otel/trace v1.13.0 go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.18.2 - go.uber.org/multierr v1.9.0 golang.org/x/crypto v0.7.0 - golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 golang.org/x/sync v0.1.0 golang.org/x/text v0.8.0 google.golang.org/grpc v1.53.0 @@ -305,7 +303,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect + go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 3ec26fc5db..fabd80ed67 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -2,6 +2,7 @@ package headertest import ( "context" + "crypto/rand" "fmt" mrand "math/rand" "sort" @@ -19,7 +20,6 @@ import ( "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" - "golang.org/x/exp/rand" "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" @@ -248,7 +248,7 @@ func RandValidator(randPower bool, minPower int64) (*types.Validator, types.Priv privVal := types.NewMockPV() votePower := minPower if randPower { - votePower += int64(rand.Uint32()) + votePower += int64(mrand.Uint32()) //nolint:gosec } pubKey, err := privVal.GetPubKey() if err != nil { @@ -287,8 +287,8 @@ func RandBlockID(*testing.T) types.BlockID { Hash: make([]byte, 32), }, } - mrand.Read(bid.Hash) //nolint:gosec - mrand.Read(bid.PartSetHeader.Hash) //nolint:gosec + _, _ = rand.Read(bid.Hash) + _, _ = rand.Read(bid.PartSetHeader.Hash) return bid } diff --git a/nodebuilder/p2p/p2p_test.go b/nodebuilder/p2p/p2p_test.go index eaee8429f8..b883889859 100644 --- a/nodebuilder/p2p/p2p_test.go +++ b/nodebuilder/p2p/p2p_test.go @@ -2,7 +2,7 @@ package p2p import ( "context" - "math/rand" + "crypto/rand" "testing" "time" diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 16c10fc828..904427c9f0 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -11,7 +11,6 @@ import ( "github.com/ipfs/go-datastore" dsbadger "github.com/ipfs/go-ds-badger2" "github.com/mitchellh/go-homedir" - "go.uber.org/multierr" "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/keystore" @@ -154,9 +153,9 @@ func (f *fsStore) Datastore() (_ datastore.Batching, err error) { } func (f *fsStore) Close() (err error) { - err = multierr.Append(err, f.dirLock.Unlock()) + err = errors.Join(err, f.dirLock.Unlock()) if f.data != nil { - err = multierr.Append(err, f.data.Close()) + err = errors.Join(err, f.data.Close()) } return } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 884b39e6d3..bbe8f7e395 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -2,8 +2,8 @@ package swamp import ( "context" + "crypto/rand" "fmt" - "math/rand" "net" "testing" "time" @@ -139,7 +139,7 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { // IPv6 will be starting with 100:0 token := make([]byte, 12) - rand.Read(token) //nolint:gosec + _, _ = rand.Read(token) ip := append(net.IP{}, blackholeIP6...) copy(ip[net.IPv6len-len(token):], token) diff --git a/share/availability/full/availability_test.go b/share/availability/full/availability_test.go index f914d3912f..48ae0534aa 100644 --- a/share/availability/full/availability_test.go +++ b/share/availability/full/availability_test.go @@ -2,20 +2,13 @@ package full import ( "context" - "math/rand" "testing" - "time" "github.com/stretchr/testify/assert" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" ) -func init() { - // randomize quadrant fetching, otherwise quadrant sampling is deterministic - rand.Seed(time.Now().UnixNano()) -} - func TestShareAvailableOverMocknet_Full(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index def9095d86..c5b93d27d2 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -3,17 +3,16 @@ package light import ( "bytes" "context" + "crypto/rand" _ "embed" "encoding/hex" "encoding/json" mrand "math/rand" "strconv" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" core "github.com/tendermint/tendermint/types" @@ -25,11 +24,6 @@ import ( availability_test "github.com/celestiaorg/celestia-node/share/availability/test" ) -func init() { - // randomize quadrant fetching, otherwise quadrant sampling is deterministic - rand.Seed(time.Now().UnixNano()) -} - func TestSharesAvailable(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -230,7 +224,7 @@ func TestSharesRoundTrip(t *testing.T) { } randBytes := func(n int) []byte { bs := make([]byte, n) - mrand.Read(bs) + _, _ = rand.Read(bs) return bs } for i := 128; i < 4192; i += mrand.Intn(256) { diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index c9615cd1d4..1bb8bd243a 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -2,6 +2,7 @@ package availability_test import ( "context" + "crypto/rand" "fmt" mrand "math/rand" "testing" @@ -90,7 +91,7 @@ func (fb FraudulentBlockstore) Put(ctx context.Context, block blocks.Block) erro // create data that doesn't match the CID with arbitrary lengths between 1 and // len(block.RawData())*2 corrupted := make([]byte, 1+mrand.Int()%(len(block.RawData())*2-1)) //nolint:gosec - mrand.Read(corrupted) //nolint:gosec + _, _ = rand.Read(corrupted) return fb.Datastore.Put(ctx, ds.NewKey("corrupt"+block.Cid().String()), corrupted) } diff --git a/share/eds/adapters_test.go b/share/eds/adapters_test.go index 1b40c9db14..d920c0720b 100644 --- a/share/eds/adapters_test.go +++ b/share/eds/adapters_test.go @@ -2,8 +2,9 @@ package eds import ( "context" + "crypto/rand" "errors" - "math/rand" + mrand "math/rand" "sort" "testing" "time" @@ -47,7 +48,7 @@ func TestBlockGetter_GetBlocks(t *testing.T) { cids := randCIDs(32) // split cids into failed and succeeded - failedLen := rand.Intn(len(cids)-1) + 1 + failedLen := mrand.Intn(len(cids)-1) + 1 failed := make(map[cid.Cid]struct{}, failedLen) succeeded := make([]cid.Cid, 0, len(cids)-failedLen) for i, cid := range cids { @@ -141,7 +142,7 @@ func (r rbsMock) HashOnRead(bool) { func randCID() cid.Cid { hash := make([]byte, ipld.NmtHashSize) - rand.Read(hash) + _, _ = rand.Read(hash) return ipld.MustCidFromNamespacedSha256(hash) } diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 0308bb9910..7f107cac96 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -3,7 +3,6 @@ package eds import ( "context" "errors" - "math/rand" "testing" "time" @@ -25,11 +24,6 @@ import ( "github.com/celestiaorg/celestia-node/share/ipld" ) -func init() { - // randomize quadrant fetching, otherwise quadrant sampling is deterministic - rand.Seed(time.Now().UnixNano()) -} - func TestRetriever_Retrieve(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/share/get_test.go b/share/get_test.go index 3913dcf4d7..0ea83fd178 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -2,7 +2,7 @@ package share import ( "context" - "math/rand" + mrand "math/rand" "strconv" "testing" "time" @@ -130,7 +130,7 @@ func removeRandShares(data [][]byte, d int) [][]byte { count := len(data) // remove shares randomly for i := 0; i < d; { - ind := rand.Intn(count) + ind := mrand.Intn(count) if len(data[ind]) == 0 { continue } @@ -331,7 +331,7 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - rand.Seed(time.Now().UnixNano()) + rand := mrand.New(mrand.NewSource(time.Now().UnixNano())) // choose random range in shares slice from := rand.Intn(len(tt.rawData)) to := rand.Intn(len(tt.rawData)) diff --git a/share/getters/cascade.go b/share/getters/cascade.go index ef616996b9..cca1eab944 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -6,7 +6,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "go.uber.org/multierr" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -127,8 +126,7 @@ func cascadeGetters[V any]( } if !errors.Is(getErr, errOperationNotSupported) { - // TODO(@Wondertan): migrate to errors.Join once Go1.20 is out! - err = multierr.Append(err, getErr) + err = errors.Join(err, getErr) span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) } } diff --git a/share/getters/cascade_test.go b/share/getters/cascade_test.go index c7b32a689b..e3a324c5e9 100644 --- a/share/getters/cascade_test.go +++ b/share/getters/cascade_test.go @@ -3,11 +3,11 @@ package getters import ( "context" "errors" + "strings" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - "go.uber.org/multierr" "github.com/celestiaorg/rsmt2d" @@ -93,7 +93,7 @@ func TestCascade(t *testing.T) { getters := []share.Getter{immediateFailGetter, timeoutGetter, immediateFailGetter} _, err := cascadeGetters(ctx, getters, get) assert.Error(t, err) - assert.Len(t, multierr.Errors(err), 3) + assert.Equal(t, strings.Count(err.Error(), "\n"), 2) }) t.Run("Single", func(t *testing.T) { diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 13b58d2771..f4e2d11f33 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -2,11 +2,10 @@ package getters import ( "context" + "errors" "fmt" "time" - "go.uber.org/multierr" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -72,7 +71,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex start := time.Now() peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) if getErr != nil { - err = multierr.Append(err, getErr) + err = errors.Join(err, getErr) log.Debugw("couldn't find peer", "datahash", root.String(), "err", getErr, @@ -96,7 +95,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex } if !ErrorContains(err, getErr) { - err = multierr.Append(err, getErr) + err = errors.Join(err, getErr) } log.Debugw("request failed", "datahash", root.String(), @@ -121,7 +120,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( start := time.Now() peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) if getErr != nil { - err = multierr.Append(err, getErr) + err = errors.Join(err, getErr) log.Debugw("couldn't find peer", "datahash", root.String(), "err", getErr, @@ -145,7 +144,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( } if !ErrorContains(err, getErr) { - err = multierr.Append(err, getErr) + err = errors.Join(err, getErr) } log.Debugw("request failed", "datahash", root.String(), diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 60bd0c2243..c47dbf6f85 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -2,7 +2,7 @@ package getters import ( "context" - "math/rand" + mrand "math/rand" "testing" "time" @@ -80,8 +80,8 @@ func newStore(t *testing.T) (*eds.Store, error) { func generateTestEDS(t *testing.T, bServ bsrv.BlockService) (*rsmt2d.ExtendedDataSquare, namespace.ID) { shares := share.RandShares(t, 16) - from := rand.Intn(len(shares)) - to := rand.Intn(len(shares)) + from := mrand.Intn(len(shares)) + to := mrand.Intn(len(shares)) if to < from { from, to = to, from diff --git a/share/getters/utils_test.go b/share/getters/utils_test.go index 421d1dcd23..73e9400010 100644 --- a/share/getters/utils_test.go +++ b/share/getters/utils_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.uber.org/multierr" ) func Test_ErrorContains(t *testing.T) { @@ -80,14 +79,14 @@ func Test_ErrorContains(t *testing.T) { }, {"multierr first in slice", args{ - err: multierr.Append(w1(err1), w2(err2)), + err: errors.Join(w1(err1), w2(err2)), target: w2(err1), }, true, }, {"multierr second in slice", args{ - err: multierr.Append(w1(err1), w2(err2)), + err: errors.Join(w1(err1), w2(err2)), target: w1(err2), }, true, diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index ff0d38ea72..cd2f22cb5b 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -2,7 +2,7 @@ package ipld import ( "bytes" - "math/rand" + "crypto/rand" "sort" "strconv" "testing" @@ -51,14 +51,14 @@ func generateRandNamespacedRawData(total, nidSize, leafSize uint32) [][]byte { for i := uint32(0); i < total; i++ { nid := make([]byte, nidSize) - rand.Read(nid) + _, _ = rand.Read(nid) data[i] = nid } sortByteArrays(data) for i := uint32(0); i < total; i++ { d := make([]byte, leafSize) - rand.Read(d) + _, _ = rand.Read(d) data[i] = append(data[i], d...) } diff --git a/share/ipld/test_helpers.go b/share/ipld/test_helpers.go index 8e1707ed89..e3456c0b25 100644 --- a/share/ipld/test_helpers.go +++ b/share/ipld/test_helpers.go @@ -1,7 +1,7 @@ package ipld import ( - mrand "math/rand" + "crypto/rand" "testing" "github.com/ipfs/go-cid" @@ -10,7 +10,7 @@ import ( func RandNamespacedCID(t *testing.T) cid.Cid { raw := make([]byte, NmtHashSize) - _, err := mrand.Read(raw) //nolint:gosec + _, err := rand.Read(raw) require.NoError(t, err) id, err := CidFromNamespacedSha256(raw) require.NoError(t, err) diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index 4b13a27bbc..8fe71f5285 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -54,7 +54,8 @@ func DefaultParameters() Parameters { // the new block before we ask them again. PeerCooldown: 3 * time.Second, GcInterval: time.Second * 30, - // blacklisting is off by default //TODO(@walldiss): enable blacklisting once all related issues are resolved + // blacklisting is off by default //TODO(@walldiss): enable blacklisting once all related issues + // are resolved EnableBlackListing: false, } } diff --git a/share/test_helpers.go b/share/test_helpers.go index 6700ebd9ef..f3cb6776f2 100644 --- a/share/test_helpers.go +++ b/share/test_helpers.go @@ -2,7 +2,7 @@ package share import ( "bytes" - mrand "math/rand" + "crypto/rand" "sort" "github.com/stretchr/testify/require" @@ -53,14 +53,14 @@ func RandShares(t require.TestingT, total int) []Share { shares := make([]Share, total) for i := range shares { nid := make([]byte, Size) - _, err := mrand.Read(nid[:NamespaceSize]) //nolint:gosec + _, err := rand.Read(nid[:NamespaceSize]) require.NoError(t, err) shares[i] = nid } sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) for i := range shares { - _, err := mrand.Read(shares[i][NamespaceSize:]) //nolint:gosec + _, err := rand.Read(shares[i][NamespaceSize:]) require.NoError(t, err) } From f794ed2f6311fbe198179957d3c3431b81b32b19 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 6 Apr 2023 19:29:01 +0800 Subject: [PATCH 0523/1008] !refactor(share/p2p/peers): expose peer manager params to nodebuilder (#1925) Makes peer manager configurable by node user. --- nodebuilder/share/config.go | 9 +++++- nodebuilder/share/module.go | 3 ++ share/p2p/peers/manager.go | 52 ++++++++++++++------------------- share/p2p/peers/manager_test.go | 20 +++++++------ share/p2p/peers/options.go | 42 ++++---------------------- 5 files changed, 49 insertions(+), 77 deletions(-) diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 07276fb2ef..789b54dd75 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" ) @@ -28,6 +29,8 @@ type Config struct { ShrExEDSParams *shrexeds.Parameters // ShrExNDParams sets shrexnd client and server configuration parameters ShrExNDParams *shrexnd.Parameters + // PeerManagerParams sets peer-manager configuration parameters + PeerManagerParams peers.Parameters } func DefaultConfig() Config { @@ -38,6 +41,7 @@ func DefaultConfig() Config { UseShareExchange: true, ShrExEDSParams: shrexeds.DefaultParameters(), ShrExNDParams: shrexnd.DefaultParameters(), + PeerManagerParams: peers.DefaultParameters(), } } @@ -49,5 +53,8 @@ func (cfg *Config) Validate() error { if err := cfg.ShrExNDParams.Validate(); err != nil { return err } - return cfg.ShrExEDSParams.Validate() + if err := cfg.ShrExEDSParams.Validate(); err != nil { + return err + } + return cfg.PeerManagerParams.Validate() } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 2fae0e8ac6..a43194d53d 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -113,6 +113,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option ) shrexGetterComponents := fx.Options( + fx.Provide(func() peers.Parameters { + return cfg.PeerManagerParams + }), fx.Provide(peers.NewManager), fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 9dc7c05ec8..6ccdd7365c 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -40,7 +40,8 @@ var log = logging.Logger("shrex/peer-manager") // Manager keeps track of peers coming from shrex.Sub and from discovery type Manager struct { - lock sync.Mutex + lock sync.Mutex + params Parameters // header subscription is necessary in order to validate the inbound eds hash headerSub libhead.Subscriber[*header.ExtendedHeader] @@ -52,11 +53,7 @@ type Manager struct { pools map[string]*syncPool // messages from shrex.Sub with height below initialHeight will be ignored, since we don't need to // track peers for those headers - initialHeight atomic.Uint64 - poolValidationTimeout time.Duration - peerCooldownTime time.Duration - gcInterval time.Duration - enableBlackListing bool + initialHeight atomic.Uint64 // fullNodes collects full nodes peer.ID found via discovery fullNodes *pool @@ -90,34 +87,29 @@ type syncPool struct { } func NewManager( + params Parameters, headerSub libhead.Subscriber[*header.ExtendedHeader], shrexSub *shrexsub.PubSub, discovery *discovery.Discovery, host host.Host, connGater *conngater.BasicConnectionGater, - opts ...Option, -) *Manager { - params := DefaultParameters() - - s := &Manager{ - headerSub: headerSub, - shrexSub: shrexSub, - connGater: connGater, - host: host, - pools: make(map[string]*syncPool), - poolValidationTimeout: params.ValidationTimeout, - peerCooldownTime: params.PeerCooldown, - gcInterval: params.GcInterval, - enableBlackListing: params.EnableBlackListing, - blacklistedHashes: make(map[string]bool), - done: make(chan struct{}), +) (*Manager, error) { + if err := params.Validate(); err != nil { + return nil, err } - for _, opt := range opts { - opt(s) + s := &Manager{ + params: params, + headerSub: headerSub, + shrexSub: shrexSub, + connGater: connGater, + host: host, + pools: make(map[string]*syncPool), + blacklistedHashes: make(map[string]bool), + done: make(chan struct{}), } - s.fullNodes = newPool(s.peerCooldownTime) + s.fullNodes = newPool(s.params.PeerCooldown) discovery.WithOnPeersUpdate( func(peerID peer.ID, isAdded bool) { @@ -135,7 +127,7 @@ func NewManager( s.fullNodes.remove(peerID) }) - return s + return s, nil } func (m *Manager) Start(startCtx context.Context) error { @@ -314,7 +306,7 @@ func (m *Manager) getOrCreatePool(datahash string) *syncPool { p, ok := m.pools[datahash] if !ok { p = &syncPool{ - pool: newPool(m.peerCooldownTime), + pool: newPool(m.params.PeerCooldown), createdAt: time.Now(), } m.pools[datahash] = p @@ -326,7 +318,7 @@ func (m *Manager) getOrCreatePool(datahash string) *syncPool { func (m *Manager) blacklistPeers(peerIDs ...peer.ID) { log.Debugw("blacklisting peers", "peers", peerIDs) - if !m.enableBlackListing { + if !m.params.EnableBlackListing { return } for _, peerID := range peerIDs { @@ -363,7 +355,7 @@ func (m *Manager) validatedPool(hashStr string) *syncPool { } func (m *Manager) GC(ctx context.Context) { - ticker := time.NewTicker(m.gcInterval) + ticker := time.NewTicker(m.params.GcInterval) defer ticker.Stop() var blacklist []peer.ID @@ -392,7 +384,7 @@ func (m *Manager) cleanUp() []peer.ID { addToBlackList := make(map[peer.ID]struct{}) for h, p := range m.pools { - if !p.isValidatedDataHash.Load() && time.Since(p.createdAt) > m.poolValidationTimeout { + if !p.isValidatedDataHash.Load() && time.Since(p.createdAt) > m.params.PoolValidationTimeout { delete(m.pools, h) if p.headerHeight.Load() < m.initialHeight.Load() { // outdated pools could still be valid even if not validated, no need to blacklist diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 18c0a82672..7d5bbaa96c 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -103,7 +103,7 @@ func TestManager(t *testing.T) { pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) require.NoError(t, err) require.Equal(t, peerID, pID) - manager.enableBlackListing = true + manager.params.EnableBlackListing = true done(ResultBlacklistPeer) // new messages from misbehaved peer should be Rejected @@ -127,7 +127,7 @@ func TestManager(t *testing.T) { require.NoError(t, headerSub.wait(ctx, 1)) // set syncTimeout to 0 to allow cleanup to find outdated datahash - manager.poolValidationTimeout = 0 + manager.params.PoolValidationTimeout = 0 // create unvalidated pool peerID := peer.ID("peer1") @@ -309,7 +309,7 @@ func TestManager(t *testing.T) { require.Len(t, manager.pools, 2) // trigger cleanup and check that no peers or hashes were blacklisted - manager.poolValidationTimeout = 0 + manager.params.PoolValidationTimeout = 0 blacklisted := manager.cleanUp() require.Len(t, blacklisted, 0) require.Len(t, manager.blacklistedHashes, 0) @@ -418,15 +418,15 @@ func TestIntegration(t *testing.T) { // hook peer manager to discovery connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) - fnPeerManager := NewManager( + fnPeerManager, err := NewManager( + DefaultParameters(), nil, nil, fnDisc, nil, connGater, - WithValidationTimeout(time.Minute), - WithPeerCooldown(time.Second), ) + require.NoError(t, err) waitCh := make(chan struct{}) fnDisc.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { @@ -464,15 +464,17 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten if err != nil { return nil, err } - manager := NewManager( + manager, err := NewManager( + DefaultParameters(), headerSub, shrexSub, disc, host, connGater, - WithValidationTimeout(time.Minute), - WithPeerCooldown(time.Second), ) + if err != nil { + return nil, err + } err = manager.Start(ctx) return manager, err } diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index 8fe71f5285..6812202578 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -5,15 +5,11 @@ import ( "time" ) -// Option is the functional option that is applied to the manager instance to configure peer manager -// parameters (the Parameters struct) -type Option func(manager *Manager) - type Parameters struct { - // ValidationTimeout is the timeout used for validating incoming datahashes. Pools that have + // PoolValidationTimeout is the timeout used for validating incoming datahashes. Pools that have // been created for datahashes from shrexsub that do not see this hash from headersub after this // timeout will be garbage collected. - ValidationTimeout time.Duration + PoolValidationTimeout time.Duration // PeerCooldown is the time a peer is put on cooldown after a ResultCooldownPeer. PeerCooldown time.Duration @@ -27,7 +23,7 @@ type Parameters struct { // Validate validates the values in Parameters func (p *Parameters) Validate() error { - if p.ValidationTimeout <= 0 { + if p.PoolValidationTimeout <= 0 { return fmt.Errorf("peer-manager: validation timeout must be positive") } @@ -45,10 +41,10 @@ func (p *Parameters) Validate() error { // DefaultParameters returns the default configuration values for the daser parameters func DefaultParameters() Parameters { return Parameters{ - // ValidationTimeout's default value is based on the default daser sampling timeout of 1 minute. + // PoolValidationTimeout's default value is based on the default daser sampling timeout of 1 minute. // If a received datahash has not tried to be sampled within these two minutes, the pool will be // removed. - ValidationTimeout: 2 * time.Minute, + PoolValidationTimeout: 2 * time.Minute, // PeerCooldown's default value is based on initial network tests that showed a ~3.5 second // sync time for large blocks. This value gives our (discovery) peers enough time to sync // the new block before we ask them again. @@ -59,31 +55,3 @@ func DefaultParameters() Parameters { EnableBlackListing: false, } } - -// WithValidationTimeout configures the manager's pool validation timeout. -func WithValidationTimeout(timeout time.Duration) Option { - return func(manager *Manager) { - manager.poolValidationTimeout = timeout - } -} - -// WithPeerCooldown configures the manager's peer cooldown time. -func WithPeerCooldown(cooldown time.Duration) Option { - return func(manager *Manager) { - manager.peerCooldownTime = cooldown - } -} - -// WithGcInterval configures the manager's garbage collection interval. -func WithGcInterval(interval time.Duration) Option { - return func(manager *Manager) { - manager.gcInterval = interval - } -} - -// WithEnabledBlacklisting turns on blacklisting of misbehaved peers. -func WithEnabledBlacklisting() Option { - return func(manager *Manager) { - manager.enableBlackListing = true - } -} From 9c0a5fb0626ada6e6cdb8bcd816d01a3aa5043ad Mon Sep 17 00:00:00 2001 From: PV <44137386+Ray-Escobar@users.noreply.github.com> Date: Thu, 6 Apr 2023 13:53:39 +0200 Subject: [PATCH 0524/1008] das/share: Change log from DAH.Hash() to DAH.String() (#1971) ## Overview Changes log input to use the string method for both the DataAvailabilityHeader and Root. Fixes #1898 ## Checklist - [x] Required CI checks are passing - [x] Linked issues closed with keywords --- das/worker.go | 6 +++--- share/availability/full/availability.go | 2 +- share/availability/light/availability.go | 10 +++++----- share/eds/retriever.go | 7 +++---- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/das/worker.go b/das/worker.go index 258ea539e8..c73ad235f8 100644 --- a/das/worker.go +++ b/das/worker.go @@ -112,7 +112,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), + "data root", h.DAH.String(), "err", err, "finished (s)", time.Since(start), ) @@ -125,7 +125,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), + "data root", h.DAH.String(), "finished (s)", time.Since(start), ) @@ -166,7 +166,7 @@ func (w *worker) getHeader(ctx context.Context, height uint64) (*header.Extended "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.Hash(), + "data root", h.DAH.String(), "finished (s)", time.Since(start), ) return h, nil diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 76476f3824..9eef8d1372 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -68,7 +68,7 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro _, err := fa.getter.GetEDS(ctx, root) if err != nil { - log.Errorw("availability validation failed", "root", root.Hash(), "err", err.Error()) + log.Errorw("availability validation failed", "root", root.String(), "err", err.Error()) var byzantineErr *byzantine.ErrByzantine if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { return share.ErrNotAvailable diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 57f044526c..c947e65e63 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -30,7 +30,7 @@ func NewShareAvailability(getter share.Getter) *ShareAvailability { // SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given // Root. This way SharesAvailable subjectively verifies that Shares are available. func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { - log.Debugw("Validate availability", "root", dah.Hash()) + log.Debugw("Validate availability", "root", dah.String()) // We assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. if err := dah.ValidateBasic(); err != nil { @@ -47,14 +47,14 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo // functionality is optional and must be supported by the used share.Getter. ctx = getters.WithSession(ctx) - log.Debugw("starting sampling session", "root", dah.Hash()) + log.Debugw("starting sampling session", "root", dah.String()) errs := make(chan error, len(samples)) for _, s := range samples { go func(s Sample) { - log.Debugw("fetching share", "root", dah.Hash(), "row", s.Row, "col", s.Col) + log.Debugw("fetching share", "root", dah.String(), "row", s.Row, "col", s.Col) _, err := la.getter.GetShare(ctx, dah, s.Row, s.Col) if err != nil { - log.Debugw("error fetching share", "root", dah.Hash(), "row", s.Row, "col", s.Col) + log.Debugw("error fetching share", "root", dah.String(), "row", s.Row, "col", s.Col) } // we don't really care about Share bodies at this point // it also means we now saved the Share in local storage @@ -75,7 +75,7 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo if err != nil { if !errors.Is(err, context.Canceled) { - log.Errorw("availability validation failed", "root", dah.Hash(), "err", err.Error()) + log.Errorw("availability validation failed", "root", dah.String(), "err", err.Error()) } if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { return share.ErrNotAvailable diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 68390ea7d6..5d3c61cabb 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -2,7 +2,6 @@ package eds import ( "context" - "encoding/hex" "errors" "sync" "sync/atomic" @@ -65,10 +64,10 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader defer span.End() span.SetAttributes( attribute.Int("size", len(dah.RowsRoots)), - attribute.String("data_hash", hex.EncodeToString(dah.Hash())), + attribute.String("data_hash", dah.String()), ) - log.Debugw("retrieving data square", "data_hash", hex.EncodeToString(dah.Hash()), "size", len(dah.RowsRoots)) + log.Debugw("retrieving data square", "data_hash", dah.String(), "size", len(dah.RowsRoots)) ses, err := r.newSession(ctx, dah) if err != nil { return nil, err @@ -175,7 +174,7 @@ func (rs *retrievalSession) Reconstruct(ctx context.Context) (*rsmt2d.ExtendedDa span.RecordError(err) return nil, err } - log.Infow("data square reconstructed", "data_hash", hex.EncodeToString(rs.dah.Hash()), "size", len(rs.dah.RowsRoots)) + log.Infow("data square reconstructed", "data_hash", rs.dah.String(), "size", len(rs.dah.RowsRoots)) close(rs.squareDn) return rs.square, nil } From d9615f03d65feacde306082e005e145915ded27e Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 11 Apr 2023 04:03:01 +0300 Subject: [PATCH 0525/1008] refactoring(share/p2p): remove TTL in pubsub (#2023) Return TTL back to the default value to avoid message duplication with the same msgID. Co-authored-by: Hlib Kanunnikov --- share/p2p/shrexsub/pubsub.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 39ffb144c5..82ed256e33 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -46,9 +46,7 @@ type PubSub struct { // NewPubSub creates a libp2p.PubSub wrapper. func NewPubSub(ctx context.Context, h host.Host, networkID string) (*PubSub, error) { - // WithSeenMessagesTTL without duration allows to process all incoming messages(even with the same - // msgId) - pubsub, err := pubsub.NewFloodSub(ctx, h, pubsub.WithSeenMessagesTTL(0)) + pubsub, err := pubsub.NewFloodSub(ctx, h) if err != nil { return nil, err } From 7156fb067f2b6d3736f591e2d9b6ffd9cde6f5be Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 11 Apr 2023 19:32:14 +0800 Subject: [PATCH 0526/1008] refactor(share/eds): add ErrNotFound to eds store and shrex server (#2048) ## Overview If item doesn't exist in store, return explicit error `ErrNotFound`. --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- share/eds/store.go | 6 ++++++ share/eds/store_test.go | 12 ++++++++++++ share/p2p/shrexnd/server.go | 13 +++++++++++++ 3 files changed, 31 insertions(+) diff --git a/share/eds/store.go b/share/eds/store.go index 257cff2b65..50bce3e0fc 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" "os" @@ -36,6 +37,8 @@ const ( defaultGCInterval = time.Hour ) +var ErrNotFound = errors.New("eds not found in store") + // Store maintains (via DAGStore) a top-level index enabling granular and efficient random access to // every share and/or Merkle proof over every registered CARv1 file. The EDSStore provides a custom // blockstore interface implementation to achieve access. The main use-case is randomized sampling @@ -282,6 +285,9 @@ func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*dagstore.Shard ch := make(chan dagstore.ShardResult, 1) err := s.dgstr.AcquireShard(ctx, key, ch, dagstore.AcquireOpts{}) if err != nil { + if errors.Is(err, dagstore.ErrShardUnknown) { + return nil, ErrNotFound + } return nil, fmt.Errorf("failed to initialize shard acquisition: %w", err) } diff --git a/share/eds/store_test.go b/share/eds/store_test.go index e30e66f848..ab0d74b2d2 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -85,6 +85,18 @@ func TestEDSStore(t *testing.T) { } }) + t.Run("item not exist", func(t *testing.T) { + root := share.DataHash{1} + _, err := edsStore.GetCAR(ctx, root) + assert.ErrorIs(t, err, ErrNotFound) + + _, err = edsStore.GetDAH(ctx, root) + assert.ErrorIs(t, err, ErrNotFound) + + _, err = edsStore.CARBlockstore(ctx, root) + assert.ErrorIs(t, err, ErrNotFound) + }) + t.Run("Remove", func(t *testing.T) { eds, dah := randomEDS(t) diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index fb2429cf93..eaa0d1d9d2 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -2,6 +2,7 @@ package shrexnd import ( "context" + "errors" "fmt" "time" @@ -100,6 +101,10 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre dah, err := srv.store.GetDAH(ctx, req.RootHash) if err != nil { + if errors.Is(err, eds.ErrNotFound) { + srv.respondNotFoundError(stream) + return + } log.Errorw("server: retrieving DAH for datahash", "err", err) srv.respondInternalError(stream) return @@ -129,6 +134,14 @@ func validateRequest(req pb.GetSharesByNamespaceRequest) error { return nil } +// respondNotFoundError sends internal error response to client +func (srv *Server) respondNotFoundError(stream network.Stream) { + resp := &pb.GetSharesByNamespaceResponse{ + Status: pb.StatusCode_NOT_FOUND, + } + srv.respond(stream, resp) +} + // respondInternalError sends internal error response to client func (srv *Server) respondInternalError(stream network.Stream) { resp := &pb.GetSharesByNamespaceResponse{ From c5bbfcb20ab152de56b4de2dcf43cb0802539928 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 11 Apr 2023 14:18:10 +0200 Subject: [PATCH 0527/1008] fix: CLI for `GetSharesByNamespace` should allow hex for namespace (#2025) Closes #2019 --- cmd/celestia/rpc.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 61274924e5..c862e662e5 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -90,6 +90,21 @@ func parseParams(method string, params []string) []interface{} { parsedParams := make([]interface{}, len(params)) switch method { + case "GetSharesByNamespace": + // 1. Share Root + parsedParams[0] = params[0] + // 2. NamespaceID + if strings.HasPrefix(params[1], "0x") { + decoded, err := hex.DecodeString(params[1][2:]) + if err != nil { + panic("Error decoding namespace ID: hex string could not be decoded.") + } + parsedParams[1] = decoded + } else { + // otherwise, it's just a base64 string + parsedParams[1] = params[1] + } + return parsedParams case "SubmitPayForBlob": // 1. NamespaceID if strings.HasPrefix(params[0], "0x") { From 1fa3de18e9e254a77e1952be3edb25863b02ae0e Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 11 Apr 2023 16:18:35 +0200 Subject: [PATCH 0528/1008] fix(swamp): fixing p2p_test and reconstruct_test (#1951) Some hotfixes that fix swamp tests. Would like to understand why these fixes work before taking out of draft --- nodebuilder/p2p/bootstrap.go | 4 +- nodebuilder/p2p/pubsub.go | 6 +-- nodebuilder/tests/p2p_test.go | 61 +++++++++------------------ nodebuilder/tests/reconstruct_test.go | 7 +++ nodebuilder/tests/sync_test.go | 1 + 5 files changed, 32 insertions(+), 47 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index f03825e9cb..0ceefa0453 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -8,10 +8,10 @@ import ( ma "github.com/multiformats/go-multiaddr" ) -const envKeyCelestiaBootstrapper = "CELESTIA_BOOTSTRAPPER" +const EnvKeyCelestiaBootstrapper = "CELESTIA_BOOTSTRAPPER" func isBootstrapper() bool { - return os.Getenv(envKeyCelestiaBootstrapper) == strconv.FormatBool(true) + return os.Getenv(EnvKeyCelestiaBootstrapper) == strconv.FormatBool(true) } // BootstrappersFor returns address information of bootstrap peers for a given network. diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 6202aa9fef..74ed1eda2a 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -67,7 +67,7 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { // * lotus // * prysm topicScores := topicScoreParams(params.Network) - peerScores, err := peerScoreParams(isBootstrapper, params.Bootstrappers, cfg) + peerScores, err := peerScoreParams(params.Bootstrappers, cfg) if err != nil { return nil, err } @@ -119,7 +119,7 @@ func topicScoreParams(network Network) map[string]*pubsub.TopicScoreParams { return mp } -func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers, cfg Config) (*pubsub.PeerScoreParams, error) { +func peerScoreParams(bootstrappers Bootstrappers, cfg Config) (*pubsub.PeerScoreParams, error) { bootstrapperSet := map[peer.ID]struct{}{} for _, b := range bootstrappers { bootstrapperSet[b.ID] = struct{}{} @@ -141,7 +141,7 @@ func peerScoreParams(isBootstrapper bool, bootstrappers Bootstrappers, cfg Confi // return a heavy positive score for bootstrappers so that we don't unilaterally prune // them and accept PX from them _, ok := bootstrapperSet[p] - if ok && !isBootstrapper { + if ok { return 2500 } diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 4e21e7d0e3..95a33b11e8 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -81,7 +81,7 @@ Test-Case: Connect Full And Light using Bridge node as a bootstrapper Steps: 1. Create a Bridge Node(BN) 2. Start a BN - 3. Create full/light nodes with bridge node as bootsrapped peer + 3. Create full/light nodes with bridge node as bootstrapped peer 4. Start full/light nodes 5. Ensure that nodes are connected to bridge 6. Wait until light will find full node @@ -101,22 +101,23 @@ func TestBootstrapNodesFromBridgeNode(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - addr := host.InfoFromHost(bridge.Host) + bridgeAddr := host.InfoFromHost(bridge.Host) cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) full := sw.NewNodeWithConfig( node.Full, cfg, - nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr}), + nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}), ) cfg = nodebuilder.DefaultConfig(node.Light) setTimeInterval(cfg, defaultTimeInterval) + cfg.P2P.PeerExchange = true light := sw.NewNodeWithConfig( node.Light, cfg, - nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr}), + nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}), ) nodes := []*nodebuilder.Node{full, light} ch := make(chan struct{}) @@ -125,25 +126,21 @@ func TestBootstrapNodesFromBridgeNode(t *testing.T) { defer sub.Close() for index := range nodes { require.NoError(t, nodes[index].Start(ctx)) - assert.Equal(t, *addr, nodes[index].Bootstrappers[0]) - assert.True(t, nodes[index].Host.Network().Connectedness(addr.ID) == network.Connected) + assert.Equal(t, *bridgeAddr, nodes[index].Bootstrappers[0]) + assert.True(t, nodes[index].Host.Network().Connectedness(bridgeAddr.ID) == network.Connected) } addrFull := host.InfoFromHost(full.Host) go func() { for e := range sub.Out() { connStatus := e.(event.EvtPeerConnectednessChanged) - if connStatus.Peer == full.Host.ID() { + if connStatus.Peer == full.Host.ID() && connStatus.Connectedness == network.NotConnected { ch <- struct{}{} } } }() - select { - case <-ctx.Done(): - t.Fatal("peer was not found") - case <-ch: - assert.True(t, light.Host.Network().Connectedness(addrFull.ID) == network.Connected) - } + // ensure that the light node is connected to the full node + assert.True(t, light.Host.Network().Connectedness(addrFull.ID) == network.Connected) sw.Disconnect(t, light.Host.ID(), full.Host.ID()) require.NoError(t, full.Stop(ctx)) @@ -182,33 +179,24 @@ func TestRestartNodeDiscovery(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - addr := host.InfoFromHost(bridge.Host) + bridgeAddr := host.InfoFromHost(bridge.Host) + nodes := make([]*nodebuilder.Node, fullNodes) cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) cfg.Share.PeersLimit = fullNodes - nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr}) + nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}) for index := 0; index < fullNodes; index++ { nodes[index] = sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) } - identitySub, err := nodes[0].Host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - defer identitySub.Close() - for index := 0; index < fullNodes; index++ { require.NoError(t, nodes[index].Start(ctx)) - assert.True(t, nodes[index].Host.Network().Connectedness(addr.ID) == network.Connected) + assert.True(t, nodes[index].Host.Network().Connectedness(bridgeAddr.ID) == network.Connected) } - // wait until full nodes connect each other - e := <-identitySub.Out() - connStatus := e.(event.EvtPeerIdentificationCompleted) - id := connStatus.Peer - if id != nodes[1].Host.ID() { - t.Fatal("unexpected peer connected") - } - require.True(t, nodes[0].Host.Network().Connectedness(id) == network.Connected) + // ensure full nodes are connected to each other + require.True(t, nodes[0].Host.Network().Connectedness(nodes[1].Host.ID()) == network.Connected) // create one more node with disabled discovery cfg = nodebuilder.DefaultConfig(node.Full) @@ -220,20 +208,9 @@ func TestRestartNodeDiscovery(t *testing.T) { defer connectSub.Close() sw.Disconnect(t, nodes[0].Host.ID(), nodes[1].Host.ID()) require.NoError(t, node.Start(ctx)) - for { - select { - case <-ctx.Done(): - require.True(t, nodes[0].Host.Network().Connectedness(node.Host.ID()) == network.Connected) - return - case conn := <-connectSub.Out(): - status := conn.(event.EvtPeerConnectednessChanged) - if status.Peer != node.Host.ID() { - continue - } - require.True(t, status.Connectedness == network.Connected) - return - } - } + + // ensure that the last node is connected to one of the nodes + require.True(t, nodes[0].Host.Network().Connectedness(node.Host.ID()) == network.Connected) } func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index d533a9aeb4..876afc09a9 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -7,6 +7,7 @@ package tests import ( "context" + "os" "testing" "time" @@ -18,6 +19,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" @@ -52,6 +54,7 @@ func TestFullReconstructFromBridge(t *testing.T) { require.NoError(t, err) cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Share.UseShareExchange = false cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, getMultiAddr(t, bridge.Host)) full := sw.NewNodeWithConfig(node.Full, cfg) err = full.Start(ctx) @@ -112,6 +115,8 @@ func TestFullReconstructFromLights(t *testing.T) { bridge := sw.NewBridgeNode() addrsBridge, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) + + os.Setenv(p2p.EnvKeyCelestiaBootstrapper, "true") bootstrapper := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, bootstrapper.Start(ctx)) require.NoError(t, bridge.Start(ctx)) @@ -119,9 +124,11 @@ func TestFullReconstructFromLights(t *testing.T) { cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) + cfg.Share.UseShareExchange = false cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrsBridge[0].String()) nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*bootstrapperAddr}) full := sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) + os.Setenv(p2p.EnvKeyCelestiaBootstrapper, "false") lights := make([]*nodebuilder.Node, lnodes) subs := make([]event.Subscription, lnodes) diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 6d35072bbf..adaf788884 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -169,6 +169,7 @@ func TestSyncFullWithBridge(t *testing.T) { cfg := nodebuilder.DefaultConfig(node.Full) cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + cfg.Share.UseShareExchange = false full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) From 186755f19504df3d689f80a2e69d1a8ac251fd2d Mon Sep 17 00:00:00 2001 From: Hoyt Ren Date: Tue, 11 Apr 2023 23:13:20 +0800 Subject: [PATCH 0529/1008] chore(log): Suppress pubsub logs to WARN and net/identify to ERROR (#2002) Partially fix #1992. I'm not sure this is a final fix, but at least let's disable the spam message first, or we could not see any real problem. I let it run on blockspacerace for about 2 hours after modify, don't see any problem besides having to resync bridge again. My bridge established no more than 20 connections, seems very low. --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- logs/logs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/logs/logs.go b/logs/logs.go index bc18fbc411..6398548023 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -17,6 +17,8 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("bitswap_network", "ERROR") _ = logging.SetLogLevel("badger", "INFO") _ = logging.SetLogLevel("basichost", "INFO") + _ = logging.SetLogLevel("pubsub", "WARN") + _ = logging.SetLogLevel("net/identify", "ERROR") } func SetDebugLogging() { From c37fcd4834064bdbbcc559ee2545e11288100c9d Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 12 Apr 2023 10:53:49 +0200 Subject: [PATCH 0530/1008] refactor(libs/fraud): fraud tests overhaul (#2039) The main goal here is to prepare everything for extraction. There was a cyclic dependency and dependence on ExtendedHeader inside of fraud, which blocked the extraction. This patch fixes this in conjunction with cleanings for testing utilities and actual tests. List of other notable changes: - Updates go-header and - migrates to the new API for testing in header - Restructures fraud pkg and extract fraud.Service and related logic into sub pkg fraudserv - Keeping only interface definitions inside of the root fraud - Extracts testing utilities into fraudtest - Adds sprinkles of documentation --- das/daser_test.go | 19 +- go.mod | 2 +- go.sum | 4 +- header/headertest/testing.go | 48 +---- libs/fraud/{ => fraudserv}/gossip_score.go | 2 +- libs/fraud/{ => fraudserv}/helpers.go | 12 +- libs/fraud/{ => fraudserv}/pb/proof.pb.go | 9 +- libs/fraud/{ => fraudserv}/pb/proof.proto | 0 libs/fraud/{ => fraudserv}/requester.go | 4 +- libs/fraud/{ => fraudserv}/service.go | 47 ++-- libs/fraud/fraudserv/service_test.go | 196 +++++++++++++++++ libs/fraud/{ => fraudserv}/store.go | 16 +- libs/fraud/{ => fraudserv}/store_test.go | 21 +- libs/fraud/{ => fraudserv}/subscription.go | 8 +- libs/fraud/{ => fraudserv}/sync.go | 11 +- libs/fraud/fraudtest/dummy_proof.go | 53 +++++ libs/fraud/fraudtest/dummy_service.go | 29 +++ libs/fraud/interface.go | 13 +- libs/fraud/metrics.go | 3 + libs/fraud/proof.go | 46 ++-- libs/fraud/service_test.go | 239 --------------------- libs/fraud/testing.go | 149 ------------- nodebuilder/fraud/constructors.go | 3 +- nodebuilder/p2p/pubsub.go | 3 +- nodebuilder/testing.go | 6 +- share/eds/retriever_test.go | 5 +- share/eds/store.go | 4 +- 27 files changed, 412 insertions(+), 540 deletions(-) rename libs/fraud/{ => fraudserv}/gossip_score.go (98%) rename libs/fraud/{ => fraudserv}/helpers.go (73%) rename libs/fraud/{ => fraudserv}/pb/proof.pb.go (99%) rename libs/fraud/{ => fraudserv}/pb/proof.proto (100%) rename libs/fraud/{ => fraudserv}/requester.go (93%) rename libs/fraud/{ => fraudserv}/service.go (82%) create mode 100644 libs/fraud/fraudserv/service_test.go rename libs/fraud/{ => fraudserv}/store.go (79%) rename libs/fraud/{ => fraudserv}/store_test.go (88%) rename libs/fraud/{ => fraudserv}/subscription.go (75%) rename libs/fraud/{ => fraudserv}/sync.go (93%) create mode 100644 libs/fraud/fraudtest/dummy_proof.go create mode 100644 libs/fraud/fraudtest/dummy_service.go delete mode 100644 libs/fraud/service_test.go delete mode 100644 libs/fraud/testing.go diff --git a/das/daser_test.go b/das/daser_test.go index 75ade111be..2c51b48fe4 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -21,6 +21,8 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv" + "github.com/celestiaorg/celestia-node/libs/fraud/fraudtest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" @@ -160,7 +162,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { getter := func(ctx context.Context, height uint64) (libhead.Header, error) { return mockGet.GetByHeight(ctx, height) } - f := fraud.NewProofService(ps, net.Hosts()[0], getter, ds, false, "private") + f := fraudserv.NewProofService(ps, net.Hosts()[0], getter, ds, false, "private") require.NoError(t, f.Start(ctx)) mockGet.headers[1], _ = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() @@ -205,8 +207,8 @@ func TestDASerSampleTimeout(t *testing.T) { }) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - sub := new(headertest.DummySubscriber) - f := new(fraud.DummyService) + sub := new(headertest.Subscriber) + f := new(fraudtest.DummyService) // create and start DASer daser, err := NewDASer(avail, sub, getter, ds, f, newBroadcastMock(1), WithSampleTimeout(1)) @@ -231,9 +233,9 @@ func createDASerSubcomponents( bServ blockservice.BlockService, numGetter, numSub int, -) (*mockGetter, *headertest.DummySubscriber, *fraud.DummyService) { +) (*mockGetter, *headertest.Subscriber, *fraudtest.DummyService) { mockGet, sub := createMockGetterAndSub(t, bServ, numGetter, numSub) - fraud := new(fraud.DummyService) + fraud := new(fraudtest.DummyService) return mockGet, sub, fraud } @@ -242,7 +244,7 @@ func createMockGetterAndSub( bServ blockservice.BlockService, numGetter, numSub int, -) (*mockGetter, *headertest.DummySubscriber) { +) (*mockGetter, *headertest.Subscriber) { mockGet := &mockGetter{ headers: make(map[int64]*header.ExtendedHeader), doneCh: make(chan struct{}), @@ -251,16 +253,15 @@ func createMockGetterAndSub( mockGet.generateHeaders(t, bServ, 0, numGetter) - sub := new(headertest.DummySubscriber) + sub := new(headertest.Subscriber) mockGet.fillSubWithHeaders(t, sub, bServ, numGetter, numGetter+numSub) - return mockGet, sub } // fillSubWithHeaders generates `num` headers from the future for p2pSub to pipe through to DASer. func (m *mockGetter) fillSubWithHeaders( t *testing.T, - sub *headertest.DummySubscriber, + sub *headertest.Subscriber, bServ blockservice.BlockService, startHeight, endHeight int, diff --git a/go.mod b/go.mod index ccf6fdc1b7..3397b40f76 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.2 - github.com/celestiaorg/go-header v0.2.3 + github.com/celestiaorg/go-header v0.2.4 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.15.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index 13117ba45b..dc56ccb4f5 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,8 @@ github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SO github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136 h1:LBvY3NDA18fcS72pBAEd2pENoUpz1iV4cCXBN2Zrj/I= github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= -github.com/celestiaorg/go-header v0.2.3 h1:41r60OtAeexWC3J3eTELgWfzcdKR2taFlfcJ/2IHZD4= -github.com/celestiaorg/go-header v0.2.3/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= +github.com/celestiaorg/go-header v0.2.4 h1:GZk0/CT/DI7p/3BUNC1bBmx5qk7PiujVseVzVj/LCqM= +github.com/celestiaorg/go-header v0.2.4/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= diff --git a/header/headertest/testing.go b/header/headertest/testing.go index fabd80ed67..11abc5f2d6 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -11,7 +11,6 @@ import ( "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" - pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bytes" @@ -23,7 +22,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" - "github.com/celestiaorg/go-header/test" + "github.com/celestiaorg/go-header/headertest" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" @@ -44,6 +43,10 @@ type TestSuite struct { head *header.ExtendedHeader } +func NewStore(t *testing.T) libhead.Store[*header.ExtendedHeader] { + return headertest.NewStore[*header.ExtendedHeader](t, NewTestSuite(t, 3), 10) +} + // NewTestSuite setups a new test suite with a given number of validators. func NewTestSuite(t *testing.T, num int) *TestSuite { valSet, vals := RandValidatorSet(num, 10) @@ -125,18 +128,14 @@ func (s *TestSuite) Head() *header.ExtendedHeader { func (s *TestSuite) GenExtendedHeaders(num int) []*header.ExtendedHeader { headers := make([]*header.ExtendedHeader, num) for i := range headers { - headers[i] = s.GenExtendedHeader() + headers[i] = s.NextHeader() } return headers } -func (s *TestSuite) GetRandomHeader() *header.ExtendedHeader { - return s.GenExtendedHeader() -} - -var _ test.Generator[*header.ExtendedHeader] = &TestSuite{} +var _ headertest.Generator[*header.ExtendedHeader] = &TestSuite{} -func (s *TestSuite) GenExtendedHeader() *header.ExtendedHeader { +func (s *TestSuite) NextHeader() *header.ExtendedHeader { if s.head == nil { s.head = s.genesis() return s.head @@ -334,33 +333,8 @@ func CreateFraudExtHeader( return eh, extended } -type DummySubscriber struct { - Headers []*header.ExtendedHeader -} - -func (mhs *DummySubscriber) AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult) error { - return nil -} - -func (mhs *DummySubscriber) Subscribe() (libhead.Subscription[*header.ExtendedHeader], error) { - return mhs, nil -} - -func (mhs *DummySubscriber) NextHeader(context.Context) (*header.ExtendedHeader, error) { - defer func() { - if len(mhs.Headers) > 1 { - // pop the already-returned header - cp := mhs.Headers - mhs.Headers = cp[1:] - } else { - mhs.Headers = make([]*header.ExtendedHeader, 0) - } - }() - if len(mhs.Headers) == 0 { - return nil, context.Canceled - } - return mhs.Headers[0], nil +type Subscriber struct { + headertest.Subscriber[*header.ExtendedHeader] } -func (mhs *DummySubscriber) Stop(context.Context) error { return nil } -func (mhs *DummySubscriber) Cancel() {} +var _ libhead.Subscriber[*header.ExtendedHeader] = &Subscriber{} diff --git a/libs/fraud/gossip_score.go b/libs/fraud/fraudserv/gossip_score.go similarity index 98% rename from libs/fraud/gossip_score.go rename to libs/fraud/fraudserv/gossip_score.go index 2a97948d9b..2f758fb751 100644 --- a/libs/fraud/gossip_score.go +++ b/libs/fraud/fraudserv/gossip_score.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "math" diff --git a/libs/fraud/helpers.go b/libs/fraud/fraudserv/helpers.go similarity index 73% rename from libs/fraud/helpers.go rename to libs/fraud/fraudserv/helpers.go index 3ba55a1c86..99b73d5535 100644 --- a/libs/fraud/helpers.go +++ b/libs/fraud/fraudserv/helpers.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "context" @@ -7,6 +7,8 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + + "github.com/celestiaorg/celestia-node/libs/fraud" ) func PubsubTopicID(fraudType, networkID string) string { @@ -17,8 +19,12 @@ func protocolID(networkID string) protocol.ID { return protocol.ID(fmt.Sprintf("/%s/fraud/v0.0.1", networkID)) } -func join(p *pubsub.PubSub, proofType ProofType, networkID string, - validate func(context.Context, ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult) (*pubsub.Topic, error) { +func join( + p *pubsub.PubSub, + proofType fraud.ProofType, + networkID string, + validate func(context.Context, fraud.ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult, +) (*pubsub.Topic, error) { topic := PubsubTopicID(proofType.String(), networkID) log.Infow("joining topic", "id", topic) t, err := p.Join(topic) diff --git a/libs/fraud/pb/proof.pb.go b/libs/fraud/fraudserv/pb/proof.pb.go similarity index 99% rename from libs/fraud/pb/proof.pb.go rename to libs/fraud/fraudserv/pb/proof.pb.go index 2d5b679490..09bbc189da 100644 --- a/libs/fraud/pb/proof.pb.go +++ b/libs/fraud/fraudserv/pb/proof.pb.go @@ -4,11 +4,12 @@ package fraud_pb import ( - fmt "fmt" - proto "github.com/gogo/protobuf/proto" - io "io" - math "math" + "fmt" + "io" + "math" math_bits "math/bits" + + "github.com/gogo/protobuf/proto" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/libs/fraud/pb/proof.proto b/libs/fraud/fraudserv/pb/proof.proto similarity index 100% rename from libs/fraud/pb/proof.proto rename to libs/fraud/fraudserv/pb/proof.proto diff --git a/libs/fraud/requester.go b/libs/fraud/fraudserv/requester.go similarity index 93% rename from libs/fraud/requester.go rename to libs/fraud/fraudserv/requester.go index 75a1d2d4c4..682bf9b4d3 100644 --- a/libs/fraud/requester.go +++ b/libs/fraud/fraudserv/requester.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "context" @@ -9,7 +9,7 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" - pb "github.com/celestiaorg/celestia-node/libs/fraud/pb" + pb "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv/pb" ) const ( diff --git a/libs/fraud/service.go b/libs/fraud/fraudserv/service.go similarity index 82% rename from libs/fraud/service.go rename to libs/fraud/fraudserv/service.go index 1e68a80f25..631c390dd1 100644 --- a/libs/fraud/service.go +++ b/libs/fraud/fraudserv/service.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "bytes" @@ -9,12 +9,21 @@ import ( "sync" "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + + "github.com/celestiaorg/celestia-node/libs/fraud" +) + +var ( + log = logging.Logger("fraudserv") + tracer = otel.Tracer("fraudserv") ) // fraudRequests is the amount of external requests that will be tried to get fraud proofs from @@ -30,14 +39,14 @@ type ProofService struct { cancel context.CancelFunc topicsLk sync.RWMutex - topics map[ProofType]*pubsub.Topic + topics map[fraud.ProofType]*pubsub.Topic storesLk sync.RWMutex - stores map[ProofType]datastore.Datastore + stores map[fraud.ProofType]datastore.Datastore pubsub *pubsub.PubSub host host.Host - getter headerFetcher + getter fraud.HeaderFetcher ds datastore.Datastore syncerEnabled bool @@ -46,7 +55,7 @@ type ProofService struct { func NewProofService( p *pubsub.PubSub, host host.Host, - getter headerFetcher, + getter fraud.HeaderFetcher, ds datastore.Datastore, syncerEnabled bool, networkID string, @@ -55,8 +64,8 @@ func NewProofService( pubsub: p, host: host, getter: getter, - topics: make(map[ProofType]*pubsub.Topic), - stores: make(map[ProofType]datastore.Datastore), + topics: make(map[fraud.ProofType]*pubsub.Topic), + stores: make(map[fraud.ProofType]datastore.Datastore), ds: ds, networkID: networkID, syncerEnabled: syncerEnabled, @@ -64,7 +73,7 @@ func NewProofService( } // registerProofTopics registers proofTypes as pubsub topics to be joined. -func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { +func (f *ProofService) registerProofTopics(proofTypes ...fraud.ProofType) error { for _, proofType := range proofTypes { t, err := join(f.pubsub, proofType, f.networkID, f.processIncoming) if err != nil { @@ -81,7 +90,7 @@ func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { // if syncer is enabled. func (f *ProofService) Start(context.Context) error { f.ctx, f.cancel = context.WithCancel(context.Background()) - if err := f.registerProofTopics(registeredProofTypes()...); err != nil { + if err := f.registerProofTopics(fraud.Registered()...); err != nil { return err } id := protocolID(f.networkID) @@ -95,13 +104,13 @@ func (f *ProofService) Start(context.Context) error { } // Stop removes the stream handler and cancels the underlying ProofService -func (f *ProofService) Stop(context.Context) error { +func (f *ProofService) Stop(context.Context) (err error) { f.host.RemoveStreamHandler(protocolID(f.networkID)) f.cancel() - return nil + return } -func (f *ProofService) Subscribe(proofType ProofType) (_ Subscription, err error) { +func (f *ProofService) Subscribe(proofType fraud.ProofType) (_ fraud.Subscription, err error) { f.topicsLk.Lock() defer f.topicsLk.Unlock() t, ok := f.topics[proofType] @@ -115,7 +124,7 @@ func (f *ProofService) Subscribe(proofType ProofType) (_ Subscription, err error return &subscription{subs}, nil } -func (f *ProofService) Broadcast(ctx context.Context, p Proof) error { +func (f *ProofService) Broadcast(ctx context.Context, p fraud.Proof) error { bin, err := p.MarshalBinary() if err != nil { return err @@ -132,7 +141,7 @@ func (f *ProofService) Broadcast(ctx context.Context, p Proof) error { // processIncoming encompasses the logic for validating fraud proofs. func (f *ProofService) processIncoming( ctx context.Context, - proofType ProofType, + proofType fraud.ProofType, from peer.ID, msg *pubsub.Message, ) pubsub.ValidationResult { @@ -143,10 +152,10 @@ func (f *ProofService) processIncoming( // unmarshal message to the Proof. // Peer will be added to black list if unmarshalling fails. - proof, err := Unmarshal(proofType, msg.Data) + proof, err := fraud.Unmarshal(proofType, msg.Data) if err != nil { log.Errorw("unmarshalling failed", "err", err) - if !errors.Is(err, &errNoUnmarshaler{}) { + if !errors.Is(err, &fraud.ErrNoUnmarshaler{}) { f.pubsub.BlacklistPeer(from) } span.RecordError(err) @@ -201,7 +210,7 @@ func (f *ProofService) processIncoming( return pubsub.ValidationAccept } -func (f *ProofService) Get(ctx context.Context, proofType ProofType) ([]Proof, error) { +func (f *ProofService) Get(ctx context.Context, proofType fraud.ProofType) ([]fraud.Proof, error) { f.storesLk.Lock() store, ok := f.stores[proofType] if !ok { @@ -214,7 +223,7 @@ func (f *ProofService) Get(ctx context.Context, proofType ProofType) ([]Proof, e } // put adds a fraud proof to the local storage. -func (f *ProofService) put(ctx context.Context, proofType ProofType, hash string, data []byte) error { +func (f *ProofService) put(ctx context.Context, proofType fraud.ProofType, hash string, data []byte) error { f.storesLk.Lock() store, ok := f.stores[proofType] if !ok { @@ -226,7 +235,7 @@ func (f *ProofService) put(ctx context.Context, proofType ProofType, hash string } // verifyLocal checks if a fraud proof has been stored locally. -func (f *ProofService) verifyLocal(ctx context.Context, proofType ProofType, hash string, data []byte) bool { +func (f *ProofService) verifyLocal(ctx context.Context, proofType fraud.ProofType, hash string, data []byte) bool { f.storesLk.RLock() storage, ok := f.stores[proofType] f.storesLk.RUnlock() diff --git a/libs/fraud/fraudserv/service_test.go b/libs/fraud/fraudserv/service_test.go new file mode 100644 index 0000000000..f297ab94ef --- /dev/null +++ b/libs/fraud/fraudserv/service_test.go @@ -0,0 +1,196 @@ +package fraudserv + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/headertest" + + "github.com/celestiaorg/celestia-node/libs/fraud/fraudtest" +) + +func TestService_SubscribeBroadcastValid(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + + serv := newTestService(ctx, t, false) + require.NoError(t, serv.Start(ctx)) + + fraud := fraudtest.NewValidProof() + sub, err := serv.Subscribe(fraud.Type()) + require.NoError(t, err) + defer sub.Cancel() + + require.NoError(t, serv.Broadcast(ctx, fraud)) + _, err = sub.Proof(ctx) + require.NoError(t, err) +} + +func TestService_SubscribeBroadcastInvalid(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + + serv := newTestService(ctx, t, false) + require.NoError(t, serv.Start(ctx)) + + fraud := fraudtest.NewInvalidProof() + sub, err := serv.Subscribe(fraud.Type()) + require.NoError(t, err) + defer sub.Cancel() + + err = serv.Broadcast(ctx, fraud) + require.Error(t, err) + + ctx2, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) + t.Cleanup(cancel) + + _, err = sub.Proof(ctx2) + require.Error(t, err) +} + +func TestService_ReGossiping(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + // create mock network + net, err := mocknet.FullMeshLinked(3) + require.NoError(t, err) + + // create services + servA := newTestServiceWithHost(ctx, t, net.Hosts()[0], false) + servB := newTestServiceWithHost(ctx, t, net.Hosts()[1], false) + servC := newTestServiceWithHost(ctx, t, net.Hosts()[2], false) + + // preconnect peers: A -> B -> C, so A and C are not connected to each other + addrB := host.InfoFromHost(net.Hosts()[1]) // -> B + require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A + require.NoError(t, net.Hosts()[2].Connect(ctx, *addrB)) // host[2] is C + + // start services + require.NoError(t, servA.Start(ctx)) + require.NoError(t, servB.Start(ctx)) + require.NoError(t, servC.Start(ctx)) + + fraud := fraudtest.NewValidProof() + subsA, err := servA.Subscribe(fraud.Type()) + require.NoError(t, err) + defer subsA.Cancel() + + subsB, err := servB.Subscribe(fraud.Type()) + require.NoError(t, err) + defer subsB.Cancel() + + subsC, err := servC.Subscribe(fraud.Type()) + require.NoError(t, err) + defer subsC.Cancel() + + // give some time for subscriptions to land + // this mitigates flakiness + time.Sleep(time.Millisecond * 100) + + // and only after broadcaster + err = servA.Broadcast(ctx, fraud) + require.NoError(t, err) + + _, err = subsA.Proof(ctx) // subscriptions of subA should also receive the proof + require.NoError(t, err) + + _, err = subsB.Proof(ctx) + require.NoError(t, err) + + _, err = subsC.Proof(ctx) + require.NoError(t, err) +} + +func TestService_Get(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + serv := newTestService(ctx, t, false) + require.NoError(t, serv.Start(ctx)) + + fraud := fraudtest.NewValidProof() + _, err := serv.Get(ctx, fraud.Type()) // try to fetch proof + require.Error(t, err) // storage is empty so should error + + sub, err := serv.Subscribe(fraud.Type()) + require.NoError(t, err) + defer sub.Cancel() + + // subscription needs some time and love to avoid flakes + time.Sleep(time.Millisecond * 100) + + err = serv.Broadcast(ctx, fraud) // broadcast stores the fraud as well + require.NoError(t, err) + + _, err = serv.Get(ctx, fraud.Type()) + require.NoError(t, err) +} + +func TestService_Sync(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + net, err := mocknet.FullMeshLinked(2) + require.NoError(t, err) + + servA := newTestServiceWithHost(ctx, t, net.Hosts()[0], false) + require.NoError(t, servA.Start(ctx)) + + fraud := fraudtest.NewValidProof() + err = servA.Broadcast(ctx, fraud) // broadcasting ensures the fraud gets stored on servA + require.NoError(t, err) + + servB := newTestServiceWithHost(ctx, t, net.Hosts()[1], true) // start servB + require.NoError(t, servB.Start(ctx)) + + sub, err := servB.Subscribe(fraud.Type()) // subscribe + require.NoError(t, err) + defer sub.Cancel() + + addrB := host.InfoFromHost(net.Hosts()[1]) + require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // connect A to B + + _, err = sub.Proof(ctx) // heck that we get it from subscription by syncing from servA + require.NoError(t, err) +} + +func newTestService(ctx context.Context, t *testing.T, enabledSyncer bool) *ProofService { + net, err := mocknet.FullMeshLinked(1) + require.NoError(t, err) + return newTestServiceWithHost(ctx, t, net.Hosts()[0], enabledSyncer) +} + +func newTestServiceWithHost(ctx context.Context, t *testing.T, host host.Host, enabledSyncer bool) *ProofService { + ps, err := pubsub.NewFloodSub(ctx, host, pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) + require.NoError(t, err) + + store := headertest.NewDummyStore(t) + serv := NewProofService( + ps, + host, + func(ctx context.Context, u uint64) (header.Header, error) { + return store.GetByHeight(ctx, u) + }, + sync.MutexWrap(datastore.NewMapDatastore()), + enabledSyncer, + "private", + ) + + t.Cleanup(func() { + err := serv.Stop(ctx) + if err != nil { + t.Fatal(err) + } + }) + return serv +} diff --git a/libs/fraud/store.go b/libs/fraud/fraudserv/store.go similarity index 79% rename from libs/fraud/store.go rename to libs/fraud/fraudserv/store.go index ff812c47c3..dfd71b0738 100644 --- a/libs/fraud/store.go +++ b/libs/fraud/fraudserv/store.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "context" @@ -9,6 +9,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" q "github.com/ipfs/go-datastore/query" + + "github.com/celestiaorg/celestia-node/libs/fraud" ) var ( @@ -36,7 +38,7 @@ func getByHash(ctx context.Context, ds datastore.Datastore, hash string) ([]byte } // getAll queries all Fraud Proofs by their type. -func getAll(ctx context.Context, ds datastore.Datastore, proofType ProofType) ([]Proof, error) { +func getAll(ctx context.Context, ds datastore.Datastore, proofType fraud.ProofType) ([]fraud.Proof, error) { entries, err := query(ctx, ds, q.Query{}) if err != nil { return nil, err @@ -44,11 +46,11 @@ func getAll(ctx context.Context, ds datastore.Datastore, proofType ProofType) ([ if len(entries) == 0 { return nil, datastore.ErrNotFound } - proofs := make([]Proof, 0) + proofs := make([]fraud.Proof, 0) for _, data := range entries { - proof, err := Unmarshal(proofType, data.Value) + proof, err := fraud.Unmarshal(proofType, data.Value) if err != nil { - if errors.Is(err, &errNoUnmarshaler{}) { + if errors.Is(err, &fraud.ErrNoUnmarshaler{}) { return nil, err } log.Warn(err) @@ -62,10 +64,10 @@ func getAll(ctx context.Context, ds datastore.Datastore, proofType ProofType) ([ return proofs, nil } -func initStore(topic ProofType, ds datastore.Datastore) datastore.Datastore { +func initStore(topic fraud.ProofType, ds datastore.Datastore) datastore.Datastore { return namespace.Wrap(ds, makeKey(topic)) } -func makeKey(topic ProofType) datastore.Key { +func makeKey(topic fraud.ProofType) datastore.Key { return datastore.NewKey(fmt.Sprintf("%s/%s", storePrefix, topic)) } diff --git a/libs/fraud/store_test.go b/libs/fraud/fraudserv/store_test.go similarity index 88% rename from libs/fraud/store_test.go rename to libs/fraud/fraudserv/store_test.go index 2556b8cf5e..90a2f84069 100644 --- a/libs/fraud/store_test.go +++ b/libs/fraud/fraudserv/store_test.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "context" @@ -9,13 +9,15 @@ import ( "github.com/ipfs/go-datastore/namespace" ds_sync "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/libs/fraud/fraudtest" ) func TestStore_Put(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer t.Cleanup(cancel) - p := newValidProof() + p := fraudtest.NewValidProof() bin, err := p.MarshalBinary() require.NoError(t, err) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) @@ -25,10 +27,10 @@ func TestStore_Put(t *testing.T) { } func TestStore_GetAll(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer t.Cleanup(cancel) - proof := newValidProof() + proof := fraudtest.NewValidProof() bin, err := proof.MarshalBinary() require.NoError(t, err) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) @@ -41,14 +43,13 @@ func TestStore_GetAll(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, proofs) require.NoError(t, proof.Validate(nil)) - } func Test_GetAllFailed(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer t.Cleanup(cancel) - proof := newValidProof() + proof := fraudtest.NewValidProof() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) store := namespace.Wrap(ds, makeKey(proof.Type())) @@ -59,10 +60,10 @@ func Test_GetAllFailed(t *testing.T) { } func Test_getByHash(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer t.Cleanup(cancel) - proof := newValidProof() + proof := fraudtest.NewValidProof() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) store := namespace.Wrap(ds, makeKey(proof.Type())) bin, err := proof.MarshalBinary() diff --git a/libs/fraud/subscription.go b/libs/fraud/fraudserv/subscription.go similarity index 75% rename from libs/fraud/subscription.go rename to libs/fraud/fraudserv/subscription.go index e9cef33974..fbf86ba378 100644 --- a/libs/fraud/subscription.go +++ b/libs/fraud/fraudserv/subscription.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "context" @@ -6,6 +6,8 @@ import ( "reflect" pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/celestiaorg/celestia-node/libs/fraud" ) // subscription wraps pubsub subscription and handles Fraud Proof from the pubsub topic. @@ -13,7 +15,7 @@ type subscription struct { subscription *pubsub.Subscription } -func (s *subscription) Proof(ctx context.Context) (Proof, error) { +func (s *subscription) Proof(ctx context.Context) (fraud.Proof, error) { if s.subscription == nil { panic("fraud: subscription is not created") } @@ -21,7 +23,7 @@ func (s *subscription) Proof(ctx context.Context) (Proof, error) { if err != nil { return nil, err } - proof, ok := data.ValidatorData.(Proof) + proof, ok := data.ValidatorData.(fraud.Proof) if !ok { panic(fmt.Sprintf("fraud: unexpected type received %s", reflect.TypeOf(data.ValidatorData))) } diff --git a/libs/fraud/sync.go b/libs/fraud/fraudserv/sync.go similarity index 93% rename from libs/fraud/sync.go rename to libs/fraud/fraudserv/sync.go index e82e33f5f5..31675932aa 100644 --- a/libs/fraud/sync.go +++ b/libs/fraud/fraudserv/sync.go @@ -1,4 +1,4 @@ -package fraud +package fraudserv import ( "context" @@ -15,7 +15,8 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" - pb "github.com/celestiaorg/celestia-node/libs/fraud/pb" + "github.com/celestiaorg/celestia-node/libs/fraud" + pb "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv/pb" ) // syncFraudProofs encompasses the behavior for fetching fraud proofs from other peers. @@ -83,10 +84,10 @@ func (f *ProofService) syncFraudProofs(ctx context.Context, id protocol.ID) { log.Debugw("got fraud proofs from peer", "pid", pid) for _, data := range respProofs { f.topicsLk.RLock() - topic, ok := f.topics[ProofType(data.Type)] + topic, ok := f.topics[fraud.ProofType(data.Type)] f.topicsLk.RUnlock() if !ok { - log.Errorf("topic for %s does not exist", ProofType(data.Type)) + log.Errorf("topic for %s does not exist", fraud.ProofType(data.Type)) continue } for _, val := range data.Value { @@ -129,7 +130,7 @@ func (f *ProofService) handleFraudMessageRequest(stream network.Stream) { resp.Proofs = make([]*pb.ProofResponse, 0, len(req.RequestedProofType)) // retrieve fraud proofs as provided by the FraudMessageRequest proofTypes. for _, p := range req.RequestedProofType { - proofs, err := f.Get(f.ctx, ProofType(p)) + proofs, err := f.Get(f.ctx, fraud.ProofType(p)) if err != nil { if err != datastore.ErrNotFound { log.Error(err) diff --git a/libs/fraud/fraudtest/dummy_proof.go b/libs/fraud/fraudtest/dummy_proof.go new file mode 100644 index 0000000000..a5b9f436ce --- /dev/null +++ b/libs/fraud/fraudtest/dummy_proof.go @@ -0,0 +1,53 @@ +package fraudtest + +import ( + "encoding/json" + "errors" + + "github.com/celestiaorg/go-header" + + "github.com/celestiaorg/celestia-node/libs/fraud" +) + +func init() { + fraud.Register(&DummyProof{}) +} + +type DummyProof struct { + Valid bool +} + +func NewValidProof() *DummyProof { + return &DummyProof{true} +} + +func NewInvalidProof() *DummyProof { + return &DummyProof{false} +} + +func (m *DummyProof) Type() fraud.ProofType { + return "DummyProof" +} + +func (m *DummyProof) HeaderHash() []byte { + return []byte("hash") +} + +func (m *DummyProof) Height() uint64 { + return 1 +} + +func (m *DummyProof) Validate(header.Header) error { + if !m.Valid { + return errors.New("DummyProof: proof is not valid") + } + return nil +} + +func (m *DummyProof) MarshalBinary() (data []byte, err error) { + return json.Marshal(m) +} + +func (m *DummyProof) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, m) +} diff --git a/libs/fraud/fraudtest/dummy_service.go b/libs/fraud/fraudtest/dummy_service.go new file mode 100644 index 0000000000..800f1b9e99 --- /dev/null +++ b/libs/fraud/fraudtest/dummy_service.go @@ -0,0 +1,29 @@ +package fraudtest + +import ( + "context" + + "github.com/celestiaorg/celestia-node/libs/fraud" +) + +type DummyService struct{} + +func (d *DummyService) Broadcast(context.Context, fraud.Proof) error { + return nil +} + +func (d *DummyService) Subscribe(fraud.ProofType) (fraud.Subscription, error) { + return &subscription{}, nil +} + +func (d *DummyService) Get(context.Context, fraud.ProofType) ([]fraud.Proof, error) { + return nil, nil +} + +type subscription struct{} + +func (s *subscription) Proof(context.Context) (fraud.Proof, error) { + return nil, nil +} + +func (s *subscription) Cancel() {} diff --git a/libs/fraud/interface.go b/libs/fraud/interface.go index 1ae8b0995c..1b318272c2 100644 --- a/libs/fraud/interface.go +++ b/libs/fraud/interface.go @@ -3,15 +3,11 @@ package fraud import ( "context" - logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/go-header" ) -var log = logging.Logger("fraud") - -// headerFetcher aliases a function that is used to fetch an ExtendedHeader from store by height. -type headerFetcher func(context.Context, uint64) (header.Header, error) +// HeaderFetcher aliases a function that is used to fetch an ExtendedHeader from store by height. +type HeaderFetcher func(context.Context, uint64) (header.Header, error) // ProofUnmarshaler aliases a function that parses data to `Proof`. type ProofUnmarshaler func([]byte) (Proof, error) @@ -27,8 +23,9 @@ type Service interface { // Broadcaster is a generic interface that sends a `Proof` to all nodes subscribed on the // Broadcaster's topic. type Broadcaster interface { - // Broadcast takes a fraud `Proof` data structure that implements standard BinaryMarshal - // interface and broadcasts it to all subscribed peers. + // Broadcast takes a fraud `Proof` data structure interface and broadcasts it to local + // subscriptions and peers. It may additionally cache/persist Proofs for future + // access via Getter and to serve Proof requests to peers in the network. Broadcast(context.Context, Proof) error } diff --git a/libs/fraud/metrics.go b/libs/fraud/metrics.go index 74127c265d..1b28101b36 100644 --- a/libs/fraud/metrics.go +++ b/libs/fraud/metrics.go @@ -5,10 +5,13 @@ import ( "github.com/ipfs/go-datastore" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/unit" ) +var meter = global.MeterProvider().Meter("fraud") + // WithMetrics enables metrics to monitor fraud proofs. func WithMetrics(store Getter) { proofTypes := registeredProofTypes() diff --git a/libs/fraud/proof.go b/libs/fraud/proof.go index d8d0a81cfa..0666cc0ef1 100644 --- a/libs/fraud/proof.go +++ b/libs/fraud/proof.go @@ -5,33 +5,9 @@ import ( "encoding" "fmt" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/metric/global" - "github.com/celestiaorg/go-header" ) -var ( - meter = global.MeterProvider().Meter("fraud") - tracer = otel.Tracer("fraud") -) - -type ErrFraudExists struct { - Proof []Proof -} - -func (e *ErrFraudExists) Error() string { - return fmt.Sprintf("fraud: %s proof exists\n", e.Proof[0].Type()) -} - -type errNoUnmarshaler struct { - proofType ProofType -} - -func (e *errNoUnmarshaler) Error() string { - return fmt.Sprintf("fraud: unmarshaler for %s type is not registered", e.proofType) -} - // ProofType type defines a unique proof type string. type ProofType string @@ -63,7 +39,6 @@ type Proof interface { func OnProof(ctx context.Context, subscriber Subscriber, p ProofType, handle func(proof Proof)) { subscription, err := subscriber.Subscribe(p) if err != nil { - log.Error(err) return } defer subscription.Cancel() @@ -72,9 +47,6 @@ func OnProof(ctx context.Context, subscriber Subscriber, p ProofType, handle fun // so there is no need to call Validate. proof, err := subscription.Proof(ctx) if err != nil { - if err != context.Canceled { - log.Errorw("reading next proof failed", "err", err) - } return } @@ -87,7 +59,23 @@ func Unmarshal(proofType ProofType, msg []byte) (Proof, error) { defer unmarshalersLk.RUnlock() unmarshaler, ok := defaultUnmarshalers[proofType] if !ok { - return nil, &errNoUnmarshaler{proofType: proofType} + return nil, &ErrNoUnmarshaler{proofType: proofType} } return unmarshaler(msg) } + +type ErrFraudExists struct { + Proof []Proof +} + +func (e *ErrFraudExists) Error() string { + return fmt.Sprintf("fraud: %s proof exists\n", e.Proof[0].Type()) +} + +type ErrNoUnmarshaler struct { + proofType ProofType +} + +func (e *ErrNoUnmarshaler) Error() string { + return fmt.Sprintf("fraud: unmarshaler for %s type is not registered", e.proofType) +} diff --git a/libs/fraud/service_test.go b/libs/fraud/service_test.go deleted file mode 100644 index 2237454d47..0000000000 --- a/libs/fraud/service_test.go +++ /dev/null @@ -1,239 +0,0 @@ -package fraud - -import ( - "context" - "encoding/hex" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - "github.com/ipfs/go-datastore/sync" - pubsub "github.com/libp2p/go-libp2p-pubsub" - pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb" - "github.com/libp2p/go-libp2p/core/host" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/go-header" - "github.com/celestiaorg/go-header/test" -) - -func TestService_Subscribe(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) - t.Cleanup(cancel) - s, _ := CreateTestService(t, false) - proof := newValidProof() - require.NoError(t, s.Start(ctx)) - _, err := s.Subscribe(proof.Type()) - require.NoError(t, err) -} - -func TestService_SubscribeFails(t *testing.T) { - s, _ := CreateTestService(t, false) - proof := newValidProof() - _, err := s.Subscribe(proof.Type()) - require.Error(t, err) -} - -func TestService_BroadcastFails(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) - t.Cleanup(cancel) - s, _ := CreateTestService(t, false) - p := newValidProof() - require.Error(t, s.Broadcast(ctx, p)) -} - -func TestService_Broadcast(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - t.Cleanup(cancel) - - s, _ := CreateTestService(t, false) - proof := newValidProof() - require.NoError(t, s.Start(ctx)) - subs, err := s.Subscribe(proof.Type()) - require.NoError(t, err) - - require.NoError(t, s.Broadcast(ctx, proof)) - _, err = subs.Proof(ctx) - require.NoError(t, err) - require.NoError(t, nil) -} - -func TestService_processIncoming(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - // create mock network - net, err := mocknet.FullMeshLinked(2) - require.NoError(t, err) - - var tests = []struct { - precondition func() - proof *mockProof - validationResult pubsub.ValidationResult - }{ - { - nil, - newValidProof(), - pubsub.ValidationAccept, - }, - { - nil, - newInvalidProof(), - pubsub.ValidationReject, - }, - { - func() { - delete(defaultUnmarshalers, mockProofType) - }, - newValidProof(), - pubsub.ValidationReject, - }, - } - for _, test := range tests { - bin, err := test.proof.MarshalBinary() - require.NoError(t, err) - // create first fraud service that will broadcast incorrect Fraud Proof - service, _ := createTestServiceWithHost(ctx, t, net.Hosts()[0], false) - msg := &pubsub.Message{ - Message: &pubsubpb.Message{ - Data: bin, - }, - ReceivedFrom: net.Hosts()[1].ID(), - } - if test.precondition != nil { - test.precondition() - } - require.NoError(t, service.Start(ctx)) - res := service.processIncoming(ctx, test.proof.Type(), net.Hosts()[1].ID(), msg) - require.True(t, res == test.validationResult) - } -} - -func TestService_ReGossiping(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - t.Cleanup(cancel) - // create mock network - net, err := mocknet.FullMeshLinked(3) - require.NoError(t, err) - - // create first fraud service that will broadcast incorrect Fraud Proof - pserviceA, _ := createTestServiceWithHost(ctx, t, net.Hosts()[0], false) - require.NoError(t, err) - // create pub sub in order to listen for Fraud Proof - psB, err := pubsub.NewGossipSub(ctx, net.Hosts()[1], // -> B - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - // create second service that will receive and validate Fraud Proof - pserviceB := NewProofService( - psB, - net.Hosts()[1], - func(ctx context.Context, u uint64) (header.Header, error) { - return &test.DummyHeader{}, nil - }, - sync.MutexWrap(datastore.NewMapDatastore()), - false, - "private", - ) - addrB := host.InfoFromHost(net.Hosts()[1]) // -> B - - // create pub sub in order to listen for Fraud Proof - psC, err := pubsub.NewGossipSub(ctx, net.Hosts()[2], // -> C - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - pserviceC := NewProofService( - psC, - net.Hosts()[2], - func(ctx context.Context, u uint64) (header.Header, error) { - return &test.DummyHeader{}, nil - }, - sync.MutexWrap(datastore.NewMapDatastore()), - false, - "private", - ) - // establish connections - // connect peers: A -> B -> C, so A and C are not connected to each other - require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A - require.NoError(t, net.Hosts()[2].Connect(ctx, *addrB)) // host[2] is C - - befp := newValidProof() - bin, err := befp.MarshalBinary() - require.NoError(t, err) - require.NoError(t, pserviceA.Start(ctx)) - require.NoError(t, pserviceB.Start(ctx)) - require.NoError(t, pserviceC.Start(ctx)) - subsA, err := pserviceA.Subscribe(mockProofType) - require.NoError(t, err) - defer subsA.Cancel() - - subsB, err := pserviceB.Subscribe(mockProofType) - require.NoError(t, err) - defer subsB.Cancel() - - subsC, err := pserviceC.Subscribe(mockProofType) - require.NoError(t, err) - defer subsC.Cancel() - // we cannot avoid sleep because it helps to avoid flakiness - time.Sleep(time.Millisecond * 100) - - err = pserviceA.topics[mockProofType].Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) - require.NoError(t, err) - - newCtx, cancel := context.WithTimeout(ctx, time.Millisecond*100) - t.Cleanup(cancel) - - _, err = subsB.Proof(newCtx) - require.NoError(t, err) - - _, err = subsC.Proof(ctx) - require.NoError(t, err) - // we cannot avoid sleep because it helps to avoid flakiness - time.Sleep(time.Millisecond * 100) -} - -func TestService_Get(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - proof := newValidProof() - bin, err := proof.MarshalBinary() - require.NoError(t, err) - pService, _ := CreateTestService(t, false) - // try to fetch proof - _, err = pService.Get(ctx, proof.Type()) - // error is expected here because storage is empty - require.Error(t, err) - - // create store - store := initStore(proof.Type(), pService.ds) - // add proof to storage - require.NoError(t, put(ctx, store, hex.EncodeToString(proof.HeaderHash()), bin)) - // fetch proof - _, err = pService.Get(ctx, proof.Type()) - require.NoError(t, err) -} - -func TestService_Sync(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - t.Cleanup(cancel) - // create mock network - net, err := mocknet.FullMeshLinked(2) - require.NoError(t, err) - - pserviceA, _ := createTestServiceWithHost(ctx, t, net.Hosts()[0], false) - pserviceB, _ := createTestServiceWithHost(ctx, t, net.Hosts()[1], true) - proof := newValidProof() - require.NoError(t, pserviceA.Start(ctx)) - require.NoError(t, pserviceB.Start(ctx)) - subs, err := pserviceB.Subscribe(mockProofType) - require.NoError(t, err) - bin, err := proof.MarshalBinary() - require.NoError(t, err) - store := namespace.Wrap(pserviceA.ds, makeKey(mockProofType)) - require.NoError(t, put(ctx, store, string(proof.HeaderHash()), bin)) - - addrB := host.InfoFromHost(net.Hosts()[1]) - require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) - - _, err = subs.Proof(ctx) - require.NoError(t, err) -} diff --git a/libs/fraud/testing.go b/libs/fraud/testing.go deleted file mode 100644 index 9e027dd45d..0000000000 --- a/libs/fraud/testing.go +++ /dev/null @@ -1,149 +0,0 @@ -package fraud - -import ( - "context" - "encoding/json" - "errors" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/host" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/go-header" - - "github.com/celestiaorg/celestia-node/header/headertest" -) - -type DummyService struct { -} - -func (d *DummyService) Broadcast(context.Context, Proof) error { - return nil -} - -func (d *DummyService) Subscribe(ProofType) (Subscription, error) { - return &subscription{}, nil -} - -func (d *DummyService) Get(context.Context, ProofType) ([]Proof, error) { - return nil, nil -} - -type mockStore struct { - headers map[int64]header.Header - headHeight int64 -} - -// createStore creates a mock store and adds several random -// headers. -func createStore(t *testing.T, numHeaders int) *mockStore { - store := &mockStore{ - headers: make(map[int64]header.Header), - headHeight: 0, - } - - suite := headertest.NewTestSuite(t, numHeaders) - - for i := 0; i < numHeaders; i++ { - header := suite.GenExtendedHeader() - store.headers[header.Height()] = header - - if header.Height() > store.headHeight { - store.headHeight = header.Height() - } - } - return store -} - -func (m *mockStore) GetByHeight(_ context.Context, height uint64) (header.Header, error) { - return m.headers[int64(height)], nil -} - -func (m *mockStore) Close() error { return nil } - -const ( - mockProofType ProofType = "mockProof" -) - -type mockProof struct { - Valid bool -} - -func newValidProof() *mockProof { - return newMockProof(true) -} - -func newInvalidProof() *mockProof { - return newMockProof(false) -} - -func newMockProof(valid bool) *mockProof { - p := &mockProof{valid} - if _, ok := defaultUnmarshalers[p.Type()]; !ok { - Register(&mockProof{}) - } - return p -} - -func (m *mockProof) Type() ProofType { - return mockProofType -} - -func (m *mockProof) HeaderHash() []byte { - return []byte("hash") -} - -func (m *mockProof) Height() uint64 { - return 1 -} - -func (m *mockProof) Validate(header.Header) error { - if !m.Valid { - return errors.New("mockProof: proof is not valid") - } - return nil -} - -func (m *mockProof) MarshalBinary() (data []byte, err error) { - return json.Marshal(m) -} - -func (m *mockProof) UnmarshalBinary(data []byte) error { - return json.Unmarshal(data, m) -} - -func CreateTestService(t *testing.T, enabledSyncer bool) (*ProofService, *mockStore) { //nolint:revive - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - t.Cleanup(cancel) - - // create mock network - net, err := mocknet.FullMeshLinked(1) - require.NoError(t, err) - return createTestServiceWithHost(ctx, t, net.Hosts()[0], enabledSyncer) -} - -func createTestServiceWithHost( - ctx context.Context, - t *testing.T, - host host.Host, - enabledSyncer bool, -) (*ProofService, *mockStore) { - // create pubsub for host - ps, err := pubsub.NewGossipSub(ctx, host, - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - store := createStore(t, 10) - return NewProofService( - ps, - host, - store.GetByHeight, - sync.MutexWrap(datastore.NewMapDatastore()), - enabledSyncer, - "private", - ), store -} diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index b6891d6d56..c1e59aa15e 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -12,6 +12,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -34,7 +35,7 @@ func newFraudService(syncerEnabled bool) func( getter := func(ctx context.Context, height uint64) (libhead.Header, error) { return hstore.GetByHeight(ctx, height) } - pservice := fraud.NewProofService(sub, host, getter, ds, syncerEnabled, network.String()) + pservice := fraudserv.NewProofService(sub, host, getter, ds, syncerEnabled, network.String()) lc.Append(fx.Hook{ OnStart: pservice.Start, OnStop: pservice.Stop, diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 74ed1eda2a..6ffa50fed0 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -18,6 +18,7 @@ import ( headp2p "github.com/celestiaorg/go-header/p2p" "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv" ) func init() { @@ -113,7 +114,7 @@ func topicScoreParams(network Network) map[string]*pubsub.TopicScoreParams { } for _, pt := range fraud.Registered() { - mp[fraud.PubsubTopicID(pt.String(), network.String())] = &fraud.GossibSubScore + mp[fraudserv.PubsubTopicID(pt.String(), network.String())] = &fraudserv.GossibSubScore } return mp diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 8c572415fa..2b13ef3fa7 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -9,10 +9,8 @@ import ( "go.uber.org/fx" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" - "github.com/celestiaorg/go-header/mocks" "github.com/celestiaorg/celestia-node/core" - coreheader "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -48,9 +46,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti // temp dir for the eds store FIXME: Should be in mem fx.Replace(node.StorePath(t.TempDir())), // avoid requesting trustedPeer during initialization - fxutil.ReplaceAs(mocks.NewStore[*coreheader.ExtendedHeader]( - t, headertest.NewTestSuite(t, 5), 20), new(header.InitStore), - ), + fxutil.ReplaceAs(headertest.NewStore(t), new(header.InitStore)), ) // in fact, we don't need core.Client in tests, but Bridge requires is a valid one diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 7f107cac96..1c98c133c4 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -18,7 +18,6 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" @@ -147,11 +146,11 @@ func generateByzantineError( t *testing.T, bServ blockservice.BlockService, ) (*header.ExtendedHeader, error) { - _, store := fraud.CreateTestService(t, false) + store := headertest.NewStore(t) h, err := store.GetByHeight(ctx, 1) require.NoError(t, err) - faultHeader, _ := headertest.CreateFraudExtHeader(t, h.(*header.ExtendedHeader), bServ) + faultHeader, _ := headertest.CreateFraudExtHeader(t, h, bServ) _, err = NewRetriever(bServ).Retrieve(ctx, faultHeader.DAH) return faultHeader, err } diff --git a/share/eds/store.go b/share/eds/store.go index 50bce3e0fc..79dbd992d7 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -207,8 +207,8 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext // The Reader strictly reads the CAR header and first quadrant (1/4) of the EDS, omitting all the // NMT Merkle proofs. Integrity of the store data is not verified. // -// The shard is cached in the Store, so subsequent calls to GetCAR with the same root will use the same reader. -// The cache is responsible for closing the underlying reader. +// The shard is cached in the Store, so subsequent calls to GetCAR with the same root will use the +// same reader. The cache is responsible for closing the underlying reader. func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { ctx, span := tracer.Start(ctx, "store/get-car", trace.WithAttributes(attribute.String("root", root.String()))) defer span.End() From b9eef30715b4313d75a3c4eed94453ca375e2d63 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 12 Apr 2023 11:41:09 +0200 Subject: [PATCH 0531/1008] feat: include function signatures in rpc cli autocompletions (#2026) Adds function signatures (excluding input context field, and output error field) to the RPC CLI autocompletions. Now the autocompletions for the methods looks like this: ![image](https://user-images.githubusercontent.com/16523232/230006576-a7263d3f-5360-4277-adaa-e500eed3fed6.png) --- cmd/celestia/cmd_test.go | 28 ++++++++++++++++++++++++++++ cmd/celestia/rpc.go | 22 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/cmd/celestia/cmd_test.go b/cmd/celestia/cmd_test.go index 0994261a74..503548b708 100644 --- a/cmd/celestia/cmd_test.go +++ b/cmd/celestia/cmd_test.go @@ -4,11 +4,39 @@ import ( "bytes" "context" "os" + "reflect" "testing" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/header" ) +func TestCompletionHelpString(t *testing.T) { + type TestFields struct { + NoInputOneOutput func(context.Context) (*header.ExtendedHeader, error) + TwoInputsOneOutputArray func( + context.Context, + *header.ExtendedHeader, + uint64, + ) ([]*header.ExtendedHeader, error) + OneInputOneOutput func(context.Context, uint64) (*header.ExtendedHeader, error) + NoInputsNoOutputs func(ctx context.Context) error + NoInputsChanOutput func(ctx context.Context) (<-chan *header.ExtendedHeader, error) + } + testOutputs := []string{ + "() -> (*header.ExtendedHeader)", + "(*header.ExtendedHeader, uint64) -> ([]*header.ExtendedHeader)", + "(uint64) -> (*header.ExtendedHeader)", + "() -> ()", + "() -> (<-chan *header.ExtendedHeader)", + } + methods := reflect.VisibleFields(reflect.TypeOf(TestFields{})) + for i, method := range methods { + require.Equal(t, testOutputs[i], parseSignatureForHelpstring(method)) + } +} + func TestLight(t *testing.T) { // Run the tests in a temporary directory tmpDir := t.TempDir() diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index c862e662e5..758af8091f 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -71,7 +71,7 @@ var rpcCmd = &cobra.Command{ methods := reflect.VisibleFields(reflect.TypeOf(module).Elem()) var methodNames []string for _, m := range methods { - methodNames = append(methodNames, m.Name) + methodNames = append(methodNames, m.Name+"\t"+parseSignatureForHelpstring(m)) } return methodNames, cobra.ShellCompDirectiveNoFileComp } @@ -297,3 +297,23 @@ func parseAddressFromString(addrStr string) (state.Address, error) { return addr, nil } + +func parseSignatureForHelpstring(methodSig reflect.StructField) string { + simplifiedSignature := "(" + in, out := methodSig.Type.NumIn(), methodSig.Type.NumOut() + for i := 1; i < in; i++ { + simplifiedSignature += methodSig.Type.In(i).String() + if i != in-1 { + simplifiedSignature += ", " + } + } + simplifiedSignature += ") -> (" + for i := 0; i < out-1; i++ { + simplifiedSignature += methodSig.Type.Out(i).String() + if i != out-2 { + simplifiedSignature += ", " + } + } + simplifiedSignature += ")" + return simplifiedSignature +} From 952a93ee5891dd072846be67d64cc9954a345cf4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 12 Apr 2023 12:01:35 +0200 Subject: [PATCH 0532/1008] fix: adding traces to pyroscope dashboard (#2054) This PR adds traces back to the pyroscope dashboard. I had them activated in the demo but for some reason it ended up deactivated in the original integration PR. --- cmd/flags_misc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index fdecb4448d..9ed66a63f2 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -235,7 +235,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e tp, otelpyroscope.WithAppName("celestia.da-node"), otelpyroscope.WithPyroscopeURL(cmd.Flag(pyroscopeEndpoint).Value.String()), - otelpyroscope.WithRootSpanOnly(false), + otelpyroscope.WithRootSpanOnly(true), otelpyroscope.WithAddSpanName(true), otelpyroscope.WithProfileURL(true), otelpyroscope.WithProfileBaselineURL(true), From 8dc1af29e68990811878775aafddf93dd88f7ff9 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 13 Apr 2023 10:11:00 +0200 Subject: [PATCH 0533/1008] chore: migrate to go-fraud (#2064) Only imports and go module changes --- api/docgen/examples.go | 2 +- das/daser.go | 2 +- das/daser_test.go | 6 +- go.mod | 3 +- go.sum | 6 +- libs/fraud/fraudserv/gossip_score.go | 40 -- libs/fraud/fraudserv/helpers.go | 41 -- libs/fraud/fraudserv/pb/proof.pb.go | 728 -------------------------- libs/fraud/fraudserv/pb/proof.proto | 16 - libs/fraud/fraudserv/requester.go | 55 -- libs/fraud/fraudserv/service.go | 255 --------- libs/fraud/fraudserv/service_test.go | 196 ------- libs/fraud/fraudserv/store.go | 73 --- libs/fraud/fraudserv/store_test.go | 75 --- libs/fraud/fraudserv/subscription.go | 35 -- libs/fraud/fraudserv/sync.go | 164 ------ libs/fraud/fraudtest/dummy_proof.go | 53 -- libs/fraud/fraudtest/dummy_service.go | 29 - libs/fraud/interface.go | 51 -- libs/fraud/metrics.go | 48 -- libs/fraud/proof.go | 81 --- libs/fraud/registry.go | 45 -- nodebuilder/das/constructors.go | 2 +- nodebuilder/das/module.go | 3 +- nodebuilder/fraud/constructors.go | 4 +- nodebuilder/fraud/fraud.go | 2 +- nodebuilder/fraud/lifecycle.go | 2 +- nodebuilder/fraud/mocks/api.go | 2 +- nodebuilder/fraud/module.go | 3 +- nodebuilder/fraud/service.go | 2 +- nodebuilder/header/module.go | 2 +- nodebuilder/header/module_test.go | 2 +- nodebuilder/p2p/pubsub.go | 5 +- nodebuilder/settings.go | 3 +- nodebuilder/state/module.go | 3 +- share/eds/byzantine/bad_encoding.go | 2 +- 36 files changed, 31 insertions(+), 2010 deletions(-) delete mode 100644 libs/fraud/fraudserv/gossip_score.go delete mode 100644 libs/fraud/fraudserv/helpers.go delete mode 100644 libs/fraud/fraudserv/pb/proof.pb.go delete mode 100644 libs/fraud/fraudserv/pb/proof.proto delete mode 100644 libs/fraud/fraudserv/requester.go delete mode 100644 libs/fraud/fraudserv/service.go delete mode 100644 libs/fraud/fraudserv/service_test.go delete mode 100644 libs/fraud/fraudserv/store.go delete mode 100644 libs/fraud/fraudserv/store_test.go delete mode 100644 libs/fraud/fraudserv/subscription.go delete mode 100644 libs/fraud/fraudserv/sync.go delete mode 100644 libs/fraud/fraudtest/dummy_proof.go delete mode 100644 libs/fraud/fraudtest/dummy_service.go delete mode 100644 libs/fraud/interface.go delete mode 100644 libs/fraud/metrics.go delete mode 100644 libs/fraud/proof.go delete mode 100644 libs/fraud/registry.go diff --git a/api/docgen/examples.go b/api/docgen/examples.go index 215d59dd2d..ae65501ba6 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -17,11 +17,11 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" + "github.com/celestiaorg/go-fraud" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" diff --git a/das/daser.go b/das/daser.go index 128fd1a40b..8b7e2a145c 100644 --- a/das/daser.go +++ b/das/daser.go @@ -9,10 +9,10 @@ import ( "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" diff --git a/das/daser_test.go b/das/daser_test.go index 2c51b48fe4..5e201a9b69 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -16,13 +16,13 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/go-fraud/fraudserv" + "github.com/celestiaorg/go-fraud/fraudtest" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" - "github.com/celestiaorg/celestia-node/libs/fraud" - "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv" - "github.com/celestiaorg/celestia-node/libs/fraud/fraudtest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" diff --git a/go.mod b/go.mod index 3397b40f76..030e8e26ca 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,8 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.2 - github.com/celestiaorg/go-header v0.2.4 + github.com/celestiaorg/go-fraud v0.1.0 + github.com/celestiaorg/go-header v0.2.5 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.15.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index dc56ccb4f5..312db4c3ae 100644 --- a/go.sum +++ b/go.sum @@ -208,8 +208,10 @@ github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SO github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136 h1:LBvY3NDA18fcS72pBAEd2pENoUpz1iV4cCXBN2Zrj/I= github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= -github.com/celestiaorg/go-header v0.2.4 h1:GZk0/CT/DI7p/3BUNC1bBmx5qk7PiujVseVzVj/LCqM= -github.com/celestiaorg/go-header v0.2.4/go.mod h1:6XKf0yhoEQqfKQTZnyTZjTjF5jH5Wq9uO9AvDMkdYbs= +github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= +github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= +github.com/celestiaorg/go-header v0.2.5 h1:TV1EybWjRRJfYc8Wf5UFVytGxEQJdPdNbVEfenUVXzo= +github.com/celestiaorg/go-header v0.2.5/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= diff --git a/libs/fraud/fraudserv/gossip_score.go b/libs/fraud/fraudserv/gossip_score.go deleted file mode 100644 index 2f758fb751..0000000000 --- a/libs/fraud/fraudserv/gossip_score.go +++ /dev/null @@ -1,40 +0,0 @@ -package fraudserv - -import ( - "math" - "time" - - pubsub "github.com/libp2p/go-libp2p-pubsub" -) - -// GossibSubScore provides a set of recommended parameters for header GossipSub topic, a.k.a -// FraudSub. TODO(@Wondertan): We should disable mesh on publish for this topic to minimize -// chances of censoring FPs by eclipsing nodes producing them. -var GossibSubScore = pubsub.TopicScoreParams{ - SkipAtomicValidation: true, - - // expected > 1 tx/second - TopicWeight: 0.1, // max cap is 5, single invalid message is -100 - - // 1 tick per second, maxes at 1 hour - TimeInMeshWeight: 0.0002778, // ~1/3600 - TimeInMeshQuantum: time.Second, - TimeInMeshCap: 1, - - // messages in such topics should almost never exist, but very valuable if happens - // so giving max weight - FirstMessageDeliveriesWeight: 50, - FirstMessageDeliveriesDecay: pubsub.ScoreParameterDecay(10 * time.Hour), - // no cap, if the peer is giving us *valid* FPs, just keep increasing peer's score with no limit - // again, this is such a rare case to happen, but if it happens, we should prefer the peer who - // gave it to us - FirstMessageDeliveriesCap: math.MaxFloat64, - - // we don't really need this, as we block list peers who give us a bad message, - // so disabled - InvalidMessageDeliveriesWeight: 0, - - // Mesh Delivery Scoring is turned off as well. - // This is on purpose as the network is still too small, which results in - // asymmetries and potential unmeshing from negative scores. -} diff --git a/libs/fraud/fraudserv/helpers.go b/libs/fraud/fraudserv/helpers.go deleted file mode 100644 index 99b73d5535..0000000000 --- a/libs/fraud/fraudserv/helpers.go +++ /dev/null @@ -1,41 +0,0 @@ -package fraudserv - -import ( - "context" - "fmt" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - - "github.com/celestiaorg/celestia-node/libs/fraud" -) - -func PubsubTopicID(fraudType, networkID string) string { - return fmt.Sprintf("/%s/fraud-sub/%s/v0.0.1", networkID, fraudType) -} - -func protocolID(networkID string) protocol.ID { - return protocol.ID(fmt.Sprintf("/%s/fraud/v0.0.1", networkID)) -} - -func join( - p *pubsub.PubSub, - proofType fraud.ProofType, - networkID string, - validate func(context.Context, fraud.ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult, -) (*pubsub.Topic, error) { - topic := PubsubTopicID(proofType.String(), networkID) - log.Infow("joining topic", "id", topic) - t, err := p.Join(topic) - if err != nil { - return nil, err - } - err = p.RegisterTopicValidator( - topic, - func(ctx context.Context, from peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - return validate(ctx, proofType, from, msg) - }, - ) - return t, err -} diff --git a/libs/fraud/fraudserv/pb/proof.pb.go b/libs/fraud/fraudserv/pb/proof.pb.go deleted file mode 100644 index 09bbc189da..0000000000 --- a/libs/fraud/fraudserv/pb/proof.pb.go +++ /dev/null @@ -1,728 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: libs/fraud/pb/proof.proto - -package fraud_pb - -import ( - "fmt" - "io" - "math" - math_bits "math/bits" - - "github.com/gogo/protobuf/proto" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -type FraudMessageRequest struct { - RequestedProofType []string `protobuf:"bytes,1,rep,name=RequestedProofType,proto3" json:"RequestedProofType,omitempty"` -} - -func (m *FraudMessageRequest) Reset() { *m = FraudMessageRequest{} } -func (m *FraudMessageRequest) String() string { return proto.CompactTextString(m) } -func (*FraudMessageRequest) ProtoMessage() {} -func (*FraudMessageRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_8ed4b0aa9157349f, []int{0} -} -func (m *FraudMessageRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *FraudMessageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_FraudMessageRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *FraudMessageRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_FraudMessageRequest.Merge(m, src) -} -func (m *FraudMessageRequest) XXX_Size() int { - return m.Size() -} -func (m *FraudMessageRequest) XXX_DiscardUnknown() { - xxx_messageInfo_FraudMessageRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_FraudMessageRequest proto.InternalMessageInfo - -func (m *FraudMessageRequest) GetRequestedProofType() []string { - if m != nil { - return m.RequestedProofType - } - return nil -} - -type ProofResponse struct { - Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"` - Value [][]byte `protobuf:"bytes,2,rep,name=Value,proto3" json:"Value,omitempty"` -} - -func (m *ProofResponse) Reset() { *m = ProofResponse{} } -func (m *ProofResponse) String() string { return proto.CompactTextString(m) } -func (*ProofResponse) ProtoMessage() {} -func (*ProofResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_8ed4b0aa9157349f, []int{1} -} -func (m *ProofResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ProofResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ProofResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ProofResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ProofResponse.Merge(m, src) -} -func (m *ProofResponse) XXX_Size() int { - return m.Size() -} -func (m *ProofResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ProofResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_ProofResponse proto.InternalMessageInfo - -func (m *ProofResponse) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *ProofResponse) GetValue() [][]byte { - if m != nil { - return m.Value - } - return nil -} - -type FraudMessageResponse struct { - Proofs []*ProofResponse `protobuf:"bytes,1,rep,name=Proofs,proto3" json:"Proofs,omitempty"` -} - -func (m *FraudMessageResponse) Reset() { *m = FraudMessageResponse{} } -func (m *FraudMessageResponse) String() string { return proto.CompactTextString(m) } -func (*FraudMessageResponse) ProtoMessage() {} -func (*FraudMessageResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_8ed4b0aa9157349f, []int{2} -} -func (m *FraudMessageResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *FraudMessageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_FraudMessageResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *FraudMessageResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_FraudMessageResponse.Merge(m, src) -} -func (m *FraudMessageResponse) XXX_Size() int { - return m.Size() -} -func (m *FraudMessageResponse) XXX_DiscardUnknown() { - xxx_messageInfo_FraudMessageResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_FraudMessageResponse proto.InternalMessageInfo - -func (m *FraudMessageResponse) GetProofs() []*ProofResponse { - if m != nil { - return m.Proofs - } - return nil -} - -func init() { - proto.RegisterType((*FraudMessageRequest)(nil), "fraud.pb.FraudMessageRequest") - proto.RegisterType((*ProofResponse)(nil), "fraud.pb.ProofResponse") - proto.RegisterType((*FraudMessageResponse)(nil), "fraud.pb.FraudMessageResponse") -} - -func init() { proto.RegisterFile("libs/fraud/pb/proof.proto", fileDescriptor_8ed4b0aa9157349f) } - -var fileDescriptor_8ed4b0aa9157349f = []byte{ - // 208 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0xc9, 0x4c, 0x2a, - 0xd6, 0x4f, 0x2b, 0x4a, 0x2c, 0x4d, 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0x28, 0xca, 0xcf, 0x4f, 0xd3, - 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x8b, 0xea, 0x15, 0x24, 0x29, 0xb9, 0x72, 0x09, - 0xbb, 0x81, 0xd8, 0xbe, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, - 0x25, 0x42, 0x7a, 0x5c, 0x42, 0x50, 0x66, 0x6a, 0x4a, 0x00, 0x48, 0x63, 0x48, 0x65, 0x41, 0xaa, - 0x04, 0xa3, 0x02, 0xb3, 0x06, 0x67, 0x10, 0x16, 0x19, 0x25, 0x4b, 0x2e, 0x5e, 0x30, 0x27, 0x28, - 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0x48, 0x88, 0x8b, 0x05, 0xaa, 0x85, 0x51, 0x83, 0x33, - 0x08, 0xcc, 0x16, 0x12, 0xe1, 0x62, 0x0d, 0x4b, 0xcc, 0x29, 0x4d, 0x95, 0x60, 0x52, 0x60, 0xd6, - 0xe0, 0x09, 0x82, 0x70, 0x94, 0xdc, 0xb9, 0x44, 0x50, 0x5d, 0x00, 0x35, 0x41, 0x9f, 0x8b, 0x0d, - 0x6c, 0x64, 0x31, 0xd8, 0x5a, 0x6e, 0x23, 0x71, 0x3d, 0x98, 0xa3, 0xf5, 0x50, 0xac, 0x0a, 0x82, - 0x2a, 0x73, 0x92, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, - 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0xb0, - 0xaf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x45, 0x72, 0xa0, 0x12, 0x01, 0x00, 0x00, -} - -func (m *FraudMessageRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *FraudMessageRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *FraudMessageRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.RequestedProofType) > 0 { - for iNdEx := len(m.RequestedProofType) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.RequestedProofType[iNdEx]) - copy(dAtA[i:], m.RequestedProofType[iNdEx]) - i = encodeVarintProof(dAtA, i, uint64(len(m.RequestedProofType[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *ProofResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ProofResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ProofResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Value) > 0 { - for iNdEx := len(m.Value) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Value[iNdEx]) - copy(dAtA[i:], m.Value[iNdEx]) - i = encodeVarintProof(dAtA, i, uint64(len(m.Value[iNdEx]))) - i-- - dAtA[i] = 0x12 - } - } - if len(m.Type) > 0 { - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarintProof(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *FraudMessageResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *FraudMessageResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *FraudMessageResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Proofs) > 0 { - for iNdEx := len(m.Proofs) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Proofs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintProof(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func encodeVarintProof(dAtA []byte, offset int, v uint64) int { - offset -= sovProof(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *FraudMessageRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.RequestedProofType) > 0 { - for _, s := range m.RequestedProofType { - l = len(s) - n += 1 + l + sovProof(uint64(l)) - } - } - return n -} - -func (m *ProofResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - if l > 0 { - n += 1 + l + sovProof(uint64(l)) - } - if len(m.Value) > 0 { - for _, b := range m.Value { - l = len(b) - n += 1 + l + sovProof(uint64(l)) - } - } - return n -} - -func (m *FraudMessageResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Proofs) > 0 { - for _, e := range m.Proofs { - l = e.Size() - n += 1 + l + sovProof(uint64(l)) - } - } - return n -} - -func sovProof(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozProof(x uint64) (n int) { - return sovProof(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *FraudMessageRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: FraudMessageRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: FraudMessageRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RequestedProofType", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthProof - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthProof - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.RequestedProofType = append(m.RequestedProofType, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProof(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProof - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ProofResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ProofResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ProofResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthProof - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthProof - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthProof - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthProof - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Value = append(m.Value, make([]byte, postIndex-iNdEx)) - copy(m.Value[len(m.Value)-1], dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProof(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProof - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *FraudMessageResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: FraudMessageResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: FraudMessageResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Proofs", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProof - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthProof - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthProof - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Proofs = append(m.Proofs, &ProofResponse{}) - if err := m.Proofs[len(m.Proofs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProof(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProof - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipProof(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowProof - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowProof - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowProof - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthProof - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupProof - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthProof - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthProof = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowProof = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupProof = fmt.Errorf("proto: unexpected end of group") -) diff --git a/libs/fraud/fraudserv/pb/proof.proto b/libs/fraud/fraudserv/pb/proof.proto deleted file mode 100644 index 95715c89c8..0000000000 --- a/libs/fraud/fraudserv/pb/proof.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package fraud.pb; - -message FraudMessageRequest { - repeated string RequestedProofType = 1; -} - -message ProofResponse { - string Type = 1; - repeated bytes Value = 2; -} - -message FraudMessageResponse { - repeated ProofResponse Proofs= 1; -} diff --git a/libs/fraud/fraudserv/requester.go b/libs/fraud/fraudserv/requester.go deleted file mode 100644 index 682bf9b4d3..0000000000 --- a/libs/fraud/fraudserv/requester.go +++ /dev/null @@ -1,55 +0,0 @@ -package fraudserv - -import ( - "context" - "time" - - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - - pb "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv/pb" -) - -const ( - // writeDeadline sets timeout for sending messages to the stream - writeDeadline = time.Second * 5 - // readDeadline sets timeout for reading messages from the stream - readDeadline = time.Minute -) - -func (f *ProofService) requestProofs( - ctx context.Context, - id protocol.ID, - pid peer.ID, - proofTypes []string, -) ([]*pb.ProofResponse, error) { - msg := &pb.FraudMessageRequest{RequestedProofType: proofTypes} - stream, err := f.host.NewStream(ctx, pid, id) - if err != nil { - return nil, err - } - - if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { - log.Warn(err) - } - _, err = serde.Write(stream, msg) - if err != nil { - stream.Reset() //nolint:errcheck - return nil, err - } - if err = stream.CloseWrite(); err != nil { - log.Warn(err) - } - if err = stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { - log.Warn(err) - } - resp := &pb.FraudMessageResponse{} - _, err = serde.Read(stream, resp) - if err != nil { - stream.Reset() //nolint:errcheck - return nil, err - } - return resp.Proofs, stream.Close() -} diff --git a/libs/fraud/fraudserv/service.go b/libs/fraud/fraudserv/service.go deleted file mode 100644 index 631c390dd1..0000000000 --- a/libs/fraud/fraudserv/service.go +++ /dev/null @@ -1,255 +0,0 @@ -package fraudserv - -import ( - "bytes" - "context" - "encoding/hex" - "errors" - "fmt" - "sync" - - "github.com/ipfs/go-datastore" - logging "github.com/ipfs/go-log/v2" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" - - "github.com/celestiaorg/celestia-node/libs/fraud" -) - -var ( - log = logging.Logger("fraudserv") - tracer = otel.Tracer("fraudserv") -) - -// fraudRequests is the amount of external requests that will be tried to get fraud proofs from -// other peers. -const fraudRequests = 5 - -// ProofService is responsible for validating and propagating Fraud Proofs. -// It implements the Service interface. -type ProofService struct { - networkID string - - ctx context.Context - cancel context.CancelFunc - - topicsLk sync.RWMutex - topics map[fraud.ProofType]*pubsub.Topic - - storesLk sync.RWMutex - stores map[fraud.ProofType]datastore.Datastore - - pubsub *pubsub.PubSub - host host.Host - getter fraud.HeaderFetcher - ds datastore.Datastore - - syncerEnabled bool -} - -func NewProofService( - p *pubsub.PubSub, - host host.Host, - getter fraud.HeaderFetcher, - ds datastore.Datastore, - syncerEnabled bool, - networkID string, -) *ProofService { - return &ProofService{ - pubsub: p, - host: host, - getter: getter, - topics: make(map[fraud.ProofType]*pubsub.Topic), - stores: make(map[fraud.ProofType]datastore.Datastore), - ds: ds, - networkID: networkID, - syncerEnabled: syncerEnabled, - } -} - -// registerProofTopics registers proofTypes as pubsub topics to be joined. -func (f *ProofService) registerProofTopics(proofTypes ...fraud.ProofType) error { - for _, proofType := range proofTypes { - t, err := join(f.pubsub, proofType, f.networkID, f.processIncoming) - if err != nil { - return err - } - f.topicsLk.Lock() - f.topics[proofType] = t - f.topicsLk.Unlock() - } - return nil -} - -// Start joins fraud proofs topics, sets the stream handler for fraudProtocolID and starts syncing -// if syncer is enabled. -func (f *ProofService) Start(context.Context) error { - f.ctx, f.cancel = context.WithCancel(context.Background()) - if err := f.registerProofTopics(fraud.Registered()...); err != nil { - return err - } - id := protocolID(f.networkID) - log.Infow("starting fraud proof service", "protocol ID", id) - - f.host.SetStreamHandler(id, f.handleFraudMessageRequest) - if f.syncerEnabled { - go f.syncFraudProofs(f.ctx, id) - } - return nil -} - -// Stop removes the stream handler and cancels the underlying ProofService -func (f *ProofService) Stop(context.Context) (err error) { - f.host.RemoveStreamHandler(protocolID(f.networkID)) - f.cancel() - return -} - -func (f *ProofService) Subscribe(proofType fraud.ProofType) (_ fraud.Subscription, err error) { - f.topicsLk.Lock() - defer f.topicsLk.Unlock() - t, ok := f.topics[proofType] - if !ok { - return nil, fmt.Errorf("topic for %s does not exist", proofType) - } - subs, err := t.Subscribe() - if err != nil { - return nil, err - } - return &subscription{subs}, nil -} - -func (f *ProofService) Broadcast(ctx context.Context, p fraud.Proof) error { - bin, err := p.MarshalBinary() - if err != nil { - return err - } - f.topicsLk.RLock() - t, ok := f.topics[p.Type()] - f.topicsLk.RUnlock() - if !ok { - return fmt.Errorf("fraud: unmarshaler for %s proof is not registered", p.Type()) - } - return t.Publish(ctx, bin) -} - -// processIncoming encompasses the logic for validating fraud proofs. -func (f *ProofService) processIncoming( - ctx context.Context, - proofType fraud.ProofType, - from peer.ID, - msg *pubsub.Message, -) pubsub.ValidationResult { - ctx, span := tracer.Start(ctx, "process_proof", trace.WithAttributes( - attribute.String("proof_type", string(proofType)), - )) - defer span.End() - - // unmarshal message to the Proof. - // Peer will be added to black list if unmarshalling fails. - proof, err := fraud.Unmarshal(proofType, msg.Data) - if err != nil { - log.Errorw("unmarshalling failed", "err", err) - if !errors.Is(err, &fraud.ErrNoUnmarshaler{}) { - f.pubsub.BlacklistPeer(from) - } - span.RecordError(err) - return pubsub.ValidationReject - } - // check the fraud proof locally and ignore if it has been already stored locally. - if f.verifyLocal(ctx, proofType, hex.EncodeToString(proof.HeaderHash()), msg.Data) { - span.AddEvent("received_known_fraud_proof", trace.WithAttributes( - attribute.String("proof_type", string(proof.Type())), - attribute.Int("block_height", int(proof.Height())), - attribute.String("block_hash", hex.EncodeToString(proof.HeaderHash())), - attribute.String("from_peer", from.String()), - )) - return pubsub.ValidationIgnore - } - - msg.ValidatorData = proof - - // fetch extended header in order to verify the fraud proof. - extHeader, err := f.getter(ctx, proof.Height()) - if err != nil { - log.Errorw("failed to fetch header to verify a fraud proof", - "err", err, "proofType", proof.Type(), "height", proof.Height()) - return pubsub.ValidationIgnore - } - // validate the fraud proof. - // Peer will be added to black list if the validation fails. - err = proof.Validate(extHeader) - if err != nil { - log.Errorw("proof validation err: ", - "err", err, "proofType", proof.Type(), "height", proof.Height()) - f.pubsub.BlacklistPeer(from) - span.RecordError(err) - return pubsub.ValidationReject - } - - span.AddEvent("received_valid_proof", trace.WithAttributes( - attribute.String("proof_type", string(proof.Type())), - attribute.Int("block_height", int(proof.Height())), - attribute.String("block_hash", hex.EncodeToString(proof.HeaderHash())), - attribute.String("from_peer", from.String()), - )) - - // add the fraud proof to storage. - err = f.put(ctx, proof.Type(), hex.EncodeToString(proof.HeaderHash()), msg.Data) - if err != nil { - log.Errorw("failed to store fraud proof", "err", err) - span.RecordError(err) - } - - span.SetStatus(codes.Ok, "") - return pubsub.ValidationAccept -} - -func (f *ProofService) Get(ctx context.Context, proofType fraud.ProofType) ([]fraud.Proof, error) { - f.storesLk.Lock() - store, ok := f.stores[proofType] - if !ok { - store = initStore(proofType, f.ds) - f.stores[proofType] = store - } - f.storesLk.Unlock() - - return getAll(ctx, store, proofType) -} - -// put adds a fraud proof to the local storage. -func (f *ProofService) put(ctx context.Context, proofType fraud.ProofType, hash string, data []byte) error { - f.storesLk.Lock() - store, ok := f.stores[proofType] - if !ok { - store = initStore(proofType, f.ds) - f.stores[proofType] = store - } - f.storesLk.Unlock() - return put(ctx, store, hash, data) -} - -// verifyLocal checks if a fraud proof has been stored locally. -func (f *ProofService) verifyLocal(ctx context.Context, proofType fraud.ProofType, hash string, data []byte) bool { - f.storesLk.RLock() - storage, ok := f.stores[proofType] - f.storesLk.RUnlock() - if !ok { - return false - } - - proof, err := getByHash(ctx, storage, hash) - if err != nil { - if !errors.Is(err, datastore.ErrNotFound) { - log.Error(err) - } - return false - } - - return bytes.Equal(proof, data) -} diff --git a/libs/fraud/fraudserv/service_test.go b/libs/fraud/fraudserv/service_test.go deleted file mode 100644 index f297ab94ef..0000000000 --- a/libs/fraud/fraudserv/service_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package fraudserv - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/host" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/go-header" - "github.com/celestiaorg/go-header/headertest" - - "github.com/celestiaorg/celestia-node/libs/fraud/fraudtest" -) - -func TestService_SubscribeBroadcastValid(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - t.Cleanup(cancel) - - serv := newTestService(ctx, t, false) - require.NoError(t, serv.Start(ctx)) - - fraud := fraudtest.NewValidProof() - sub, err := serv.Subscribe(fraud.Type()) - require.NoError(t, err) - defer sub.Cancel() - - require.NoError(t, serv.Broadcast(ctx, fraud)) - _, err = sub.Proof(ctx) - require.NoError(t, err) -} - -func TestService_SubscribeBroadcastInvalid(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - t.Cleanup(cancel) - - serv := newTestService(ctx, t, false) - require.NoError(t, serv.Start(ctx)) - - fraud := fraudtest.NewInvalidProof() - sub, err := serv.Subscribe(fraud.Type()) - require.NoError(t, err) - defer sub.Cancel() - - err = serv.Broadcast(ctx, fraud) - require.Error(t, err) - - ctx2, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) - t.Cleanup(cancel) - - _, err = sub.Proof(ctx2) - require.Error(t, err) -} - -func TestService_ReGossiping(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - // create mock network - net, err := mocknet.FullMeshLinked(3) - require.NoError(t, err) - - // create services - servA := newTestServiceWithHost(ctx, t, net.Hosts()[0], false) - servB := newTestServiceWithHost(ctx, t, net.Hosts()[1], false) - servC := newTestServiceWithHost(ctx, t, net.Hosts()[2], false) - - // preconnect peers: A -> B -> C, so A and C are not connected to each other - addrB := host.InfoFromHost(net.Hosts()[1]) // -> B - require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A - require.NoError(t, net.Hosts()[2].Connect(ctx, *addrB)) // host[2] is C - - // start services - require.NoError(t, servA.Start(ctx)) - require.NoError(t, servB.Start(ctx)) - require.NoError(t, servC.Start(ctx)) - - fraud := fraudtest.NewValidProof() - subsA, err := servA.Subscribe(fraud.Type()) - require.NoError(t, err) - defer subsA.Cancel() - - subsB, err := servB.Subscribe(fraud.Type()) - require.NoError(t, err) - defer subsB.Cancel() - - subsC, err := servC.Subscribe(fraud.Type()) - require.NoError(t, err) - defer subsC.Cancel() - - // give some time for subscriptions to land - // this mitigates flakiness - time.Sleep(time.Millisecond * 100) - - // and only after broadcaster - err = servA.Broadcast(ctx, fraud) - require.NoError(t, err) - - _, err = subsA.Proof(ctx) // subscriptions of subA should also receive the proof - require.NoError(t, err) - - _, err = subsB.Proof(ctx) - require.NoError(t, err) - - _, err = subsC.Proof(ctx) - require.NoError(t, err) -} - -func TestService_Get(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - serv := newTestService(ctx, t, false) - require.NoError(t, serv.Start(ctx)) - - fraud := fraudtest.NewValidProof() - _, err := serv.Get(ctx, fraud.Type()) // try to fetch proof - require.Error(t, err) // storage is empty so should error - - sub, err := serv.Subscribe(fraud.Type()) - require.NoError(t, err) - defer sub.Cancel() - - // subscription needs some time and love to avoid flakes - time.Sleep(time.Millisecond * 100) - - err = serv.Broadcast(ctx, fraud) // broadcast stores the fraud as well - require.NoError(t, err) - - _, err = serv.Get(ctx, fraud.Type()) - require.NoError(t, err) -} - -func TestService_Sync(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - net, err := mocknet.FullMeshLinked(2) - require.NoError(t, err) - - servA := newTestServiceWithHost(ctx, t, net.Hosts()[0], false) - require.NoError(t, servA.Start(ctx)) - - fraud := fraudtest.NewValidProof() - err = servA.Broadcast(ctx, fraud) // broadcasting ensures the fraud gets stored on servA - require.NoError(t, err) - - servB := newTestServiceWithHost(ctx, t, net.Hosts()[1], true) // start servB - require.NoError(t, servB.Start(ctx)) - - sub, err := servB.Subscribe(fraud.Type()) // subscribe - require.NoError(t, err) - defer sub.Cancel() - - addrB := host.InfoFromHost(net.Hosts()[1]) - require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // connect A to B - - _, err = sub.Proof(ctx) // heck that we get it from subscription by syncing from servA - require.NoError(t, err) -} - -func newTestService(ctx context.Context, t *testing.T, enabledSyncer bool) *ProofService { - net, err := mocknet.FullMeshLinked(1) - require.NoError(t, err) - return newTestServiceWithHost(ctx, t, net.Hosts()[0], enabledSyncer) -} - -func newTestServiceWithHost(ctx context.Context, t *testing.T, host host.Host, enabledSyncer bool) *ProofService { - ps, err := pubsub.NewFloodSub(ctx, host, pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - - store := headertest.NewDummyStore(t) - serv := NewProofService( - ps, - host, - func(ctx context.Context, u uint64) (header.Header, error) { - return store.GetByHeight(ctx, u) - }, - sync.MutexWrap(datastore.NewMapDatastore()), - enabledSyncer, - "private", - ) - - t.Cleanup(func() { - err := serv.Stop(ctx) - if err != nil { - t.Fatal(err) - } - }) - return serv -} diff --git a/libs/fraud/fraudserv/store.go b/libs/fraud/fraudserv/store.go deleted file mode 100644 index dfd71b0738..0000000000 --- a/libs/fraud/fraudserv/store.go +++ /dev/null @@ -1,73 +0,0 @@ -package fraudserv - -import ( - "context" - "errors" - "fmt" - "sort" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - q "github.com/ipfs/go-datastore/query" - - "github.com/celestiaorg/celestia-node/libs/fraud" -) - -var ( - storePrefix = "fraud" -) - -// put adds a Fraud Proof to the datastore with the given hash as the key. -func put(ctx context.Context, ds datastore.Datastore, hash string, value []byte) error { - return ds.Put(ctx, datastore.NewKey(hash), value) -} - -// query performs a custom query on the given datastore. -func query(ctx context.Context, ds datastore.Datastore, q q.Query) ([]q.Entry, error) { - results, err := ds.Query(ctx, q) - if err != nil { - return nil, err - } - - return results.Rest() -} - -// getByHash fetches a fraud proof by its hash from local storage. -func getByHash(ctx context.Context, ds datastore.Datastore, hash string) ([]byte, error) { - return ds.Get(ctx, datastore.NewKey(hash)) -} - -// getAll queries all Fraud Proofs by their type. -func getAll(ctx context.Context, ds datastore.Datastore, proofType fraud.ProofType) ([]fraud.Proof, error) { - entries, err := query(ctx, ds, q.Query{}) - if err != nil { - return nil, err - } - if len(entries) == 0 { - return nil, datastore.ErrNotFound - } - proofs := make([]fraud.Proof, 0) - for _, data := range entries { - proof, err := fraud.Unmarshal(proofType, data.Value) - if err != nil { - if errors.Is(err, &fraud.ErrNoUnmarshaler{}) { - return nil, err - } - log.Warn(err) - continue - } - proofs = append(proofs, proof) - } - sort.Slice(proofs, func(i, j int) bool { - return proofs[i].Height() < proofs[j].Height() - }) - return proofs, nil -} - -func initStore(topic fraud.ProofType, ds datastore.Datastore) datastore.Datastore { - return namespace.Wrap(ds, makeKey(topic)) -} - -func makeKey(topic fraud.ProofType) datastore.Key { - return datastore.NewKey(fmt.Sprintf("%s/%s", storePrefix, topic)) -} diff --git a/libs/fraud/fraudserv/store_test.go b/libs/fraud/fraudserv/store_test.go deleted file mode 100644 index 90a2f84069..0000000000 --- a/libs/fraud/fraudserv/store_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package fraudserv - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" - ds_sync "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/libs/fraud/fraudtest" -) - -func TestStore_Put(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer t.Cleanup(cancel) - - p := fraudtest.NewValidProof() - bin, err := p.MarshalBinary() - require.NoError(t, err) - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - store := namespace.Wrap(ds, makeKey(p.Type())) - err = put(ctx, store, string(p.HeaderHash()), bin) - require.NoError(t, err) -} - -func TestStore_GetAll(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer t.Cleanup(cancel) - - proof := fraudtest.NewValidProof() - bin, err := proof.MarshalBinary() - require.NoError(t, err) - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - proofStore := namespace.Wrap(ds, makeKey(proof.Type())) - - err = put(ctx, proofStore, string(proof.HeaderHash()), bin) - require.NoError(t, err) - - proofs, err := getAll(ctx, proofStore, proof.Type()) - require.NoError(t, err) - require.NotEmpty(t, proofs) - require.NoError(t, proof.Validate(nil)) -} - -func Test_GetAllFailed(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer t.Cleanup(cancel) - - proof := fraudtest.NewValidProof() - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - store := namespace.Wrap(ds, makeKey(proof.Type())) - - proofs, err := getAll(ctx, store, proof.Type()) - require.Error(t, err) - require.ErrorIs(t, err, datastore.ErrNotFound) - require.Nil(t, proofs) -} - -func Test_getByHash(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer t.Cleanup(cancel) - - proof := fraudtest.NewValidProof() - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - store := namespace.Wrap(ds, makeKey(proof.Type())) - bin, err := proof.MarshalBinary() - require.NoError(t, err) - err = put(ctx, store, string(proof.HeaderHash()), bin) - require.NoError(t, err) - _, err = getByHash(ctx, store, string(proof.HeaderHash())) - require.NoError(t, err) -} diff --git a/libs/fraud/fraudserv/subscription.go b/libs/fraud/fraudserv/subscription.go deleted file mode 100644 index fbf86ba378..0000000000 --- a/libs/fraud/fraudserv/subscription.go +++ /dev/null @@ -1,35 +0,0 @@ -package fraudserv - -import ( - "context" - "fmt" - "reflect" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - - "github.com/celestiaorg/celestia-node/libs/fraud" -) - -// subscription wraps pubsub subscription and handles Fraud Proof from the pubsub topic. -type subscription struct { - subscription *pubsub.Subscription -} - -func (s *subscription) Proof(ctx context.Context) (fraud.Proof, error) { - if s.subscription == nil { - panic("fraud: subscription is not created") - } - data, err := s.subscription.Next(ctx) - if err != nil { - return nil, err - } - proof, ok := data.ValidatorData.(fraud.Proof) - if !ok { - panic(fmt.Sprintf("fraud: unexpected type received %s", reflect.TypeOf(data.ValidatorData))) - } - return proof, nil -} - -func (s *subscription) Cancel() { - s.subscription.Cancel() -} diff --git a/libs/fraud/fraudserv/sync.go b/libs/fraud/fraudserv/sync.go deleted file mode 100644 index 31675932aa..0000000000 --- a/libs/fraud/fraudserv/sync.go +++ /dev/null @@ -1,164 +0,0 @@ -package fraudserv - -import ( - "context" - "time" - - "github.com/ipfs/go-datastore" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/event" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - - "github.com/celestiaorg/go-libp2p-messenger/serde" - - "github.com/celestiaorg/celestia-node/libs/fraud" - pb "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv/pb" -) - -// syncFraudProofs encompasses the behavior for fetching fraud proofs from other peers. -// syncFraudProofs subscribes to EvtPeerIdentificationCompleted to get newly connected peers -// to request fraud proofs from and request fraud proofs from them. -// After fraud proofs are received, they are published to all local subscriptions for verification -// order to be verified. -func (f *ProofService) syncFraudProofs(ctx context.Context, id protocol.ID) { - log.Debug("start fetching fraud proofs") - // subscribe to new peer connections that we can request fraud proofs from - sub, err := f.host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - if err != nil { - log.Error(err) - return - } - defer sub.Close() - f.topicsLk.RLock() - // get proof types from subscribed pubsub topics - proofTypes := make([]string, 0, len(f.topics)) - for proofType := range f.topics { - proofTypes = append(proofTypes, string(proofType)) - } - f.topicsLk.RUnlock() - connStatus := event.EvtPeerIdentificationCompleted{} - // peerCache is used to store discovered peers to avoid sending multiple requests to the same peer - peerCache := make(map[peer.ID]struct{}) - // request proofs from `fraudRequests` many peers - for i := 0; i < fraudRequests; i++ { - select { - case <-ctx.Done(): - return - case e := <-sub.Out(): - connStatus = e.(event.EvtPeerIdentificationCompleted) - } - - // ignore already requested peers or ourselves as a peer - if _, ok := peerCache[connStatus.Peer]; ok || connStatus.Peer == f.host.ID() { - i-- - continue - } - - peerCache[connStatus.Peer] = struct{}{} - // valid peer found, so go send proof requests - go func(pid peer.ID) { - ctx, span := tracer.Start(ctx, "sync_proofs") - defer span.End() - - span.SetAttributes( - attribute.String("peer_id", pid.String()), - attribute.StringSlice("proof_types", proofTypes), - ) - log.Debugw("requesting proofs from peer", "pid", pid) - respProofs, err := f.requestProofs(ctx, id, pid, proofTypes) - if err != nil { - log.Errorw("error while requesting fraud proofs", "err", err, "peer", pid) - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - return - } - if len(respProofs) == 0 { - log.Debugw("peer did not return any proofs", "pid", pid) - span.SetStatus(codes.Ok, "") - return - } - log.Debugw("got fraud proofs from peer", "pid", pid) - for _, data := range respProofs { - f.topicsLk.RLock() - topic, ok := f.topics[fraud.ProofType(data.Type)] - f.topicsLk.RUnlock() - if !ok { - log.Errorf("topic for %s does not exist", fraud.ProofType(data.Type)) - continue - } - for _, val := range data.Value { - err = topic.Publish( - ctx, - val, - // broadcast across all local subscriptions in order to verify fraud proof and to stop services - pubsub.WithLocalPublication(true), - // key can be nil because it will not be verified in this case - pubsub.WithSecretKeyAndPeerId(nil, pid), - ) - if err != nil { - log.Error(err) - span.RecordError(err) - } - } - } - span.SetStatus(codes.Ok, "") - }(connStatus.Peer) - } -} - -// handleFraudMessageRequest handles an incoming FraudMessageRequest. -func (f *ProofService) handleFraudMessageRequest(stream network.Stream) { - req := &pb.FraudMessageRequest{} - if err := stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { - log.Warn(err) - } - _, err := serde.Read(stream, req) - if err != nil { - stream.Reset() //nolint:errcheck - log.Errorw("handling fraud message request failed", "err", err) - return - } - if err = stream.CloseRead(); err != nil { - log.Warn(err) - } - - resp := &pb.FraudMessageResponse{} - resp.Proofs = make([]*pb.ProofResponse, 0, len(req.RequestedProofType)) - // retrieve fraud proofs as provided by the FraudMessageRequest proofTypes. - for _, p := range req.RequestedProofType { - proofs, err := f.Get(f.ctx, fraud.ProofType(p)) - if err != nil { - if err != datastore.ErrNotFound { - log.Error(err) - } - continue - } - pbProofs := &pb.ProofResponse{Type: p, Value: make([][]byte, 0, len(proofs))} - for _, proof := range proofs { - bin, err := proof.MarshalBinary() - if err != nil { - log.Error(err) - continue - } - pbProofs.Value = append(pbProofs.Value, bin) - } - resp.Proofs = append(resp.Proofs, pbProofs) - } - - if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { - log.Warn(err) - } - _, err = serde.Write(stream, resp) - if err != nil { - stream.Reset() //nolint:errcheck - log.Errorw("error while writing a response", "err", err) - return - } - if err = stream.Close(); err != nil { - log.Errorw("error while closing a writer in stream", "err", err) - } -} diff --git a/libs/fraud/fraudtest/dummy_proof.go b/libs/fraud/fraudtest/dummy_proof.go deleted file mode 100644 index a5b9f436ce..0000000000 --- a/libs/fraud/fraudtest/dummy_proof.go +++ /dev/null @@ -1,53 +0,0 @@ -package fraudtest - -import ( - "encoding/json" - "errors" - - "github.com/celestiaorg/go-header" - - "github.com/celestiaorg/celestia-node/libs/fraud" -) - -func init() { - fraud.Register(&DummyProof{}) -} - -type DummyProof struct { - Valid bool -} - -func NewValidProof() *DummyProof { - return &DummyProof{true} -} - -func NewInvalidProof() *DummyProof { - return &DummyProof{false} -} - -func (m *DummyProof) Type() fraud.ProofType { - return "DummyProof" -} - -func (m *DummyProof) HeaderHash() []byte { - return []byte("hash") -} - -func (m *DummyProof) Height() uint64 { - return 1 -} - -func (m *DummyProof) Validate(header.Header) error { - if !m.Valid { - return errors.New("DummyProof: proof is not valid") - } - return nil -} - -func (m *DummyProof) MarshalBinary() (data []byte, err error) { - return json.Marshal(m) -} - -func (m *DummyProof) UnmarshalBinary(data []byte) error { - return json.Unmarshal(data, m) -} diff --git a/libs/fraud/fraudtest/dummy_service.go b/libs/fraud/fraudtest/dummy_service.go deleted file mode 100644 index 800f1b9e99..0000000000 --- a/libs/fraud/fraudtest/dummy_service.go +++ /dev/null @@ -1,29 +0,0 @@ -package fraudtest - -import ( - "context" - - "github.com/celestiaorg/celestia-node/libs/fraud" -) - -type DummyService struct{} - -func (d *DummyService) Broadcast(context.Context, fraud.Proof) error { - return nil -} - -func (d *DummyService) Subscribe(fraud.ProofType) (fraud.Subscription, error) { - return &subscription{}, nil -} - -func (d *DummyService) Get(context.Context, fraud.ProofType) ([]fraud.Proof, error) { - return nil, nil -} - -type subscription struct{} - -func (s *subscription) Proof(context.Context) (fraud.Proof, error) { - return nil, nil -} - -func (s *subscription) Cancel() {} diff --git a/libs/fraud/interface.go b/libs/fraud/interface.go deleted file mode 100644 index 1b318272c2..0000000000 --- a/libs/fraud/interface.go +++ /dev/null @@ -1,51 +0,0 @@ -package fraud - -import ( - "context" - - "github.com/celestiaorg/go-header" -) - -// HeaderFetcher aliases a function that is used to fetch an ExtendedHeader from store by height. -type HeaderFetcher func(context.Context, uint64) (header.Header, error) - -// ProofUnmarshaler aliases a function that parses data to `Proof`. -type ProofUnmarshaler func([]byte) (Proof, error) - -// Service encompasses the behavior necessary to subscribe and broadcast -// fraud proofs within the network. -type Service interface { - Subscriber - Broadcaster - Getter -} - -// Broadcaster is a generic interface that sends a `Proof` to all nodes subscribed on the -// Broadcaster's topic. -type Broadcaster interface { - // Broadcast takes a fraud `Proof` data structure interface and broadcasts it to local - // subscriptions and peers. It may additionally cache/persist Proofs for future - // access via Getter and to serve Proof requests to peers in the network. - Broadcast(context.Context, Proof) error -} - -// Subscriber encompasses the behavior necessary to -// subscribe/unsubscribe from new FraudProof events from the -// network. -type Subscriber interface { - // Subscribe allows to subscribe on a Proof pub sub topic by its type. - Subscribe(ProofType) (Subscription, error) -} - -// Getter encompasses the behavior to fetch stored fraud proofs. -type Getter interface { - // Get fetches fraud proofs from the disk by its type. - Get(context.Context, ProofType) ([]Proof, error) -} - -// Subscription returns a valid proof if one is received on the topic. -type Subscription interface { - // Proof returns already verified valid proof. - Proof(context.Context) (Proof, error) - Cancel() -} diff --git a/libs/fraud/metrics.go b/libs/fraud/metrics.go deleted file mode 100644 index 1b28101b36..0000000000 --- a/libs/fraud/metrics.go +++ /dev/null @@ -1,48 +0,0 @@ -package fraud - -import ( - "context" - - "github.com/ipfs/go-datastore" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/unit" -) - -var meter = global.MeterProvider().Meter("fraud") - -// WithMetrics enables metrics to monitor fraud proofs. -func WithMetrics(store Getter) { - proofTypes := registeredProofTypes() - for _, proofType := range proofTypes { - counter, _ := meter.AsyncInt64().Gauge(string(proofType), - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription("Stored fraud proof"), - ) - err := meter.RegisterCallback( - []instrument.Asynchronous{ - counter, - }, - func(ctx context.Context) { - proofs, err := store.Get(ctx, proofType) - switch err { - case nil: - counter.Observe(ctx, - int64(len(proofs)), - attribute.String("proof_type", string(proofType)), - ) - case datastore.ErrNotFound: - counter.Observe(ctx, 0, attribute.String("err", "not_found")) - return - default: - counter.Observe(ctx, 0, attribute.String("err", "unknown")) - } - }, - ) - - if err != nil { - panic(err) - } - } -} diff --git a/libs/fraud/proof.go b/libs/fraud/proof.go deleted file mode 100644 index 0666cc0ef1..0000000000 --- a/libs/fraud/proof.go +++ /dev/null @@ -1,81 +0,0 @@ -package fraud - -import ( - "context" - "encoding" - "fmt" - - "github.com/celestiaorg/go-header" -) - -// ProofType type defines a unique proof type string. -type ProofType string - -// String returns string representation of ProofType. -func (pt ProofType) String() string { - return string(pt) -} - -// Proof is a generic interface that will be used for all types of fraud proofs in the network. -type Proof interface { - // Type returns the exact type of fraud proof. - Type() ProofType - // HeaderHash returns the block hash. - HeaderHash() []byte - // Height returns the block height corresponding to the Proof. - Height() uint64 - // Validate check the validity of fraud proof. - // Validate throws an error if some conditions don't pass and thus fraud proof is not valid. - // NOTE: header.ExtendedHeader should pass basic validation otherwise it will panic if it's - // malformed. - Validate(header.Header) error - - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler -} - -// OnProof subscribes to the given Fraud Proof topic via the given Subscriber. -// In case a Fraud Proof is received, then the given handle function will be invoked. -func OnProof(ctx context.Context, subscriber Subscriber, p ProofType, handle func(proof Proof)) { - subscription, err := subscriber.Subscribe(p) - if err != nil { - return - } - defer subscription.Cancel() - - // At this point we receive already verified fraud proof, - // so there is no need to call Validate. - proof, err := subscription.Proof(ctx) - if err != nil { - return - } - - handle(proof) -} - -// Unmarshal converts raw bytes into respective Proof type. -func Unmarshal(proofType ProofType, msg []byte) (Proof, error) { - unmarshalersLk.RLock() - defer unmarshalersLk.RUnlock() - unmarshaler, ok := defaultUnmarshalers[proofType] - if !ok { - return nil, &ErrNoUnmarshaler{proofType: proofType} - } - return unmarshaler(msg) -} - -type ErrFraudExists struct { - Proof []Proof -} - -func (e *ErrFraudExists) Error() string { - return fmt.Sprintf("fraud: %s proof exists\n", e.Proof[0].Type()) -} - -type ErrNoUnmarshaler struct { - proofType ProofType -} - -func (e *ErrNoUnmarshaler) Error() string { - return fmt.Sprintf("fraud: unmarshaler for %s type is not registered", e.proofType) -} diff --git a/libs/fraud/registry.go b/libs/fraud/registry.go deleted file mode 100644 index f22a7b54fe..0000000000 --- a/libs/fraud/registry.go +++ /dev/null @@ -1,45 +0,0 @@ -package fraud - -import ( - "fmt" - "reflect" - "sync" -) - -var ( - unmarshalersLk = sync.RWMutex{} - defaultUnmarshalers = map[ProofType]ProofUnmarshaler{} -) - -// Register adds a string representation and unmarshaller for the provided ProofType. -func Register(p Proof) { - unmarshalersLk.Lock() - defer unmarshalersLk.Unlock() - if _, ok := defaultUnmarshalers[p.Type()]; ok { - panic(fmt.Sprintf("fraud: unmarshaler for %s proof is registered", p.Type())) - } - defaultUnmarshalers[p.Type()] = func(data []byte) (Proof, error) { - // the underlying type of `p` is a pointer to a struct and assigning `p` to a new variable is not - // the case, because it could lead to data races. - // So, there is no easier way to create a hard copy of Proof other than using a reflection. - proof := reflect.New(reflect.ValueOf(p).Elem().Type()).Interface().(Proof) - err := proof.UnmarshalBinary(data) - return proof, err - } -} - -// Registered reports a set of registered proof types by Register. -func Registered() []ProofType { - return registeredProofTypes() -} - -// registeredProofTypes returns all available proofTypes. -func registeredProofTypes() []ProofType { - unmarshalersLk.Lock() - defer unmarshalersLk.Unlock() - proofs := make([]ProofType, 0, len(defaultUnmarshalers)) - for proof := range defaultUnmarshalers { - proofs = append(proofs, proof) - } - return proofs -} diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index d298c0447f..40341b686a 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -6,11 +6,11 @@ import ( "github.com/ipfs/go-datastore" + "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 311ad697aa..59e8d36f69 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -5,8 +5,9 @@ import ( "go.uber.org/fx" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/libs/fraud" fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/share/eds/byzantine" diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index c1e59aa15e..a70ee3e3d4 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -8,11 +8,11 @@ import ( "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/go-fraud/fraudserv" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" - "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 7a932a540b..8d10d34e88 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -3,7 +3,7 @@ package fraud import ( "context" - "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/go-fraud" ) var _ Module = (*API)(nil) diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go index f3c74e9c63..6db7bc7266 100644 --- a/nodebuilder/fraud/lifecycle.go +++ b/nodebuilder/fraud/lifecycle.go @@ -6,7 +6,7 @@ import ( "github.com/ipfs/go-datastore" - "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/go-fraud" ) // Lifecycle controls the lifecycle of service depending on fraud proofs. diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 0561602fd6..5ede6f27c5 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -10,8 +10,8 @@ import ( gomock "github.com/golang/mock/gomock" - "github.com/celestiaorg/celestia-node/libs/fraud" fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/go-fraud" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index c3c8971c5e..718b702f84 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -4,7 +4,8 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go index 6ac1338efa..0337c375ef 100644 --- a/nodebuilder/fraud/service.go +++ b/nodebuilder/fraud/service.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/go-fraud" ) var _ Module = (*Service)(nil) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 5138305cd4..97939b7101 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -8,13 +8,13 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "go.uber.org/fx" + "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/p2p" "github.com/celestiaorg/go-header/store" "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 00485b9725..de604c9412 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -13,13 +13,13 @@ import ( "go.uber.org/fx" "go.uber.org/fx/fxtest" + "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/p2p" "github.com/celestiaorg/go-header/store" "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 6ffa50fed0..0061ab9eea 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -15,10 +15,9 @@ import ( "go.uber.org/fx" "golang.org/x/crypto/blake2b" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/go-fraud/fraudserv" headp2p "github.com/celestiaorg/go-header/p2p" - - "github.com/celestiaorg/celestia-node/libs/fraud" - "github.com/celestiaorg/celestia-node/libs/fraud/fraudserv" ) func init() { diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index d103f42060..f4775ce26b 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -14,7 +14,8 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/das" modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index bce96b3f1f..18c1eaaf3b 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -6,7 +6,8 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fraud" + "github.com/celestiaorg/go-fraud" + fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/share/eds/byzantine" diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 288c14d37a..9ca481d552 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -7,11 +7,11 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fraud" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" ) From e264069d02fe070f136cca9515e9bcb47e97d083 Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Thu, 13 Apr 2023 08:26:39 -0400 Subject: [PATCH 0534/1008] chore: Make use of common adr template (#2030) To unify the ADR structure we added a template in the .github repo. This PR adds a helpful command to generate new ADRs from that template. Example: asciinema.org/a/56X6g1bA9NpWq0FxW8wDMRJ3g Ref: celestiaorg/celestia-app#1415 Co-authored-by: Hlib Kanunnikov --- Makefile | 5 +++ docs/adr/README.md | 6 +++- docs/adr/adr-template.md | 72 ---------------------------------------- 3 files changed, 10 insertions(+), 73 deletions(-) delete mode 100644 docs/adr/adr-template.md diff --git a/Makefile b/Makefile index c82fc5dff9..eb3e9cce4f 100644 --- a/Makefile +++ b/Makefile @@ -165,3 +165,8 @@ lint-imports: sort-imports: @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... .PHONY: sort-imports + +adr-gen: + @echo "--> Generating ADRs" + @curl -sSL https://raw.githubusercontent.com/celestiaorg/.github/main/adr-template.md > docs/architecture/adr-$(NUM)-$(TITLE).md +.PHONY: adr-gen diff --git a/docs/adr/README.md b/docs/adr/README.md index 3f6266337a..c2ade99ea4 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -27,6 +27,10 @@ If recorded decisions turned out to be lacking, convene a discussion, record the Note the context/background should be written in the present tense. -To start a new ADR, you can use this template: [adr-template.md](./adr-template.md) +To start a new ADR, you can generate a new file with the following command: + +```bash +make adr-gen NUM=001 TITLE=my-adr-title +``` ## Table of Contents diff --git a/docs/adr/adr-template.md b/docs/adr/adr-template.md deleted file mode 100644 index 32cf5b3b41..0000000000 --- a/docs/adr/adr-template.md +++ /dev/null @@ -1,72 +0,0 @@ -# ADR {ADR-NUMBER}: {TITLE} - -## Changelog - -- {date}: {changelog} - -## Context - -> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. - -## Alternative Approaches - -> This section contains information around alternative options that are considered before making a decision. It should contain an explanation of why the alternative approach(es) were not chosen. - -## Decision - -> This section records the decision that was made. -> It is best to record as much info as possible from the discussion that happened. This aids in not having to go back to the Pull Request to get the needed information. - -## Detailed Design - -> This section does not need to be filled in at the start of the ADR, but must be completed prior to the merging of the implementation. -> -> Here are some common questions that get answered as part of the detailed design: -> -> - What are the user requirements? -> -> - What systems will be affected? -> -> - What new data structures are needed, what data structures will be changed? -> -> - What new APIs will be needed, what APIs will be changed? -> -> - What are the efficiency considerations (time/space)? -> -> - What are the expected access patterns (load/throughput)? -> -> - Are there any logging, monitoring or observability needs? -> -> - Are there any security considerations? -> -> - Are there any privacy considerations? -> -> - How will the changes be tested? -> -> - If the change is large, how will the changes be broken up for ease of review? -> -> - Will these changes require a breaking (major) release? -> -> - Does this change require coordination with the Celestia fork of the SDK, celestia-app/-core, or any other celestiaorg repository? - -## Status - -> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. Once the ADR has been implemented mark the ADR as "implemented". If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. - -{Deprecated|Proposed|Accepted|Declined} - -## Consequences - -> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. - -### Positive - -### Negative - -### Neutral - -## References - -> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here! - -- {reference link} From 24ea082dd00c8f6bc99786470721f65723099559 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 13 Apr 2023 16:13:30 +0200 Subject: [PATCH 0535/1008] fix(share/eds): add custom inverted index for badger as storage backend (#2053) This PR closes #2045, more specifically it closes #2057 . It introduces a custom inverted index plugin for the dagstore that is more compatible with our usage of badger as a storage backend, as well as more compatible with our usecase (we only need to index one shard per multihash). The issue with the previous implementation was that many keys were being updated very often. This is probably due to padding, but there is more investigation necessary to determine why we have so many CIDs that appear in a lot of shards. Because we use badger as a storage backend, these updates were filling the value log, duplicating the value and adding an extra shard key every time. This resulted in explosive data usage on blockspacerace. At the time of writing, without this fix, the size of the store balloons past 40gb (even more depending on how lucky you are with garbage collection), even though the size of the underlying data is ~4gb and almost all header storage. GC was effective in cleaning up the value log to a point, until they were cleaned to a point where they no longer pass the GC ratio of new to old data (20% is the default from go-badger-ds). This means that the old update logs would just accumulate and not be cleaned further once they are old enough. In this graph, the lines including the fix finished syncing much earlier (unrelated issue), but you can see the data usage stays low and increases monotonically. Without the fix, you can see the explosion of data and the effects of garbage collection. You can also see the size of the /blocks/ storage is not much in comparison to the /data/. This gives us more time to refine the approach in #2038. ![image](https://user-images.githubusercontent.com/16523232/231383399-450d6cc5-4263-4cad-a1cd-c4a7bf043488.png) Upon investigation of which CIDs are being stored in multiple shards, we see that the overwhelming majority only point to a single shard. (The value is an array of shard keys) ![image](https://user-images.githubusercontent.com/16523232/231383998-d299a889-b558-4f1c-8a77-e72f97c8910d.png) By removing the first two buckets, we can see there are still a significant amount of CIDs shared between multiple shards. A lot of these CIDs probably just represent blocks that make up padding, but further investigation is needed to see why there are so many. ![image](https://user-images.githubusercontent.com/16523232/231384457-d820e840-d474-4c4b-a323-8cecd7136ec3.png) --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/eds/inverted_index.go | 88 ++++++++++++++++++++++++++++++++ share/eds/inverted_index_test.go | 55 ++++++++++++++++++++ share/eds/store.go | 2 +- 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 share/eds/inverted_index.go create mode 100644 share/eds/inverted_index_test.go diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go new file mode 100644 index 0000000000..f917619676 --- /dev/null +++ b/share/eds/inverted_index.go @@ -0,0 +1,88 @@ +package eds + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/filecoin-project/dagstore/index" + "github.com/filecoin-project/dagstore/shard" + ds "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + "github.com/multiformats/go-multihash" +) + +// simpleInvertedIndex is an inverted index that only stores a single shard key per multihash. Its +// implementation is modified from the default upstream implementation in dagstore/index. +type simpleInvertedIndex struct { + ds ds.Batching +} + +// newSimpleInvertedIndex returns a new inverted index that only stores a single shard key per +// multihash. This is because we use badger as a storage backend, so updates are expensive, and we +// don't care which shard is used to serve a cid. +func newSimpleInvertedIndex(dts ds.Batching) *simpleInvertedIndex { + return &simpleInvertedIndex{ + ds: namespace.Wrap(dts, ds.NewKey("/inverted/index")), + } +} + +func (s *simpleInvertedIndex) AddMultihashesForShard( + ctx context.Context, + mhIter index.MultihashIterator, + sk shard.Key, +) error { + // in the original implementation, a mutex is used here to prevent unnecessary updates to the + // key. The amount of extra data produced by this is negligible, and the performance benefits + // from removing the lock are significant (indexing is a hot path during sync). + batch, err := s.ds.Batch(ctx) + if err != nil { + return fmt.Errorf("failed to create ds batch: %w", err) + } + + if err := mhIter.ForEach(func(mh multihash.Multihash) error { + key := ds.NewKey(string(mh)) + ok, err := s.ds.Has(ctx, key) + if err != nil { + return fmt.Errorf("failed to check if value for multihash exists %s, err: %w", mh, err) + } + + if !ok { + bz, err := json.Marshal(sk) + if err != nil { + return fmt.Errorf("failed to marshal shard key to bytes: %w", err) + } + if err := batch.Put(ctx, key, bz); err != nil { + return fmt.Errorf("failed to put mh=%s, err=%w", mh, err) + } + } + + return nil + }); err != nil { + return fmt.Errorf("failed to add index entry: %w", err) + } + + if err := batch.Commit(ctx); err != nil { + return fmt.Errorf("failed to commit batch: %w", err) + } + + if err := s.ds.Sync(ctx, ds.Key{}); err != nil { + return fmt.Errorf("failed to sync puts: %w", err) + } + return nil +} + +func (s *simpleInvertedIndex) GetShardsForMultihash(ctx context.Context, mh multihash.Multihash) ([]shard.Key, error) { + key := ds.NewKey(string(mh)) + sbz, err := s.ds.Get(ctx, key) + if err != nil { + return nil, fmt.Errorf("failed to lookup index for mh %s, err: %w", mh, err) + } + + var shardKey shard.Key + if err := json.Unmarshal(sbz, &shardKey); err != nil { + return nil, fmt.Errorf("failed to unmarshal shard key for mh=%s, err=%w", mh, err) + } + + return []shard.Key{shardKey}, nil +} diff --git a/share/eds/inverted_index_test.go b/share/eds/inverted_index_test.go new file mode 100644 index 0000000000..fafbca6c28 --- /dev/null +++ b/share/eds/inverted_index_test.go @@ -0,0 +1,55 @@ +package eds + +import ( + "context" + "testing" + + "github.com/filecoin-project/dagstore/shard" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" +) + +type mockIterator struct { + mhs []multihash.Multihash +} + +func (m *mockIterator) ForEach(f func(mh multihash.Multihash) error) error { + for _, mh := range m.mhs { + if err := f(mh); err != nil { + return err + } + } + return nil +} + +// TestMultihashesForShard ensures that the inverted index correctly stores a single shard key per duplicate multihash +func TestMultihashesForShard(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + mhs := []multihash.Multihash{ + multihash.Multihash("mh1"), + multihash.Multihash("mh2"), + multihash.Multihash("mh3"), + } + + mi := &mockIterator{mhs: mhs} + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + invertedIndex := newSimpleInvertedIndex(ds) + + // 1. Add all 3 multihashes to shard1 + err := invertedIndex.AddMultihashesForShard(ctx, mi, shard.KeyFromString("shard1")) + require.NoError(t, err) + shardKeys, err := invertedIndex.GetShardsForMultihash(ctx, mhs[0]) + require.NoError(t, err) + require.Equal(t, []shard.Key{shard.KeyFromString("shard1")}, shardKeys) + + // 2. Add mh1 to shard2, and ensure that mh1 still points to shard1 + err = invertedIndex.AddMultihashesForShard(ctx, &mockIterator{mhs: mhs[:1]}, shard.KeyFromString("shard2")) + require.NoError(t, err) + shardKeys, err = invertedIndex.GetShardsForMultihash(ctx, mhs[0]) + require.NoError(t, err) + require.Equal(t, []shard.Key{shard.KeyFromString("shard1")}, shardKeys) +} diff --git a/share/eds/store.go b/share/eds/store.go index 79dbd992d7..94c86158f6 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -79,7 +79,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to create index repository: %w", err) } - invertedRepo := index.NewInverted(ds) + invertedRepo := newSimpleInvertedIndex(ds) dagStore, err := dagstore.NewDAGStore( dagstore.Config{ TransientsDir: basepath + transientsPath, From 9087e0e6195f5e9a4956b7b29b76548504a7e400 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 13 Apr 2023 17:01:57 +0200 Subject: [PATCH 0536/1008] fix(eds/store): use `Path` instead of `Host` in `FileMount` serialization/deserialization (#2071) Temporary fix until our fixes are merged upstream (and we can remove this replace). REQUIRES a full store reset. Didnt know whether to make the PR chore(deps) or fix(eds/store) Closes #2031 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 030e8e26ca..4c888ba85b 100644 --- a/go.mod +++ b/go.mod @@ -328,7 +328,7 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 - github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136 + github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 312db4c3ae..dbcc0457dc 100644 --- a/go.sum +++ b/go.sum @@ -206,8 +206,8 @@ github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/ github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= -github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136 h1:LBvY3NDA18fcS72pBAEd2pENoUpz1iV4cCXBN2Zrj/I= -github.com/celestiaorg/dagstore v0.0.0-20230404123415-177451f83136/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= +github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= +github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= github.com/celestiaorg/go-header v0.2.5 h1:TV1EybWjRRJfYc8Wf5UFVytGxEQJdPdNbVEfenUVXzo= From 71b50785b720799fbd4aa9fd83d81c774fac4689 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 14 Apr 2023 15:03:26 +0800 Subject: [PATCH 0537/1008] feat(share/getter): add support for ErrNotFound in IPLD/Store getters (#2050) ## Overview - introduce ErrNotFound to getter interface - add support in ipld getter - add support in store getter Resolves https://github.com/celestiaorg/celestia-node/issues/2036 --- share/availability/light/availability_test.go | 5 +- share/eds/adapters_test.go | 17 +--- share/eds/blockstore.go | 22 +++-- share/eds/blockstore_test.go | 8 ++ share/getter.go | 4 + share/getters/getter_test.go | 98 +++++++++++++++++++ share/getters/ipld.go | 17 +++- share/getters/store.go | 32 ++++-- share/getters/utils.go | 7 +- share/ipld/get.go | 4 + share/ipld/nmt.go | 4 +- 11 files changed, 184 insertions(+), 34 deletions(-) diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index c5b93d27d2..a0586c12bc 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -133,9 +133,8 @@ func TestService_GetSharesByNamespaceNotFound(t *testing.T) { getter, root := GetterWithRandSquare(t, 1) root.RowsRoots = nil - shares, err := getter.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) - assert.Len(t, shares, 0) - assert.NoError(t, err) + _, err := getter.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) + assert.ErrorIs(t, err, share.ErrNotFound) } func BenchmarkService_GetSharesByNamespace(b *testing.B) { diff --git a/share/eds/adapters_test.go b/share/eds/adapters_test.go index d920c0720b..6ccd29e99d 100644 --- a/share/eds/adapters_test.go +++ b/share/eds/adapters_test.go @@ -2,7 +2,6 @@ package eds import ( "context" - "crypto/rand" "errors" mrand "math/rand" "sort" @@ -18,7 +17,7 @@ import ( func TestBlockGetter_GetBlocks(t *testing.T) { t.Run("happy path", func(t *testing.T) { - cids := randCIDs(32) + cids := randCIDs(t, 32) // sort cids in asc order sort.Slice(cids, func(i, j int) bool { return cids[i].String() < cids[j].String() @@ -45,7 +44,7 @@ func TestBlockGetter_GetBlocks(t *testing.T) { } }) t.Run("retrieval error", func(t *testing.T) { - cids := randCIDs(32) + cids := randCIDs(t, 32) // split cids into failed and succeeded failedLen := mrand.Intn(len(cids)-1) + 1 @@ -85,7 +84,7 @@ func TestBlockGetter_GetBlocks(t *testing.T) { } }) t.Run("retrieval timeout", func(t *testing.T) { - cids := randCIDs(128) + cids := randCIDs(t, 128) bg := &BlockGetter{ store: rbsMock{}, @@ -140,16 +139,10 @@ func (r rbsMock) HashOnRead(bool) { panic("implement me") } -func randCID() cid.Cid { - hash := make([]byte, ipld.NmtHashSize) - _, _ = rand.Read(hash) - return ipld.MustCidFromNamespacedSha256(hash) -} - -func randCIDs(n int) []cid.Cid { +func randCIDs(t *testing.T, n int) []cid.Cid { cids := make([]cid.Cid, n) for i := range cids { - cids[i] = randCID() + cids[i] = ipld.RandNamespacedCID(t) } return cids } diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 746f364bac..3c63fa3945 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -7,6 +7,7 @@ import ( "github.com/filecoin-project/dagstore" "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" bstore "github.com/ipfs/go-ipfs-blockstore" ipld "github.com/ipfs/go-ipld-format" blocks "github.com/ipfs/go-libipfs/blocks" @@ -16,7 +17,6 @@ var _ bstore.Blockstore = (*blockstore)(nil) var ( errUnsupportedOperation = errors.New("unsupported operation") - errShardNotFound = errors.New("the provided cid does not map to any shard") ) // blockstore implements the store.Blockstore interface on an EDSStore. @@ -41,6 +41,9 @@ func newBlockstore(store *Store, cache *blockstoreCache) *blockstore { func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) + if errors.Is(err, ErrNotFound) { + return false, nil + } if err != nil { return false, fmt.Errorf("failed to find shards containing multihash: %w", err) } @@ -49,16 +52,23 @@ func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) - if err != nil { - log.Debugf("failed to get blockstore for cid %s: %s", cid, err) + if errors.Is(err, ErrNotFound) { // nmt's GetNode expects an ipld.ErrNotFound when a cid is not found. return nil, ipld.ErrNotFound{Cid: cid} } + if err != nil { + log.Debugf("failed to get blockstore for cid %s: %s", cid, err) + return nil, err + } return blockstr.Get(ctx, cid) } func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) + if errors.Is(err, ErrNotFound) { + // nmt's GetSize expects an ipld.ErrNotFound when a cid is not found. + return 0, ipld.ErrNotFound{Cid: cid} + } if err != nil { return 0, err } @@ -102,12 +112,12 @@ func (bs *blockstore) HashOnRead(bool) { // getReadOnlyBlockstore finds the underlying blockstore of the shard that contains the given CID. func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (dagstore.ReadBlockstore, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) + if errors.Is(err, datastore.ErrNotFound) { + return nil, ErrNotFound + } if err != nil { return nil, fmt.Errorf("failed to find shards containing multihash: %w", err) } - if len(keys) == 0 { - return nil, errShardNotFound - } // a share can exist in multiple EDSes, so just take the first one. shardKey := keys[0] diff --git a/share/eds/blockstore_test.go b/share/eds/blockstore_test.go index 72af7c3d40..745797bf42 100644 --- a/share/eds/blockstore_test.go +++ b/share/eds/blockstore_test.go @@ -6,9 +6,12 @@ import ( "testing" "github.com/filecoin-project/dagstore" + ipld "github.com/ipfs/go-ipld-format" "github.com/ipld/go-car" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + ipld2 "github.com/celestiaorg/celestia-node/share/ipld" ) // TestBlockstore_Operations tests Has, Get, and GetSize on the top level eds.Store blockstore. @@ -48,6 +51,7 @@ func TestBlockstore_Operations(t *testing.T) { break } blockCid := next.Cid() + randomCid := ipld2.RandNamespacedCID(t) for _, bs := range blockstores { // test GetSize @@ -61,6 +65,10 @@ func TestBlockstore_Operations(t *testing.T) { assert.Equal(t, block.Cid(), blockCid) assert.Equal(t, block.RawData(), next.RawData()) + // test Get (cid not found) + _, err = bs.Get(ctx, randomCid) + require.ErrorAs(t, err, &ipld.ErrNotFound{Cid: randomCid}) + // test GetSize size, err := bs.GetSize(ctx, blockCid) assert.NotZerof(t, size, "blocksize.GetSize reported a root block from blockstore was empty") diff --git a/share/getter.go b/share/getter.go index f89c8616f7..325253e974 100644 --- a/share/getter.go +++ b/share/getter.go @@ -2,6 +2,7 @@ package share import ( "context" + "errors" "fmt" "github.com/minio/sha256-simd" @@ -11,6 +12,9 @@ import ( "github.com/celestiaorg/rsmt2d" ) +// ErrNotFound is used to indicated that requested data could not be found. +var ErrNotFound = errors.New("data not found") + // Getter interface provides a set of accessors for shares by the Root. // Automatically verifies integrity of shares(exceptions possible depending on the implementation). // diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 9695eeadb5..6075f6678e 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -3,9 +3,12 @@ package getters import ( "context" "testing" + "time" + bsrv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" + offline "github.com/ipfs/go-ipfs-exchange-offline" mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -100,6 +103,11 @@ func TestStoreGetter(t *testing.T) { assert.Equal(t, eds.GetCell(uint(i), uint(j)), share) } } + + // root not found + _, dah = randomEDS(t) + _, err = sg.GetShare(ctx, &dah, 0, 0) + require.ErrorIs(t, err, share.ErrNotFound) }) t.Run("GetEDS", func(t *testing.T) { @@ -110,6 +118,11 @@ func TestStoreGetter(t *testing.T) { retrievedEDS, err := sg.GetEDS(ctx, &dah) require.NoError(t, err) assert.True(t, share.EqualEDS(eds, retrievedEDS)) + + // root not found + root := share.Root{} + _, err = sg.GetEDS(ctx, &root) + require.ErrorIs(t, err, share.ErrNotFound) }) t.Run("GetSharesByNamespace", func(t *testing.T) { @@ -121,8 +134,93 @@ func TestStoreGetter(t *testing.T) { require.NoError(t, err) require.NoError(t, shares.Verify(&dah, nID)) assert.Len(t, shares.Flatten(), 2) + + // nid not found + nID = make([]byte, 8) + _, err = sg.GetSharesByNamespace(ctx, &dah, nID) + require.ErrorIs(t, err, share.ErrNotFound) + + // root not found + root := share.Root{} + _, err = sg.GetSharesByNamespace(ctx, &root, nID) + require.ErrorIs(t, err, share.ErrNotFound) + }) +} + +func TestIPLDGetter(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + + err = edsStore.Start(ctx) + require.NoError(t, err) + + bserv := bsrv.New(edsStore.Blockstore(), offline.Exchange(edsStore.Blockstore())) + sg := NewIPLDGetter(bserv) + + t.Run("GetShare", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + squareSize := int(eds.Width()) + for i := 0; i < squareSize; i++ { + for j := 0; j < squareSize; j++ { + share, err := sg.GetShare(ctx, &dah, i, j) + require.NoError(t, err) + assert.Equal(t, eds.GetCell(uint(i), uint(j)), share) + } + } + + // root not found + _, dah = randomEDS(t) + _, err = sg.GetShare(ctx, &dah, 0, 0) + require.ErrorIs(t, err, share.ErrNotFound) }) + t.Run("GetEDS", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + retrievedEDS, err := sg.GetEDS(ctx, &dah) + require.NoError(t, err) + assert.True(t, share.EqualEDS(eds, retrievedEDS)) + }) + + t.Run("GetSharesByNamespace", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + eds, nID, dah := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + shares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + require.NoError(t, err) + require.NoError(t, shares.Verify(&dah, nID)) + assert.Len(t, shares.Flatten(), 2) + + // nid not found + nID = make([]byte, 8) + _, err = sg.GetSharesByNamespace(ctx, &dah, nID) + require.ErrorIs(t, err, share.ErrNotFound) + + // root not found + root := share.Root{} + _, err = sg.GetSharesByNamespace(ctx, &root, nID) + require.ErrorIs(t, err, share.ErrNotFound) + }) } func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { diff --git a/share/getters/ipld.go b/share/getters/ipld.go index d8004ddb42..5ad8c92fb2 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -2,6 +2,7 @@ package getters import ( "context" + "errors" "fmt" "sync" "sync/atomic" @@ -53,12 +54,16 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - nd, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + if errors.Is(err, ipld.ErrNodeNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/ipld: failed to retrieve share: %w", err) } - return nd, nil + return s, nil } func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { @@ -71,6 +76,10 @@ func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d // rtrv.Retrieve calls shares.GetShares until enough shares are retrieved to reconstruct the EDS eds, err = ig.rtrv.Retrieve(ctx, root) + if errors.Is(err, ipld.ErrNodeNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/ipld: failed to retrieve eds: %w", err) } @@ -98,6 +107,10 @@ func (ig *IPLDGetter) GetSharesByNamespace( // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) + if errors.Is(err, ipld.ErrNodeNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/ipld: failed to retrieve shares by namespace: %w", err) } diff --git a/share/getters/store.go b/share/getters/store.go index 9c1fdfc70c..1c88206577 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "github.com/filecoin-project/dagstore" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -48,22 +47,30 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i root, leaf := ipld.Translate(dah, row, col) bs, err := sg.store.CARBlockstore(ctx, dah.Hash()) + if errors.Is(err, eds.ErrNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) } // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) - share, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + if errors.Is(err, ipld.ErrNodeNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve share: %w", err) } - return share, nil + return s, nil } // GetEDS gets the EDS identified by the given root from the EDS store. -func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { +func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (data *rsmt2d.ExtendedDataSquare, err error) { ctx, span := tracer.Start(ctx, "store/get-eds", trace.WithAttributes( attribute.String("root", root.String()), )) @@ -71,14 +78,15 @@ func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2 utils.SetStatusAndEnd(span, err) }() - eds, err = sg.store.Get(ctx, root.Hash()) - if errors.Is(err, dagstore.ErrShardUnknown) { - return nil, fmt.Errorf("getter/store: eds not found") + data, err = sg.store.Get(ctx, root.Hash()) + if errors.Is(err, eds.ErrNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve eds: %w", err) } - return eds, nil + return data, nil } // GetSharesByNamespace gets all EDS shares in the given namespace from the EDS store through the @@ -102,6 +110,10 @@ func (sg *StoreGetter) GetSharesByNamespace( } bs, err := sg.store.CARBlockstore(ctx, root.Hash()) + if errors.Is(err, eds.ErrNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) } @@ -109,6 +121,10 @@ func (sg *StoreGetter) GetSharesByNamespace( // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) + if errors.Is(err, ipld.ErrNodeNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) } diff --git a/share/getters/utils.go b/share/getters/utils.go index 88804ead84..6c7c2ba7e6 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -59,7 +59,7 @@ func collectSharesByNamespace( rootCIDs := filterRootsByNamespace(root, nID) if len(rootCIDs) == 0 { - return nil, nil + return nil, share.ErrNotFound } errGroup, ctx := errgroup.WithContext(ctx) @@ -84,6 +84,11 @@ func collectSharesByNamespace( return nil, err } + // return ErrNotFound if no shares are found for namespaceID + if len(rootCIDs) == 1 && len(shares[0].Shares) == 0 { + return nil, share.ErrNotFound + } + return shares, nil } diff --git a/share/ipld/get.go b/share/ipld/get.go index f3c0f8fe1a..70385f73f7 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -2,6 +2,7 @@ package ipld import ( "context" + "errors" "sync" "sync/atomic" @@ -31,6 +32,9 @@ var NumWorkersLimit = MaxSquareSize * MaxSquareSize / 2 * NumConcurrentSquares // concurrently/simultaneously. var NumConcurrentSquares = 8 +// ErrNodeNotFound is used to signal when a nmt Node could not be found. +var ErrNodeNotFound = errors.New("nmt node not found") + // Global worker pool that globally controls and limits goroutines spawned by // GetShares. // diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 854d74587a..91c413e05b 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -77,9 +77,9 @@ func init() { func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid) (ipld.Node, error) { block, err := bGetter.GetBlock(ctx, root) if err != nil { - var errNotFound *ipld.ErrNotFound + var errNotFound ipld.ErrNotFound if errors.As(err, &errNotFound) { - return nil, errNotFound + return nil, ErrNodeNotFound } return nil, err } From acc89f40e1cb379ea1c2c3fe775cbbea9f0d3758 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 14 Apr 2023 10:10:13 +0200 Subject: [PATCH 0538/1008] feat(cli): adding unsafe-reset-store command (#2070) Closes #109. Doesn't include any flags. Tested for all node types locally, works well. --- cmd/celestia/bridge.go | 1 + cmd/celestia/full.go | 1 + cmd/celestia/light.go | 1 + cmd/reset_store.go | 26 ++++++++++++++++++++ nodebuilder/init.go | 56 ++++++++++++++++++++++++++++++++++++++++++ nodebuilder/store.go | 13 ++++++++++ 6 files changed, 98 insertions(+) create mode 100644 cmd/reset_store.go diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index 9014236541..70025e115a 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -31,6 +31,7 @@ func init() { cmdnode.Init(flags...), cmdnode.Start(flags...), cmdnode.AuthCmd(flags...), + cmdnode.ResetStore(flags...), ) } diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index c5356b3ee5..1f25a0a737 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -35,6 +35,7 @@ func init() { cmdnode.Init(flags...), cmdnode.Start(flags...), cmdnode.AuthCmd(flags...), + cmdnode.ResetStore(flags...), ) } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 19580638b9..07cff91435 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -35,6 +35,7 @@ func init() { cmdnode.Init(flags...), cmdnode.Start(flags...), cmdnode.AuthCmd(flags...), + cmdnode.ResetStore(flags...), ) } diff --git a/cmd/reset_store.go b/cmd/reset_store.go new file mode 100644 index 0000000000..d386549efa --- /dev/null +++ b/cmd/reset_store.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + + "github.com/celestiaorg/celestia-node/nodebuilder" +) + +// ResetStore constructs a CLI command to reset the store of Celestia Node. +func ResetStore(fsets ...*flag.FlagSet) *cobra.Command { + cmd := &cobra.Command{ + Use: "unsafe-reset-store", + Short: "Resets the node's store to a new state. Leaves the keystore and config intact.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + return nodebuilder.Reset(StorePath(ctx), NodeType(ctx)) + }, + } + for _, set := range fsets { + cmd.Flags().AddFlagSet(set) + } + return cmd +} diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 83abed945f..2e7a415df7 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -71,6 +71,53 @@ func Init(cfg Config, path string, tp node.Type) error { return nil } +// Reset removes all data from the datastore and dagstore directories. It leaves the keystore and config intact. +func Reset(path string, tp node.Type) error { + path, err := storePath(path) + if err != nil { + return err + } + log.Infof("Resetting %s Node Store over '%s'", tp, path) + + flock, err := fslock.Lock(lockPath(path)) + if err != nil { + if err == fslock.ErrLocked { + return ErrOpened + } + return err + } + defer flock.Unlock() //nolint: errcheck + + err = resetDir(dataPath(path)) + if err != nil { + return err + } + + // light nodes don't have dagstore paths + if tp == node.Light { + log.Info("Node Store reset") + return nil + } + + err = resetDir(blocksPath(path)) + if err != nil { + return err + } + + err = resetDir(transientsPath(path)) + if err != nil { + return err + } + + err = resetDir(indexPath(path)) + if err != nil { + return err + } + + log.Info("Node Store reset") + return nil +} + // IsInit checks whether FileSystem Store was setup under given 'path'. // If any required file/subdirectory does not exist, then false is reported. func IsInit(path string) bool { @@ -117,6 +164,15 @@ func initRoot(path string) error { return os.Remove(f.Name()) } +// resetDir removes all files from the given directory and reinitializes it +func resetDir(path string) error { + err := os.RemoveAll(path) + if err != nil { + return err + } + return initDir(path) +} + // initDir creates a dir if not exist func initDir(path string) error { if utils.Exists(path) { diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 904427c9f0..415e56bd8c 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -186,6 +186,19 @@ func keysPath(base string) string { return filepath.Join(base, "keys") } +func blocksPath(base string) string { + return filepath.Join(base, "blocks") +} + +func transientsPath(base string) string { + // we don't actually use the transients directory anymore, but it could be populated from previous versions. + return filepath.Join(base, "transients") +} + +func indexPath(base string) string { + return filepath.Join(base, "index") +} + func dataPath(base string) string { return filepath.Join(base, "data") } From e804359ce9e973464984c8e5542e02cf57711a23 Mon Sep 17 00:00:00 2001 From: Samuel Enderwitz <18609909+smuu@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:57:41 +0200 Subject: [PATCH 0539/1008] feat: if NODE_STORE is set use it in custom init command (#2069) This change in the entrypoint.sh is for supporting docker compose setup in rollkit/gm. See here. When the NODE_STORE env variable is set, the init command specifies the node.store parameter to this environment variable. This is non-breaking; if the variable is not set, nothing changes. --- docker/entrypoint.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index b9f9e77e77..6f064be830 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -4,12 +4,17 @@ set -e if [ "$1" = 'celestia' ]; then echo "Initializing Celestia Node with command:" - echo "celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}"" - echo "" - celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" + if [[ -n "$NODE_STORE" ]]; then + echo "celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" --node.store "${NODE_STORE}"" + celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" --node.store "${NODE_STORE}" + else + echo "celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}"" + celestia "${NODE_TYPE}" init --p2p.network "${P2P_NETWORK}" + fi echo "" + echo "" fi echo "Starting Celestia Node with command:" From 42f60445c59f96253199c7ac16c88e4f37333351 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 10:13:41 +0000 Subject: [PATCH 0540/1008] chore(deps): bump celestiaorg/.github from 0.1.0 to 0.1.1 (#2047) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_release.yml | 6 +++--- .github/workflows/docker-build-publish.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index fa51c0499f..574f425c7b 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -24,7 +24,7 @@ on: jobs: # Dockerfile Linting hadolint: - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.1.0 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.1.1 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: celestiaorg/.github/.github/actions/yamllint@v0.1.0 + - uses: celestiaorg/.github/.github/actions/yamllint@v0.1.1 markdown-lint: name: Markdown Lint @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@v0.1.0 + uses: celestiaorg/.github/.github/actions/version-release@v0.1.1 with: github-token: ${{secrets.GITHUB_TOKEN}} version-bump: ${{inputs.version}} diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 1ae52d54a7..ce629145b9 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -17,6 +17,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.1.0 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.1.1 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From d375373b162552276aed5b0d93ac4bcb3f1c6d0c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 14 Apr 2023 18:23:54 +0800 Subject: [PATCH 0541/1008] feat(share/getter): add support for ErrNotFound in shrex getter implementation (#2074) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/2037 --- share/eds/inverted_index_test.go | 3 +- share/eds/store_test.go | 3 - share/getters/shrex.go | 24 ++- share/getters/shrex_test.go | 188 +++++++++++++----- share/p2p/errors.go | 8 +- share/p2p/peers/manager.go | 8 +- share/p2p/peers/manager_test.go | 26 +-- share/p2p/shrexeds/client.go | 8 +- share/p2p/shrexeds/exchange_test.go | 11 +- .../shrexeds/pb/extended_data_square.pb.go | 16 +- .../shrexeds/pb/extended_data_square.proto | 1 + share/p2p/shrexeds/server.go | 10 +- share/p2p/shrexnd/client.go | 6 +- share/p2p/shrexnd/exchange_test.go | 92 ++++++++- share/p2p/shrexnd/server.go | 4 + 15 files changed, 312 insertions(+), 96 deletions(-) diff --git a/share/eds/inverted_index_test.go b/share/eds/inverted_index_test.go index fafbca6c28..f228aa0d92 100644 --- a/share/eds/inverted_index_test.go +++ b/share/eds/inverted_index_test.go @@ -24,7 +24,8 @@ func (m *mockIterator) ForEach(f func(mh multihash.Multihash) error) error { return nil } -// TestMultihashesForShard ensures that the inverted index correctly stores a single shard key per duplicate multihash +// TestMultihashesForShard ensures that the inverted index correctly stores a single shard key per +// duplicate multihash func TestMultihashesForShard(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index ab0d74b2d2..6c6d7d10b2 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -2,7 +2,6 @@ package eds import ( "context" - "fmt" "os" "testing" "time" @@ -71,8 +70,6 @@ func TestEDSStore(t *testing.T) { r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) carReader, err := car.NewCarReader(r) - - fmt.Println(car.HeaderSize(carReader.Header)) assert.NoError(t, err) for i := 0; i < 4; i++ { diff --git a/share/getters/shrex.go b/share/getters/shrex.go index f4e2d11f33..95f06d821e 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -83,12 +83,16 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) eds, getErr := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) cancel() - switch getErr { - case nil: + switch { + case getErr == nil: setStatus(peers.ResultSynced) return eds, nil - case context.DeadlineExceeded: - case p2p.ErrInvalidResponse: + case errors.Is(getErr, context.DeadlineExceeded), + errors.Is(getErr, context.Canceled): + case errors.Is(getErr, p2p.ErrNotFound): + getErr = share.ErrNotFound + setStatus(peers.ResultCooldownPeer) + case errors.Is(getErr, p2p.ErrInvalidResponse): setStatus(peers.ResultBlacklistPeer) default: setStatus(peers.ResultCooldownPeer) @@ -132,12 +136,16 @@ func (sg *ShrexGetter) GetSharesByNamespace( reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) nd, getErr := sg.ndClient.RequestND(reqCtx, root, id, peer) cancel() - switch getErr { - case nil: + switch { + case getErr == nil: setStatus(peers.ResultSuccess) return nd, nil - case context.DeadlineExceeded: - case p2p.ErrInvalidResponse: + case errors.Is(getErr, context.DeadlineExceeded), + errors.Is(getErr, context.Canceled): + case errors.Is(getErr, p2p.ErrNotFound): + getErr = share.ErrNotFound + setStatus(peers.ResultCooldownPeer) + case errors.Is(getErr, p2p.ErrInvalidResponse): setStatus(peers.ResultBlacklistPeer) default: setStatus(peers.ResultCooldownPeer) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index c47dbf6f85..b9960c1fac 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -2,71 +2,122 @@ package getters import ( "context" - mrand "math/rand" "testing" "time" - bsrv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "github.com/libp2p/go-libp2p/core/host" + routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "github.com/libp2p/go-libp2p/p2p/net/conngater" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/p2p/peers" + "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) -func TestGetSharesWithProofByNamespace(t *testing.T) { +func TestShrexGetter(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) // create test net - net := availability_test.NewTestDAGNet(ctx, t) - - // generate test data - bServ := mdutils.Bserv() - randomEDS, nID := generateTestEDS(t, bServ) - dah := da.NewDataAvailabilityHeader(randomEDS) + net, err := mocknet.FullMeshConnected(2) + require.NoError(t, err) + clHost, srvHost := net.Hosts()[0], net.Hosts()[1] // launch eds store and put test data into it edsStore, err := newStore(t) require.NoError(t, err) err = edsStore.Start(ctx) require.NoError(t, err) - err = edsStore.Put(ctx, dah.Hash(), randomEDS) - require.NoError(t, err) - // create server and register handler - srvHost := net.NewTestNode().Host - params := shrexnd.DefaultParameters() - srv, err := shrexnd.NewServer(params, srvHost, edsStore, NewIPLDGetter(bServ)) - require.NoError(t, err) - err = srv.Start(ctx) + ndClient, _ := newNDClientServer(ctx, t, edsStore, srvHost, clHost) + edsClient, _ := newEDSClientServer(ctx, t, edsStore, srvHost, clHost) + + // create shrex Getter + sub := new(headertest.Subscriber) + peerManager, err := testManager(ctx, clHost, sub) require.NoError(t, err) + getter := NewShrexGetter(edsClient, ndClient, peerManager) + require.NoError(t, getter.Start(ctx)) + + t.Run("ND_Available", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + // generate test data + eds, dah, nID := generateTestEDS(t) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + got, err := getter.GetSharesByNamespace(ctx, &dah, nID) + require.NoError(t, err) + require.NoError(t, got.Verify(&dah, nID)) + }) - t.Cleanup(func() { - _ = srv.Stop(ctx) + t.Run("ND_err_not_found", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + // generate test data + _, dah, nID := generateTestEDS(t) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + _, err := getter.GetSharesByNamespace(ctx, &dah, nID) + require.ErrorIs(t, err, share.ErrNotFound) }) - // create client and connect it to server - client, err := shrexnd.NewClient(params, net.NewTestNode().Host) - require.NoError(t, err) - net.ConnectAll() + t.Run("EDS_Available", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + // generate test data + eds, dah, _ := generateTestEDS(t) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + got, err := getter.GetEDS(ctx, &dah) + require.NoError(t, err) + require.Equal(t, eds.Flattened(), got.Flattened()) + }) - got, err := client.RequestND( - ctx, - &dah, - nID, - srvHost.ID()) + t.Run("EDS_err_not_found", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) - require.NoError(t, err) - require.NoError(t, got.Verify(&dah, nID)) + // generate test data + _, dah, _ := generateTestEDS(t) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + _, err := getter.GetEDS(ctx, &dah) + require.ErrorIs(t, err, share.ErrNotFound) + }) } func newStore(t *testing.T) (*eds.Store, error) { @@ -77,24 +128,71 @@ func newStore(t *testing.T) (*eds.Store, error) { return eds.NewStore(tmpDir, ds) } -func generateTestEDS(t *testing.T, bServ bsrv.BlockService) (*rsmt2d.ExtendedDataSquare, namespace.ID) { - shares := share.RandShares(t, 16) - - from := mrand.Intn(len(shares)) - to := mrand.Intn(len(shares)) +func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, namespace.ID) { + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + randNID := dah.RowsRoots[(len(dah.RowsRoots)-1)/2][:8] + return eds, dah, randNID +} - if to < from { - from, to = to, from +func testManager(ctx context.Context, host host.Host, headerSub libhead.Subscriber[*header.ExtendedHeader], +) (*peers.Manager, error) { + shrexSub, err := shrexsub.NewPubSub(ctx, host, "test") + if err != nil { + return nil, err } - nID := shares[from][:share.NamespaceSize] - // change some shares to have same nID - for i := from; i <= to; i++ { - copy(shares[i][:share.NamespaceSize], nID) + disc := discovery.NewDiscovery(nil, + routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + connGater, err := conngater.NewBasicConnectionGater(ds_sync.MutexWrap(datastore.NewMapDatastore())) + if err != nil { + return nil, err } + manager, err := peers.NewManager( + peers.DefaultParameters(), + headerSub, + shrexSub, + disc, + host, + connGater, + ) + return manager, err +} - eds, err := share.AddShares(context.Background(), shares, bServ) +func newNDClientServer(ctx context.Context, t *testing.T, edsStore *eds.Store, srvHost, clHost host.Host, +) (*shrexnd.Client, *shrexnd.Server) { + params := shrexnd.DefaultParameters() + + // create server and register handler + server, err := shrexnd.NewServer(params, srvHost, edsStore, NewStoreGetter(edsStore)) + require.NoError(t, err) + require.NoError(t, server.Start(ctx)) + + t.Cleanup(func() { + _ = server.Stop(ctx) + }) + + // create client and connect it to server + client, err := shrexnd.NewClient(params, clHost) require.NoError(t, err) + return client, server +} - return eds, nID +func newEDSClientServer(ctx context.Context, t *testing.T, edsStore *eds.Store, srvHost, clHost host.Host, +) (*shrexeds.Client, *shrexeds.Server) { + params := shrexeds.DefaultParameters() + + // create server and register handler + server, err := shrexeds.NewServer(params, srvHost, edsStore) + require.NoError(t, err) + require.NoError(t, server.Start(ctx)) + + t.Cleanup(func() { + _ = server.Stop(ctx) + }) + + // create client and connect it to server + client, err := shrexeds.NewClient(params, clHost) + require.NoError(t, err) + return client, server } diff --git a/share/p2p/errors.go b/share/p2p/errors.go index b3c1b6e503..77f23c554e 100644 --- a/share/p2p/errors.go +++ b/share/p2p/errors.go @@ -4,10 +4,10 @@ import ( "errors" ) -// ErrUnavailable is returned when a peer doesn't have the requested data or doesn't have the -// capacity to serve it at the moment. It is used to signal that the peer couldn't serve the data -// successfully, but should be retried later. -var ErrUnavailable = errors.New("server cannot serve the requested data at this time") +// ErrNotFound is returned when a peer is unable to find the requested data or resource. +// It is used to signal that the peer couldn't serve the data successfully, and it's not +// available at the moment. The request may be retried later, but it's unlikely to succeed. +var ErrNotFound = errors.New("the requested data or resource could not be found") // ErrInvalidResponse is returned when a peer returns an invalid response or caused an internal // error. It is used to signal that the peer couldn't serve the data successfully, and should not be diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 6ccdd7365c..c18e91bdb1 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -43,7 +43,7 @@ type Manager struct { lock sync.Mutex params Parameters - // header subscription is necessary in order to validate the inbound eds hash + // header subscription is necessary in order to Validate the inbound eds hash headerSub libhead.Subscriber[*header.ExtendedHeader] shrexSub *shrexsub.PubSub host host.Host @@ -134,7 +134,7 @@ func (m *Manager) Start(startCtx context.Context) error { ctx, cancel := context.WithCancel(context.Background()) m.cancel = cancel - err := m.shrexSub.AddValidator(m.validate) + err := m.shrexSub.AddValidator(m.Validate) if err != nil { return fmt.Errorf("registering validator: %w", err) } @@ -256,8 +256,8 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri } } -// validate will collect peer.ID into corresponding peer pool -func (m *Manager) validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { +// Validate will collect peer.ID into corresponding peer pool +func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { // messages broadcast from self should bypass the validation with Accept if peerID == m.host.ID() { diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 7d5bbaa96c..05c1f5b33e 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -28,7 +28,7 @@ import ( // TODO: add broadcast to tests func TestManager(t *testing.T) { - t.Run("validate pool by headerSub", func(t *testing.T) { + t.Run("Validate pool by headerSub", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) t.Cleanup(cancel) @@ -49,7 +49,7 @@ func TestManager(t *testing.T) { stopManager(t, manager) }) - t.Run("validate pool by shrex.Getter", func(t *testing.T) { + t.Run("Validate pool by shrex.Getter", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -61,7 +61,7 @@ func TestManager(t *testing.T) { require.NoError(t, err) peerID, msg := peer.ID("peer1"), newShrexSubMsg(h) - result := manager.validate(ctx, peerID, msg) + result := manager.Validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) @@ -91,12 +91,12 @@ func TestManager(t *testing.T) { // own messages should be be accepted msg := newShrexSubMsg(h) - result := manager.validate(ctx, manager.host.ID(), msg) + result := manager.Validate(ctx, manager.host.ID(), msg) require.Equal(t, pubsub.ValidationAccept, result) // normal messages should be ignored peerID := peer.ID("peer1") - result = manager.validate(ctx, peerID, msg) + result = manager.Validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) // mark peer as misbehaved to blacklist it @@ -107,7 +107,7 @@ func TestManager(t *testing.T) { done(ResultBlacklistPeer) // new messages from misbehaved peer should be Rejected - result = manager.validate(ctx, pID, msg) + result = manager.Validate(ctx, pID, msg) require.Equal(t, pubsub.ValidationReject, result) stopManager(t, manager) @@ -135,7 +135,7 @@ func TestManager(t *testing.T) { DataHash: share.DataHash("datahash1"), Height: 2, } - manager.validate(ctx, peerID, msg) + manager.Validate(ctx, peerID, msg) // create validated pool validDataHash := share.DataHash("datahash2") @@ -148,7 +148,7 @@ func TestManager(t *testing.T) { // messages with blacklisted hash should be rejected right away peerID2 := peer.ID("peer2") - result := manager.validate(ctx, peerID2, msg) + result := manager.Validate(ctx, peerID2, msg) require.Equal(t, pubsub.ValidationReject, result) // check blacklisted pools @@ -235,7 +235,7 @@ func TestManager(t *testing.T) { require.NoError(t, err) peerID, msg := peer.ID("peer1"), newShrexSubMsg(h) - result := manager.validate(ctx, peerID, msg) + result := manager.Validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) @@ -249,7 +249,7 @@ func TestManager(t *testing.T) { require.True(t, pool.isSynced.Load()) // add peer on synced pool should be noop - result = manager.validate(ctx, "peer2", msg) + result = manager.Validate(ctx, "peer2", msg) require.Equal(t, pubsub.ValidationIgnore, result) require.Len(t, pool.peersList, 0) }) @@ -276,7 +276,7 @@ func TestManager(t *testing.T) { DataHash: share.DataHash("datahash"), Height: uint64(h.Height() - 1), } - result := manager.validate(ctx, "peer", msg) + result := manager.Validate(ctx, "peer", msg) require.Equal(t, pubsub.ValidationIgnore, result) // amount of pools should not change @@ -300,7 +300,7 @@ func TestManager(t *testing.T) { DataHash: share.DataHash("datahash"), Height: uint64(h.Height() - 1), } - result := manager.validate(ctx, "peer", msg) + result := manager.Validate(ctx, "peer", msg) require.Equal(t, pubsub.ValidationIgnore, result) // unlock header sub after message validator @@ -339,7 +339,7 @@ func TestIntegration(t *testing.T) { require.NoError(t, err) fnPeerManager.host = nw.Hosts()[1] - require.NoError(t, fnPubSub.AddValidator(fnPeerManager.validate)) + require.NoError(t, fnPubSub.AddValidator(fnPeerManager.Validate)) _, err = fnPubSub.Subscribe() require.NoError(t, err) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 4c96265c63..b7c3e28402 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -60,7 +60,7 @@ func (c *Client) RequestEDS( return nil, context.DeadlineExceeded } } - if err != p2p.ErrUnavailable { + if err != p2p.ErrNotFound { log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) } @@ -104,7 +104,7 @@ func (c *Client) doRequest( if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { - return nil, p2p.ErrUnavailable + return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck return nil, fmt.Errorf("failed to read status from stream: %w", err) @@ -120,8 +120,8 @@ func (c *Client) doRequest( return eds, nil case pb.Status_NOT_FOUND: log.Debugf("client: peer %s couldn't serve eds %s with status %s", to.String(), dataHash.String(), resp.GetStatus()) - return nil, p2p.ErrUnavailable - case pb.Status_INVALID: + return nil, p2p.ErrNotFound + case pb.Status_INVALID, pb.Status_INTERNAL: fallthrough default: log.Errorf("request status %s returned for root %s", resp.Status.String(), dataHash.String()) diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 3085e1e32e..31d017fa13 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -86,6 +86,15 @@ func TestExchange_RequestEDS(t *testing.T) { assert.Nil(t, requestedEDS) }) + t.Run("EDS_err_not_found", func(t *testing.T) { + timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + _, err := client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) + require.ErrorIs(t, err, p2p.ErrNotFound) + }) + // Testcase: Concurrency limit reached t.Run("EDS_concurrency_limit", func(t *testing.T) { store, client, server := makeExchange(t) @@ -124,7 +133,7 @@ func TestExchange_RequestEDS(t *testing.T) { // wait until all server slots are taken wg.Wait() _, err = client.RequestEDS(ctx, nil, server.host.ID()) - require.ErrorIs(t, err, p2p.ErrUnavailable) + require.ErrorIs(t, err, p2p.ErrNotFound) }) } diff --git a/share/p2p/shrexeds/pb/extended_data_square.pb.go b/share/p2p/shrexeds/pb/extended_data_square.pb.go index c6f7e07123..ed1a96ae3b 100644 --- a/share/p2p/shrexeds/pb/extended_data_square.pb.go +++ b/share/p2p/shrexeds/pb/extended_data_square.pb.go @@ -28,18 +28,21 @@ const ( Status_INVALID Status = 0 Status_OK Status = 1 Status_NOT_FOUND Status = 2 + Status_INTERNAL Status = 3 ) var Status_name = map[int32]string{ 0: "INVALID", 1: "OK", 2: "NOT_FOUND", + 3: "INTERNAL", } var Status_value = map[string]int32{ "INVALID": 0, "OK": 1, "NOT_FOUND": 2, + "INTERNAL": 3, } func (x Status) String() string { @@ -149,7 +152,7 @@ func init() { } var fileDescriptor_49d42aa96098056e = []byte{ - // 213 bytes of a gzipped FileDescriptorProto + // 227 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x28, 0xce, 0x48, 0x2c, 0x4a, 0xd5, 0x2f, 0x30, 0x2a, 0xd0, 0x2f, 0xce, 0x28, 0x4a, 0xad, 0x48, 0x4d, 0x29, 0xd6, 0x2f, 0x48, 0xd2, 0x4f, 0xad, 0x28, 0x49, 0xcd, 0x4b, 0x49, 0x4d, 0x89, 0x4f, 0x49, 0x2c, 0x49, 0x8c, @@ -157,13 +160,14 @@ var fileDescriptor_49d42aa96098056e = []byte{ 0x72, 0x75, 0x09, 0x0e, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x48, 0x2c, 0xce, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x02, 0xb3, 0x95, 0xf4, 0xb8, 0xb8, 0xc1, 0x2a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xe4, 0xb9, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, 0x4a, - 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x0e, 0x17, + 0x8b, 0xc1, 0x8a, 0xf8, 0x8c, 0xd8, 0xf5, 0x82, 0xc1, 0xdc, 0x20, 0xa8, 0xb0, 0x96, 0x15, 0x17, 0x1b, 0x44, 0x44, 0x88, 0x9b, 0x8b, 0xdd, 0xd3, 0x2f, 0xcc, 0xd1, 0xc7, 0xd3, 0x45, 0x80, 0x41, 0x88, 0x8d, 0x8b, 0xc9, 0xdf, 0x5b, 0x80, 0x51, 0x88, 0x97, 0x8b, 0xd3, 0xcf, 0x3f, 0x24, 0xde, - 0xcd, 0x3f, 0xd4, 0xcf, 0x45, 0x80, 0xc9, 0x49, 0xe2, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, - 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, - 0xe5, 0x18, 0x92, 0xd8, 0xc0, 0x0e, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x34, 0x8f, 0xa7, - 0x57, 0xd4, 0x00, 0x00, 0x00, + 0xcd, 0x3f, 0xd4, 0xcf, 0x45, 0x80, 0x49, 0x88, 0x87, 0x8b, 0xc3, 0xd3, 0x2f, 0xc4, 0x35, 0xc8, + 0xcf, 0xd1, 0x47, 0x80, 0xd9, 0x49, 0xe2, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, + 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, + 0x92, 0xd8, 0xc0, 0xce, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x7b, 0x1d, 0xd4, 0xa7, 0xe2, + 0x00, 0x00, 0x00, } func (m *EDSRequest) Marshal() (dAtA []byte, err error) { diff --git a/share/p2p/shrexeds/pb/extended_data_square.proto b/share/p2p/shrexeds/pb/extended_data_square.proto index 1830d85f8e..63750962e9 100644 --- a/share/p2p/shrexeds/pb/extended_data_square.proto +++ b/share/p2p/shrexeds/pb/extended_data_square.proto @@ -8,6 +8,7 @@ enum Status { INVALID = 0; OK = 1; // data found NOT_FOUND = 2; // data not found + INTERNAL = 3; // internal server error } message EDSResponse { diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index ccc841df64..bae472871e 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -2,6 +2,7 @@ package shrexeds import ( "context" + "errors" "fmt" "io" "time" @@ -78,13 +79,18 @@ func (s *Server) handleStream(stream network.Stream) { ctx, cancel := context.WithTimeout(s.ctx, s.params.HandleRequestTimeout) defer cancel() - status := p2p_pb.Status_OK + // determine whether the EDS is available in our store // we do not close the reader, so that other requests will not need to re-open the file. // closing is handled by the LRU cache. edsReader, err := s.store.GetCAR(ctx, hash) - if err != nil { + status := p2p_pb.Status_OK + switch { + case errors.Is(err, eds.ErrNotFound): status = p2p_pb.Status_NOT_FOUND + case err != nil: + log.Debugw("server: get car", "err", err) + status = p2p_pb.Status_INTERNAL } // inform the client of our status diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 1264412868..467376605f 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -68,7 +68,7 @@ func (c *Client) RequestND( return nil, context.DeadlineExceeded } } - if err != p2p.ErrUnavailable { + if err != p2p.ErrNotFound { log.Debugw("client-nd: peer returned err", "peer", peer, "err", err) } return nil, err @@ -109,7 +109,7 @@ func (c *Client) doRequest( if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { - return nil, p2p.ErrUnavailable + return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck return nil, fmt.Errorf("client-nd: reading response: %w", err) @@ -188,7 +188,7 @@ func statusToErr(code pb.StatusCode) error { case pb.StatusCode_OK: return nil case pb.StatusCode_NOT_FOUND: - return p2p.ErrUnavailable + return p2p.ErrNotFound case pb.StatusCode_INTERNAL, pb.StatusCode_INVALID: fallthrough default: diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index b1d09bc721..8542992e0e 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -6,15 +6,54 @@ import ( "testing" "time" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + libhost "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p" ) +func TestExchange_RequestND_NotFound(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + t.Cleanup(cancel) + edsStore, client, server := makeExchange(t, notFoundGetter{}) + require.NoError(t, edsStore.Start(ctx)) + require.NoError(t, server.Start(ctx)) + + t.Run("CAR_not_exist", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + root := share.Root{} + nID := make([]byte, 8) + _, err := client.RequestND(ctx, &root, nID, server.host.ID()) + require.ErrorIs(t, err, p2p.ErrNotFound) + }) + + t.Run("Getter_err_not_found", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + eds := share.RandEDS(t, 4) + dah := da.NewDataAvailabilityHeader(eds) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + + randNID := dah.RowsRoots[(len(dah.RowsRoots)-1)/2][:8] + _, err := client.RequestND(ctx, &dah, randNID, server.host.ID()) + require.ErrorIs(t, err, p2p.ErrNotFound) + }) +} + func TestExchange_RequestND(t *testing.T) { - // Testcase: Concurrency limit reached t.Run("ND_concurrency_limit", func(t *testing.T) { net, err := mocknet.FullMeshConnected(2) require.NoError(t, err) @@ -57,6 +96,55 @@ func TestExchange_RequestND(t *testing.T) { // wait until all server slots are taken wg.Wait() _, err = client.RequestND(ctx, nil, nil, server.host.ID()) - require.ErrorIs(t, err, p2p.ErrUnavailable) + require.ErrorIs(t, err, p2p.ErrNotFound) }) } + +type notFoundGetter struct{} + +func (m notFoundGetter) GetShare(_ context.Context, _ *share.Root, _, _ int, +) (share.Share, error) { + return nil, share.ErrNotFound +} + +func (m notFoundGetter) GetEDS(_ context.Context, _ *share.Root, +) (*rsmt2d.ExtendedDataSquare, error) { + return nil, share.ErrNotFound +} + +func (m notFoundGetter) GetSharesByNamespace(_ context.Context, _ *share.Root, _ namespace.ID, +) (share.NamespacedShares, error) { + return nil, share.ErrNotFound +} + +func newStore(t *testing.T) *eds.Store { + t.Helper() + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + store, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + return store +} + +func createMocknet(t *testing.T, amount int) []libhost.Host { + t.Helper() + + net, err := mocknet.FullMeshConnected(amount) + require.NoError(t, err) + // get host and peer + return net.Hosts() +} + +func makeExchange(t *testing.T, getter share.Getter) (*eds.Store, *Client, *Server) { + t.Helper() + store := newStore(t) + hosts := createMocknet(t, 2) + + client, err := NewClient(DefaultParameters(), hosts[0]) + require.NoError(t, err) + server, err := NewServer(DefaultParameters(), hosts[1], store, getter) + require.NoError(t, err) + + return store, client, server +} diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index eaa0d1d9d2..8668dcff14 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -111,6 +111,10 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre } shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) + if errors.Is(err, share.ErrNotFound) { + srv.respondNotFoundError(stream) + return + } if err != nil { log.Errorw("server: retrieving shares", "err", err) srv.respondInternalError(stream) From da38644529e0eabe23bdaf4f7191cc08c5315c2e Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 14 Apr 2023 14:44:43 +0200 Subject: [PATCH 0542/1008] fix(modp2p): fix libp2p metrics (#2078) * Introduces unit tests for p2p module builds to avoid such issues in future * Reworks and cleans up resource manager registering with an actual fix * Removes libp2p metrics config * There is no need for users to configure the Prometheus port because of future #2007 * Moves WithP2PMetrics nodebuilder -> modp2p, because it needs access to private funcs --- cmd/flags_misc.go | 3 +- nodebuilder/init.go | 3 +- nodebuilder/p2p/config.go | 5 +- nodebuilder/p2p/host.go | 6 +++ nodebuilder/p2p/metrics.go | 94 ++++++++-------------------------- nodebuilder/p2p/module.go | 55 ++------------------ nodebuilder/p2p/module_test.go | 67 ++++++++++++++++++++++++ nodebuilder/p2p/resources.go | 83 ++++++++++++++++++++++++++++++ nodebuilder/settings.go | 8 --- nodebuilder/store.go | 3 +- 10 files changed, 189 insertions(+), 138 deletions(-) create mode 100644 nodebuilder/p2p/module_test.go create mode 100644 nodebuilder/p2p/resources.go diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 9ed66a63f2..f671840eab 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -21,6 +21,7 @@ import ( "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" + modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) var ( @@ -273,7 +274,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e if metricsEnabled, _ := cmd.Flags().GetBool(metricsFlag); !metricsEnabled { log.Error("--p2p.metrics used without --metrics being enabled") } else { - ctx = WithNodeOptions(ctx, nodebuilder.WithP2PMetrics()) + ctx = WithNodeOptions(ctx, modp2p.WithMetrics()) } } diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 2e7a415df7..2ccda0c19d 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -71,7 +71,8 @@ func Init(cfg Config, path string, tp node.Type) error { return nil } -// Reset removes all data from the datastore and dagstore directories. It leaves the keystore and config intact. +// Reset removes all data from the datastore and dagstore directories. It leaves the keystore and +// config intact. func Reset(path string, tp node.Type) error { path, err := storePath(path) if err != nil { diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 284f63e2a0..9f01513949 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -32,8 +32,6 @@ type Config struct { ConnManager connManagerConfig RoutingTableRefreshPeriod time.Duration - // Libp2p Metrics Configuration - Metrics MetricsConfig // Allowlist for IPColocation PubSub parameter, a list of string CIDRs IPColocationWhitelist []string } @@ -60,7 +58,6 @@ func DefaultConfig(tp node.Type) Config { PeerExchange: tp == node.Bridge || tp == node.Full, ConnManager: defaultConnManagerConfig(), RoutingTableRefreshPeriod: defaultRoutingRefreshPeriod, - Metrics: DefaultMetricsConfig(), } } @@ -82,5 +79,5 @@ func (cfg *Config) Validate() error { cfg.RoutingTableRefreshPeriod = defaultRoutingRefreshPeriod log.Warnf("routingTableRefreshPeriod is not valid. restoring to default value: %d", cfg.RoutingTableRefreshPeriod) } - return cfg.Metrics.Validate() + return nil } diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index 727ac81c9b..b3288e64cd 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -16,6 +16,7 @@ import ( "github.com/libp2p/go-libp2p/core/routing" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/conngater" + "github.com/prometheus/client_golang/prometheus" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -47,6 +48,10 @@ func host(params hostParams) (HostBase, error) { libp2p.DefaultMuxers, } + if params.Registry != nil { + opts = append(opts, libp2p.PrometheusRegisterer(params.Registry)) + } + // All node types except light (bridge, full) will enable NATService if params.Tp != node.Light { opts = append(opts, libp2p.EnableNATService()) @@ -79,6 +84,7 @@ type hostParams struct { ConnGater *conngater.BasicConnectionGater Bandwidth *metrics.BandwidthCounter ResourceManager network.ResourceManager + Registry prometheus.Registerer `optional:"true"` Tp node.Type } diff --git a/nodebuilder/p2p/metrics.go b/nodebuilder/p2p/metrics.go index 2a9f61aa9f..f9f525e5dc 100644 --- a/nodebuilder/p2p/metrics.go +++ b/nodebuilder/p2p/metrics.go @@ -4,60 +4,41 @@ import ( "context" "fmt" "net/http" - "strconv" "time" - "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p/core/network" - rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs" - ma "github.com/multiformats/go-multiaddr" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -const promAgentEndpoint = "/metrics" - -type MetricsConfig struct { - // Prometheus Agent http configuration - PrometheusAgentPort string -} - -// DefaultMetricsConfig returns default configuration for P2P subsystem. -func DefaultMetricsConfig() MetricsConfig { - return MetricsConfig{ - PrometheusAgentPort: "8890", - } +// WithMetrics option sets up native libp2p metrics up. +func WithMetrics() fx.Option { + return fx.Options( + fx.Provide(resourceManagerOpt(traceReporter)), + fx.Provide(prometheusRegisterer), + fx.Invoke(prometheusMetrics), + ) } -func (cfg *MetricsConfig) Validate() error { - if cfg.PrometheusAgentPort == "" { - return fmt.Errorf("p2p metrics: prometheus agent port cannot be empty") - } - - if _, err := strconv.Atoi(cfg.PrometheusAgentPort); err != nil { - return fmt.Errorf("p2p metrics: prometheus agent port must be a number") - } - - return nil -} +const ( + promAgentEndpoint = "/metrics" + promAgentPort = "8890" +) -// WithMetrics option sets up native libp2p metrics up -func WithMetrics(lifecycle fx.Lifecycle, cfg Config) error { - rcmgrObs.MustRegisterWith(prometheus.DefaultRegisterer) +// prometheusMetrics option sets up native libp2p metrics up +func prometheusMetrics(lifecycle fx.Lifecycle, registerer prometheus.Registerer) error { + rcmgrObs.MustRegisterWith(registerer) - reg := prometheus.DefaultRegisterer - registry := reg.(*prometheus.Registry) + registry := registerer.(*prometheus.Registry) mux := http.NewServeMux() - handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{Registry: reg}) + handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{Registry: registerer}) mux.Handle(promAgentEndpoint, handler) + // TODO(@Wondertan): Unify all the servers into one (See #2007) promHTTPServer := &http.Server{ - Addr: fmt.Sprintf(":%s", cfg.Metrics.PrometheusAgentPort), + Addr: fmt.Sprintf(":%s", promAgentPort), Handler: mux, ReadHeaderTimeout: 10 * time.Second, } @@ -66,12 +47,11 @@ func WithMetrics(lifecycle fx.Lifecycle, cfg Config) error { OnStart: func(ctx context.Context) error { go func() { if err := promHTTPServer.ListenAndServe(); err != nil { - log.Error("Error starting Prometheus metrics exporter http server") - panic(err) + log.Errorf("Error starting Prometheus metrics exporter http server: %s", err) } }() - log.Info("Prometheus agent started on :%s/%s", cfg.Metrics.PrometheusAgentPort, promAgentEndpoint) + log.Info("Prometheus agent started on :%s/%s", promAgentPort, promAgentEndpoint) return nil }, OnStop: func(ctx context.Context) error { @@ -81,36 +61,6 @@ func WithMetrics(lifecycle fx.Lifecycle, cfg Config) error { return nil } -func WithMonitoredResourceManager(nodeType node.Type, allowlist []ma.Multiaddr) (network.ResourceManager, error) { - str, err := rcmgrObs.NewStatsTraceReporter() - if err != nil { - return nil, err - } - - var monitoredRcmgr network.ResourceManager - - switch nodeType { - case node.Full, node.Bridge: - monitoredRcmgr, err = rcmgr.NewResourceManager( - rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits), - rcmgr.WithTraceReporter(str), - ) - - case node.Light: - limits := rcmgr.DefaultLimits - libp2p.SetDefaultServiceLimits(&limits) - - monitoredRcmgr, err = rcmgr.NewResourceManager( - rcmgr.NewFixedLimiter(limits.AutoScale()), - rcmgr.WithAllowlistedMultiaddrs(allowlist), - rcmgr.WithTraceReporter(str), - ) - default: - panic("invalid node type") - } - if err != nil { - return nil, err - } - - return monitoredRcmgr, err +func prometheusRegisterer() prometheus.Registerer { + return prometheus.NewRegistry() } diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index 0b4fc3379f..e7f60956c7 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -1,15 +1,8 @@ package p2p import ( - "context" - logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/metrics" - "github.com/libp2p/go-libp2p/core/network" - rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" - ma "github.com/multiformats/go-multiaddr" - madns "github.com/multiformats/go-multiaddr-dns" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -41,6 +34,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(metrics.NewBandwidthCounter), fx.Provide(newModule), fx.Invoke(Listen(cfg.ListenAddresses)), + fx.Provide(resourceManager), + fx.Provide(resourceManagerOpt(allowList)), ) switch tp { @@ -49,56 +44,14 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "p2p", baseComponents, fx.Provide(blockstoreFromEDSStore), - fx.Provide(func() (network.ResourceManager, error) { - return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.InfiniteLimits)) - }), + fx.Provide(infiniteResources), ) case node.Light: return fx.Module( "p2p", baseComponents, fx.Provide(blockstoreFromDatastore), - fx.Provide(func(ctx context.Context, bootstrappers Bootstrappers) (allowlist []ma.Multiaddr, err error) { - mutual, err := cfg.mutualPeers() - if err != nil { - return nil, err - } - - // TODO(@Wondertan): We should resolve their addresses only once, but currently - // we resolve it here and libp2p stuck does that as well internally - allowlist = make([]ma.Multiaddr, 0, len(bootstrappers)+len(mutual)) - for _, b := range bootstrappers { - for _, baddr := range b.Addrs { - resolved, err := madns.DefaultResolver.Resolve(ctx, baddr) - if err != nil { - log.Warnw("error resolving bootstrapper DNS", "addr", baddr.String(), "err", err) - continue - } - allowlist = append(allowlist, resolved...) - } - } - for _, m := range mutual { - for _, maddr := range m.Addrs { - resolved, err := madns.DefaultResolver.Resolve(ctx, maddr) - if err != nil { - log.Warnw("error resolving mutual peer DNS", "addr", maddr.String(), "err", err) - continue - } - allowlist = append(allowlist, resolved...) - } - } - - return allowlist, nil - }), - fx.Provide(func(ctx context.Context, allowlist []ma.Multiaddr) (network.ResourceManager, error) { - limits := rcmgr.DefaultLimits - libp2p.SetDefaultServiceLimits(&limits) - - return rcmgr.NewResourceManager( - rcmgr.NewFixedLimiter(limits.AutoScale()), - rcmgr.WithAllowlistedMultiaddrs(allowlist), - ) - }), + fx.Provide(autoscaleResources), ) default: panic("invalid node type") diff --git a/nodebuilder/p2p/module_test.go b/nodebuilder/p2p/module_test.go new file mode 100644 index 0000000000..cb7c945547 --- /dev/null +++ b/nodebuilder/p2p/module_test.go @@ -0,0 +1,67 @@ +package p2p + +import ( + "context" + "testing" + + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + + "github.com/celestiaorg/celestia-node/libs/keystore" + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +func testModule(tp node.Type) fx.Option { + cfg := DefaultConfig(tp) + // TODO(@Wondertan): Most of these can be deduplicated + // by moving Store into the modnode and introducing there a TestModNode module + // that testers would import + return fx.Options( + fx.NopLogger, + ConstructModule(tp, &cfg), + fx.Provide(context.Background), + fx.Supply(Private), + fx.Supply(Bootstrappers{}), + fx.Supply(tp), + fx.Provide(keystore.NewMapKeystore), + fx.Supply(fx.Annotate(ds_sync.MutexWrap(datastore.NewMapDatastore()), fx.As(new(datastore.Batching)))), + ) +} + +func TestModuleBuild(t *testing.T) { + var test = []struct { + tp node.Type + }{ + {tp: node.Bridge}, + {tp: node.Full}, + {tp: node.Light}, + } + + for _, tt := range test { + t.Run(tt.tp.String(), func(t *testing.T) { + app := fxtest.New(t, testModule(tt.tp)) + app.RequireStart() + app.RequireStop() + }) + } +} + +func TestModuleBuild_WithMetrics(t *testing.T) { + var test = []struct { + tp node.Type + }{ + {tp: node.Full}, + {tp: node.Bridge}, + {tp: node.Light}, + } + + for _, tt := range test { + t.Run(tt.tp.String(), func(t *testing.T) { + app := fxtest.New(t, testModule(tt.tp), WithMetrics()) + app.RequireStart() + app.RequireStop() + }) + } +} diff --git a/nodebuilder/p2p/resources.go b/nodebuilder/p2p/resources.go new file mode 100644 index 0000000000..371747463a --- /dev/null +++ b/nodebuilder/p2p/resources.go @@ -0,0 +1,83 @@ +package p2p + +import ( + "context" + + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/network" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" + rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs" + ma "github.com/multiformats/go-multiaddr" + madns "github.com/multiformats/go-multiaddr-dns" + "go.uber.org/fx" +) + +func resourceManager(params resourceManagerParams) (network.ResourceManager, error) { + return rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(params.Limits)) +} + +func infiniteResources() rcmgr.ConcreteLimitConfig { + return rcmgr.InfiniteLimits +} + +func autoscaleResources() rcmgr.ConcreteLimitConfig { + limits := rcmgr.DefaultLimits + libp2p.SetDefaultServiceLimits(&limits) + return limits.AutoScale() +} + +func allowList(ctx context.Context, cfg Config, bootstrappers Bootstrappers) (rcmgr.Option, error) { + mutual, err := cfg.mutualPeers() + if err != nil { + return nil, err + } + + // TODO(@Wondertan): We should resolve their addresses only once, but currently + // we resolve it here and libp2p stuck does that as well internally + allowlist := make([]ma.Multiaddr, 0, len(bootstrappers)+len(mutual)) + for _, b := range bootstrappers { + for _, baddr := range b.Addrs { + resolved, err := madns.DefaultResolver.Resolve(ctx, baddr) + if err != nil { + log.Warnw("error resolving bootstrapper DNS", "addr", baddr.String(), "err", err) + continue + } + allowlist = append(allowlist, resolved...) + } + } + for _, m := range mutual { + for _, maddr := range m.Addrs { + resolved, err := madns.DefaultResolver.Resolve(ctx, maddr) + if err != nil { + log.Warnw("error resolving mutual peer DNS", "addr", maddr.String(), "err", err) + continue + } + allowlist = append(allowlist, resolved...) + } + } + + return rcmgr.WithAllowlistedMultiaddrs(allowlist), nil +} + +func traceReporter() rcmgr.Option { + str, err := rcmgrObs.NewStatsTraceReporter() + if err != nil { + panic(err) // err is always nil as per sources + } + + return rcmgr.WithTraceReporter(str) +} + +type resourceManagerParams struct { + fx.In + + Limits rcmgr.ConcreteLimitConfig + Opts []rcmgr.Option `group:"rcmgr-opts"` +} + +func resourceManagerOpt(opt any) fx.Annotated { + return fx.Annotated{ + Group: "rcmgr-opts", + Target: opt, + } +} diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index f4775ce26b..3f947feba4 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -90,14 +90,6 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti return opts } -// WithP2PMetrics option enables native libp2p metrisc for node -func WithP2PMetrics() fx.Option { - return fx.Options( - fx.Decorate(p2p.WithMonitoredResourceManager), - fx.Invoke(p2p.WithMetrics), - ) -} - // initializeMetrics initializes the global meter provider. func initializeMetrics( ctx context.Context, diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 415e56bd8c..046dd41bbd 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -191,7 +191,8 @@ func blocksPath(base string) string { } func transientsPath(base string) string { - // we don't actually use the transients directory anymore, but it could be populated from previous versions. + // we don't actually use the transients directory anymore, but it could be populated from previous + // versions. return filepath.Join(base, "transients") } From 94cd2a6664a24336629caf12b7560cc7cb353f6e Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 14 Apr 2023 21:13:23 +0800 Subject: [PATCH 0543/1008] refactor(share/shrex): cleanup shrex logs (#2079) ## Overview Sort out logging level for shrex client / server logs. DEBUG: unimportant errors, that doesn't affect logic WARN: errors that could been induced by either party (client or server) ERROR: node malfunction that needs operator attention --- logs/logs.go | 2 ++ share/p2p/shrexeds/client.go | 14 +++++++------- share/p2p/shrexeds/params.go | 2 +- share/p2p/shrexeds/server.go | 24 ++++++++++++++---------- share/p2p/shrexnd/client.go | 16 +++++++++------- share/p2p/shrexnd/server.go | 17 +++++++++-------- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/logs/logs.go b/logs/logs.go index 6398548023..bedb74cc58 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -19,6 +19,8 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("basichost", "INFO") _ = logging.SetLogLevel("pubsub", "WARN") _ = logging.SetLogLevel("net/identify", "ERROR") + _ = logging.SetLogLevel("shrex/nd", "WARN") + _ = logging.SetLogLevel("shrex/eds", "WARN") } func SetDebugLogging() { diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index b7c3e28402..4a68d689dd 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -61,7 +61,7 @@ func (c *Client) RequestEDS( } } if err != p2p.ErrNotFound { - log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) + log.Warnw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) } return nil, err @@ -79,7 +79,7 @@ func (c *Client) doRequest( if dl, ok := ctx.Deadline(); ok { if err = stream.SetDeadline(dl); err != nil { - log.Debugf("error setting deadline: %s", err) + log.Debugw("error setting deadline: %s", "err", err) } } @@ -94,8 +94,7 @@ func (c *Client) doRequest( } err = stream.CloseWrite() if err != nil { - stream.Reset() //nolint:errcheck - return nil, fmt.Errorf("failed to close write on stream: %w", err) + log.Debugw("error closing write", "err", err) } // read and parse status from peer @@ -119,12 +118,13 @@ func (c *Client) doRequest( } return eds, nil case pb.Status_NOT_FOUND: - log.Debugf("client: peer %s couldn't serve eds %s with status %s", to.String(), dataHash.String(), resp.GetStatus()) return nil, p2p.ErrNotFound - case pb.Status_INVALID, pb.Status_INTERNAL: + case pb.Status_INVALID: + log.Debug("client: invalid request") + fallthrough + case pb.Status_INTERNAL: fallthrough default: - log.Errorf("request status %s returned for root %s", resp.Status.String(), dataHash.String()) return nil, p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexeds/params.go b/share/p2p/shrexeds/params.go index c6d8d83b8b..a6ed42bd83 100644 --- a/share/p2p/shrexeds/params.go +++ b/share/p2p/shrexeds/params.go @@ -10,7 +10,7 @@ import ( const protocolString = "/shrex/eds/v0.0.1" -var log = logging.Logger("shrex-eds") +var log = logging.Logger("shrex/eds") // Parameters is the set of parameters that must be configured for the shrex/eds protocol. type Parameters struct { diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index bae472871e..f7e68bb271 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -64,7 +64,7 @@ func (s *Server) handleStream(stream network.Stream) { // read request from stream to get the dataHash for store lookup req, err := s.readRequest(stream) if err != nil { - log.Errorw("server: reading request from stream", "err", err) + log.Warnw("server: reading request from stream", "err", err) stream.Reset() //nolint:errcheck return } @@ -73,6 +73,7 @@ func (s *Server) handleStream(stream network.Stream) { hash := share.DataHash(req.Hash) err = hash.Validate() if err != nil { + log.Debugw("server: invalid request", "err", err) stream.Reset() //nolint:errcheck return } @@ -89,41 +90,44 @@ func (s *Server) handleStream(stream network.Stream) { case errors.Is(err, eds.ErrNotFound): status = p2p_pb.Status_NOT_FOUND case err != nil: - log.Debugw("server: get car", "err", err) + log.Errorw("server: get car", "err", err) status = p2p_pb.Status_INTERNAL } // inform the client of our status err = s.writeStatus(status, stream) if err != nil { - log.Errorw("server: writing status to stream", "err", err) + log.Warnw("server: writing status to stream", "err", err) stream.Reset() //nolint:errcheck return } // if we cannot serve the EDS, we are already done if status != p2p_pb.Status_OK { - stream.Close() + err = stream.Close() + if err != nil { + log.Debugw("server: closing stream", "err", err) + } return } // start streaming the ODS to the client err = s.writeODS(edsReader, stream) if err != nil { - log.Errorw("server: writing ods to stream", "hash", hash.String(), "err", err) + log.Warnw("server: writing ods to stream", "hash", hash.String(), "err", err) stream.Reset() //nolint:errcheck return } err = stream.Close() if err != nil { - log.Errorw("server: closing stream", "err", err) + log.Debugw("server: closing stream", "err", err) } } func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) { err := stream.SetReadDeadline(time.Now().Add(s.params.ServerReadTimeout)) if err != nil { - log.Debug(err) + log.Debugw("server: set read deadline", "err", err) } req := new(p2p_pb.EDSRequest) @@ -133,7 +137,7 @@ func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) } err = stream.CloseRead() if err != nil { - log.Error(err) + log.Debugw("server: closing read", "err", err) } return req, nil @@ -142,7 +146,7 @@ func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error { err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { - log.Debug(err) + log.Debugw("server: set write deadline", "err", err) } resp := &p2p_pb.EDSResponse{Status: status} @@ -153,7 +157,7 @@ func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error func (s *Server) writeODS(edsReader io.Reader, stream network.Stream) error { err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { - log.Debug(err) + log.Debugw("server: set read deadline", "err", err) } odsReader, err := eds.ODSReader(edsReader) diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 467376605f..553c0ffdd9 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -69,7 +69,7 @@ func (c *Client) RequestND( } } if err != p2p.ErrNotFound { - log.Debugw("client-nd: peer returned err", "peer", peer, "err", err) + log.Warnw("client-nd: peer returned err", "peer", peer, "err", err) } return nil, err } @@ -101,7 +101,7 @@ func (c *Client) doRequest( err = stream.CloseWrite() if err != nil { - log.Debugf("client-nd: closing write side of the stream: %s", err) + log.Debugw("client-nd: closing write side of the stream", "err", err) } var resp pb.GetSharesByNamespaceResponse @@ -161,7 +161,7 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) if ok { err := stream.SetDeadline(deadline) if err != nil { - log.Debugf("client-nd: set write deadline: %s", err) + log.Debugw("client-nd: set write deadline", "err", err) } return } @@ -170,7 +170,7 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) if c.params.ServerWriteTimeout != 0 { err := stream.SetReadDeadline(time.Now().Add(c.params.ServerWriteTimeout)) if err != nil { - log.Debugf("client-nd: set read deadline: %s", err) + log.Debugw("client-nd: set read deadline", "err", err) } } @@ -178,7 +178,7 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) if c.params.ServerReadTimeout != 0 { err := stream.SetWriteDeadline(time.Now().Add(c.params.ServerReadTimeout)) if err != nil { - log.Debugf("client-nd: set write deadline: %s", err) + log.Debugw("client-nd: set write deadline", "err", err) } } } @@ -189,10 +189,12 @@ func statusToErr(code pb.StatusCode) error { return nil case pb.StatusCode_NOT_FOUND: return p2p.ErrNotFound - case pb.StatusCode_INTERNAL, pb.StatusCode_INVALID: + case pb.StatusCode_INVALID: + log.Debug("client-nd: invalid request") + fallthrough + case pb.StatusCode_INTERNAL: fallthrough default: - log.Errorf("client-nd: request status %s returned", code.String()) return p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 8668dcff14..5e37dec3e0 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -70,15 +70,17 @@ func (srv *Server) Stop(context.Context) error { } func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { + log.Debug("server: handling nd request") + err := stream.SetReadDeadline(time.Now().Add(srv.params.ServerReadTimeout)) if err != nil { - log.Debugf("server: setting read deadline: %s", err) + log.Debugw("server: setting read deadline", "err", err) } var req pb.GetSharesByNamespaceRequest _, err = serde.Read(stream, &req) if err != nil { - log.Errorw("server: reading request", "err", err) + log.Warnw("server: reading request", "err", err) stream.Reset() //nolint:errcheck return } @@ -86,12 +88,12 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre err = stream.CloseRead() if err != nil { - log.Debugf("server: closing read side of the stream: %s", err) + log.Debugw("server: closing read side of the stream", "err", err) } err = validateRequest(req) if err != nil { - log.Errorw("server: invalid request", "err", err) + log.Debugw("server: invalid request", "err", err) stream.Reset() //nolint:errcheck return } @@ -123,7 +125,6 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre resp := namespacedSharesToResponse(shares) srv.respond(stream, resp) - log.Debugw("server: handled request", "namespaceId", string(req.NamespaceId), "roothash", string(req.RootHash)) } // validateRequest checks correctness of the request @@ -181,17 +182,17 @@ func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNa func (srv *Server) respond(stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { err := stream.SetWriteDeadline(time.Now().Add(srv.params.ServerWriteTimeout)) if err != nil { - log.Debugf("server: seting write deadline: %s", err) + log.Debugw("server: seting write deadline", "err", err) } _, err = serde.Write(stream, resp) if err != nil { - log.Errorf("server: writing response: %s", err.Error()) + log.Warnw("server: writing response", "err", err) stream.Reset() //nolint:errcheck return } if err = stream.Close(); err != nil { - log.Errorf("server: closing stream: %s", err.Error()) + log.Debugw("server: closing stream", "err", err) } } From 6bd5adc3ec910631a80b7b67ce1e624c9426099a Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Sun, 16 Apr 2023 12:44:40 +0200 Subject: [PATCH 0544/1008] fix(modp2p): fix(modp2p): use Infof (#2081) --- nodebuilder/p2p/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/metrics.go b/nodebuilder/p2p/metrics.go index f9f525e5dc..3e6caf08ca 100644 --- a/nodebuilder/p2p/metrics.go +++ b/nodebuilder/p2p/metrics.go @@ -51,7 +51,7 @@ func prometheusMetrics(lifecycle fx.Lifecycle, registerer prometheus.Registerer) } }() - log.Info("Prometheus agent started on :%s/%s", promAgentPort, promAgentEndpoint) + log.Infof("Prometheus agent started on :%s/%s", promAgentPort, promAgentEndpoint) return nil }, OnStop: func(ctx context.Context) error { From a48312718f5947a869142673f667bfa30c585da9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 13:01:15 +0200 Subject: [PATCH 0545/1008] chore(deps): bump github.com/hashicorp/go-getter from 1.6.1 to 1.7.0 (#2014) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- go.mod | 8 +- go.sum | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 347 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 4c888ba85b..99b9d955e5 100644 --- a/go.mod +++ b/go.mod @@ -93,7 +93,7 @@ require ( github.com/StackExchange/wmi v1.2.1 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go v1.40.45 // indirect + github.com/aws/aws-sdk-go v1.44.122 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect @@ -176,7 +176,7 @@ require ( github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.6.1 // indirect + github.com/hashicorp/go-getter v1.7.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect @@ -237,7 +237,7 @@ require ( github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect @@ -290,7 +290,7 @@ require ( github.com/tendermint/tm-db v0.6.7 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect - github.com/ulikunitz/xz v0.5.8 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect diff --git a/go.sum b/go.sum index dbcc0457dc..2152c99493 100644 --- a/go.sum +++ b/go.sum @@ -21,36 +21,177 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= @@ -139,10 +280,9 @@ github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.40.45 h1:QN1nsY27ssD/JmW4s83qmSb+uL6DG4GmCDzjmJB4xUI= -github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= @@ -263,6 +403,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= @@ -404,8 +545,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakRE22OTI12k+2LkyY= github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= @@ -558,6 +701,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -576,6 +720,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -602,6 +747,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -618,6 +764,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -630,6 +777,11 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -639,14 +791,24 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -697,8 +859,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY= -github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= +github.com/hashicorp/go-getter v1.7.0 h1:bzrYP+qu/gMrL1au7/aDvkoOVGUJpeKBgbqRHACAFDY= +github.com/hashicorp/go-getter v1.7.0/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -719,7 +881,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -921,7 +1082,6 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -959,11 +1119,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -1336,8 +1496,9 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -1792,8 +1953,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= -github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -1842,6 +2003,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.0 h1:dlMC7aO8Wss1CxBq2I96kZ69Nh1ligzbs8UWOtq/AsA= @@ -2009,6 +2171,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= @@ -2023,6 +2186,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2085,16 +2249,26 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2108,9 +2282,22 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= @@ -2125,6 +2312,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2202,12 +2392,15 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2218,27 +2411,46 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2250,6 +2462,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2324,9 +2537,13 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2334,6 +2551,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -2365,6 +2584,35 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -2423,8 +2671,73 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -2455,11 +2768,27 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2474,6 +2803,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From 0c35091770006e46c08de918237a3c23aa12b482 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 11:11:44 +0000 Subject: [PATCH 0546/1008] chore(deps): bump mheap/github-action-required-labels from 3 to 4 (#2003) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 38c9d2820f..314f2fcb14 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -12,7 +12,7 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@v3 + - uses: mheap/github-action-required-labels@v4 with: mode: minimum count: 1 From 1517b8685cbf49b7c5ff45479a27c7f37681d1f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 11:31:49 +0000 Subject: [PATCH 0547/1008] chore(deps): bump alpine from 3.17.2 to 3.17.3 (#2004) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ef34be4198..214003dc08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ COPY . . RUN make build && make cel-key -FROM docker.io/alpine:3.17.2 +FROM docker.io/alpine:3.17.3 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From fc026aa793fc3f3bb0d994b124e36c377254737e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Apr 2023 11:45:47 +0000 Subject: [PATCH 0548/1008] chore(deps): bump go.uber.org/multierr from 1.9.0 to 1.11.0 (#1998) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 99b9d955e5..88a242f21c 100644 --- a/go.mod +++ b/go.mod @@ -304,7 +304,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.15.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect golang.org/x/mod v0.8.0 // indirect diff --git a/go.sum b/go.sum index 2152c99493..c110b6857c 100644 --- a/go.sum +++ b/go.sum @@ -2083,8 +2083,8 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= From 5a17eb5ad1c38cd4e290d81138c28d765d4f6680 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 17 Apr 2023 11:31:23 +0200 Subject: [PATCH 0549/1008] refactor(nodebuilder): Use `my_celes_key` as default if no `--keyring.accname` is specified (#2084) Resolves some confusion for users as previous behaviour would pick the first key in the keyring if no keyring.accname was specified and several keys were present in the keyring. --- nodebuilder/init.go | 5 ++--- nodebuilder/state/keyring.go | 22 +++++----------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 2ccda0c19d..2cabfc8abd 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -15,10 +15,9 @@ import ( "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -const defaultKeyName = "my_celes_key" - // Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under // 'path'. func Init(cfg Config, path string, tp node.Type) error { @@ -222,6 +221,6 @@ func generateKeys(cfg Config, ksPath string) error { // generateNewKey generates and returns a new key on the given keyring called // "my_celes_key". func generateNewKey(ring keyring.Keyring) (*keyring.Record, string, error) { - return ring.NewMnemonic(defaultKeyName, keyring.English, sdk.GetConfig().GetFullBIP44Path(), + return ring.NewMnemonic(state.DefaultAccountName, keyring.English, sdk.GetConfig().GetFullBIP44Path(), keyring.DefaultBIP39Passphrase, hd.Secp256k1) } diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go index 277bce2fd0..5aeaff69e2 100644 --- a/nodebuilder/state/keyring.go +++ b/nodebuilder/state/keyring.go @@ -1,8 +1,6 @@ package state import ( - "fmt" - kr "github.com/cosmos/cosmos-sdk/crypto/keyring" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" @@ -11,6 +9,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) +const DefaultAccountName = "my_celes_key" + // KeyringSigner constructs a new keyring signer. // NOTE: we construct keyring signer before constructing node for easier UX // as having keyring-backend set to `file` prompts user for password. @@ -26,22 +26,10 @@ func KeyringSigner(cfg Config, ks keystore.Keystore, net p2p.Network) (*apptypes } info = keyInfo } else { - // check if key exists for signer - keys, err := ring.List() - if err != nil { - return nil, err - } - // if no key was found in keystore path, generate new key for node - if len(keys) == 0 { - log.Errorw("no keys found in path", "path", ks.Path(), "keyring backend", - cfg.KeyringBackend) - return nil, fmt.Errorf("no keys found in path %s using keyring backend %s", ks.Path(), - cfg.KeyringBackend) - } - // if one or more keys are present and no keyringAccName was given, use the first key in list - keyInfo, err := ring.Key(keys[0].Name) + // use default key + keyInfo, err := ring.Key(DefaultAccountName) if err != nil { - log.Errorw("could not access key in keyring", "name", keys[0].Name) + log.Errorw("could not access key in keyring", "name", DefaultAccountName) return nil, err } info = keyInfo From abc3897257c00636fb83f63482aab33a3502848f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 17 Apr 2023 12:11:05 +0200 Subject: [PATCH 0550/1008] fix(modp2p): disable metrics on libp2p by default (#2082) So metrics were always enabled, even when we didn't ask libp2p. This PR changes that, so they are permanently disabled unless --p2p.metrics are given. --- nodebuilder/p2p/host.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index b3288e64cd..e55cb65d1f 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -50,6 +50,8 @@ func host(params hostParams) (HostBase, error) { if params.Registry != nil { opts = append(opts, libp2p.PrometheusRegisterer(params.Registry)) + } else { + opts = append(opts, libp2p.DisableMetrics()) } // All node types except light (bridge, full) will enable NATService From c4962b846184ee18333ce835df2e7745a5925663 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 17 Apr 2023 14:07:20 +0200 Subject: [PATCH 0551/1008] fix: removing skip from shrexeds tests and updating them to current logic (#2075) @walldiss pointed out that there are several tests in shrexeds that were being skipped. This was from the removal of retry logic inside the protocol itself, but now I have unskipped the tests and adapted the logic --- share/p2p/shrexeds/exchange_test.go | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 31d017fa13..6bd971c535 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -46,7 +46,6 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is unavailable initially, but is found after multiple requests t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { - t.Skip() storageDelay := time.Second eds := share.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) @@ -56,33 +55,21 @@ func TestExchange_RequestEDS(t *testing.T) { // require.NoError(t, err) }() - now := time.Now() requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), server.host.ID()) - finished := time.Now() + assert.ErrorIs(t, err, p2p.ErrNotFound) + assert.Nil(t, requestedEDS) - assert.Greater(t, finished.Sub(now), storageDelay) + time.Sleep(storageDelay * 2) + requestedEDS, err = client.RequestEDS(ctx, dah.Hash(), server.host.ID()) assert.NoError(t, err) assert.Equal(t, eds.Flattened(), requestedEDS.Flattened()) }) // Testcase: Invalid request excludes peer from round-robin, stopping request t.Run("EDS_InvalidRequest", func(t *testing.T) { - t.Skip() dataHash := []byte("invalid") requestedEDS, err := client.RequestEDS(ctx, dataHash, server.host.ID()) - assert.ErrorIs(t, err, p2p.ErrInvalidResponse) - assert.Nil(t, requestedEDS) - }) - - // Testcase: Valid request, which server cannot serve, waits forever - t.Run("EDS_ValidTimeout", func(t *testing.T) { - t.Skip() - timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) - t.Cleanup(cancel) - eds := share.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) - requestedEDS, err := client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) - assert.ErrorIs(t, err, timeoutCtx.Err()) + assert.ErrorContains(t, err, "stream reset") assert.Nil(t, requestedEDS) }) From be3a1655fb70e58af44cf432dccee4e0bd992f19 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 17 Apr 2023 15:40:43 +0200 Subject: [PATCH 0552/1008] deps: bump go-header (#2088) Tested multiple times on high latency connections, and it fixes #2006 and fixes #2032 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 88a242f21c..76855c1f1c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.2 github.com/celestiaorg/go-fraud v0.1.0 - github.com/celestiaorg/go-header v0.2.5 + github.com/celestiaorg/go-header v0.2.6 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.15.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index c110b6857c..c0579d3d53 100644 --- a/go.sum +++ b/go.sum @@ -350,8 +350,8 @@ github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPc github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= -github.com/celestiaorg/go-header v0.2.5 h1:TV1EybWjRRJfYc8Wf5UFVytGxEQJdPdNbVEfenUVXzo= -github.com/celestiaorg/go-header v0.2.5/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= +github.com/celestiaorg/go-header v0.2.6 h1:f1Mlyu+EfDpkuzO3SWU5dow+ga2vLQ7hNuvsOe//z64= +github.com/celestiaorg/go-header v0.2.6/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= From b8c24bfd76784b4c0f1c2d19d67d0cd3776af556 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 17 Apr 2023 16:35:32 +0200 Subject: [PATCH 0553/1008] feat(cmd): Add `config-remove` and `config-update` commands to CLI (#2068) This PR supersedes #1481 as it was outdated (my fault for not addressing earlier) and also simplifies the `config-update` command. It introduces two new commands: * `config-remove` which will delete a node's config * `config-update` which will update an outdated config by loading it, applying any newly added fields, and saving it back in the config path. There is one unfortunate caveat which is that if a newly added field is merged into the old config, some old custom values from that field may be overwritten which is why I included a prompt to the user to ensure their old custom values were preserved. Custom values from other fields that were unchanged in the config are preserved however. If there's time, I will investigate how to preserve custom values of a specific field that has been upgraded. Credit to @dixitaniket for implementing this feature. Closes #1414 --------- Co-authored-by: Hlib Kanunnikov --- cmd/celestia/bridge.go | 2 + cmd/celestia/full.go | 2 + cmd/celestia/light.go | 2 + cmd/config.go | 44 ++++++++++++++++ go.mod | 1 + go.sum | 2 + nodebuilder/config.go | 72 ++++++++++++++++++++++++++ nodebuilder/config_test.go | 102 +++++++++++++++++++++++++++++++++++++ 8 files changed, 227 insertions(+) create mode 100644 cmd/config.go diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index 70025e115a..fb5066e5d4 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -32,6 +32,8 @@ func init() { cmdnode.Start(flags...), cmdnode.AuthCmd(flags...), cmdnode.ResetStore(flags...), + cmdnode.RemoveConfigCmd(flags...), + cmdnode.UpdateConfigCmd(flags...), ) } diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 1f25a0a737..912de0bca8 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -36,6 +36,8 @@ func init() { cmdnode.Start(flags...), cmdnode.AuthCmd(flags...), cmdnode.ResetStore(flags...), + cmdnode.RemoveConfigCmd(flags...), + cmdnode.UpdateConfigCmd(flags...), ) } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 07cff91435..9c63945445 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -36,6 +36,8 @@ func init() { cmdnode.Start(flags...), cmdnode.AuthCmd(flags...), cmdnode.ResetStore(flags...), + cmdnode.RemoveConfigCmd(flags...), + cmdnode.UpdateConfigCmd(flags...), ) } diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000000..4a2d322adf --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + + "github.com/celestiaorg/celestia-node/nodebuilder" +) + +func RemoveConfigCmd(fsets ...*flag.FlagSet) *cobra.Command { + cmd := &cobra.Command{ + Use: "config-remove", + Short: "Deletes the node's config", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + return nodebuilder.RemoveConfig(StorePath(ctx)) + }, + } + + for _, set := range fsets { + cmd.Flags().AddFlagSet(set) + } + return cmd +} + +func UpdateConfigCmd(fsets ...*flag.FlagSet) *cobra.Command { + cmd := &cobra.Command{ + Use: "config-update", + Short: "Updates the node's outdated config", + Long: "Updates the node's outdated config with default values from newly-added fields. Check the config " + + " afterwards to ensure all old custom values were preserved.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + return nodebuilder.UpdateConfig(NodeType(ctx), StorePath(ctx)) + }, + } + + for _, set := range fsets { + cmd.Flags().AddFlagSet(set) + } + return cmd +} diff --git a/go.mod b/go.mod index 76855c1f1c..719ded3e9c 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/imdario/mergo v0.3.15 github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 diff --git a/go.sum b/go.sum index c0579d3d53..aea97478f3 100644 --- a/go.sum +++ b/go.sum @@ -917,6 +917,8 @@ github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= diff --git a/nodebuilder/config.go b/nodebuilder/config.go index eda59c25b7..d4df75d2f6 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -5,7 +5,9 @@ import ( "os" "github.com/BurntSushi/toml" + "github.com/imdario/mergo" + "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/gateway" @@ -80,6 +82,76 @@ func LoadConfig(path string) (*Config, error) { return &cfg, cfg.Decode(f) } +// RemoveConfig removes the Config from the given store path. +func RemoveConfig(path string) (err error) { + path, err = storePath(path) + if err != nil { + return + } + + flock, err := fslock.Lock(lockPath(path)) + if err != nil { + if err == fslock.ErrLocked { + err = ErrOpened + } + return + } + defer flock.Unlock() //nolint: errcheck + + return removeConfig(configPath(path)) +} + +// removeConfig removes Config from the given 'path'. +func removeConfig(path string) error { + return os.Remove(path) +} + +// UpdateConfig loads the node's config and applies new values +// from the default config of the given node type, saving the +// newly updated config into the node's config path. +func UpdateConfig(tp node.Type, path string) (err error) { + path, err = storePath(path) + if err != nil { + return + } + + flock, err := fslock.Lock(lockPath(path)) + if err != nil { + if err == fslock.ErrLocked { + err = ErrOpened + } + return + } + defer flock.Unlock() //nolint: errcheck + + newCfg := DefaultConfig(tp) + + cfgPath := configPath(path) + cfg, err := LoadConfig(cfgPath) + if err != nil { + return + } + + cfg, err = updateConfig(cfg, newCfg) + if err != nil { + return + } + + // save the updated config + err = removeConfig(cfgPath) + if err != nil { + return + } + return SaveConfig(cfgPath, cfg) +} + +// updateConfig merges new values from the new config into the old +// config, returning the updated old config. +func updateConfig(oldCfg *Config, newCfg *Config) (*Config, error) { + err := mergo.Merge(oldCfg, newCfg, mergo.WithOverrideEmptySlice) + return oldCfg, err +} + // TODO(@Wondertan): We should have a description for each field written into w, // so users can instantly understand purpose of each field. Ideally, we should have a utility // program to parse comments from actual sources(*.go files) and generate docs from comments. diff --git a/nodebuilder/config_test.go b/nodebuilder/config_test.go index 202a54a848..db9af3a64d 100644 --- a/nodebuilder/config_test.go +++ b/nodebuilder/config_test.go @@ -4,6 +4,7 @@ import ( "bytes" "testing" + "github.com/BurntSushi/toml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,3 +34,104 @@ func TestConfigWriteRead(t *testing.T) { }) } } + +// TestUpdateConfig tests that updating an outdated config +// using a new default config applies the correct values and +// preserves old custom values. +func TestUpdateConfig(t *testing.T) { + cfg := new(Config) + _, err := toml.Decode(outdatedConfig, cfg) + require.NoError(t, err) + + newCfg := DefaultConfig(node.Light) + // ensure this config field is not filled in the outdated config + require.NotEqual(t, newCfg.Share.PeerManagerParams, cfg.Share.PeerManagerParams) + + cfg, err = updateConfig(cfg, newCfg) + require.NoError(t, err) + // ensure this config field is now set after updating the config + require.Equal(t, newCfg.Share.PeerManagerParams, cfg.Share.PeerManagerParams) + // ensure old custom values were not changed + require.Equal(t, "thisshouldnthavechanged", cfg.State.KeyringAccName) + require.Equal(t, "7979", cfg.RPC.Port) + require.True(t, cfg.Gateway.Enabled) +} + +// outdatedConfig is an outdated config from a light node +var outdatedConfig = ` +[Core] + IP = "0.0.0.0" + RPCPort = "0" + GRPCPort = "0" + +[State] + KeyringAccName = "thisshouldnthavechanged" + KeyringBackend = "test" + +[P2P] + ListenAddresses = ["/ip4/0.0.0.0/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", "/ip4/0.0.0.0/tcp/2121", +"/ip6/::/tcp/2121"] + AnnounceAddresses = [] + NoAnnounceAddresses = ["/ip4/0.0.0.0/udp/2121/quic-v1", "/ip4/127.0.0.1/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", +"/ip4/0.0.0.0/tcp/2121", "/ip4/127.0.0.1/tcp/2121", "/ip6/::/tcp/2121"] + MutualPeers = [] + PeerExchange = false + RoutingTableRefreshPeriod = "1m0s" + [P2P.ConnManager] + Low = 50 + High = 100 + GracePeriod = "1m0s" + [P2P.Metrics] + PrometheusAgentPort = "8890" + +[RPC] + Address = "0.0.0.0" + Port = "7979" + +[Gateway] + Address = "0.0.0.0" + Port = "26659" + Enabled = true + +[Share] + PeersLimit = 5 + DiscoveryInterval = "30s" + AdvertiseInterval = "30s" + UseShareExchange = true + [Share.ShrExEDSParams] + ServerReadTimeout = "5s" + ServerWriteTimeout = "1m0s" + HandleRequestTimeout = "1m0s" + ConcurrencyLimit = 10 + BufferSize = 32768 + [Share.ShrExNDParams] + ServerReadTimeout = "5s" + ServerWriteTimeout = "2m35s" + HandleRequestTimeout = "1m0s" + ConcurrencyLimit = 10 + +[Header] + TrustedHash = "" + TrustedPeers = [] + [Header.Store] + StoreCacheSize = 4096 + IndexCacheSize = 16384 + WriteBatchSize = 2048 + [Header.Syncer] + TrustingPeriod = "168h0m0s" + [Header.Server] + WriteDeadline = "8s" + ReadDeadline = "1m0s" + RangeRequestTimeout = "10s" + [Header.Client] + MaxHeadersPerRangeRequest = 64 + RangeRequestTimeout = "8s" + TrustedPeersRequestTimeout = "300ms" + +[DASer] + SamplingRange = 100 + ConcurrencyLimit = 16 + BackgroundStoreInterval = "10m0s" + SampleFrom = 1 + SampleTimeout = "4m0s" +` From bbe68bba7d3fe233b1b3e0afa08ac54681d13b1f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 18 Apr 2023 10:24:10 +0200 Subject: [PATCH 0554/1008] refactor(nodebuiler): introduce ServiceBreaker and update FX (#2091) This PR solves two problems: * Updates FX and fixes issues we observed in [the PR](https://github.com/celestiaorg/celestia-node/pull/1801) * Fixes https://github.com/celestiaorg/celestia-node/issues/2041 Surprisingly, those two problems were related, so I decided to fix them once and for all. The issue with FX happens after [this change](https://github.com/uber-go/fx/pull/983). The outcome of this change is summarized: > In other words, lifecycle hook annotations can no longer pull in extra > dependencies outside of things on which > the annotated constructor is dependent, results that the annotated > constructor provides, context.Context > object which is injected by Lifecycle, and the Lifecycle object itself. Our current code does pull extra dependencies in the service having `modfraud.Lifecycle` on them, like [DASer](https://github.com/celestiaorg/celestia-node/blob/main/nodebuilder/das/module.go#L47). Specifically, it pulls FraudService, and this is no longer allowed. This forces us to rewrite the fraud lifecycling and here is the solution, which additionally satisfies #2041. This also unblocks https://github.com/celestiaorg/celestia-node/issues/2040, which is now implemented in https://github.com/celestiaorg/go-fraud/pull/1 I tried to split FX update and the refactor into two diff PRs. However, the solution does not work with the old FX version, so we have to couple those. The chain here is that I needed a new version of FX that fixes `OnStart/OnStop` hooks, and updating created the whole story. The PR that does clean-ups basing on new version of FX will com right after. --- go.mod | 4 +- go.sum | 8 +-- nodebuilder/das/constructors.go | 15 +++++- nodebuilder/das/module.go | 14 ++---- nodebuilder/fraud/lifecycle.go | 79 ++++++++++++++++++++++-------- nodebuilder/header/constructors.go | 17 ++++++- nodebuilder/header/module.go | 17 +++---- nodebuilder/node.go | 1 - nodebuilder/state/core.go | 14 +++++- nodebuilder/state/module.go | 13 ++--- 10 files changed, 122 insertions(+), 60 deletions(-) diff --git a/go.mod b/go.mod index 719ded3e9c..8a05b6dd72 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.34.0 go.opentelemetry.io/otel/trace v1.13.0 go.opentelemetry.io/proto/otlp v0.19.0 - go.uber.org/fx v1.18.2 + go.uber.org/fx v1.19.2 golang.org/x/crypto v0.7.0 golang.org/x/sync v0.1.0 golang.org/x/text v0.8.0 @@ -304,7 +304,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.15.0 // indirect + go.uber.org/dig v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect diff --git a/go.sum b/go.sum index aea97478f3..9c542114b6 100644 --- a/go.sum +++ b/go.sum @@ -2069,10 +2069,10 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= -go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= -go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= +go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= +go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= +go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index 40341b686a..18f6962f40 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -11,7 +11,9 @@ import ( "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" + modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -43,6 +45,15 @@ func newDASer( fraudServ fraud.Service, bFn shrexsub.BroadcastFn, options ...das.Option, -) (*das.DASer, error) { - return das.NewDASer(da, hsub, store, batching, fraudServ, bFn, options...) +) (*das.DASer, *modfraud.ServiceBreaker[*das.DASer], error) { + ds, err := das.NewDASer(da, hsub, store, batching, fraudServ, bFn, options...) + if err != nil { + return nil, nil, err + } + + return ds, &modfraud.ServiceBreaker[*das.DASer]{ + Service: ds, + FraudServ: fraudServ, + FraudType: byzantine.BadEncoding, + }, nil } diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 59e8d36f69..61c935fd40 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -5,12 +5,9 @@ import ( "go.uber.org/fx" - "github.com/celestiaorg/go-fraud" - "github.com/celestiaorg/celestia-node/das" - fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) func ConstructModule(tp node.Type, cfg *Config) fx.Option { @@ -44,12 +41,11 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents, fx.Provide(fx.Annotate( newDASer, - fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, das *das.DASer) error { - return fraudServ.Lifecycle(startCtx, ctx, byzantine.BadEncoding, fservice, - das.Start, das.Stop) + fx.OnStart(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*das.DASer]) error { + return breaker.Start(ctx) }), - fx.OnStop(func(ctx context.Context, das *das.DASer) error { - return das.Stop(ctx) + fx.OnStop(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*das.DASer]) error { + return breaker.Stop(ctx) }), )), // Module is needed for the RPC handler diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go index 6db7bc7266..24ed402f5d 100644 --- a/nodebuilder/fraud/lifecycle.go +++ b/nodebuilder/fraud/lifecycle.go @@ -2,41 +2,78 @@ package fraud import ( "context" - "time" + "fmt" "github.com/ipfs/go-datastore" "github.com/celestiaorg/go-fraud" ) -// Lifecycle controls the lifecycle of service depending on fraud proofs. -// It starts the service only if no fraud-proof exists and stops the service automatically -// if a proof arrives after the service was started. -func Lifecycle( - startCtx, lifecycleCtx context.Context, - p fraud.ProofType, - fraudServ fraud.Service, - start, stop func(context.Context) error, -) error { - proofs, err := fraudServ.Get(startCtx, p) +// service defines minimal interface with service lifecycle methods +type service interface { + Start(context.Context) error + Stop(context.Context) error +} + +// ServiceBreaker wraps any service with fraud proof subscription of a specific type. +// If proof happens the service is Stopped automatically. +// TODO(@Wondertan): Support multiple fraud types. +type ServiceBreaker[S service] struct { + Service S + FraudType fraud.ProofType + FraudServ fraud.Service + + ctx context.Context + cancel context.CancelFunc + sub fraud.Subscription +} + +// Start starts the inner service if there are no fraud proofs stored. +// Subscribes for fraud and stops the service whenever necessary. +func (breaker *ServiceBreaker[S]) Start(ctx context.Context) error { + proofs, err := breaker.FraudServ.Get(ctx, breaker.FraudType) switch err { default: - return err + return fmt.Errorf("getting proof(%s): %w", breaker.FraudType, err) case nil: return &fraud.ErrFraudExists{Proof: proofs} case datastore.ErrNotFound: } - err = start(startCtx) + + err = breaker.Service.Start(ctx) if err != nil { return err } - // handle incoming Fraud Proofs - go fraud.OnProof(lifecycleCtx, fraudServ, p, func(fraud.Proof) { - ctx, cancel := context.WithTimeout(lifecycleCtx, time.Minute) - defer cancel() - if err := stop(ctx); err != nil { - log.Error(err) - } - }) + + breaker.sub, err = breaker.FraudServ.Subscribe(breaker.FraudType) + if err != nil { + return fmt.Errorf("subscribing for proof(%s): %w", breaker.FraudType, err) + } + + breaker.ctx, breaker.cancel = context.WithCancel(context.Background()) + go breaker.awaitProof() return nil } + +// Stop stops the service and cancels subscription. +func (breaker *ServiceBreaker[S]) Stop(ctx context.Context) error { + if breaker.ctx.Err() != nil { + // short circuit if the service was already stopped + return nil + } + + breaker.sub.Cancel() + breaker.cancel() + return breaker.Service.Stop(ctx) +} + +func (breaker *ServiceBreaker[S]) awaitProof() { + _, err := breaker.sub.Proof(breaker.ctx) + if err != nil { + return + } + + if err := breaker.Stop(breaker.ctx); err != nil && err != context.Canceled { + log.Errorw("stopping service: %s", err.Error()) + } +} diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 4650ed77d9..e6bb6d6c48 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -9,13 +9,16 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" + libfraud "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/p2p" "github.com/celestiaorg/go-header/store" "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" + modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) // newP2PServer constructs a new ExchangeServer using the given Network as a protocolID prefix. @@ -72,11 +75,21 @@ func newP2PExchange(cfg Config) func( // newSyncer constructs new Syncer for headers. func newSyncer( ex libhead.Exchange[*header.ExtendedHeader], + fservice libfraud.Service, store InitStore, sub libhead.Subscriber[*header.ExtendedHeader], opts []sync.Options, -) (*sync.Syncer[*header.ExtendedHeader], error) { - return sync.NewSyncer[*header.ExtendedHeader](ex, store, sub, opts...) +) (*sync.Syncer[*header.ExtendedHeader], *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], error) { + syncer, err := sync.NewSyncer[*header.ExtendedHeader](ex, store, sub, opts...) + if err != nil { + return nil, nil, err + } + + return syncer, &modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]]{ + Service: syncer, + FraudType: byzantine.BadEncoding, + FraudServ: fservice, + }, nil } // InitStore is a type representing initialized header store. diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 97939b7101..2b94feaec8 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -8,7 +8,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "go.uber.org/fx" - "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/p2p" "github.com/celestiaorg/go-header/store" @@ -18,7 +17,6 @@ import ( modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) var log = logging.Logger("module/header") @@ -73,15 +71,16 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(fx.Annotate( newSyncer, fx.OnStart(func( - startCtx, ctx context.Context, - fservice fraud.Service, - syncer *sync.Syncer[*header.ExtendedHeader], + ctx context.Context, + breaker *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], ) error { - return modfraud.Lifecycle(startCtx, ctx, byzantine.BadEncoding, fservice, - syncer.Start, syncer.Stop) + return breaker.Start(ctx) }), - fx.OnStop(func(ctx context.Context, syncer *sync.Syncer[*header.ExtendedHeader]) error { - return syncer.Stop(ctx) + fx.OnStop(func( + ctx context.Context, + breaker *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], + ) error { + return breaker.Stop(ctx) }), )), fx.Provide(fx.Annotate( diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 6968379c02..fff79cfc7f 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -149,7 +149,6 @@ func (n *Node) Stop(ctx context.Context) error { func newNode(opts ...fx.Option) (*Node, error) { node := new(Node) app := fx.New( - fx.NopLogger, fx.Populate(node), fx.Options(opts...), ) diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index 052b6eef0c..a3eb7f7b6d 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -2,10 +2,13 @@ package state import ( apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + libfraud "github.com/celestiaorg/go-fraud" "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/core" + modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/state" ) @@ -15,6 +18,13 @@ func coreAccessor( corecfg core.Config, signer *apptypes.KeyringSigner, sync *sync.Syncer[*header.ExtendedHeader], -) *state.CoreAccessor { - return state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) + fraudServ libfraud.Service, +) (*state.CoreAccessor, *modfraud.ServiceBreaker[*state.CoreAccessor]) { + ca := state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) + + return ca, &modfraud.ServiceBreaker[*state.CoreAccessor]{ + Service: ca, + FraudType: byzantine.BadEncoding, + FraudServ: fraudServ, + } } diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index 18c1eaaf3b..24305dabe1 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -6,11 +6,8 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/go-fraud" - - fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/state" ) @@ -27,11 +24,11 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Error(cfgErr), fx.Provide(fx.Annotate( coreAccessor, - fx.OnStart(func(startCtx, ctx context.Context, fservice fraud.Service, ca *state.CoreAccessor) error { - return fraudServ.Lifecycle(startCtx, ctx, byzantine.BadEncoding, fservice, ca.Start, ca.Stop) + fx.OnStart(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*state.CoreAccessor]) error { + return breaker.Start(ctx) }), - fx.OnStop(func(ctx context.Context, ca *state.CoreAccessor) error { - return ca.Stop(ctx) + fx.OnStop(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*state.CoreAccessor]) error { + return breaker.Stop(ctx) }), )), // the module is needed for the handler From 552e811605472eaf174c1afc07d01044c53a673c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 19 Apr 2023 19:38:32 +0800 Subject: [PATCH 0555/1008] feat(daser): introduce retry logic for daser (#1860) ## Overview DASer jobs now have `job_type` field that identifies purpose of the job. There are 3 possible types of jobs: - catchup - recent - retry Introduce new type of jobs for DASer - `retry`. `retry` jobs will be launched only if there is enough concurrency slots after running `recent` jobs and `catchup` jobs. Meaning DASer will start retry with lowest priority, only when catchup is done and recent header are synced without falling behind. Also includes minor simplifications for affected code. Resolves https://github.com/celestiaorg/celestia-node/issues/1782, https://github.com/celestiaorg/celestia-node/issues/2027 ## Checklist - [ ] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Ryan Co-authored-by: Hlib Kanunnikov --- api/docgen/exampledata/samplingStats.json | 102 ++++--------- das/checkpoint.go | 12 +- das/checkpoint_test.go | 10 +- das/coordinator.go | 6 +- das/coordinator_test.go | 29 ++-- das/daser_test.go | 14 +- das/metrics.go | 24 +++- das/state.go | 165 +++++++++++++--------- das/state_test.go | 48 ++++--- das/stats.go | 16 ++- das/worker.go | 58 ++++---- 11 files changed, 255 insertions(+), 229 deletions(-) diff --git a/api/docgen/exampledata/samplingStats.json b/api/docgen/exampledata/samplingStats.json index 77bf5203e1..f6efe7d6da 100644 --- a/api/docgen/exampledata/samplingStats.json +++ b/api/docgen/exampledata/samplingStats.json @@ -1,90 +1,46 @@ { - "head_of_sampled_chain": 27499, - "head_of_catchup": 29101, - "network_head_height": 30483, + "head_of_sampled_chain": 1092, + "head_of_catchup": 34101, + "network_head_height": 470292, "workers": [ { - "current": 28806, - "from": 28802, - "to": 28901 + "job_type": "catchup", + "current": 1093, + "from": 1002, + "to": 1101 }, { - "current": 28906, - "from": 28902, - "to": 29001 + "job_type": "catchup", + "current": 33343, + "from": 33302, + "to": 33401 }, { - "current": 27794, - "from": 27702, - "to": 27801 + "job_type": "catchup", + "current": 34047, + "from": 34002, + "to": 34101 }, { - "current": 28191, - "from": 28102, - "to": 28201 + "job_type": "catchup", + "current": 1327, + "from": 1302, + "to": 1401 }, { - "current": 28420, - "from": 28402, - "to": 28501 + "job_type": "catchup", + "current": 1197, + "from": 1102, + "to": 1201 }, { - "current": 28334, - "from": 28302, - "to": 28401 - }, - { - "current": 27691, - "from": 27602, - "to": 27701 - }, - { - "current": 27889, - "from": 27802, - "to": 27901 - }, - { - "current": 27990, - "from": 27902, - "to": 28001 - }, - { - "current": 28293, - "from": 28202, - "to": 28301 - }, - { - "current": 28092, - "from": 28002, - "to": 28101 - }, - { - "current": 29004, - "from": 29002, - "to": 29101 - }, - { - "current": 28708, - "from": 28702, - "to": 28801 - }, - { - "current": 28513, - "from": 28502, - "to": 28601 - }, - { - "current": 27500, - "from": 27402, - "to": 27501 - }, - { - "current": 28615, - "from": 28602, - "to": 28701 + "job_type": "catchup", + "current": 1408, + "from": 1402, + "to": 1501 } ], - "concurrency": 16, + "concurrency": 6, "catch_up_done": false, "is_running": true -} \ No newline at end of file +} diff --git a/das/checkpoint.go b/das/checkpoint.go index 860c72cb35..59685de413 100644 --- a/das/checkpoint.go +++ b/das/checkpoint.go @@ -7,7 +7,7 @@ import ( type checkpoint struct { SampleFrom uint64 `json:"sample_from"` NetworkHead uint64 `json:"network_head"` - // Failed will be prioritized on restart + // Failed heights will be retried Failed map[uint64]int `json:"failed,omitempty"` // Workers will resume on restart from previous state Workers []workerCheckpoint `json:"workers,omitempty"` @@ -15,16 +15,18 @@ type checkpoint struct { // workerCheckpoint will be used to resume worker on restart type workerCheckpoint struct { - From uint64 `json:"from"` - To uint64 `json:"to"` + From uint64 `json:"from"` + To uint64 `json:"to"` + JobType jobType `json:"job_type"` } func newCheckpoint(stats SamplingStats) checkpoint { workers := make([]workerCheckpoint, 0, len(stats.Workers)) for _, w := range stats.Workers { workers = append(workers, workerCheckpoint{ - From: w.Curr, - To: w.To, + From: w.Curr, + To: w.To, + JobType: w.JobType, }) } return checkpoint{ diff --git a/das/checkpoint_test.go b/das/checkpoint_test.go index 3035e8fae5..4ad2a952e6 100644 --- a/das/checkpoint_test.go +++ b/das/checkpoint_test.go @@ -22,12 +22,14 @@ func TestCheckpointStore(t *testing.T) { Failed: failed, Workers: []workerCheckpoint{ { - From: 1, - To: 2, + From: 1, + To: 2, + JobType: retryJob, }, { - From: 5, - To: 10, + From: 5, + To: 10, + JobType: recentJob, }, }, } diff --git a/das/coordinator.go b/das/coordinator.go index cd41073fa9..2184dce2c8 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -37,7 +37,7 @@ type samplingCoordinator struct { // result will carry errors to coordinator after worker finishes the job type result struct { job - failed []uint64 + failed map[uint64]int err error } @@ -66,7 +66,7 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { // resume workers for _, wk := range cp.Workers { - sc.runWorker(ctx, sc.state.newJob(wk.From, wk.To)) + sc.runWorker(ctx, sc.state.newJob(wk.JobType, wk.From, wk.To)) } for { @@ -81,7 +81,7 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { select { case head := <-sc.updHeadCh: if sc.state.isNewHead(head.Height()) { - sc.runWorker(ctx, sc.state.newRecentJob(head)) + sc.runWorker(ctx, sc.state.recentJob(head)) sc.state.updateHead(head.Height()) // run worker without concurrency limit restrictions to reduced delay sc.metrics.observeNewHead(ctx) diff --git a/das/coordinator_test.go b/das/coordinator_test.go index af57c40ca1..d10ff8848d 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -195,20 +195,27 @@ func TestCoordinator(t *testing.T) { ) go coordinator.run(ctx, sampler.checkpoint) - // wait for coordinator to indicateDone catchup - assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + // wait for coordinator to go over all headers + assert.NoError(t, sampler.finished(ctx)) cancel() stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) defer cancel() assert.NoError(t, coordinator.wait(stopCtx)) - // set failed items in expectedState - expectedState := sampler.finalState() - for _, h := range bornToFail { - expectedState.Failed[h] = 1 + // failed item should be either in failed map or be processed by worker + cp := newCheckpoint(coordinator.state.unsafeStats()) + for _, failedHeight := range bornToFail { + if _, ok := cp.Failed[failedHeight]; ok { + continue + } + for _, w := range cp.Workers { + if w.JobType == retryJob && w.From == failedHeight { + continue + } + } + t.Error("header is not found neither in failed nor in workers") } - assert.Equal(t, expectedState, newCheckpoint(coordinator.state.unsafeStats())) }) t.Run("failed should retry on restart", func(t *testing.T) { @@ -218,9 +225,8 @@ func TestCoordinator(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) failedLastRun := map[uint64]int{4: 1, 8: 2, 15: 1, 16: 1, 23: 1, 42: 1, testParams.sampleFrom - 1: 1} - failedAgain := []uint64{16} - sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead, failedAgain...) + sampler := newMockSampler(testParams.sampleFrom, testParams.networkHead) sampler.checkpoint.Failed = failedLastRun coordinator := newSamplingCoordinator( @@ -244,9 +250,6 @@ func TestCoordinator(t *testing.T) { expectedState := sampler.finalState() expectedState.Failed = make(map[uint64]int) - for _, v := range failedAgain { - expectedState.Failed[v] = failedLastRun[v] + 1 - } assert.Equal(t, expectedState, newCheckpoint(coordinator.state.unsafeStats())) }) } @@ -330,7 +333,7 @@ func (m *mockSampler) sample(ctx context.Context, h *header.ExtendedHeader) erro } if height > m.NetworkHead || height < m.SampleFrom { - if m.Failed[height] == 0 { + if _, ok := m.checkpoint.Failed[height]; !ok { return fmt.Errorf("header: %v out of range: %v-%v", h, m.SampleFrom, m.NetworkHead) } } diff --git a/das/daser_test.go b/das/daser_test.go index 5e201a9b69..2f1c494309 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -195,11 +195,13 @@ func TestDASerSampleTimeout(t *testing.T) { getter := getterStub{} avail := mocks.NewMockAvailability(gomock.NewController(t)) + doneCh := make(chan struct{}) avail.EXPECT().SharesAvailable(gomock.Any(), gomock.Any()).DoAndReturn( func(sampleCtx context.Context, h *share.Root) error { select { case <-sampleCtx.Done(): - return sampleCtx.Err() + close(doneCh) + return nil case <-ctx.Done(): t.Fatal("call context didn't timeout in time") return ctx.Err() @@ -217,11 +219,11 @@ func TestDASerSampleTimeout(t *testing.T) { require.NoError(t, daser.Start(ctx)) require.NoError(t, daser.sampler.state.waitCatchUp(ctx)) - stats, err := daser.SamplingStats(ctx) - require.NoError(t, err) - - // failed map should contain first header as failed - require.Equal(t, stats.Failed[1], 1) + select { + case <-doneCh: + case <-ctx.Done(): + t.Fatal("call context didn't timeout in time") + } } // createDASerSubcomponents takes numGetter (number of headers diff --git a/das/metrics.go b/das/metrics.go index 0fca084732..5a272ac4cc 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -15,6 +15,12 @@ import ( "github.com/celestiaorg/celestia-node/header" ) +const ( + jobTypeLabel = "job_type" + headerWidthLabel = "header_width" + failedLabel = "failed" +) + var ( meter = global.MeterProvider().Meter("das") ) @@ -108,7 +114,11 @@ func (d *DASer) InitMetrics() error { log.Errorf("observing stats: %s", err.Error()) } - busyWorkers.Observe(ctx, int64(len(stats.Workers))) + for jobType, amount := range stats.workersByJobType() { + busyWorkers.Observe(ctx, amount, + attribute.String(jobTypeLabel, string(jobType))) + } + networkHead.Observe(ctx, int64(stats.NetworkHead)) sampledChainHead.Observe(ctx, int64(stats.SampledChainHead)) @@ -133,20 +143,22 @@ func (m *metrics) observeSample( ctx context.Context, h *header.ExtendedHeader, sampleTime time.Duration, + jobType jobType, err error, - _ bool, ) { if m == nil { return } m.sampleTime.Record(ctx, sampleTime.Seconds(), - attribute.Bool("failed", err != nil), - attribute.Int("header_width", len(h.DAH.RowsRoots)), + attribute.Bool(failedLabel, err != nil), + attribute.Int(headerWidthLabel, len(h.DAH.RowsRoots)), + attribute.String(jobTypeLabel, string(jobType)), ) m.sampled.Add(ctx, 1, - attribute.Bool("failed", err != nil), - attribute.Int("header_width", len(h.DAH.RowsRoots)), + attribute.Bool(failedLabel, err != nil), + attribute.Int(headerWidthLabel, len(h.DAH.RowsRoots)), + attribute.String(jobTypeLabel, string(jobType)), ) atomic.StoreUint64(&m.lastSampledTS, uint64(time.Now().UTC().Unix())) diff --git a/das/state.go b/das/state.go index 8e854e7fb3..3fddb7ce17 100644 --- a/das/state.go +++ b/das/state.go @@ -7,21 +7,32 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -// coordinatorState represents the current state of sampling +// coordinatorState represents the current state of sampling process type coordinatorState struct { - sampleFrom uint64 // is the height from which the DASer will start sampling - samplingRange uint64 // is the maximum amount of headers processed in one job. - - retry []job // list of headers heights that will be retried after last run - inProgress map[int]func() workerState // keeps track of running workers - failed map[uint64]int // stores heights of failed headers with amount of attempt as value - - nextJobID int - next uint64 // all headers before next were sent to workers + // sampleFrom is the height from which the DASer will start sampling + sampleFrom uint64 + // samplingRange is the maximum amount of headers processed in one job. + samplingRange uint64 + + // keeps track of running workers + inProgress map[int]func() workerState + // stores heights of failed headers with amount of retry attempt as value + failed map[uint64]int + // inRetry stores (height -> attempt count) of failed headers that are currently being retried by + // workers + inRetry map[uint64]int + + // nextJobID is a unique identifier that will be used for creation of next job + nextJobID int + // all headers before next were sent to workers + next uint64 + // networkHead is the height of the latest known network head networkHead uint64 - catchUpDone atomic.Bool // indicates if all headers are sampled - catchUpDoneCh chan struct{} // blocks until all headers are sampled + // catchUpDone indicates if all headers are sampled + catchUpDone atomic.Bool + // catchUpDoneCh blocks until all headers are sampled + catchUpDoneCh chan struct{} } // newCoordinatorState initiates state for samplingCoordinator @@ -29,9 +40,9 @@ func newCoordinatorState(params Parameters) coordinatorState { return coordinatorState{ sampleFrom: params.SampleFrom, samplingRange: params.SamplingRange, - retry: make([]job, 0), inProgress: make(map[int]func() workerState), failed: make(map[uint64]int), + inRetry: make(map[uint64]int), nextJobID: 0, next: params.SampleFrom, networkHead: params.SampleFrom, @@ -42,34 +53,38 @@ func newCoordinatorState(params Parameters) coordinatorState { func (s *coordinatorState) resumeFromCheckpoint(c checkpoint) { s.next = c.SampleFrom s.networkHead = c.NetworkHead - // store failed to retry them on restart - for h, count := range c.Failed { - s.failed[h] = count - s.retry = append(s.retry, s.newJob(h, h)) + + for h := range c.Failed { + // TODO(@walldiss): reset retry counter to allow retries after restart. Will be removed when retry + // backoff is implemented. + s.failed[h] = 0 } } func (s *coordinatorState) handleResult(res result) { delete(s.inProgress, res.id) - failedFromWorker := make(map[uint64]bool) - for _, h := range res.failed { - failedFromWorker[h] = true - } - // check if the worker retried any of the previously failed heights for h := range s.failed { - if h < res.From || h > res.To { + if h < res.from || h > res.to { continue } - if !failedFromWorker[h] { + if res.failed[h] == 0 { delete(s.failed, h) } } - // add newly failed heights - for h := range failedFromWorker { - s.failed[h]++ + + // update failed heights + for h := range res.failed { + failCount := 1 + if res.job.jobType == retryJob { + // if job was already in retry and failed again, persist attempt count + failCount += s.inRetry[h] + delete(s.inRetry, h) + } + + s.failed[h] = failCount } s.checkDone() } @@ -93,7 +108,8 @@ func (s *coordinatorState) updateHead(newHead int64) { s.checkDone() } -func (s *coordinatorState) newRecentJob(header *header.ExtendedHeader) job { +// recentJob creates a job to process a recent header. +func (s *coordinatorState) recentJob(header *header.ExtendedHeader) job { height := uint64(header.Height()) // move next, to prevent catchup job from processing same height if s.next == height { @@ -101,61 +117,69 @@ func (s *coordinatorState) newRecentJob(header *header.ExtendedHeader) job { } s.nextJobID++ return job{ - id: s.nextJobID, - isRecentHeader: true, - header: header, - From: height, - To: height, + id: s.nextJobID, + jobType: recentJob, + header: header, + from: height, + to: height, } } -// nextJob will return header height to be processed and done flag if there is none +// nextJob will return next catchup or retry job according to priority (catchup > retry) func (s *coordinatorState) nextJob() (next job, found bool) { - // all headers were sent to workers. - if s.next > s.networkHead { - return job{}, false - } - - // try to take from retry first - if next, found := s.nextFromRetry(); found { - return next, found + // check for catchup job + if job, found := s.catchupJob(); found { + return job, found } - j := s.newJob(s.next, s.networkHead) + // if caught up already, make a retry job + return s.retryJob() +} - s.next += s.samplingRange +// catchupJob creates a catchup job if catchup is not finished +func (s *coordinatorState) catchupJob() (next job, found bool) { if s.next > s.networkHead { - s.next = s.networkHead + 1 + return job{}, false } + to := s.next + s.samplingRange - 1 + if to > s.networkHead { + to = s.networkHead + } + j := s.newJob(catchupJob, s.next, to) + s.next = to + 1 return j, true } -func (s *coordinatorState) nextFromRetry() (job, bool) { - if len(s.retry) == 0 { - return job{}, false - } +// retryJob creates a job to retry previously failed header +func (s *coordinatorState) retryJob() (next job, found bool) { + for h, count := range s.failed { + // TODO(@walldiss): limit max amount of retries until retry backoff is implemented + if count > 3 { + continue + } - next := s.retry[len(s.retry)-1] - s.retry = s.retry[:len(s.retry)-1] + // move header from failed into retry + delete(s.failed, h) + s.inRetry[h] = count + j := s.newJob(retryJob, h, h) + return j, true + } - return next, true + return job{}, false } func (s *coordinatorState) putInProgress(jobID int, getState func() workerState) { s.inProgress[jobID] = getState } -func (s *coordinatorState) newJob(from, max uint64) job { +func (s *coordinatorState) newJob(jobType jobType, from, to uint64) job { s.nextJobID++ - to := from + s.samplingRange - 1 - if to > max { - to = max - } return job{ - id: s.nextJobID, - From: from, - To: to, + id: s.nextJobID, + jobType: jobType, + from: from, + to: to, } } @@ -169,25 +193,26 @@ func (s *coordinatorState) unsafeStats() SamplingStats { for _, getStats := range s.inProgress { wstats := getStats() var errMsg string - if wstats.Err != nil { - errMsg = wstats.Err.Error() + if wstats.err != nil { + errMsg = wstats.err.Error() } workers = append(workers, WorkerStats{ - Curr: wstats.Curr, - From: wstats.From, - To: wstats.To, - ErrMsg: errMsg, + JobType: wstats.job.jobType, + Curr: wstats.curr, + From: wstats.from, + To: wstats.to, + ErrMsg: errMsg, }) - for _, h := range wstats.failed { + for h := range wstats.failed { failed[h]++ if h < lowestFailedOrInProgress { lowestFailedOrInProgress = h } } - if wstats.Curr < lowestFailedOrInProgress { - lowestFailedOrInProgress = wstats.Curr + if wstats.curr < lowestFailedOrInProgress { + lowestFailedOrInProgress = wstats.curr } } @@ -212,7 +237,7 @@ func (s *coordinatorState) unsafeStats() SamplingStats { } func (s *coordinatorState) checkDone() { - if len(s.inProgress) == 0 && len(s.retry) == 0 && s.next > s.networkHead { + if len(s.inProgress) == 0 && len(s.failed) == 0 && s.next > s.networkHead { if s.catchUpDone.CompareAndSwap(false, true) { close(s.catchUpDoneCh) } diff --git a/das/state_test.go b/das/state_test.go index 8dc7873d30..1bef1ba223 100644 --- a/das/state_test.go +++ b/das/state_test.go @@ -20,24 +20,30 @@ func Test_coordinatorStats(t *testing.T) { inProgress: map[int]func() workerState{ 1: func() workerState { return workerState{ - job: job{ - From: 21, - To: 30, + result: result{ + job: job{ + jobType: recentJob, + from: 21, + to: 30, + }, + failed: map[uint64]int{22: 1}, + err: errors.New("22: failed"), }, - Curr: 25, - failed: []uint64{22}, - Err: errors.New("22: failed"), + curr: 25, } }, 2: func() workerState { return workerState{ - job: job{ - From: 11, - To: 20, + result: result{ + job: job{ + jobType: catchupJob, + from: 11, + to: 20, + }, + failed: map[uint64]int{12: 1, 13: 1}, + err: errors.Join(errors.New("12: failed"), errors.New("13: failed")), }, - Curr: 15, - failed: []uint64{12, 13}, - Err: errors.Join(errors.New("12: failed"), errors.New("13: failed")), + curr: 15, } }, }, @@ -53,16 +59,18 @@ func Test_coordinatorStats(t *testing.T) { Failed: map[uint64]int{22: 2, 23: 1, 24: 2, 12: 1, 13: 1}, Workers: []WorkerStats{ { - Curr: 25, - From: 21, - To: 30, - ErrMsg: "22: failed", + JobType: recentJob, + Curr: 25, + From: 21, + To: 30, + ErrMsg: "22: failed", }, { - Curr: 15, - From: 11, - To: 20, - ErrMsg: "12: failed\n13: failed", + JobType: catchupJob, + Curr: 15, + From: 11, + To: 20, + ErrMsg: "12: failed\n13: failed", }, }, Concurrency: 2, diff --git a/das/stats.go b/das/stats.go index a07e8fadaf..5799370f91 100644 --- a/das/stats.go +++ b/das/stats.go @@ -22,9 +22,10 @@ type SamplingStats struct { } type WorkerStats struct { - Curr uint64 `json:"current"` - From uint64 `json:"from"` - To uint64 `json:"to"` + JobType jobType `json:"job_type"` + Curr uint64 `json:"current"` + From uint64 `json:"from"` + To uint64 `json:"to"` ErrMsg string `json:"error,omitempty"` } @@ -37,3 +38,12 @@ func (s SamplingStats) totalSampled() uint64 { } return s.CatchupHead - inProgress - uint64(len(s.Failed)) } + +// workersByJobType returns a map of job types to the number of workers assigned to those types. +func (s SamplingStats) workersByJobType() map[jobType]int64 { + workers := make(map[jobType]int64) + for _, w := range s.Workers { + workers[w.JobType]++ + } + return workers +} diff --git a/das/worker.go b/das/worker.go index c73ad235f8..4319928b1f 100644 --- a/das/worker.go +++ b/das/worker.go @@ -13,6 +13,12 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) +const ( + catchupJob jobType = "catchup" + recentJob jobType = "recent" + retryJob jobType = "retry" +) + type worker struct { lock sync.Mutex state workerState @@ -26,21 +32,22 @@ type worker struct { // workerState contains important information about the state of a // current sampling routine. type workerState struct { - job + result - Curr uint64 - Err error - failed []uint64 + curr uint64 } +type jobType string + // job represents headers interval to be processed by worker type job struct { - id int - isRecentHeader bool - header *header.ExtendedHeader + id int + jobType jobType + from uint64 + to uint64 - From uint64 - To uint64 + // header is set only for recentJobs, avoiding an unnecessary call to the header store + header *header.ExtendedHeader } func newWorker(j job, @@ -55,18 +62,20 @@ func newWorker(j job, broadcast: broadcast, metrics: metrics, state: workerState{ - job: j, - Curr: j.From, - failed: make([]uint64, 0), + curr: j.from, + result: result{ + job: j, + failed: make(map[uint64]int), + }, }, } } func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- result) { jobStart := time.Now() - log.Debugw("start sampling worker", "from", w.state.From, "to", w.state.To) + log.Debugw("start sampling worker", "from", w.state.from, "to", w.state.to) - for curr := w.state.From; curr <= w.state.To; curr++ { + for curr := w.state.from; curr <= w.state.to; curr++ { err := w.sample(ctx, timeout, curr) w.setResult(curr, err) if errors.Is(err, context.Canceled) { @@ -75,20 +84,17 @@ func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- } } + log.With() log.Infow( "finished sampling headers", - "from", w.state.From, - "to", w.state.Curr, + "from", w.state.from, + "to", w.state.curr, "errors", len(w.state.failed), "finished (s)", time.Since(jobStart), ) select { - case resultCh <- result{ - job: w.state.job, - failed: w.state.failed, - err: w.state.Err, - }: + case resultCh <- w.state.result: case <-ctx.Done(): } } @@ -104,7 +110,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 defer cancel() err = w.sampleFn(ctx, h) - w.metrics.observeSample(ctx, h, time.Since(start), err, w.state.isRecentHeader) + w.metrics.observeSample(ctx, h, time.Since(start), w.state.jobType, err) if err != nil { if !errors.Is(err, context.Canceled) { log.Debugw( @@ -130,7 +136,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 ) // notify network about availability of new block data (note: only full nodes can notify) - if w.state.isRecentHeader { + if w.state.job.jobType == recentJob { err = w.broadcast(ctx, shrexsub.Notification{ DataHash: h.DataHash.Bytes(), Height: uint64(h.Height()), @@ -176,10 +182,10 @@ func (w *worker) setResult(curr uint64, err error) { w.lock.Lock() defer w.lock.Unlock() if err != nil { - w.state.failed = append(w.state.failed, curr) - w.state.Err = errors.Join(w.state.Err, fmt.Errorf("height: %v, err: %w", curr, err)) + w.state.failed[curr]++ + w.state.err = errors.Join(w.state.err, fmt.Errorf("height: %d, err: %w", curr, err)) } - w.state.Curr = curr + w.state.curr = curr } func (w *worker) getState() workerState { From a98923bf1b19aae65db99c599578f8226af67b5a Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 19 Apr 2023 19:18:03 +0200 Subject: [PATCH 0556/1008] refactor(modheader): use WithParams options (#2102) Just removes the need to pass every particular option and pass the whole config instead. The main motivation for this PR is to minimize cases where we add a new config field but forget to add and set the option for it. This has happened with TrustedPeersRequestTimeout, and made a new waste a ton of time debugging in the wrong direction as I assumed the configuration value was set correctly. --- nodebuilder/header/constructors.go | 86 +++++++++++++----------------- nodebuilder/header/module.go | 52 +++++------------- nodebuilder/node.go | 1 + 3 files changed, 53 insertions(+), 86 deletions(-) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index e6bb6d6c48..7d70f0f5a8 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -21,55 +21,42 @@ import ( "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) -// newP2PServer constructs a new ExchangeServer using the given Network as a protocolID prefix. -func newP2PServer( - host host.Host, - store libhead.Store[*header.ExtendedHeader], - opts []p2p.Option[p2p.ServerParameters], -) (*p2p.ExchangeServer[*header.ExtendedHeader], error) { - return p2p.NewExchangeServer[*header.ExtendedHeader](host, store, opts...) -} - // newP2PExchange constructs a new Exchange for headers. -func newP2PExchange(cfg Config) func( - fx.Lifecycle, - modp2p.Bootstrappers, - modp2p.Network, - host.Host, - *conngater.BasicConnectionGater, - []p2p.Option[p2p.ClientParameters], +func newP2PExchange( + lc fx.Lifecycle, + bpeers modp2p.Bootstrappers, + network modp2p.Network, + host host.Host, + conngater *conngater.BasicConnectionGater, + cfg Config, ) (libhead.Exchange[*header.ExtendedHeader], error) { - return func( - lc fx.Lifecycle, - bpeers modp2p.Bootstrappers, - network modp2p.Network, - host host.Host, - conngater *conngater.BasicConnectionGater, - opts []p2p.Option[p2p.ClientParameters], - ) (libhead.Exchange[*header.ExtendedHeader], error) { - peers, err := cfg.trustedPeers(bpeers) - if err != nil { - return nil, err - } - ids := make([]peer.ID, len(peers)) - for index, peer := range peers { - ids[index] = peer.ID - host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) - } - exchange, err := p2p.NewExchange[*header.ExtendedHeader](host, ids, conngater, opts...) - if err != nil { - return nil, err - } - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return exchange.Start(ctx) - }, - OnStop: func(ctx context.Context) error { - return exchange.Stop(ctx) - }, - }) - return exchange, nil + peers, err := cfg.trustedPeers(bpeers) + if err != nil { + return nil, err } + ids := make([]peer.ID, len(peers)) + for index, peer := range peers { + ids[index] = peer.ID + host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) + } + exchange, err := p2p.NewExchange[*header.ExtendedHeader](host, ids, conngater, + p2p.WithParams(cfg.Client), + p2p.WithNetworkID[p2p.ClientParameters](network.String()), + p2p.WithChainID(network.String()), + ) + if err != nil { + return nil, err + } + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return exchange.Start(ctx) + }, + OnStop: func(ctx context.Context) error { + return exchange.Stop(ctx) + }, + }) + return exchange, nil + } // newSyncer constructs new Syncer for headers. @@ -78,9 +65,12 @@ func newSyncer( fservice libfraud.Service, store InitStore, sub libhead.Subscriber[*header.ExtendedHeader], - opts []sync.Options, + cfg Config, ) (*sync.Syncer[*header.ExtendedHeader], *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], error) { - syncer, err := sync.NewSyncer[*header.ExtendedHeader](ex, store, sub, opts...) + syncer, err := sync.NewSyncer[*header.ExtendedHeader](ex, store, sub, + sync.WithParams(cfg.Syncer), + sync.WithBlockTime(modp2p.BlockTime), + ) if err != nil { return nil, nil, err } diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 2b94feaec8..77e7c5eb99 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -6,6 +6,7 @@ import ( "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" libhead "github.com/celestiaorg/go-header" @@ -28,28 +29,10 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents := fx.Options( fx.Supply(*cfg), fx.Error(cfgErr), - fx.Provide( - func(cfg Config) []store.Option { - return []store.Option{ - store.WithStoreCacheSize(cfg.Store.StoreCacheSize), - store.WithIndexCacheSize(cfg.Store.IndexCacheSize), - store.WithWriteBatchSize(cfg.Store.WriteBatchSize), - } - }, - ), - fx.Provide( - func(cfg Config, network modp2p.Network) []p2p.Option[p2p.ServerParameters] { - return []p2p.Option[p2p.ServerParameters]{ - p2p.WithWriteDeadline(cfg.Server.WriteDeadline), - p2p.WithReadDeadline(cfg.Server.ReadDeadline), - p2p.WithRangeRequestTimeout[p2p.ServerParameters](cfg.Server.RangeRequestTimeout), - p2p.WithNetworkID[p2p.ServerParameters](network.String()), - } - }), fx.Provide(newHeaderService), fx.Provide(fx.Annotate( - func(ds datastore.Batching, opts []store.Option) (libhead.Store[*header.ExtendedHeader], error) { - return store.NewStore[*header.ExtendedHeader](ds, opts...) + func(ds datastore.Batching) (libhead.Store[*header.ExtendedHeader], error) { + return store.NewStore[*header.ExtendedHeader](ds, store.WithParams(cfg.Store)) }, fx.OnStart(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { return store.Start(ctx) @@ -62,12 +45,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(func(subscriber *p2p.Subscriber[*header.ExtendedHeader]) libhead.Subscriber[*header.ExtendedHeader] { return subscriber }), - fx.Provide(func(cfg Config) []sync.Options { - return []sync.Options{ - sync.WithBlockTime(modp2p.BlockTime), - sync.WithTrustingPeriod(cfg.Syncer.TrustingPeriod), - } - }), fx.Provide(fx.Annotate( newSyncer, fx.OnStart(func( @@ -95,7 +72,16 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { }), )), fx.Provide(fx.Annotate( - newP2PServer, + func( + host host.Host, + store libhead.Store[*header.ExtendedHeader], + network modp2p.Network, + ) (*p2p.ExchangeServer[*header.ExtendedHeader], error) { + return p2p.NewExchangeServer[*header.ExtendedHeader](host, store, + p2p.WithParams(cfg.Server), + p2p.WithNetworkID[p2p.ServerParameters](network.String()), + ) + }, fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer[*header.ExtendedHeader]) error { return server.Start(ctx) }), @@ -110,17 +96,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "header", baseComponents, - fx.Provide( - func(cfg Config, network modp2p.Network) []p2p.Option[p2p.ClientParameters] { - return []p2p.Option[p2p.ClientParameters]{ - p2p.WithMaxHeadersPerRangeRequest(cfg.Client.MaxHeadersPerRangeRequest), - p2p.WithRangeRequestTimeout[p2p.ClientParameters](cfg.Client.RangeRequestTimeout), - p2p.WithNetworkID[p2p.ClientParameters](network.String()), - p2p.WithChainID(network.String()), - } - }, - ), - fx.Provide(newP2PExchange(*cfg)), + fx.Provide(newP2PExchange), ) case node.Bridge: return fx.Module( diff --git a/nodebuilder/node.go b/nodebuilder/node.go index fff79cfc7f..6968379c02 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -149,6 +149,7 @@ func (n *Node) Stop(ctx context.Context) error { func newNode(opts ...fx.Option) (*Node, error) { node := new(Node) app := fx.New( + fx.NopLogger, fx.Populate(node), fx.Options(opts...), ) From c4e994b03fe2a451df2f0df099c24d09ce76aec6 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 20 Apr 2023 21:03:49 +0800 Subject: [PATCH 0557/1008] refactor(share/shrex): improve shrex logging (#2101) ## Overview - Previously shrex-nd and shrex-eds error logs were indistinguishable, that lead to confusion of errors source. - add peerID to server logs - Improve some other shrex logging. Resolves https://github.com/celestiaorg/celestia-node/issues/2035 --- go.mod | 2 +- share/getters/shrex.go | 16 ++++++------- share/p2p/middleware.go | 2 +- share/p2p/peers/manager.go | 44 +++++++++++++++++++++-------------- share/p2p/peers/pool.go | 8 ++++++- share/p2p/shrexeds/client.go | 9 +++++--- share/p2p/shrexeds/server.go | 39 ++++++++++++++++--------------- share/p2p/shrexnd/client.go | 4 ++-- share/p2p/shrexnd/server.go | 45 +++++++++++++++++++----------------- 9 files changed, 97 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index 8a05b6dd72..15557baa63 100644 --- a/go.mod +++ b/go.mod @@ -71,6 +71,7 @@ require ( go.opentelemetry.io/otel/trace v1.13.0 go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.19.2 + go.uber.org/zap v1.24.0 golang.org/x/crypto v0.7.0 golang.org/x/sync v0.1.0 golang.org/x/text v0.8.0 @@ -306,7 +307,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 95f06d821e..72626f406a 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -72,8 +72,8 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) if getErr != nil { err = errors.Join(err, getErr) - log.Debugw("couldn't find peer", - "datahash", root.String(), + log.Debugw("eds: couldn't find peer", + "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) return nil, fmt.Errorf("getter/shrex: %w", err) @@ -101,8 +101,8 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex if !ErrorContains(err, getErr) { err = errors.Join(err, getErr) } - log.Debugw("request failed", - "datahash", root.String(), + log.Debugw("eds: request failed", + "hash", root.String(), "peer", peer.String(), "attempt", attempt, "err", getErr, @@ -125,8 +125,8 @@ func (sg *ShrexGetter) GetSharesByNamespace( peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) if getErr != nil { err = errors.Join(err, getErr) - log.Debugw("couldn't find peer", - "datahash", root.String(), + log.Debugw("nd: couldn't find peer", + "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) return nil, fmt.Errorf("getter/shrex: %w", err) @@ -154,8 +154,8 @@ func (sg *ShrexGetter) GetSharesByNamespace( if !ErrorContains(err, getErr) { err = errors.Join(err, getErr) } - log.Debugw("request failed", - "datahash", root.String(), + log.Debugw("nd: request failed", + "hash", root.String(), "peer", peer.String(), "attempt", attempt, "err", getErr, diff --git a/share/p2p/middleware.go b/share/p2p/middleware.go index 355ea7c468..25d733f43b 100644 --- a/share/p2p/middleware.go +++ b/share/p2p/middleware.go @@ -20,7 +20,7 @@ func RateLimitMiddleware(inner network.StreamHandler, concurrencyLimit int) netw log.Debug("concurrency limit reached") err := stream.Close() if err != nil { - log.Errorw("server: closing stream", "err", err) + log.Debugw("server: closing stream", "err", err) } return } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index c18e91bdb1..71774a9211 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -174,6 +174,7 @@ func (m *Manager) Stop(ctx context.Context) error { func (m *Manager) Peer( ctx context.Context, datahash share.DataHash, ) (peer.ID, DoneFunc, error) { + logger := log.With("hash", datahash.String()) p := m.validatedPool(datahash.String()) // first, check if a peer is available for the given datahash @@ -181,13 +182,14 @@ func (m *Manager) Peer( if ok { // some pools could still have blacklisted peers in storage if m.isBlacklistedPeer(peerID) { - log.Debugw("removing blacklisted peer from pool", "hash", datahash.String(), + logger.Debugw("removing blacklisted peer from pool", "peer", peerID.String()) p.remove(peerID) return m.Peer(ctx, datahash) } - log.Debugw("returning shrex-sub peer", "hash", datahash.String(), - "peer", peerID.String()) + logger.Debugw("get peer from shrexsub pool", + "peer", peerID.String(), + "pool_size", p.size()) return peerID, m.doneFunc(datahash, peerID, false), nil } @@ -195,17 +197,26 @@ func (m *Manager) Peer( // obtained from discovery peerID, ok = m.fullNodes.tryGet() if ok { - log.Debugw("got peer from full nodes discovery pool", "peer", peerID, "datahash", datahash.String()) + logger.Debugw("got peer from full nodes pool", + "peer", peerID.String(), + "pool_size", m.fullNodes.size()) return peerID, m.doneFunc(datahash, peerID, true), nil } // no peers are available right now, wait for the first one + start := time.Now() select { case peerID = <-p.next(ctx): - log.Debugw("got peer from shrexSub pool after wait", "peer", peerID, "datahash", datahash.String()) + logger.Debugw("got peer from shrexSub pool after wait", + "peer", peerID.String(), + "pool_size", p.size(), + "after (s)", time.Since(start)) return peerID, m.doneFunc(datahash, peerID, false), nil case peerID = <-m.fullNodes.next(ctx): - log.Debugw("got peer from discovery pool after wait", "peer", peerID, "datahash", datahash.String()) + logger.Debugw("got peer from full nodes pool after wait", + "peer", peerID.String(), + "pool_size", m.fullNodes.size(), + "after (s)", time.Since(start)) return peerID, m.doneFunc(datahash, peerID, true), nil case <-ctx.Done(): return "", nil, ctx.Err() @@ -216,7 +227,7 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, fromFull boo return func(result result) { log.Debugw("set peer status", "peer", peerID, - "datahash", datahash.String(), + "hash", datahash.String(), "result", result) switch result { case ResultSuccess: @@ -258,28 +269,27 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri // Validate will collect peer.ID into corresponding peer pool func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { + logger := log.With("peer", peerID, "hash", msg.DataHash.String()) // messages broadcast from self should bypass the validation with Accept if peerID == m.host.ID() { - log.Debugw("received datahash from self", "datahash", msg.DataHash.String()) + logger.Debug("received datahash from self") return pubsub.ValidationAccept } // punish peer for sending invalid hash if it has misbehaved in the past if m.isBlacklistedHash(msg.DataHash) { - log.Debugw("received blacklisted hash, reject validation", "peer", peerID, "datahash", msg.DataHash.String()) + logger.Debug("received blacklisted hash, reject validation") return pubsub.ValidationReject } if m.isBlacklistedPeer(peerID) { - log.Debugw("received message from blacklisted peer, reject validation", - "peer", peerID, - "datahash", msg.DataHash.String()) + logger.Debug("received message from blacklisted peer, reject validation") return pubsub.ValidationReject } if msg.Height == 0 { - log.Debugw("received message with 0 height", "peer", peerID) + logger.Debug("received message with 0 height") return pubsub.ValidationReject } @@ -288,14 +298,14 @@ func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notif // if we allow pool creation for those headers, there is chance the pool will not be validated in // time and will be false-positively trigger blacklisting of hash and all peers that sent msgs for // that hash - log.Debugw("received message for past header", "peer", peerID, "datahash", msg.DataHash.String()) + logger.Debug("received message for past header") return pubsub.ValidationIgnore } p := m.getOrCreatePool(msg.DataHash.String()) p.headerHeight.Store(msg.Height) p.add(peerID) - log.Debugw("got hash from shrex-sub", "peer", peerID, "datahash", msg.DataHash.String()) + logger.Debug("got hash from shrex-sub") return pubsub.ValidationIgnore } @@ -349,7 +359,7 @@ func (m *Manager) isBlacklistedHash(hash share.DataHash) bool { func (m *Manager) validatedPool(hashStr string) *syncPool { p := m.getOrCreatePool(hashStr) if p.isValidatedDataHash.CompareAndSwap(false, true) { - log.Debugw("pool marked validated", "datahash", hashStr) + log.Debugw("pool marked validated", "hash", hashStr) } return p } @@ -391,7 +401,7 @@ func (m *Manager) cleanUp() []peer.ID { continue } log.Debug("blacklisting datahash with all corresponding peers", - "datahash", h, + "hash", h, "peer_list", p.peersList) // blacklist hash m.blacklistedHashes[h] = true diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index a31c4345b7..bcf425a475 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -12,7 +12,7 @@ const defaultCleanupThreshold = 2 // pool stores peers and provides methods for simple round-robin access. type pool struct { - m sync.Mutex + m sync.RWMutex peersList []peer.ID statuses map[peer.ID]status cooldown *timedQueue @@ -194,3 +194,9 @@ func (p *pool) checkHasPeers() { p.hasPeer = false } } + +func (p *pool) size() int { + p.m.RLock() + defer p.m.RUnlock() + return p.activeCount +} diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 4a68d689dd..330fc2c026 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -61,7 +61,10 @@ func (c *Client) RequestEDS( } } if err != p2p.ErrNotFound { - log.Warnw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String()) + log.Warnw("client: eds request to peer failed", + "peer", peer, + "hash", dataHash.String(), + "err", err) } return nil, err @@ -79,7 +82,7 @@ func (c *Client) doRequest( if dl, ok := ctx.Deadline(); ok { if err = stream.SetDeadline(dl); err != nil { - log.Debugw("error setting deadline: %s", "err", err) + log.Debugw("client: error setting deadline: %s", "err", err) } } @@ -94,7 +97,7 @@ func (c *Client) doRequest( } err = stream.CloseWrite() if err != nil { - log.Debugw("error closing write", "err", err) + log.Debugw("client: error closing write", "err", err) } // read and parse status from peer diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index f7e68bb271..8b0b674b88 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -10,6 +10,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" + "go.uber.org/zap" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -59,12 +60,13 @@ func (s *Server) Stop(context.Context) error { } func (s *Server) handleStream(stream network.Stream) { - log.Debug("server: handling eds request") + logger := log.With("peer", stream.Conn().RemotePeer()) + logger.Debug("server: handling eds request") // read request from stream to get the dataHash for store lookup - req, err := s.readRequest(stream) + req, err := s.readRequest(logger, stream) if err != nil { - log.Warnw("server: reading request from stream", "err", err) + logger.Warnw("server: reading request from stream", "err", err) stream.Reset() //nolint:errcheck return } @@ -73,10 +75,11 @@ func (s *Server) handleStream(stream network.Stream) { hash := share.DataHash(req.Hash) err = hash.Validate() if err != nil { - log.Debugw("server: invalid request", "err", err) + logger.Debugw("server: invalid request", "err", err) stream.Reset() //nolint:errcheck return } + logger = logger.With("hash", hash) ctx, cancel := context.WithTimeout(s.ctx, s.params.HandleRequestTimeout) defer cancel() @@ -90,14 +93,14 @@ func (s *Server) handleStream(stream network.Stream) { case errors.Is(err, eds.ErrNotFound): status = p2p_pb.Status_NOT_FOUND case err != nil: - log.Errorw("server: get car", "err", err) + logger.Errorw("server: get CAR", "err", err) status = p2p_pb.Status_INTERNAL } // inform the client of our status - err = s.writeStatus(status, stream) + err = s.writeStatus(logger, status, stream) if err != nil { - log.Warnw("server: writing status to stream", "err", err) + logger.Warnw("server: writing status to stream", "err", err) stream.Reset() //nolint:errcheck return } @@ -105,29 +108,29 @@ func (s *Server) handleStream(stream network.Stream) { if status != p2p_pb.Status_OK { err = stream.Close() if err != nil { - log.Debugw("server: closing stream", "err", err) + logger.Debugw("server: closing stream", "err", err) } return } // start streaming the ODS to the client - err = s.writeODS(edsReader, stream) + err = s.writeODS(logger, edsReader, stream) if err != nil { - log.Warnw("server: writing ods to stream", "hash", hash.String(), "err", err) + logger.Warnw("server: writing ods to stream", "err", err) stream.Reset() //nolint:errcheck return } err = stream.Close() if err != nil { - log.Debugw("server: closing stream", "err", err) + logger.Debugw("server: closing stream", "err", err) } } -func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) { +func (s *Server) readRequest(logger *zap.SugaredLogger, stream network.Stream) (*p2p_pb.EDSRequest, error) { err := stream.SetReadDeadline(time.Now().Add(s.params.ServerReadTimeout)) if err != nil { - log.Debugw("server: set read deadline", "err", err) + logger.Debugw("server: set read deadline", "err", err) } req := new(p2p_pb.EDSRequest) @@ -137,16 +140,16 @@ func (s *Server) readRequest(stream network.Stream) (*p2p_pb.EDSRequest, error) } err = stream.CloseRead() if err != nil { - log.Debugw("server: closing read", "err", err) + logger.Debugw("server: closing read", "err", err) } return req, nil } -func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error { +func (s *Server) writeStatus(logger *zap.SugaredLogger, status p2p_pb.Status, stream network.Stream) error { err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { - log.Debugw("server: set write deadline", "err", err) + logger.Debugw("server: set write deadline", "err", err) } resp := &p2p_pb.EDSResponse{Status: status} @@ -154,10 +157,10 @@ func (s *Server) writeStatus(status p2p_pb.Status, stream network.Stream) error return err } -func (s *Server) writeODS(edsReader io.Reader, stream network.Stream) error { +func (s *Server) writeODS(logger *zap.SugaredLogger, edsReader io.Reader, stream network.Stream) error { err := stream.SetWriteDeadline(time.Now().Add(s.params.ServerWriteTimeout)) if err != nil { - log.Debugw("server: set read deadline", "err", err) + logger.Debugw("server: set read deadline", "err", err) } odsReader, err := eds.ODSReader(edsReader) diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 553c0ffdd9..78af04e61b 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -58,7 +58,7 @@ func (c *Client) RequestND( return shares, err } if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - return nil, ctx.Err() + return nil, err } // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not // unwrap to a ctx err @@ -69,7 +69,7 @@ func (c *Client) RequestND( } } if err != p2p.ErrNotFound { - log.Warnw("client-nd: peer returned err", "peer", peer, "err", err) + log.Warnw("client-nd: peer returned err", "err", err) } return nil, err } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 5e37dec3e0..cb71ad3053 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -10,6 +10,7 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" "github.com/minio/sha256-simd" + "go.uber.org/zap" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -70,30 +71,32 @@ func (srv *Server) Stop(context.Context) error { } func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { - log.Debug("server: handling nd request") + logger := log.With("peer", stream.Conn().RemotePeer()) + logger.Debug("server: handling nd request") err := stream.SetReadDeadline(time.Now().Add(srv.params.ServerReadTimeout)) if err != nil { - log.Debugw("server: setting read deadline", "err", err) + logger.Debugw("server: setting read deadline", "err", err) } var req pb.GetSharesByNamespaceRequest _, err = serde.Read(stream, &req) if err != nil { - log.Warnw("server: reading request", "err", err) + logger.Warnw("server: reading request", "err", err) stream.Reset() //nolint:errcheck return } - log.Debugw("server: new request", "namespaceId", string(req.NamespaceId), "roothash", string(req.RootHash)) + logger = logger.With("namespaceId", string(req.NamespaceId), "hash", string(req.RootHash)) + logger.Debugw("server: new request") err = stream.CloseRead() if err != nil { - log.Debugw("server: closing read side of the stream", "err", err) + logger.Debugw("server: closing read side of the stream", "err", err) } err = validateRequest(req) if err != nil { - log.Debugw("server: invalid request", "err", err) + logger.Debugw("server: invalid request", "err", err) stream.Reset() //nolint:errcheck return } @@ -104,27 +107,27 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre dah, err := srv.store.GetDAH(ctx, req.RootHash) if err != nil { if errors.Is(err, eds.ErrNotFound) { - srv.respondNotFoundError(stream) + srv.respondNotFoundError(logger, stream) return } - log.Errorw("server: retrieving DAH for datahash", "err", err) - srv.respondInternalError(stream) + logger.Errorw("server: retrieving DAH", "err", err) + srv.respondInternalError(logger, stream) return } shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) if errors.Is(err, share.ErrNotFound) { - srv.respondNotFoundError(stream) + srv.respondNotFoundError(logger, stream) return } if err != nil { - log.Errorw("server: retrieving shares", "err", err) - srv.respondInternalError(stream) + logger.Errorw("server: retrieving shares", "err", err) + srv.respondInternalError(logger, stream) return } resp := namespacedSharesToResponse(shares) - srv.respond(stream, resp) + srv.respond(logger, stream, resp) } // validateRequest checks correctness of the request @@ -140,19 +143,19 @@ func validateRequest(req pb.GetSharesByNamespaceRequest) error { } // respondNotFoundError sends internal error response to client -func (srv *Server) respondNotFoundError(stream network.Stream) { +func (srv *Server) respondNotFoundError(logger *zap.SugaredLogger, stream network.Stream) { resp := &pb.GetSharesByNamespaceResponse{ Status: pb.StatusCode_NOT_FOUND, } - srv.respond(stream, resp) + srv.respond(logger, stream, resp) } // respondInternalError sends internal error response to client -func (srv *Server) respondInternalError(stream network.Stream) { +func (srv *Server) respondInternalError(logger *zap.SugaredLogger, stream network.Stream) { resp := &pb.GetSharesByNamespaceResponse{ Status: pb.StatusCode_INTERNAL, } - srv.respond(stream, resp) + srv.respond(logger, stream, resp) } // namespacedSharesToResponse encodes shares into proto and sends it to client with OK status code @@ -179,20 +182,20 @@ func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNa } } -func (srv *Server) respond(stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { +func (srv *Server) respond(logger *zap.SugaredLogger, stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { err := stream.SetWriteDeadline(time.Now().Add(srv.params.ServerWriteTimeout)) if err != nil { - log.Debugw("server: seting write deadline", "err", err) + logger.Debugw("server: setting write deadline", "err", err) } _, err = serde.Write(stream, resp) if err != nil { - log.Warnw("server: writing response", "err", err) + logger.Warnw("server: writing response", "err", err) stream.Reset() //nolint:errcheck return } if err = stream.Close(); err != nil { - log.Debugw("server: closing stream", "err", err) + logger.Debugw("server: closing stream", "err", err) } } From 0d4e4bb8c74baac814f7b32a87ac80a0aa7a4088 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:49:47 +0800 Subject: [PATCH 0558/1008] refactor(api/gateway) return error from gateway if header is not synced yet (#2108) ## Overview If header was not in header store, gateway would hang until client context was canceled. PR allows gateway to return an error immediately in this case. --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- api/gateway/availability.go | 15 +++++++++++++++ api/gateway/header.go | 14 ++++++++++++++ api/gateway/share.go | 18 ++++++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/api/gateway/availability.go b/api/gateway/availability.go index e5e53e0dba..b35593e24f 100644 --- a/api/gateway/availability.go +++ b/api/gateway/availability.go @@ -2,6 +2,7 @@ package gateway import ( "encoding/json" + "fmt" "net/http" "strconv" @@ -27,6 +28,20 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http return } + //TODO: change this to NetworkHead once the adjacency in the store is fixed. + head, err := h.header.LocalHead(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) + return + } + if headHeight := int(head.Height()); headHeight < height { + err = fmt.Errorf( + "current head local chain head: %d is lower than requested height: %d"+ + " give header sync some time and retry later", headHeight, height) + writeError(w, http.StatusServiceUnavailable, heightAvailabilityEndpoint, err) + return + } + header, err := h.header.GetByHeight(r.Context(), uint64(height)) if err != nil { writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) diff --git a/api/gateway/header.go b/api/gateway/header.go index 50fb058213..88ac3b0923 100644 --- a/api/gateway/header.go +++ b/api/gateway/header.go @@ -2,6 +2,7 @@ package gateway import ( "encoding/json" + "fmt" "net/http" "strconv" @@ -69,6 +70,19 @@ func (h *Handler) performGetHeaderRequest( writeError(w, http.StatusBadRequest, endpoint, err) return nil, err } + //TODO: change this to NetworkHead once the adjacency in the store is fixed. + head, err := h.header.LocalHead(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) + return nil, err + } + if headHeight := int(head.Height()); headHeight < height { + err = fmt.Errorf( + "current head local chain head: %d is lower than requested height: %d"+ + " give header sync some time and retry later", headHeight, height) + writeError(w, http.StatusServiceUnavailable, endpoint, err) + return nil, err + } // perform request header, err := h.header.GetByHeight(r.Context(), uint64(height)) if err != nil { diff --git a/api/gateway/share.go b/api/gateway/share.go index caa1714eef..13903fd14b 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "encoding/json" + "fmt" "net/http" "strconv" @@ -98,10 +99,19 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID err error header *header.ExtendedHeader ) - switch height { - case 0: - header, err = h.header.LocalHead(ctx) - default: + + //TODO: change this to NetworkHead once the adjacency in the store is fixed. + header, err = h.header.LocalHead(ctx) + if err != nil { + return nil, 0, err + } + + if height > 0 { + if storeHeight := uint64(header.Height()); storeHeight < height { + return nil, 0, fmt.Errorf( + "current head local chain head: %d is lower than requested height: %d"+ + " give header sync some time and retry later", storeHeight, height) + } header, err = h.header.GetByHeight(ctx, height) } if err != nil { From 7f95c72c669edd39faa01ca22fffae1b947bb160 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 24 Apr 2023 19:18:45 +0200 Subject: [PATCH 0559/1008] fix(share/eds): pass correct context to DagStore (#2124) --- share/eds/store.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/share/eds/store.go b/share/eds/store.go index 94c86158f6..8222d04dd6 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -111,12 +111,16 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return store, nil } -func (s *Store) Start(context.Context) error { +func (s *Store) Start(ctx context.Context) error { + err := s.dgstr.Start(ctx) + if err != nil { + return err + } + // start Store only if DagStore succeeds ctx, cancel := context.WithCancel(context.Background()) s.cancel = cancel - go s.gc(ctx) - return s.dgstr.Start(ctx) + return nil } // Stop stops the underlying DAGStore. From 82fbba833efbaca4dc3bbb9b73827d32223274db Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 25 Apr 2023 17:35:54 +0200 Subject: [PATCH 0560/1008] deps: bump libp2p (#2127) --- go.mod | 60 +++++++++++++-------------- go.sum | 125 ++++++++++++++++++++++++++++++--------------------------- 2 files changed, 95 insertions(+), 90 deletions(-) diff --git a/go.mod b/go.mod index 15557baa63..27500f3dc4 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/imdario/mergo v0.3.15 github.com/ipfs/go-blockservice v0.5.0 - github.com/ipfs/go-cid v0.3.2 + github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 @@ -43,7 +43,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 - github.com/libp2p/go-libp2p v0.26.3 + github.com/libp2p/go-libp2p v0.27.1 github.com/libp2p/go-libp2p-kad-dht v0.21.0 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 @@ -51,7 +51,7 @@ require ( github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.8.0 + github.com/multiformats/go-multiaddr v0.9.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 @@ -60,7 +60,7 @@ require ( github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/tendermint/tendermint v0.35.4 go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 @@ -76,7 +76,7 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/text v0.8.0 google.golang.org/grpc v1.53.0 - google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 + google.golang.org/protobuf v1.30.0 ) require ( @@ -106,11 +106,11 @@ require ( github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chzyer/readline v1.5.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/confio/ics23/go v0.9.0 // indirect - github.com/containerd/cgroups v1.0.4 // indirect + github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha8 // indirect @@ -152,19 +152,19 @@ require ( github.com/go-openapi/spec v0.19.11 // indirect github.com/go-openapi/swag v0.19.11 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect + github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect @@ -183,10 +183,10 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/huin/goupnp v1.0.3 // indirect + github.com/huin/goupnp v1.1.0 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -212,15 +212,15 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.15.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/klauspost/compress v1.16.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect - github.com/koron/go-ssdp v0.0.3 // indirect + github.com/koron/go-ssdp v0.0.4 // indirect github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect + github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect @@ -232,9 +232,9 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.50 // indirect + github.com/miekg/dns v1.1.53 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -245,11 +245,11 @@ require ( github.com/mtibben/percent v0.2.1 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multicodec v0.8.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.8.1 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.5.1 // indirect + github.com/onsi/ginkgo/v2 v2.9.2 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -261,12 +261,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.2.1 // indirect - github.com/quic-go/qtls-go1-20 v0.1.1 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/quic-go/quic-go v0.33.0 // indirect github.com/quic-go/webtransport-go v0.5.2 // indirect github.com/rakyll/statik v0.1.7 // indirect @@ -307,13 +307,13 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect - golang.org/x/mod v0.8.0 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/sys v0.7.0 // indirect golang.org/x/term v0.6.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 9c542114b6..5d7dcd43e0 100644 --- a/go.sum +++ b/go.sum @@ -381,14 +381,14 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= @@ -421,8 +421,8 @@ github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1 github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= -github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -660,8 +660,9 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -721,8 +722,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -782,8 +784,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= -github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4= +github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -890,8 +892,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= -github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= +github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -908,8 +910,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goupnp v1.1.0 h1:gEe0Dp/lZmPZiDFzJJaOfUpOvv2MKUkoBX8lDrn9vKU= +github.com/huin/goupnp v1.1.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= @@ -957,8 +960,8 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= -github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -1126,15 +1129,15 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= -github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= +github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= @@ -1143,8 +1146,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= -github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= -github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -1191,11 +1194,11 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= -github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= -github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= +github.com/libp2p/go-libp2p v0.27.1 h1:k1u6RHsX3hqKnslDjsSgLNURxJ3O1atIZCY4gpMbbus= +github.com/libp2p/go-libp2p v0.27.1/go.mod h1:FAvvfQa/YOShUYdiSS03IR9OXzkcJXwcNA2FUCh9ImE= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= -github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= -github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= +github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= +github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= @@ -1453,8 +1456,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1473,8 +1476,8 @@ github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw= +github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1549,8 +1552,8 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= -github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= +github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= +github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -1569,13 +1572,13 @@ github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= -github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.8.0 h1:evBmgkbSQux+Ds2IgfhkO38Dl2GDtRW8/Rp6YiSHX/Q= -github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= +github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -1637,8 +1640,8 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= -github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1646,7 +1649,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1742,8 +1745,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1754,8 +1757,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.0 h1:LWuuqPQ1oa6x7BnmUOuo/aGwdX85QGhWZUBYWWW3zdk= @@ -1766,10 +1769,10 @@ github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKA github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= -github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= -github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= @@ -1909,8 +1912,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= @@ -2156,8 +2160,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 h1:5sPMf9HJXrvBWIamTw+rTST0bZ3Mho2n1p58M0+W99c= -golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2189,8 +2193,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2300,8 +2304,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2447,8 +2451,9 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2546,8 +2551,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2806,8 +2811,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From f8872920608eadd974444fb956ad680f31caea20 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 26 Apr 2023 09:26:58 +0200 Subject: [PATCH 0561/1008] feat(core): Add span for handle-new-signed-block in listener (#2093) Small span addition to listener --- core/listener.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/listener.go b/core/listener.go index 82b758a7fc..24d83cda12 100644 --- a/core/listener.go +++ b/core/listener.go @@ -8,6 +8,8 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/tendermint/tendermint/types" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" libhead "github.com/celestiaorg/go-header" @@ -16,6 +18,8 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) +var tracer = otel.Tracer("core/listener") + // Listener is responsible for listening to Core for // new block events and converting new Core blocks into // the main data structure used in the Celestia DA network: @@ -140,6 +144,11 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSigned } func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataSignedBlock) error { + ctx, span := tracer.Start(ctx, "handle-new-signed-block") + defer span.End() + span.SetAttributes( + attribute.Int64("height", b.Header.Height), + ) // extend block data eds, err := extendBlock(b.Data) if err != nil { From e39dfaba5eb48b5a9eb843e1d3304ca8ef583449 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 12:16:09 +0200 Subject: [PATCH 0562/1008] chore(deps): bump codecov/codecov-action from 3.1.1 to 3.1.3 (#2122) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 5f9fe88a06..f4c5626246 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -57,7 +57,7 @@ jobs: - name: Test & Coverage run: make cover - - uses: codecov/codecov-action@v3.1.1 + - uses: codecov/codecov-action@v3.1.3 with: file: ./coverage.txt From b10c447a3f7b4b8199156d5df0b6854c8f9b81c1 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 26 Apr 2023 20:24:34 +0800 Subject: [PATCH 0563/1008] refactor(share/p2p/shrex): use default timeout for shrex-eds client (#2114) ## Overview Use default timeout for shrex-eds client if no deadline specified by provided context. --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/p2p/shrexeds/client.go | 39 ++++++++++++++++++++++++++++++------ share/p2p/shrexnd/client.go | 6 +++--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 330fc2c026..f6ad9d2992 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -9,6 +9,7 @@ import ( "time" "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" @@ -23,8 +24,10 @@ import ( // Client is responsible for requesting EDSs for blocksync over the ShrEx/EDS protocol. type Client struct { + params *Parameters protocolID protocol.ID - host host.Host + + host host.Host } // NewClient creates a new ShrEx/EDS client. @@ -34,6 +37,7 @@ func NewClient(params *Parameters, host host.Host) (*Client, error) { } return &Client{ + params: params, host: host, protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), }, nil @@ -80,11 +84,7 @@ func (c *Client) doRequest( return nil, fmt.Errorf("failed to open stream: %w", err) } - if dl, ok := ctx.Deadline(); ok { - if err = stream.SetDeadline(dl); err != nil { - log.Debugw("client: error setting deadline: %s", "err", err) - } - } + c.setStreamDeadlines(ctx, stream) req := &pb.EDSRequest{Hash: dataHash} @@ -131,3 +131,30 @@ func (c *Client) doRequest( return nil, p2p.ErrInvalidResponse } } + +func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) { + // set read/write deadline to use context deadline if it exists + if dl, ok := ctx.Deadline(); ok { + err := stream.SetDeadline(dl) + if err == nil { + return + } + log.Debugw("client: setting deadline: %s", "err", err) + } + + // if deadline not set, client read deadline defaults to server write deadline + if c.params.ServerWriteTimeout != 0 { + err := stream.SetReadDeadline(time.Now().Add(c.params.ServerWriteTimeout)) + if err != nil { + log.Debugw("client: setting read deadline", "err", err) + } + } + + // if deadline not set, client write deadline defaults to server read deadline + if c.params.ServerReadTimeout != 0 { + err := stream.SetWriteDeadline(time.Now().Add(c.params.ServerReadTimeout)) + if err != nil { + log.Debugw("client: setting write deadline", "err", err) + } + } +} diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 78af04e61b..89e9e76e7d 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -160,10 +160,10 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) deadline, ok := ctx.Deadline() if ok { err := stream.SetDeadline(deadline) - if err != nil { - log.Debugw("client-nd: set write deadline", "err", err) + if err == nil { + return } - return + log.Debugw("client-nd: set stream deadline", "err", err) } // if deadline not set, client read deadline defaults to server write deadline From 2d5b3859fa6001e65abbec96584473375ea29649 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 26 Apr 2023 20:50:10 +0800 Subject: [PATCH 0564/1008] feat(shrex/peer-manager): add metrics to peer manager (#1924) ## Overview Introduce metrics to peer manager. Resolves https://github.com/celestiaorg/celestia-node/issues/1780 --- das/metrics.go | 2 +- nodebuilder/settings.go | 2 + nodebuilder/share/opts.go | 11 ++ share/getters/shrex.go | 2 +- share/p2p/peers/manager.go | 117 +++++++++------- share/p2p/peers/metrics.go | 277 +++++++++++++++++++++++++++++++++++++ share/p2p/peers/options.go | 10 ++ share/p2p/peers/pool.go | 2 +- 8 files changed, 368 insertions(+), 55 deletions(-) create mode 100644 nodebuilder/share/opts.go create mode 100644 share/p2p/peers/metrics.go diff --git a/das/metrics.go b/das/metrics.go index 5a272ac4cc..5a6262969b 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -131,7 +131,7 @@ func (d *DASer) InitMetrics() error { ) if err != nil { - return fmt.Errorf("regestering metrics callback: %w", err) + return fmt.Errorf("registering metrics callback: %w", err) } return nil diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 3f947feba4..0ef19d8fba 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -20,6 +20,7 @@ import ( modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/state" ) @@ -77,6 +78,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti opts = fx.Options( baseComponents, fx.Invoke(das.WithMetrics), + fx.Invoke(share.WithPeerManagerMetrics), // add more monitoring here ) case node.Bridge: diff --git a/nodebuilder/share/opts.go b/nodebuilder/share/opts.go new file mode 100644 index 0000000000..93a9f4c4d5 --- /dev/null +++ b/nodebuilder/share/opts.go @@ -0,0 +1,11 @@ +package share + +import ( + "github.com/celestiaorg/celestia-node/share/p2p/peers" +) + +// WithPeerManagerMetrics is a utility function that is expected to be +// "invoked" by the fx lifecycle. +func WithPeerManagerMetrics(m *peers.Manager) error { + return m.WithMetrics() +} diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 72626f406a..2313c4499f 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -138,7 +138,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( cancel() switch { case getErr == nil: - setStatus(peers.ResultSuccess) + setStatus(peers.ResultNoop) return nd, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 71774a9211..7a2dcc6dc3 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -24,18 +24,20 @@ import ( ) const ( - // ResultSuccess indicates operation was successful and no extra action is required - ResultSuccess result = iota + // ResultNoop indicates operation was successful and no extra action is required + ResultNoop result = "result_noop" // ResultSynced will save the status of pool as "synced" and will remove peers from it - ResultSynced + ResultSynced = "result_synced" // ResultCooldownPeer will put returned peer on cooldown, meaning it won't be available by Peer // method for some time - ResultCooldownPeer + ResultCooldownPeer = "result_cooldown_peer" // ResultBlacklistPeer will blacklist peer. Blacklisted peers will be disconnected and blocked from // any p2p communication in future by libp2p Gater - ResultBlacklistPeer + ResultBlacklistPeer = "result_blacklist_peer" ) +type result string + var log = logging.Logger("shrex/peer-manager") // Manager keeps track of peers coming from shrex.Sub and from discovery @@ -61,6 +63,8 @@ type Manager struct { // hashes that are not in the chain blacklistedHashes map[string]bool + metrics *metrics + cancel context.CancelFunc done chan struct{} } @@ -69,8 +73,6 @@ type Manager struct { // peer from Peer method type DoneFunc func(result) -type result int - type syncPool struct { *pool @@ -134,7 +136,8 @@ func (m *Manager) Start(startCtx context.Context) error { ctx, cancel := context.WithCancel(context.Background()) m.cancel = cancel - err := m.shrexSub.AddValidator(m.Validate) + validatorFn := m.metrics.validationObserver(m.Validate) + err := m.shrexSub.AddValidator(validatorFn) if err != nil { return fmt.Errorf("registering validator: %w", err) } @@ -174,7 +177,6 @@ func (m *Manager) Stop(ctx context.Context) error { func (m *Manager) Peer( ctx context.Context, datahash share.DataHash, ) (peer.ID, DoneFunc, error) { - logger := log.With("hash", datahash.String()) p := m.validatedPool(datahash.String()) // first, check if a peer is available for the given datahash @@ -182,64 +184,68 @@ func (m *Manager) Peer( if ok { // some pools could still have blacklisted peers in storage if m.isBlacklistedPeer(peerID) { - logger.Debugw("removing blacklisted peer from pool", + log.Debugw("removing blacklisted peer from pool", "peer", peerID.String()) p.remove(peerID) return m.Peer(ctx, datahash) } - logger.Debugw("get peer from shrexsub pool", - "peer", peerID.String(), - "pool_size", p.size()) - return peerID, m.doneFunc(datahash, peerID, false), nil + return m.newPeer(datahash, peerID, sourceShrexSub, p.len(), 0) } // if no peer for datahash is currently available, try to use full node // obtained from discovery peerID, ok = m.fullNodes.tryGet() if ok { - logger.Debugw("got peer from full nodes pool", - "peer", peerID.String(), - "pool_size", m.fullNodes.size()) - return peerID, m.doneFunc(datahash, peerID, true), nil + return m.newPeer(datahash, peerID, sourceFullNodes, m.fullNodes.len(), 0) } // no peers are available right now, wait for the first one start := time.Now() select { case peerID = <-p.next(ctx): - logger.Debugw("got peer from shrexSub pool after wait", - "peer", peerID.String(), - "pool_size", p.size(), - "after (s)", time.Since(start)) - return peerID, m.doneFunc(datahash, peerID, false), nil + return m.newPeer(datahash, peerID, sourceShrexSub, p.len(), time.Since(start)) case peerID = <-m.fullNodes.next(ctx): - logger.Debugw("got peer from full nodes pool after wait", - "peer", peerID.String(), - "pool_size", m.fullNodes.size(), - "after (s)", time.Since(start)) - return peerID, m.doneFunc(datahash, peerID, true), nil + return m.newPeer(datahash, peerID, sourceFullNodes, m.fullNodes.len(), time.Since(start)) case <-ctx.Done(): return "", nil, ctx.Err() } } -func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, fromFull bool) DoneFunc { +func (m *Manager) newPeer( + datahash share.DataHash, + peerID peer.ID, + source peerSource, + poolSize int, + waitTime time.Duration) (peer.ID, DoneFunc, error) { + log.Debugw("got peer", + "hash", datahash.String(), + "peer", peerID.String(), + "source", source, + "pool_size", poolSize, + "wait (s)", waitTime) + m.metrics.observeGetPeer(source, poolSize, waitTime) + return peerID, m.doneFunc(datahash, peerID, source), nil +} + +func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, source peerSource) DoneFunc { return func(result result) { - log.Debugw("set peer status", - "peer", peerID, + log.Debugw("set peer result", "hash", datahash.String(), + "peer", peerID, + "source", source, "result", result) + m.metrics.observeDoneResult(source, result) switch result { - case ResultSuccess: + case ResultNoop: case ResultSynced: - m.getOrCreatePool(datahash.String()).markSynced() + m.markPoolAsSynced(datahash.String()) case ResultCooldownPeer: m.getOrCreatePool(datahash.String()).putOnCooldown(peerID) - if fromFull { + if source == sourceFullNodes { m.fullNodes.putOnCooldown(peerID) } case ResultBlacklistPeer: - m.blacklistPeers(peerID) + m.blacklistPeers(reasonMisbehave, peerID) } } } @@ -309,6 +315,16 @@ func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notif return pubsub.ValidationIgnore } +func (m *Manager) validatedPool(datahash string) *syncPool { + p := m.getOrCreatePool(datahash) + if p.isValidatedDataHash.CompareAndSwap(false, true) { + log.Debugw("pool marked validated", + "hash", datahash, + "after (s)", time.Since(p.createdAt)) + } + return p +} + func (m *Manager) getOrCreatePool(datahash string) *syncPool { m.lock.Lock() defer m.lock.Unlock() @@ -325,8 +341,11 @@ func (m *Manager) getOrCreatePool(datahash string) *syncPool { return p } -func (m *Manager) blacklistPeers(peerIDs ...peer.ID) { - log.Debugw("blacklisting peers", "peers", peerIDs) +func (m *Manager) blacklistPeers(reason blacklistPeerReason, peerIDs ...peer.ID) { + log.Debugw("blacklisting peers", + "peers", peerIDs, + "reason", reason) + m.metrics.observeBlacklistPeers(reason, len(peerIDs)) if !m.params.EnableBlackListing { return @@ -356,14 +375,6 @@ func (m *Manager) isBlacklistedHash(hash share.DataHash) bool { return m.blacklistedHashes[hash.String()] } -func (m *Manager) validatedPool(hashStr string) *syncPool { - p := m.getOrCreatePool(hashStr) - if p.isValidatedDataHash.CompareAndSwap(false, true) { - log.Debugw("pool marked validated", "hash", hashStr) - } - return p -} - func (m *Manager) GC(ctx context.Context) { ticker := time.NewTicker(m.params.GcInterval) defer ticker.Stop() @@ -378,7 +389,7 @@ func (m *Manager) GC(ctx context.Context) { blacklist = m.cleanUp() if len(blacklist) > 0 { - m.blacklistPeers(blacklist...) + m.blacklistPeers(reasonInvalidHash, blacklist...) } } } @@ -420,13 +431,15 @@ func (m *Manager) cleanUp() []peer.ID { return blacklist } -func (p *syncPool) markSynced() { - p.isSynced.Store(true) - old := (*unsafe.Pointer)(unsafe.Pointer(&p.pool)) - // release pointer to old pool to free up memory - atomic.StorePointer(old, unsafe.Pointer(newPool(time.Second))) +func (m *Manager) markPoolAsSynced(datahash string) { + p := m.getOrCreatePool(datahash) + if p.isSynced.CompareAndSwap(false, true) { + p.isSynced.Store(true) + old := (*unsafe.Pointer)(unsafe.Pointer(&p.pool)) + // release pointer to old pool to free up memory + atomic.StorePointer(old, unsafe.Pointer(newPool(time.Second))) + } } - func (p *syncPool) add(peers ...peer.ID) { if !p.isSynced.Load() { p.pool.add(peers...) diff --git a/share/p2p/peers/metrics.go b/share/p2p/peers/metrics.go new file mode 100644 index 0000000000..412cc01996 --- /dev/null +++ b/share/p2p/peers/metrics.go @@ -0,0 +1,277 @@ +package peers + +import ( + "context" + "fmt" + "sync" + "time" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" + + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" +) + +const ( + observeTimeout = 100 * time.Millisecond + + isInstantKey = "is_instant" + doneResultKey = "done_result" + + sourceKey = "source" + sourceShrexSub peerSource = "shrexsub" + sourceFullNodes peerSource = "full_nodes" + + blacklistPeerReasonKey = "blacklist_reason" + reasonInvalidHash blacklistPeerReason = "invalid_hash" + reasonMisbehave blacklistPeerReason = "misbehave" + + validationResultKey = "validation_result" + validationAccept = "accept" + validationReject = "reject" + validationIgnore = "ignore" + + peerStatusKey = "peer_status" + peerStatusActive peerStatus = "active" + peerStatusCooldown peerStatus = "cooldown" + + poolStatusKey = "pool_status" + poolStatusCreated poolStatus = "created" + poolStatusValidated poolStatus = "validated" + poolStatusSynced poolStatus = "synced" + poolStatusBlacklisted poolStatus = "blacklisted" + // Pool status model: + // created(unvalidated) + // / \ + // validated(unsynced) blacklisted + // | + // synced +) + +var ( + meter = global.MeterProvider().Meter("shrex_peer_manager") +) + +type blacklistPeerReason string + +type peerStatus string + +type poolStatus string + +type peerSource string + +type metrics struct { + getPeer syncint64.Counter // attributes: source, is_instant + getPeerWaitTimeHistogram syncint64.Histogram // attributes: source + getPeerPoolSizeHistogram syncint64.Histogram // attributes: source + doneResult syncint64.Counter // attributes: source, done_result + validationResult syncint64.Counter // attributes: validation_result + + shrexPools asyncint64.Gauge // attributes: pool_status + fullNodesPool asyncint64.Gauge // attributes: pool_status + blacklistedPeersByReason sync.Map + blacklistedPeers asyncint64.Gauge // attributes: blacklist_reason +} + +func initMetrics(manager *Manager) (*metrics, error) { + getPeer, err := meter.SyncInt64().Counter("peer_manager_get_peer_counter", + instrument.WithDescription("get peer counter")) + if err != nil { + return nil, err + } + + getPeerWaitTimeHistogram, err := meter.SyncInt64().Histogram("peer_manager_get_peer_ms_time_hist", + instrument.WithDescription("get peer time histogram(ms), observed only for async get(is_instant = false)")) + if err != nil { + return nil, err + } + + getPeerPoolSizeHistogram, err := meter.SyncInt64().Histogram("peer_manager_get_peer_pool_size_hist", + instrument.WithDescription("amount of available active peers in pool at time when get was called")) + if err != nil { + return nil, err + } + + doneResult, err := meter.SyncInt64().Counter("peer_manager_done_result_counter", + instrument.WithDescription("done results counter")) + if err != nil { + return nil, err + } + + validationResult, err := meter.SyncInt64().Counter("peer_manager_validation_result_counter", + instrument.WithDescription("validation result counter")) + if err != nil { + return nil, err + } + + shrexPools, err := meter.AsyncInt64().Gauge("peer_manager_pools_gauge", + instrument.WithDescription("pools amount")) + if err != nil { + return nil, err + } + + fullNodesPool, err := meter.AsyncInt64().Gauge("peer_manager_full_nodes_gauge", + instrument.WithDescription("full nodes pool peers amount")) + if err != nil { + return nil, err + } + + blacklisted, err := meter.AsyncInt64().Gauge("peer_manager_blacklisted_peers", + instrument.WithDescription("blacklisted peers amount")) + if err != nil { + return nil, err + } + + metrics := &metrics{ + getPeer: getPeer, + getPeerWaitTimeHistogram: getPeerWaitTimeHistogram, + doneResult: doneResult, + validationResult: validationResult, + shrexPools: shrexPools, + fullNodesPool: fullNodesPool, + getPeerPoolSizeHistogram: getPeerPoolSizeHistogram, + blacklistedPeers: blacklisted, + } + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + shrexPools, + fullNodesPool, + blacklisted, + }, + func(ctx context.Context) { + for poolStatus, count := range manager.shrexPools() { + shrexPools.Observe(ctx, count, + attribute.String(poolStatusKey, string(poolStatus))) + } + + fullNodesPool.Observe(ctx, int64(manager.fullNodes.len()), + attribute.String(peerStatusKey, string(peerStatusActive))) + fullNodesPool.Observe(ctx, int64(manager.fullNodes.cooldown.len()), + attribute.String(peerStatusKey, string(peerStatusCooldown))) + + metrics.blacklistedPeersByReason.Range(func(key, value any) bool { + reason := key.(blacklistPeerReason) + amount := value.(int) + blacklisted.Observe(ctx, int64(amount), + attribute.String(blacklistPeerReasonKey, string(reason))) + return true + }) + }, + ) + + if err != nil { + return nil, fmt.Errorf("registering metrics callback: %w", err) + } + return metrics, nil +} + +func (m *metrics) observeGetPeer(source peerSource, poolSize int, waitTime time.Duration) { + if m == nil { + return + } + ctx, cancel := context.WithTimeout(context.Background(), observeTimeout) + defer cancel() + m.getPeer.Add(ctx, 1, + attribute.String(sourceKey, string(source)), + attribute.Bool(isInstantKey, waitTime == 0)) + if source == sourceShrexSub { + m.getPeerPoolSizeHistogram.Record(ctx, int64(poolSize), + attribute.String(sourceKey, string(source))) + } + + // record wait time only for async gets + if waitTime > 0 { + m.getPeerWaitTimeHistogram.Record(ctx, waitTime.Milliseconds(), + attribute.String(sourceKey, string(source))) + } +} + +func (m *metrics) observeDoneResult(source peerSource, result result) { + if m == nil { + return + } + ctx, cancel := context.WithTimeout(context.Background(), observeTimeout) + defer cancel() + + m.doneResult.Add(ctx, 1, + attribute.String(sourceKey, string(source)), + attribute.String(doneResultKey, string(result))) +} + +// validationObserver is a middleware that observes validation results as metrics +func (m *metrics) validationObserver(validator shrexsub.ValidatorFn) shrexsub.ValidatorFn { + if m == nil { + return validator + } + return func(ctx context.Context, id peer.ID, n shrexsub.Notification) pubsub.ValidationResult { + res := validator(ctx, id, n) + + var resStr string + switch res { + case pubsub.ValidationAccept: + resStr = validationAccept + case pubsub.ValidationReject: + resStr = validationReject + case pubsub.ValidationIgnore: + resStr = validationIgnore + default: + resStr = "unknown" + } + + observeCtx, cancel := context.WithTimeout(context.Background(), observeTimeout) + defer cancel() + + m.validationResult.Add(observeCtx, 1, + attribute.String(validationResultKey, resStr)) + return res + } +} + +// observeBlacklistPeers stores amount of blacklisted peers by reason +func (m *metrics) observeBlacklistPeers(reason blacklistPeerReason, amount int) { + if m == nil { + return + } + for { + prevVal, loaded := m.blacklistedPeersByReason.LoadOrStore(reason, amount) + if !loaded { + return + } + + newVal := prevVal.(int) + amount + if m.blacklistedPeersByReason.CompareAndSwap(reason, prevVal, newVal) { + return + } + } +} + +// shrexPools collects amount of shrex pools by poolStatus +func (m *Manager) shrexPools() map[poolStatus]int64 { + m.lock.Lock() + defer m.lock.Unlock() + + shrexPools := make(map[poolStatus]int64) + for _, p := range m.pools { + if !p.isValidatedDataHash.Load() { + shrexPools[poolStatusCreated]++ + continue + } + + if p.isSynced.Load() { + shrexPools[poolStatusSynced]++ + continue + } + + // pool is validated but not synced + shrexPools[poolStatusValidated]++ + } + + shrexPools[poolStatusBlacklisted] = int64(len(m.blacklistedHashes)) + return shrexPools +} diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index 6812202578..cfda906071 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -55,3 +55,13 @@ func DefaultParameters() Parameters { EnableBlackListing: false, } } + +// WithMetrics turns on metric collection in peer manager. +func (m *Manager) WithMetrics() error { + metrics, err := initMetrics(m) + if err != nil { + return fmt.Errorf("peer-manager: init metrics: %w", err) + } + m.metrics = metrics + return nil +} diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index bcf425a475..4eae614ca1 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -195,7 +195,7 @@ func (p *pool) checkHasPeers() { } } -func (p *pool) size() int { +func (p *pool) len() int { p.m.RLock() defer p.m.RUnlock() return p.activeCount From fc691eaa3d21d8a6351dbd1879b2b3d08fe54b38 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 26 Apr 2023 07:28:59 -0700 Subject: [PATCH 0565/1008] fix(share/shrex): hotfixes for shrex getter respecting context (#2115) Closes https://github.com/celestiaorg/celestia-node/issues/2110, related to https://github.com/celestiaorg/celestia-node/issues/2109. Only adds a hotfix for 2109 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Vlad Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> --- share/getters/shrex.go | 6 ++++++ share/getters/shrex_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 2313c4499f..c93a138ef7 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -67,6 +67,9 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex err error ) for { + if ctx.Err() != nil { + return nil, ctx.Err() + } attempt++ start := time.Now() peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) @@ -120,6 +123,9 @@ func (sg *ShrexGetter) GetSharesByNamespace( err error ) for { + if ctx.Err() != nil { + return nil, ctx.Err() + } attempt++ start := time.Now() peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index b9960c1fac..7578195639 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -104,6 +104,21 @@ func TestShrexGetter(t *testing.T) { require.Equal(t, eds.Flattened(), got.Flattened()) }) + t.Run("EDS_ctx_deadline", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + + // generate test data + _, dah, _ := generateTestEDS(t) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + cancel() + _, err := getter.GetEDS(ctx, &dah) + require.ErrorIs(t, err, context.Canceled) + }) + t.Run("EDS_err_not_found", func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) From 71fc46d0f0a118f263efe94fe9796d9d5debd3f7 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:33:39 +0800 Subject: [PATCH 0566/1008] feat(das): add backoff to retry jobs (#2103) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/2027 --- das/backoff.go | 59 ++++++++++++++++++++++++ das/backoff_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++++ das/state.go | 74 ++++++++++++++++++++---------- das/state_test.go | 6 ++- 4 files changed, 222 insertions(+), 25 deletions(-) create mode 100644 das/backoff.go create mode 100644 das/backoff_test.go diff --git a/das/backoff.go b/das/backoff.go new file mode 100644 index 0000000000..f92c04d1ef --- /dev/null +++ b/das/backoff.go @@ -0,0 +1,59 @@ +package das + +import ( + "time" +) + +var ( + // first retry attempt should happen after defaultBackoffInitialInterval + defaultBackoffInitialInterval = time.Minute + // next retry attempt will happen with delay of previous one multiplied by defaultBackoffMultiplier + defaultBackoffMultiplier = 4 + // after defaultBackoffMaxRetryCount amount of attempts retry backoff interval will stop growing + // and each retry attempt will produce WARN log + defaultBackoffMaxRetryCount = 4 +) + +// retryStrategy defines a backoff for retries. +type retryStrategy struct { + // attempts delays will follow durations stored in retryIntervals + retryIntervals []time.Duration +} + +// newRetryStrategy creates and initializes a new retry backoff. +func newRetryStrategy(retryIntervals []time.Duration) retryStrategy { + return retryStrategy{retryIntervals: retryIntervals} +} + +// nextRetry creates a retry attempt with a backoff delay based on the retry backoff. +// It takes the number of retry attempts and the time of the last attempt as inputs and returns a +// retry instance and a boolean value indicating whether the retries amount have exceeded. +func (s retryStrategy) nextRetry(lastRetry retryAttempt, lastAttempt time.Time, +) (retry retryAttempt, retriesExceeded bool) { + lastRetry.count++ + + if len(s.retryIntervals) == 0 { + return lastRetry, false + } + + if lastRetry.count > len(s.retryIntervals) { + // try count exceeded backoff try limit + lastRetry.after = lastAttempt.Add(s.retryIntervals[len(s.retryIntervals)-1]) + return lastRetry, true + } + + lastRetry.after = lastAttempt.Add(s.retryIntervals[lastRetry.count-1]) + return lastRetry, false +} + +// exponentialBackoff generates an array of time.Duration values using an exponential growth +// multiplier. +func exponentialBackoff(baseInterval time.Duration, multiplier, amount int) []time.Duration { + backoff := make([]time.Duration, 0, amount) + next := baseInterval + for i := 0; i < amount; i++ { + backoff = append(backoff, next) + next *= time.Duration(multiplier) + } + return backoff +} diff --git a/das/backoff_test.go b/das/backoff_test.go new file mode 100644 index 0000000000..e032ec175a --- /dev/null +++ b/das/backoff_test.go @@ -0,0 +1,108 @@ +package das + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func Test_exponentialBackoff(t *testing.T) { + type args struct { + baseInterval time.Duration + factor int + amount int + } + tests := []struct { + name string + args args + want []time.Duration + }{ + { + name: "defaults", + args: args{ + baseInterval: time.Minute, + factor: 4, + amount: 4, + }, + want: []time.Duration{ + time.Minute, + 4 * time.Minute, + 16 * time.Minute, + 64 * time.Minute, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, + tt.want, exponentialBackoff(tt.args.baseInterval, tt.args.factor, tt.args.amount), + "exponentialBackoff(%v, %v, %v)", tt.args.baseInterval, tt.args.factor, tt.args.amount) + }) + } +} + +func Test_retryStrategy_nextRetry(t *testing.T) { + tNow := time.Now() + type args struct { + retry retryAttempt + lastAttempt time.Time + } + tests := []struct { + name string + backoff retryStrategy + args args + wantRetry retryAttempt + wantRetriesExceeded bool + }{ + { + name: "empty_strategy", + backoff: newRetryStrategy(nil), + args: args{ + retry: retryAttempt{count: 1}, + lastAttempt: tNow, + }, + wantRetry: retryAttempt{ + count: 2, + }, + wantRetriesExceeded: false, + }, + { + name: "before_limit", + backoff: newRetryStrategy([]time.Duration{time.Second, time.Minute}), + args: args{ + retry: retryAttempt{count: 1}, + lastAttempt: tNow, + }, + wantRetry: retryAttempt{ + count: 2, + after: tNow.Add(time.Minute), + }, + wantRetriesExceeded: false, + }, + { + name: "after_limit", + backoff: newRetryStrategy([]time.Duration{time.Second, time.Minute}), + args: args{ + retry: retryAttempt{count: 2}, + lastAttempt: tNow, + }, + wantRetry: retryAttempt{ + count: 3, + after: tNow.Add(time.Minute), + }, + wantRetriesExceeded: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := retryStrategy{ + retryIntervals: tt.backoff.retryIntervals, + } + gotRetry, gotRetriesExceeded := s.nextRetry(tt.args.retry, tt.args.lastAttempt) + assert.Equalf(t, tt.wantRetry, gotRetry, + "nextRetry(%v, %v)", tt.args.retry, tt.args.lastAttempt) + assert.Equalf(t, tt.wantRetriesExceeded, gotRetriesExceeded, + "nextRetry(%v, %v)", tt.args.retry, tt.args.lastAttempt) + }) + } +} diff --git a/das/state.go b/das/state.go index 3fddb7ce17..12de2a0b01 100644 --- a/das/state.go +++ b/das/state.go @@ -3,6 +3,7 @@ package das import ( "context" "sync/atomic" + "time" "github.com/celestiaorg/celestia-node/header" ) @@ -16,11 +17,14 @@ type coordinatorState struct { // keeps track of running workers inProgress map[int]func() workerState + + // retryStrategy implements retry backoff + retryStrategy retryStrategy // stores heights of failed headers with amount of retry attempt as value - failed map[uint64]int + failed map[uint64]retryAttempt // inRetry stores (height -> attempt count) of failed headers that are currently being retried by // workers - inRetry map[uint64]int + inRetry map[uint64]retryAttempt // nextJobID is a unique identifier that will be used for creation of next job nextJobID int @@ -35,14 +39,26 @@ type coordinatorState struct { catchUpDoneCh chan struct{} } +// retryAttempt represents a retry attempt with a backoff delay. +type retryAttempt struct { + // count specifies the number of retry attempts made so far. + count int + // after specifies the time for the next retry attempt. + after time.Time +} + // newCoordinatorState initiates state for samplingCoordinator func newCoordinatorState(params Parameters) coordinatorState { return coordinatorState{ sampleFrom: params.SampleFrom, samplingRange: params.SamplingRange, inProgress: make(map[int]func() workerState), - failed: make(map[uint64]int), - inRetry: make(map[uint64]int), + retryStrategy: newRetryStrategy(exponentialBackoff( + defaultBackoffInitialInterval, + defaultBackoffMultiplier, + defaultBackoffMaxRetryCount)), + failed: make(map[uint64]retryAttempt), + inRetry: make(map[uint64]retryAttempt), nextJobID: 0, next: params.SampleFrom, networkHead: params.SampleFrom, @@ -54,10 +70,10 @@ func (s *coordinatorState) resumeFromCheckpoint(c checkpoint) { s.next = c.SampleFrom s.networkHead = c.NetworkHead - for h := range c.Failed { - // TODO(@walldiss): reset retry counter to allow retries after restart. Will be removed when retry - // backoff is implemented. - s.failed[h] = 0 + for h, count := range c.Failed { + s.failed[h] = retryAttempt{ + count: count, + } } } @@ -77,14 +93,19 @@ func (s *coordinatorState) handleResult(res result) { // update failed heights for h := range res.failed { - failCount := 1 - if res.job.jobType == retryJob { - // if job was already in retry and failed again, persist attempt count - failCount += s.inRetry[h] + // if job was already in retry and failed again, carry over attempt count + lastRetry, ok := s.inRetry[h] + if ok { delete(s.inRetry, h) } - s.failed[h] = failCount + nextRetry, retryExceeded := s.retryStrategy.nextRetry(lastRetry, time.Now()) + if retryExceeded { + log.Warnw("header exceeded maximum amount of sampling attempts", + "height", h, + "attempts", nextRetry.count) + } + s.failed[h] = nextRetry } s.checkDone() } @@ -125,15 +146,15 @@ func (s *coordinatorState) recentJob(header *header.ExtendedHeader) job { } } -// nextJob will return next catchup or retry job according to priority (catchup > retry) +// nextJob will return next catchup or retry job according to priority (retry -> catchup) func (s *coordinatorState) nextJob() (next job, found bool) { - // check for catchup job - if job, found := s.catchupJob(); found { + // check for if any retry jobs are available + if job, found := s.retryJob(); found { return job, found } - // if caught up already, make a retry job - return s.retryJob() + // if no retry jobs, make a catchup job + return s.catchupJob() } // catchupJob creates a catchup job if catchup is not finished @@ -153,15 +174,15 @@ func (s *coordinatorState) catchupJob() (next job, found bool) { // retryJob creates a job to retry previously failed header func (s *coordinatorState) retryJob() (next job, found bool) { - for h, count := range s.failed { - // TODO(@walldiss): limit max amount of retries until retry backoff is implemented - if count > 3 { + for h, attempt := range s.failed { + if !attempt.canRetry() { + // height will be retried later continue } // move header from failed into retry delete(s.failed, h) - s.inRetry[h] = count + s.inRetry[h] = attempt j := s.newJob(retryJob, h, h) return j, true } @@ -217,8 +238,8 @@ func (s *coordinatorState) unsafeStats() SamplingStats { } // set lowestFailedOrInProgress to minimum failed - 1 - for h, count := range s.failed { - failed[h] += count + for h, retry := range s.failed { + failed[h] += retry.count if h < lowestFailedOrInProgress { lowestFailedOrInProgress = h } @@ -263,3 +284,8 @@ func (s *coordinatorState) waitCatchUp(ctx context.Context) error { } return nil } + +// canRetry returns true if the time stored in the "after" has passed. +func (r retryAttempt) canRetry() bool { + return r.after.Before(time.Now()) +} diff --git a/das/state_test.go b/das/state_test.go index 1bef1ba223..57425082eb 100644 --- a/das/state_test.go +++ b/das/state_test.go @@ -47,7 +47,11 @@ func Test_coordinatorStats(t *testing.T) { } }, }, - failed: map[uint64]int{22: 1, 23: 1, 24: 2}, + failed: map[uint64]retryAttempt{ + 22: {count: 1}, + 23: {count: 1}, + 24: {count: 2}, + }, nextJobID: 0, next: 31, networkHead: 100, From 1dbd549c4d3997ec652c55bd36cf8954e69cc1fd Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 27 Apr 2023 15:29:27 +0200 Subject: [PATCH 0567/1008] Revert "deps: bump libp2p (#2127)" (#2136) This reverts commit 82fbba8. The update breaks the event system. It likely happened after this libp2p/go-libp2p#1574, but we don't need to debug it right now, as a simple revert fixes this --- go.mod | 60 +++++++++++++-------------- go.sum | 125 +++++++++++++++++++++++++++------------------------------ 2 files changed, 90 insertions(+), 95 deletions(-) diff --git a/go.mod b/go.mod index 27500f3dc4..15557baa63 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/imdario/mergo v0.3.15 github.com/ipfs/go-blockservice v0.5.0 - github.com/ipfs/go-cid v0.4.1 + github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 @@ -43,7 +43,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 - github.com/libp2p/go-libp2p v0.27.1 + github.com/libp2p/go-libp2p v0.26.3 github.com/libp2p/go-libp2p-kad-dht v0.21.0 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 @@ -51,7 +51,7 @@ require ( github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.9.0 + github.com/multiformats/go-multiaddr v0.8.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 @@ -60,7 +60,7 @@ require ( github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.1 github.com/tendermint/tendermint v0.35.4 go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 @@ -76,7 +76,7 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/text v0.8.0 google.golang.org/grpc v1.53.0 - google.golang.org/protobuf v1.30.0 + google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) require ( @@ -106,11 +106,11 @@ require ( github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chzyer/readline v1.5.1 // indirect + github.com/chzyer/readline v1.5.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/confio/ics23/go v0.9.0 // indirect - github.com/containerd/cgroups v1.1.0 // indirect + github.com/containerd/cgroups v1.0.4 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha8 // indirect @@ -152,19 +152,19 @@ require ( github.com/go-openapi/spec v0.19.11 // indirect github.com/go-openapi/swag v0.19.11 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect + github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect @@ -183,10 +183,10 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/huin/goupnp v1.1.0 // indirect + github.com/huin/goupnp v1.0.3 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -212,15 +212,15 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.16.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/compress v1.15.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect - github.com/koron/go-ssdp v0.0.4 // indirect + github.com/koron/go-ssdp v0.0.3 // indirect github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect + github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect @@ -232,9 +232,9 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.53 // indirect + github.com/miekg/dns v1.1.50 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -245,11 +245,11 @@ require ( github.com/mtibben/percent v0.2.1 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.8.1 // indirect + github.com/multiformats/go-multibase v0.1.1 // indirect + github.com/multiformats/go-multicodec v0.8.0 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.9.2 // indirect + github.com/onsi/ginkgo/v2 v2.5.1 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -261,12 +261,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + github.com/quic-go/qtls-go1-19 v0.2.1 // indirect + github.com/quic-go/qtls-go1-20 v0.1.1 // indirect github.com/quic-go/quic-go v0.33.0 // indirect github.com/quic-go/webtransport-go v0.5.2 // indirect github.com/rakyll/statik v0.1.7 // indirect @@ -307,13 +307,13 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.10.0 // indirect + golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.6.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 5d7dcd43e0..9c542114b6 100644 --- a/go.sum +++ b/go.sum @@ -381,14 +381,14 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= -github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= -github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= -github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= @@ -421,8 +421,8 @@ github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1 github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= +github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -660,9 +660,8 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -722,9 +721,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -784,8 +782,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4= -github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -892,8 +890,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= -github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= +github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -910,9 +908,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.1.0 h1:gEe0Dp/lZmPZiDFzJJaOfUpOvv2MKUkoBX8lDrn9vKU= -github.com/huin/goupnp v1.1.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= @@ -960,8 +957,8 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= -github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= +github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -1129,15 +1126,15 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= -github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= @@ -1146,8 +1143,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= -github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= -github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= +github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= +github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -1194,11 +1191,11 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= -github.com/libp2p/go-libp2p v0.27.1 h1:k1u6RHsX3hqKnslDjsSgLNURxJ3O1atIZCY4gpMbbus= -github.com/libp2p/go-libp2p v0.27.1/go.mod h1:FAvvfQa/YOShUYdiSS03IR9OXzkcJXwcNA2FUCh9ImE= +github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= +github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= -github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= -github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= +github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= +github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= @@ -1456,8 +1453,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1476,8 +1473,8 @@ github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw= -github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1552,8 +1549,8 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= -github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= +github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -1572,13 +1569,13 @@ github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= +github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= +github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= -github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multicodec v0.8.0 h1:evBmgkbSQux+Ds2IgfhkO38Dl2GDtRW8/Rp6YiSHX/Q= +github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -1640,8 +1637,8 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= +github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1649,7 +1646,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1745,8 +1742,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1757,8 +1754,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.0 h1:LWuuqPQ1oa6x7BnmUOuo/aGwdX85QGhWZUBYWWW3zdk= @@ -1769,10 +1766,10 @@ github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKA github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= -github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= -github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= +github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= +github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= @@ -1912,9 +1909,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= @@ -2160,8 +2156,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 h1:5sPMf9HJXrvBWIamTw+rTST0bZ3Mho2n1p58M0+W99c= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2193,8 +2189,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2304,8 +2300,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2451,9 +2447,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2551,8 +2546,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2811,8 +2806,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= +google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 6bea32037de65ddea9cc4bbdf8811a400dc43afb Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 27 Apr 2023 15:50:47 +0200 Subject: [PATCH 0568/1008] fix(modp2p): increase ConnMgr defaults for Full and Bridge (#2139) We have ~1000 peers on BSR. Full and Bridge nodes can handle such load as libp2p connections, especially QUIC are cheap. Low defaults cause aggressive connection trimming and trimmed peers have to rediscover new FN/BNs over and over. This eventually leaves peers with no FN/BNs to get data from, as we don't reconnect to previously discovered peers due to backoff. --- nodebuilder/p2p/config.go | 2 +- nodebuilder/p2p/misc.go | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 9f01513949..589945ae9f 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -56,7 +56,7 @@ func DefaultConfig(tp node.Type) Config { }, MutualPeers: []string{}, PeerExchange: tp == node.Bridge || tp == node.Full, - ConnManager: defaultConnManagerConfig(), + ConnManager: defaultConnManagerConfig(tp), RoutingTableRefreshPeriod: defaultRoutingRefreshPeriod, } } diff --git a/nodebuilder/p2p/misc.go b/nodebuilder/p2p/misc.go index 4812d85cf1..9b7d8d8108 100644 --- a/nodebuilder/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -9,6 +9,8 @@ import ( "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/libp2p/go-libp2p/p2p/net/connmgr" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // connManagerConfig configures connection manager. @@ -21,11 +23,22 @@ type connManagerConfig struct { } // defaultConnManagerConfig returns defaults for ConnManagerConfig. -func defaultConnManagerConfig() connManagerConfig { - return connManagerConfig{ - Low: 50, - High: 100, - GracePeriod: time.Minute, +func defaultConnManagerConfig(tp node.Type) connManagerConfig { + switch tp { + case node.Light: + return connManagerConfig{ + Low: 50, + High: 100, + GracePeriod: time.Minute, + } + case node.Bridge, node.Full: + return connManagerConfig{ + Low: 800, + High: 1000, + GracePeriod: time.Minute, + } + default: + panic("unknown node type") } } From f8535b1b77d1fd5c663908efbc32540a02259a3a Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 27 Apr 2023 16:29:45 +0200 Subject: [PATCH 0569/1008] deps: bump go-header (#2140) Just a regular bump to fix the issue when either of boostrappers becomes unresponsive --- go.mod | 2 +- go.sum | 4 ++-- nodebuilder/header/module_test.go | 2 ++ nodebuilder/testing.go | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 15557baa63..a7221327bd 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/celestiaorg/celestia-app v0.12.2 github.com/celestiaorg/go-fraud v0.1.0 - github.com/celestiaorg/go-header v0.2.6 + github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.15.0 github.com/celestiaorg/rsmt2d v0.8.0 diff --git a/go.sum b/go.sum index 9c542114b6..e04d77814d 100644 --- a/go.sum +++ b/go.sum @@ -350,8 +350,8 @@ github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPc github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= -github.com/celestiaorg/go-header v0.2.6 h1:f1Mlyu+EfDpkuzO3SWU5dow+ga2vLQ7hNuvsOe//z64= -github.com/celestiaorg/go-header v0.2.6/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= +github.com/celestiaorg/go-header v0.2.7 h1:r0X9Dl7lqBkQpwG3ekQHC61n/QdwO6epuIxDkQ4YX4o= +github.com/celestiaorg/go-header v0.2.7/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index de604c9412..89293e4ab4 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -56,6 +56,7 @@ func TestConstructModule_StoreParams(t *testing.T) { func TestConstructModule_SyncerParams(t *testing.T) { cfg := DefaultConfig(node.Light) cfg.Syncer.TrustingPeriod = time.Hour + cfg.TrustedPeers = []string{"/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p"} var syncer *sync.Syncer[*header.ExtendedHeader] app := fxtest.New(t, fx.Supply(modp2p.Private), @@ -88,6 +89,7 @@ func TestConstructModule_SyncerParams(t *testing.T) { func TestConstructModule_ExchangeParams(t *testing.T) { cfg := DefaultConfig(node.Light) cfg.Client.MaxHeadersPerRangeRequest = 15 + cfg.TrustedPeers = []string{"/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p"} var exchange *p2p.Exchange[*header.ExtendedHeader] var exchangeServer *p2p.ExchangeServer[*header.ExtendedHeader] diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 2b13ef3fa7..6cb40a2b6c 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -35,6 +35,7 @@ func TestNode(t *testing.T, tp node.Type, opts ...fx.Option) *Node { func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Option) *Node { // avoids port conflicts cfg.RPC.Port = "0" + cfg.Header.TrustedPeers = []string{"/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p"} store := MockStore(t, cfg) ks, err := store.Keystore() From 11fea99767f549bebd1355b4c9065115af25ea91 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 27 Apr 2023 16:40:13 +0200 Subject: [PATCH 0570/1008] fix(nodebuilder/header): Cancel subscription when done (#2134) Found while debugging swamp. We never cancel subscription so when node attempts to shut down, there are outstanding subs that need to be closed. --------- Co-authored-by: Hlib Kanunnikov --- nodebuilder/header/service.go | 2 ++ nodebuilder/tests/api_test.go | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 nodebuilder/tests/api_test.go diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index a4c6149f49..7947b8975f 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -79,6 +79,8 @@ func (s *Service) Subscribe(ctx context.Context) (<-chan *header.ExtendedHeader, headerCh := make(chan *header.ExtendedHeader) go func() { defer close(headerCh) + defer subscription.Cancel() + for { h, err := subscription.NextHeader(ctx) if err != nil { diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go new file mode 100644 index 0000000000..27a10a5592 --- /dev/null +++ b/nodebuilder/tests/api_test.go @@ -0,0 +1,57 @@ +package tests + +import ( + "context" + "testing" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" +) + +// TestHeaderSubscription ensures that the header subscription over RPC works +// as intended and gets canceled successfully after rpc context cancellation. +func TestHeaderSubscription(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + + // start a bridge node + bridge := sw.NewBridgeNode() + err := bridge.Start(ctx) + require.NoError(t, err) + + cfg := nodebuilder.DefaultConfig(node.Light) + addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + require.NoError(t, err) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + + // start a light node that's connected to the bridge node + light := sw.NewNodeWithConfig(node.Light, cfg) + err = light.Start(ctx) + require.NoError(t, err) + + // subscribe to headers via the light node's RPC header subscription + subctx, subcancel := context.WithCancel(ctx) + sub, err := light.HeaderServ.Subscribe(subctx) + require.NoError(t, err) + // listen for 5 headers + for i := 0; i < 5; i++ { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case <-sub: + } + } + // cancel subscription via context + subcancel() + + // stop the light node and expect no outstanding subscription errors + err = light.Stop(ctx) + require.NoError(t, err) +} From 0af563b497e7fa7cbd93ca04a5a372b1c3fff861 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 2 May 2023 11:10:27 +0200 Subject: [PATCH 0571/1008] fix(gateway): deescalating log in gateway's writeErr (#2142) Closes #2141 writeError is called by almost all gateway endpoints. If a client makes a request for bad data, that is not an error for the server - but still useful as a debug log. --- api/gateway/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/gateway/util.go b/api/gateway/util.go index 6ae130c7f4..bffd7ebc88 100644 --- a/api/gateway/util.go +++ b/api/gateway/util.go @@ -6,7 +6,7 @@ import ( ) func writeError(w http.ResponseWriter, statusCode int, endpoint string, err error) { - log.Errorw("serving request", "endpoint", endpoint, "err", err) + log.Debugw("serving request", "endpoint", endpoint, "err", err) w.WriteHeader(statusCode) errBody, jerr := json.Marshal(err.Error()) From 5c0b1286af189fa8646f3984098b7ab260cb0504 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 2 May 2023 15:22:35 +0200 Subject: [PATCH 0572/1008] chore(nodebuilder): timeouts, errors and logs (#2125) Was originally for debugging purposes but actually solved the issue. The bootstrapper with a corrupted DAGstore state was not able to start quickly enough. --- nodebuilder/node.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 6968379c02..95f5d02116 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -2,6 +2,7 @@ package nodebuilder import ( "context" + "errors" "fmt" "strings" "time" @@ -15,6 +16,8 @@ import ( "github.com/libp2p/go-libp2p/core/routing" "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap/zapcore" "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" @@ -27,9 +30,12 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -const Timeout = time.Second * 15 +var Timeout = time.Second * 30 -var log = logging.Logger("node") +var ( + log = logging.Logger("node") + fxLog = logging.Logger("fx") +) // Node represents the core structure of a Celestia node. It keeps references to all // Celestia-specific components and services in one place and provides flexibility to run a @@ -92,7 +98,10 @@ func (n *Node) Start(ctx context.Context) error { err := n.start(ctx) if err != nil { - log.Errorf("starting %s Node: %s", n.Type, err) + log.Debugf("error starting %s Node: %s", n.Type, err) + if errors.Is(err, context.DeadlineExceeded) { + return fmt.Errorf("node: failed to start within timeout(%s): %w", Timeout, errors.Unwrap(err)) + } return fmt.Errorf("node: failed to start: %w", err) } @@ -134,11 +143,14 @@ func (n *Node) Stop(ctx context.Context) error { err := n.stop(ctx) if err != nil { - log.Errorf("Stopping %s Node: %s", n.Type, err) - return err + log.Debugf("error stopping %s Node: %s", n.Type, err) + if errors.Is(err, context.DeadlineExceeded) { + return fmt.Errorf("node: failed to stop within timeout(%s): %w", Timeout, errors.Unwrap(err)) + } + return fmt.Errorf("node: failed to stop: %w", err) } - log.Infof("stopped %s Node", n.Type) + log.Debugf("stopped %s Node", n.Type) return nil } @@ -149,7 +161,11 @@ func (n *Node) Stop(ctx context.Context) error { func newNode(opts ...fx.Option) (*Node, error) { node := new(Node) app := fx.New( - fx.NopLogger, + fx.WithLogger(func() fxevent.Logger { + zl := &fxevent.ZapLogger{Logger: fxLog.Desugar()} + zl.UseLogLevel(zapcore.DebugLevel) + return zl + }), fx.Populate(node), fx.Options(opts...), ) From 7e65a72da9f52955959b9c30dbc91083e6dc6835 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 2 May 2023 17:49:11 +0200 Subject: [PATCH 0573/1008] fix(cmd/rpc): parsing JSON for all parameters, and adding handler to GetSharesByNamespace (#2113) --- cmd/celestia/rpc.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 758af8091f..afbe13c3f8 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -92,7 +92,11 @@ func parseParams(method string, params []string) []interface{} { switch method { case "GetSharesByNamespace": // 1. Share Root - parsedParams[0] = params[0] + root, err := parseJSON(params[0]) + if err != nil { + panic(fmt.Errorf("couldn't parse share root as json: %v", err)) + } + parsedParams[0] = root // 2. NamespaceID if strings.HasPrefix(params[1], "0x") { decoded, err := hex.DecodeString(params[1][2:]) @@ -219,11 +223,11 @@ func parseParams(method string, params []string) []interface{} { for i, param := range params { if param[0] == '{' || param[0] == '[' { - var raw json.RawMessage - if err := json.Unmarshal([]byte(param), &raw); err == nil { - parsedParams[i] = raw - } else { + rawJSON, err := parseJSON(param) + if err != nil { parsedParams[i] = param + } else { + parsedParams[i] = rawJSON } } else { // try to parse arguments as numbers before adding them as strings @@ -317,3 +321,9 @@ func parseSignatureForHelpstring(methodSig reflect.StructField) string { simplifiedSignature += ")" return simplifiedSignature } + +func parseJSON(param string) (json.RawMessage, error) { + var raw json.RawMessage + err := json.Unmarshal([]byte(param), &raw) + return raw, err +} From 2055f22cc521f1c805b3996e00fe1591cec7302f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 3 May 2023 10:16:07 +0200 Subject: [PATCH 0574/1008] fix(nodebuilder/tests): Add dummy trusted peers to satisfy fx (#2154) Fx requires trusted peers now so this fix is needed in the meantime to satisfy that. Don't worry, these tests will be THOROUGHLY cleanussied in follow up PRs. --- nodebuilder/tests/reconstruct_test.go | 3 +++ nodebuilder/tests/swamp/swamp.go | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 876afc09a9..2c7d67a7e5 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -117,6 +117,9 @@ func TestFullReconstructFromLights(t *testing.T) { require.NoError(t, err) os.Setenv(p2p.EnvKeyCelestiaBootstrapper, "true") + cfg.Header.TrustedPeers = []string{ + "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", + } bootstrapper := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, bootstrapper.Start(ctx)) require.NoError(t, bridge.Start(ctx)) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index bbe8f7e395..c36e59b742 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -188,6 +188,9 @@ func (s *Swamp) NewBridgeNode(options ...fx.Option) *nodebuilder.Node { // and a mockstore to the NewNodeWithStore method func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Header.TrustedPeers = []string{ + "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", + } store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Full, store, options...) @@ -197,6 +200,10 @@ func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { // and a mockstore to the NewNodeWithStore method func (s *Swamp) NewLightNode(options ...fx.Option) *nodebuilder.Node { cfg := nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = []string{ + "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", + } + store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Light, store, options...) From 1329f8b55725258d05c4ba77420ffc2f33205eff Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 3 May 2023 11:34:45 +0200 Subject: [PATCH 0575/1008] fix(share/discovery)!: revamp Discovery (#2117) Closes #2107 There was a discrepancy between the amount of peers inside of the peer manager full node pool, and the limitedSet inside of discovery. It was possible for the node to get in a state where it had no more full nodes to sample from, because the peers inside the limitedSet were blocking on the connection, and never being handed off to the OnPeersUpdate callback. To fix this, we (@Wondertan, @walldiss and I): * Set a context deadline for the call to Connect with a peer. Previously, it would never timeout. This is because RoutedHost will block until the context is canceled, even though there is a peer dial timeout. * Only add peers to the limitedSet after we have successfully connected. This turns the previous peerLimit into a "soft" limit. This prevents in-progress connections from clogging spots in the limitedSet, but also allows for more peers to end up in the limited set than the set limit (we don't throw away already connected peers). * Removed the timer reset upon a peer disconnecting. This peer disconnection happens often, causing a significant delay in the next call to FindPeers. * Added logs, cleaned up various comments * Backoff * Adds a RemoveBackoff method to the backoffConnector, to remove cancelled connections from the cache * Adds Backoff method * Cleans up limited set * Added tests for Discovery * Handle the case where discovered peer is already connected * Introduces importable Discovery params * Revisits constans Co-authored-by: Wondertan Co-authored-by: Vlad Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> --- nodebuilder/share/config.go | 25 +- nodebuilder/share/constructors.go | 4 +- nodebuilder/tests/p2p_test.go | 9 +- share/availability/discovery/backoff.go | 75 ++-- share/availability/discovery/backoff_test.go | 2 +- share/availability/discovery/discovery.go | 413 ++++++++++++------ .../availability/discovery/discovery_test.go | 136 ++++++ share/availability/discovery/set.go | 33 +- share/availability/discovery/set_test.go | 30 +- share/availability/full/testing.go | 6 +- share/getters/shrex_test.go | 6 +- share/p2p/peers/manager_test.go | 23 +- 12 files changed, 515 insertions(+), 247 deletions(-) create mode 100644 share/availability/discovery/discovery_test.go diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 789b54dd75..179035e21d 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -1,27 +1,13 @@ package share import ( - "errors" - "fmt" - "time" - + disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" ) -var ( - ErrNegativeInterval = errors.New("interval must be positive") -) - type Config struct { - // PeersLimit defines how many peers will be added during discovery. - PeersLimit uint - // DiscoveryInterval is an interval between discovery sessions. - DiscoveryInterval time.Duration - // AdvertiseInterval is a interval between advertising sessions. - // NOTE: only full and bridge can advertise themselves. - AdvertiseInterval time.Duration // UseShareExchange is a flag toggling the usage of shrex protocols for blocksync. // NOTE: This config variable only has an effect on full and bridge nodes. UseShareExchange bool @@ -31,25 +17,22 @@ type Config struct { ShrExNDParams *shrexnd.Parameters // PeerManagerParams sets peer-manager configuration parameters PeerManagerParams peers.Parameters + // Discovery sets peer discovery configuration parameters. + Discovery disc.Parameters } func DefaultConfig() Config { return Config{ - PeersLimit: 5, - DiscoveryInterval: time.Second * 30, - AdvertiseInterval: time.Second * 30, UseShareExchange: true, ShrExEDSParams: shrexeds.DefaultParameters(), ShrExNDParams: shrexnd.DefaultParameters(), PeerManagerParams: peers.DefaultParameters(), + Discovery: disc.DefaultParameters(), } } // Validate performs basic validation of the config. func (cfg *Config) Validate() error { - if cfg.DiscoveryInterval <= 0 || cfg.AdvertiseInterval <= 0 { - return fmt.Errorf("nodebuilder/share: %s", ErrNegativeInterval) - } if err := cfg.ShrExNDParams.Validate(); err != nil { return err } diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index a8d45f6a40..59189243a5 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -29,9 +29,7 @@ func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discove return disc.NewDiscovery( h, routingdisc.NewRoutingDiscovery(r), - cfg.PeersLimit, - cfg.DiscoveryInterval, - cfg.AdvertiseInterval, + cfg.Discovery, ) } } diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 95a33b11e8..39c3c985a2 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -171,7 +171,7 @@ func TestRestartNodeDiscovery(t *testing.T) { const fullNodes = 2 setTimeInterval(cfg, defaultTimeInterval) - cfg.Share.PeersLimit = fullNodes + cfg.Share.Discovery.PeersLimit = fullNodes bridge := sw.NewNodeWithConfig(node.Bridge, cfg) ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) @@ -184,7 +184,7 @@ func TestRestartNodeDiscovery(t *testing.T) { nodes := make([]*nodebuilder.Node, fullNodes) cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) - cfg.Share.PeersLimit = fullNodes + cfg.Share.Discovery.PeersLimit = fullNodes nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}) for index := 0; index < fullNodes; index++ { nodes[index] = sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) @@ -201,7 +201,7 @@ func TestRestartNodeDiscovery(t *testing.T) { // create one more node with disabled discovery cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) - cfg.Share.PeersLimit = 0 + cfg.Share.Discovery.PeersLimit = 0 node := sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) connectSub, err := nodes[0].Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) require.NoError(t, err) @@ -215,6 +215,5 @@ func TestRestartNodeDiscovery(t *testing.T) { func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { cfg.P2P.RoutingTableRefreshPeriod = interval - cfg.Share.DiscoveryInterval = interval - cfg.Share.AdvertiseInterval = interval + cfg.Share.Discovery.AdvertiseInterval = interval } diff --git a/share/availability/discovery/backoff.go b/share/availability/discovery/backoff.go index e76449f2c6..5cd20e1497 100644 --- a/share/availability/discovery/backoff.go +++ b/share/availability/discovery/backoff.go @@ -2,7 +2,7 @@ package discovery import ( "context" - "fmt" + "errors" "sync" "time" @@ -11,10 +11,17 @@ import ( "github.com/libp2p/go-libp2p/p2p/discovery/backoff" ) -// gcInterval is a default period after which disconnected peers will be removed from cache -const gcInterval = time.Hour +const ( + // gcInterval is a default period after which disconnected peers will be removed from cache + gcInterval = time.Minute + // connectTimeout is the timeout used for dialing peers and discovering peer addresses. + connectTimeout = time.Minute * 2 +) -var defaultBackoffFactory = backoff.NewFixedBackoff(time.Hour) +var ( + defaultBackoffFactory = backoff.NewFixedBackoff(time.Minute * 10) + errBackoffNotEnded = errors.New("share/discovery: backoff period has not ended") +) // backoffConnector wraps a libp2p.Host to establish a connection with peers // with adding a delay for the next connection attempt. @@ -23,7 +30,7 @@ type backoffConnector struct { backoff backoff.BackoffFactory cacheLk sync.Mutex - cacheData map[peer.ID]*backoffData + cacheData map[peer.ID]backoffData } // backoffData stores time when next connection attempt with the remote peer. @@ -36,48 +43,52 @@ func newBackoffConnector(h host.Host, factory backoff.BackoffFactory) *backoffCo return &backoffConnector{ h: h, backoff: factory, - cacheData: make(map[peer.ID]*backoffData), + cacheData: make(map[peer.ID]backoffData), } } // Connect puts peer to the backoffCache and tries to establish a connection with it. func (b *backoffConnector) Connect(ctx context.Context, p peer.AddrInfo) error { - // we should lock the mutex before calling connectionData and not inside because otherwise it could - // be modified from another goroutine as it returns a pointer - b.cacheLk.Lock() - cache := b.connectionData(p.ID) - if time.Now().Before(cache.nexttry) { - b.cacheLk.Unlock() - return fmt.Errorf("share/discovery: backoff period has not ended for peer=%s", p.ID.String()) + if b.HasBackoff(p.ID) { + return errBackoffNotEnded } - cache.nexttry = time.Now().Add(cache.backoff.Delay()) - b.cacheLk.Unlock() - return b.h.Connect(ctx, p) + + ctx, cancel := context.WithTimeout(ctx, connectTimeout) + defer cancel() + + err := b.h.Connect(ctx, p) + // we don't want to add backoff when the context is canceled. + if !errors.Is(err, context.Canceled) { + b.Backoff(p.ID) + } + return err } -// connectionData returns backoffData from the map if it was stored, otherwise it will instantiate -// a new one. -func (b *backoffConnector) connectionData(p peer.ID) *backoffData { - cache, ok := b.cacheData[p] +// Backoff adds or extends backoff delay for the peer. +func (b *backoffConnector) Backoff(p peer.ID) { + b.cacheLk.Lock() + defer b.cacheLk.Unlock() + + data, ok := b.cacheData[p] if !ok { - cache = &backoffData{} - cache.backoff = b.backoff() - b.cacheData[p] = cache + data = backoffData{} + data.backoff = b.backoff() + b.cacheData[p] = data } - return cache + + data.nexttry = time.Now().Add(data.backoff.Delay()) + b.cacheData[p] = data } -// RestartBackoff resets delay time between attempts and adds a delay for the next connection -// attempt to remote peer. It will mostly be called when host receives a notification that remote -// peer was disconnected. -func (b *backoffConnector) RestartBackoff(p peer.ID) { +// HasBackoff checks if peer is in backoff. +func (b *backoffConnector) HasBackoff(p peer.ID) bool { b.cacheLk.Lock() - defer b.cacheLk.Unlock() - cache := b.connectionData(p) - cache.backoff.Reset() - cache.nexttry = time.Now().Add(cache.backoff.Delay()) + cache, ok := b.cacheData[p] + b.cacheLk.Unlock() + return ok && time.Now().Before(cache.nexttry) } +// GC is a perpetual GCing loop. func (b *backoffConnector) GC(ctx context.Context) { ticker := time.NewTicker(gcInterval) for { diff --git a/share/availability/discovery/backoff_test.go b/share/availability/discovery/backoff_test.go index 95e84fbb8c..24814ed199 100644 --- a/share/availability/discovery/backoff_test.go +++ b/share/availability/discovery/backoff_test.go @@ -42,6 +42,6 @@ func TestBackoff_ResetBackoffPeriod(t *testing.T) { info := host.InfoFromHost(m.Hosts()[1]) require.NoError(t, b.Connect(ctx, *info)) nexttry := b.cacheData[info.ID].nexttry - b.RestartBackoff(info.ID) + b.Backoff(info.ID) require.True(t, b.cacheData[info.ID].nexttry.After(nexttry)) } diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 47733a9281..0a2deb7858 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -2,6 +2,7 @@ package discovery import ( "context" + "fmt" "time" logging "github.com/ipfs/go-log/v2" @@ -11,20 +12,27 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/host/eventbus" + "golang.org/x/sync/errgroup" ) var log = logging.Logger("share/discovery") const ( - // peerWeight is a weight of discovered peers. - // peerWeight is a number that will be assigned to all discovered full nodes, - // so ConnManager will not break a connection with them. - peerWeight = 1000 - topic = "full" + // rendezvousPoint is the namespace where peers advertise and discover each other. + rendezvousPoint = "full" // eventbusBufSize is the size of the buffered channel to handle - // events in libp2p - eventbusBufSize = 32 + // events in libp2p. We specify a larger buffer size for the channel + // to avoid overflowing and blocking subscription during disconnection bursts. + // (by default it is 16) + eventbusBufSize = 64 + + // findPeersStuckWarnDelay is the duration after which discover will log an error message to + // notify that it is stuck. + findPeersStuckWarnDelay = time.Minute + + // defaultRetryTimeout defines time interval between discovery attempts. + defaultRetryTimeout = time.Second ) // waitF calculates time to restart announcing. @@ -32,21 +40,51 @@ var waitF = func(ttl time.Duration) time.Duration { return 7 * ttl / 8 } +type Parameters struct { + // PeersLimit defines the soft limit of FNs to connect to via discovery. + // Set 0 to disable. + PeersLimit uint + // AdvertiseInterval is a interval between advertising sessions. + // Set -1 to disable. + // NOTE: only full and bridge can advertise themselves. + AdvertiseInterval time.Duration + // discoveryRetryTimeout is an interval between discovery attempts + // when we discovered lower than PeersLimit peers. + // Set -1 to disable. + discoveryRetryTimeout time.Duration +} + +func (p Parameters) withDefaults() Parameters { + def := DefaultParameters() + if p.AdvertiseInterval == 0 { + p.AdvertiseInterval = def.AdvertiseInterval + } + if p.discoveryRetryTimeout == 0 { + p.discoveryRetryTimeout = defaultRetryTimeout + } + return p +} + +func DefaultParameters() Parameters { + return Parameters{ + PeersLimit: 5, + AdvertiseInterval: time.Hour * 8, + } +} + // Discovery combines advertise and discover services and allows to store discovered nodes. +// TODO: The code here gets horribly hairy, so we should refactor this at some point type Discovery struct { - set *limitedSet - host host.Host - disc discovery.Discovery - connector *backoffConnector - // peersLimit is max amount of peers that will be discovered during a discovery session. - peersLimit uint - // discInterval is an interval between discovery sessions. - discoveryInterval time.Duration - // advertiseInterval is an interval between advertising sessions. - advertiseInterval time.Duration - // onUpdatedPeers will be called on peer set changes + params Parameters + + set *limitedSet + host host.Host + disc discovery.Discovery + connector *backoffConnector onUpdatedPeers OnUpdatedPeers + triggerDisc chan struct{} + cancel context.CancelFunc } @@ -56,19 +94,16 @@ type OnUpdatedPeers func(peerID peer.ID, isAdded bool) func NewDiscovery( h host.Host, d discovery.Discovery, - peersLimit uint, - discInterval, - advertiseInterval time.Duration, + params Parameters, ) *Discovery { return &Discovery{ - set: newLimitedSet(peersLimit), - host: h, - disc: d, - connector: newBackoffConnector(h, defaultBackoffFactory), - peersLimit: peersLimit, - discoveryInterval: discInterval, - advertiseInterval: advertiseInterval, - onUpdatedPeers: func(peer.ID, bool) {}, + params: params.withDefaults(), + set: newLimitedSet(params.PeersLimit), + host: h, + disc: d, + connector: newBackoffConnector(h, defaultBackoffFactory), + onUpdatedPeers: func(peer.ID, bool) {}, + triggerDisc: make(chan struct{}), } } @@ -76,7 +111,19 @@ func (d *Discovery) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) d.cancel = cancel - go d.ensurePeers(ctx) + if d.params.PeersLimit == 0 { + log.Warn("peers limit is set to 0. Skipping discovery...") + return nil + } + + sub, err := d.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}, eventbus.BufSize(eventbusBufSize)) + if err != nil { + return fmt.Errorf("subscribing for connection events: %w", err) + } + + go d.discoveryLoop(ctx) + go d.disconnectsLoop(ctx, sub) + go d.connector.GC(ctx) return nil } @@ -94,144 +141,238 @@ func (d *Discovery) WithOnPeersUpdate(f OnUpdatedPeers) { } } -// handlePeersFound receives peers and tries to establish a connection with them. -// Peer will be added to PeerCache if connection succeeds. -func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer.AddrInfo) { - if peer.ID == d.host.ID() || len(peer.Addrs) == 0 || d.set.Contains(peer.ID) { - return - } - err := d.set.TryAdd(peer.ID) - if err != nil { - log.Debug(err) - return - } - - err = d.connector.Connect(ctx, peer) - if err != nil { - log.Debug(err) - d.set.Remove(peer.ID) - return - } - - d.onUpdatedPeers(peer.ID, true) - log.Debugw("added peer to set", "id", peer.ID) - // add tag to protect peer of being killed by ConnManager - d.host.ConnManager().TagPeer(peer.ID, topic, peerWeight) +// Peers provides a list of discovered peers in the "full" topic. +// If Discovery hasn't found any peers, it blocks until at least one peer is found. +func (d *Discovery) Peers(ctx context.Context) ([]peer.ID, error) { + return d.set.Peers(ctx) } -// ensurePeers ensures we always have 'peerLimit' connected peers. -// It starts peer discovery every 30 seconds until peer cache reaches peersLimit. -// Discovery is restarted if any previously connected peers disconnect. -func (d *Discovery) ensurePeers(ctx context.Context) { - if d.peersLimit == 0 { - log.Warn("peers limit is set to 0. Skipping discovery...") - return - } - // subscribe on EventBus in order to catch disconnected peers and restart - // the discovery. We specify a larger buffer size for the channel where - // EvtPeerConnectednessChanged events are sent (by default it is 16, we - // specify 32) to avoid any blocks on writing to the full channel. - sub, err := d.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}, eventbus.BufSize(eventbusBufSize)) - if err != nil { - log.Error(err) +// Advertise is a utility function that persistently advertises a service through an Advertiser. +// TODO: Start advertising only after the reachability is confirmed by AutoNAT +func (d *Discovery) Advertise(ctx context.Context) { + if d.params.AdvertiseInterval == -1 { return } - go d.connector.GC(ctx) - t := time.NewTicker(d.discoveryInterval) - defer func() { - t.Stop() - if err = sub.Close(); err != nil { - log.Error(err) - } - }() + timer := time.NewTimer(d.params.AdvertiseInterval) + defer timer.Stop() + for { + ttl, err := d.disc.Advertise(ctx, rendezvousPoint) + if err != nil { + log.Debugf("Error advertising %s: %s", rendezvousPoint, err.Error()) + if ctx.Err() != nil { + return + } - // starting to listen to subscriptions async will help us to avoid any blocking - // in the case when we will not have the needed amount of FNs and will be blocked in `FindPeers`. - go func() { - for { select { + case <-timer.C: + timer.Reset(d.params.AdvertiseInterval) + continue case <-ctx.Done(): - log.Debug("Context canceled. Finish listening for connectedness events.") return - case e, ok := <-sub.Out(): - if !ok { - log.Debug("Subscription for connectedness events is closed.") - return - } - // listen to disconnect event to remove peer from set and reset backoff time - // reset timer in order to restart the discovery, once stored peer is disconnected - connStatus := e.(event.EvtPeerConnectednessChanged) - if connStatus.Connectedness == network.NotConnected { - if d.set.Contains(connStatus.Peer) { - log.Debugw("removing the peer from the peer set", - "peer", connStatus.Peer, "status", connStatus.Connectedness.String()) - d.connector.RestartBackoff(connStatus.Peer) - d.set.Remove(connStatus.Peer) - d.onUpdatedPeers(connStatus.Peer, false) - d.host.ConnManager().UntagPeer(connStatus.Peer, topic) - t.Reset(d.discoveryInterval) - } - } } } - }() - for { + log.Debugf("advertised") select { + case <-timer.C: + timer.Reset(waitF(ttl)) case <-ctx.Done(): - log.Info("Context canceled. Finishing peer discovery") return + } + } +} + +// discoveryLoop ensures we always have '~peerLimit' connected peers. +// It starts peer discovery per request and restarts the process until the soft limit reached. +func (d *Discovery) discoveryLoop(ctx context.Context) { + t := time.NewTicker(d.params.discoveryRetryTimeout) + defer t.Stop() + for { + // drain all previous ticks from channel + drainChannel(t.C) + select { case <-t.C: - if uint(d.set.Size()) == d.peersLimit { - // stop ticker if we have reached the limit - t.Stop() - continue - } - peers, err := d.disc.FindPeers(ctx, topic) - if err != nil { - log.Error(err) + found := d.discover(ctx) + if !found { + // rerun discovery if amount of peers didn't reach the limit continue } - for p := range peers { - go d.handlePeerFound(ctx, topic, p) - } + case <-ctx.Done(): + return + } + + select { + case <-d.triggerDisc: + case <-ctx.Done(): + return } } } -// Advertise is a utility function that persistently advertises a service through an Advertiser. -func (d *Discovery) Advertise(ctx context.Context) { - timer := time.NewTimer(d.advertiseInterval) - defer timer.Stop() +// disconnectsLoop listen for disconnect events and ensures Discovery state +// is updated. +func (d *Discovery) disconnectsLoop(ctx context.Context, sub event.Subscription) { + defer sub.Close() + for { - ttl, err := d.disc.Advertise(ctx, topic) - if err != nil { - log.Debugf("Error advertising %s: %s", topic, err.Error()) - if ctx.Err() != nil { + select { + case <-ctx.Done(): + return + case e, ok := <-sub.Out(): + if !ok { + log.Error("connection subscription was closed unexpectedly") return } - select { - case <-timer.C: - timer.Reset(d.advertiseInterval) - continue - case <-ctx.Done(): - return + if evnt := e.(event.EvtPeerConnectednessChanged); evnt.Connectedness == network.NotConnected { + if !d.set.Contains(evnt.Peer) { + continue + } + + d.host.ConnManager().Unprotect(evnt.Peer, rendezvousPoint) + d.connector.Backoff(evnt.Peer) + d.set.Remove(evnt.Peer) + d.onUpdatedPeers(evnt.Peer, false) + log.Debugw("removed peer from the peer set", + "peer", evnt.Peer, "status", evnt.Connectedness.String()) + + if d.set.Size() < d.set.Limit() { + // trigger discovery + select { + case d.triggerDisc <- struct{}{}: + default: + } + } } } + } +} + +// discover finds new peers and reports whether it succeeded. +func (d *Discovery) discover(ctx context.Context) bool { + size := d.set.Size() + want := d.set.Limit() - size + if want == 0 { + log.Debugw("reached soft peer limit, skipping discovery", "size", size) + return true + } + log.Infow("discovering peers", "want", want) + + // we use errgroup as it provide limits + var wg errgroup.Group + // limit to minimize chances of overreaching the limit + wg.SetLimit(int(d.set.Limit())) + defer wg.Wait() //nolint:errcheck + + // stop discovery when we are done + findCtx, findCancel := context.WithCancel(ctx) + defer findCancel() + + peers, err := d.disc.FindPeers(findCtx, rendezvousPoint) + if err != nil { + log.Error("unable to start discovery", "err", err) + return false + } + ticker := time.NewTicker(findPeersStuckWarnDelay) + defer ticker.Stop() + for { + ticker.Reset(findPeersStuckWarnDelay) + // drain all previous ticks from channel + drainChannel(ticker.C) select { - case <-timer.C: - timer.Reset(waitF(ttl)) - case <-ctx.Done(): - return + case <-findCtx.Done(): + return true + case <-ticker.C: + log.Warn("wasn't able to find new peers for long time") + continue + case p, ok := <-peers: + if !ok { + log.Debugw("discovery channel closed", "find_is_canceled", findCtx.Err() != nil) + return d.set.Size() >= d.set.Limit() + } + + peer := p + wg.Go(func() error { + if findCtx.Err() != nil { + log.Debug("find has been canceled, skip peer") + return nil + } + + // we don't pass findCtx so that we don't cancel in progress connections + // that are likely to be valuable + if !d.handleDiscoveredPeer(ctx, peer) { + return nil + } + + size := d.set.Size() + log.Debugw("found peer", "peer", peer.ID, "found_amount", size) + if size < d.set.Limit() { + return nil + } + + log.Infow("discovered wanted peers", "amount", size) + findCancel() + return nil + }) } } } -// Peers provides a list of discovered peers in the "full" topic. -// If Discovery hasn't found any peers, it blocks until at least one peer is found. -func (d *Discovery) Peers(ctx context.Context) ([]peer.ID, error) { - return d.set.Peers(ctx) +// handleDiscoveredPeer adds peer to the internal if can connect or is connected. +// Report whether it succeeded. +func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo) bool { + logger := log.With("peer", peer.ID) + switch { + case peer.ID == d.host.ID(): + logger.Debug("skip handle: self discovery") + return false + case len(peer.Addrs) == 0: + logger.Debug("skip handle: empty address list") + return false + case d.set.Size() >= d.set.Limit(): + logger.Debug("skip handle: enough peers found") + return false + case d.connector.HasBackoff(peer.ID): + logger.Debug("skip handle: backoff") + return false + } + + switch d.host.Network().Connectedness(peer.ID) { + case network.Connected: + d.connector.Backoff(peer.ID) // we still have to backoff the connected peer + case network.NotConnected: + err := d.connector.Connect(ctx, peer) + if err != nil { + logger.Debugw("unable to connect", "err", err) + return false + } + default: + panic("unknown connectedness") + } + + if !d.set.Add(peer.ID) { + logger.Debug("peer is already in discovery set") + return false + } + d.onUpdatedPeers(peer.ID, true) + logger.Debug("added peer to set") + + // tag to protect peer from being killed by ConnManager + // NOTE: This is does not protect from remote killing the connection. + // In the future, we should design a protocol that keeps bidirectional agreement on whether + // connection should be kept or not, similar to mesh link in GossipSub. + d.host.ConnManager().Protect(peer.ID, rendezvousPoint) + return true +} + +func drainChannel(c <-chan time.Time) { + for { + select { + case <-c: + default: + return + } + } } diff --git a/share/availability/discovery/discovery_test.go b/share/availability/discovery/discovery_test.go new file mode 100644 index 0000000000..fd88a98586 --- /dev/null +++ b/share/availability/discovery/discovery_test.go @@ -0,0 +1,136 @@ +package discovery + +import ( + "context" + "testing" + "time" + + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/discovery" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/discovery/routing" + basic "github.com/libp2p/go-libp2p/p2p/host/basic" + swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDiscovery(t *testing.T) { + const nodes = 10 // higher number brings higher coverage + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + t.Cleanup(cancel) + + tn := newTestnet(ctx, t) + + peerA := tn.discovery(Parameters{ + PeersLimit: nodes, + discoveryRetryTimeout: time.Millisecond * 100, + AdvertiseInterval: -1, // we don't want to be found but only find + }) + + type peerUpdate struct { + peerID peer.ID + isAdded bool + } + updateCh := make(chan peerUpdate) + peerA.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { + updateCh <- peerUpdate{peerID: peerID, isAdded: isAdded} + }) + + discs := make([]*Discovery, nodes) + for i := range discs { + discs[i] = tn.discovery(Parameters{ + PeersLimit: 0, + discoveryRetryTimeout: -1, + AdvertiseInterval: time.Millisecond * 100, + }) + + select { + case res := <-updateCh: + require.Equal(t, discs[i].host.ID(), res.peerID) + require.True(t, res.isAdded) + case <-ctx.Done(): + t.Fatal("did not discover peer in time") + } + } + + assert.EqualValues(t, nodes, peerA.set.Size()) + + for _, disc := range discs { + peerID := disc.host.ID() + err := peerA.host.Network().ClosePeer(peerID) + require.NoError(t, err) + + select { + case res := <-updateCh: + require.Equal(t, peerID, res.peerID) + require.False(t, res.isAdded) + case <-ctx.Done(): + t.Fatal("did not disconnect from peer in time") + } + } + + assert.EqualValues(t, 0, peerA.set.Size()) +} + +type testnet struct { + ctx context.Context + T *testing.T + + bootstrapper peer.AddrInfo +} + +func newTestnet(ctx context.Context, t *testing.T) *testnet { + swarm := swarmt.GenSwarm(t, swarmt.OptDisableTCP) + hst, err := basic.NewHost(swarm, &basic.HostOpts{}) + require.NoError(t, err) + hst.Start() + + _, err = dht.New(ctx, hst, + dht.Mode(dht.ModeServer), + dht.BootstrapPeers(), + dht.ProtocolPrefix("/test"), + ) + require.NoError(t, err) + + return &testnet{ctx: ctx, T: t, bootstrapper: *host.InfoFromHost(hst)} +} + +func (t *testnet) discovery(params Parameters) *Discovery { + hst, routingDisc := t.peer() + disc := NewDiscovery(hst, routingDisc, params) + err := disc.Start(t.ctx) + require.NoError(t.T, err) + t.T.Cleanup(func() { + err := disc.Stop(t.ctx) + require.NoError(t.T, err) + }) + + go disc.Advertise(t.ctx) + return disc +} + +func (t *testnet) peer() (host.Host, discovery.Discovery) { + swarm := swarmt.GenSwarm(t.T, swarmt.OptDisableTCP) + hst, err := basic.NewHost(swarm, &basic.HostOpts{}) + require.NoError(t.T, err) + hst.Start() + + err = hst.Connect(t.ctx, t.bootstrapper) + require.NoError(t.T, err) + + dht, err := dht.New(t.ctx, hst, + dht.Mode(dht.ModeServer), + dht.ProtocolPrefix("/test"), + // needed to reduce connections to peers on DHT level + dht.BucketSize(1), + ) + require.NoError(t.T, err) + + err = dht.Bootstrap(t.ctx) + require.NoError(t.T, err) + + return hst, routing.NewRoutingDiscovery(dht) +} diff --git a/share/availability/discovery/set.go b/share/availability/discovery/set.go index a7b330fadd..37b3851bdf 100644 --- a/share/availability/discovery/set.go +++ b/share/availability/discovery/set.go @@ -2,7 +2,6 @@ package discovery import ( "context" - "errors" "sync" "github.com/libp2p/go-libp2p/core/peer" @@ -34,36 +33,34 @@ func (ps *limitedSet) Contains(p peer.ID) bool { return ok } -func (ps *limitedSet) Size() int { +func (ps *limitedSet) Limit() uint { + return ps.limit +} + +func (ps *limitedSet) Size() uint { ps.lk.RLock() defer ps.lk.RUnlock() - return len(ps.ps) + return uint(len(ps.ps)) } -// TryAdd attempts to add the given peer into the set. -// This operation will fail if the number of peers in the set is equal to size. -func (ps *limitedSet) TryAdd(p peer.ID) error { +// Add attempts to add the given peer into the set. +func (ps *limitedSet) Add(p peer.ID) (added bool) { ps.lk.Lock() - defer ps.lk.Unlock() if _, ok := ps.ps[p]; ok { - return errors.New("share: discovery: peer already added") + return false } - if len(ps.ps) >= int(ps.limit) { - return errors.New("share: discovery: peers limit reached") - } - ps.ps[p] = struct{}{} -LOOP: + ps.lk.Unlock() + for { // peer will be pushed to the channel only when somebody is reading from it. // this is done to handle case when Peers() was called on empty set. select { case ps.waitPeer <- p: default: - break LOOP + return true } } - return nil } func (ps *limitedSet) Remove(id peer.ID) { @@ -76,16 +73,16 @@ func (ps *limitedSet) Remove(id peer.ID) { // Peers returns all discovered peers from the set. func (ps *limitedSet) Peers(ctx context.Context) ([]peer.ID, error) { - ps.lk.Lock() + ps.lk.RLock() if len(ps.ps) > 0 { out := make([]peer.ID, 0, len(ps.ps)) for p := range ps.ps { out = append(out, p) } - ps.lk.Unlock() + ps.lk.RUnlock() return out, nil } - ps.lk.Unlock() + ps.lk.RUnlock() // block until a new peer will be discovered select { diff --git a/share/availability/discovery/set_test.go b/share/availability/discovery/set_test.go index b4c6a8fb49..d5113a2291 100644 --- a/share/availability/discovery/set_test.go +++ b/share/availability/discovery/set_test.go @@ -15,29 +15,17 @@ func TestSet_TryAdd(t *testing.T) { require.NoError(t, err) set := newLimitedSet(1) - require.NoError(t, set.TryAdd(h.ID())) + set.Add(h.ID()) require.True(t, set.Contains(h.ID())) } -func TestSet_TryAddFails(t *testing.T) { - m := mocknet.New() - h1, err := m.GenPeer() - require.NoError(t, err) - h2, err := m.GenPeer() - require.NoError(t, err) - - set := newLimitedSet(1) - require.NoError(t, set.TryAdd(h1.ID())) - require.Error(t, set.TryAdd(h2.ID())) -} - func TestSet_Remove(t *testing.T) { m := mocknet.New() h, err := m.GenPeer() require.NoError(t, err) set := newLimitedSet(1) - require.NoError(t, set.TryAdd(h.ID())) + set.Add(h.ID()) set.Remove(h.ID()) require.False(t, set.Contains(h.ID())) } @@ -50,8 +38,8 @@ func TestSet_Peers(t *testing.T) { require.NoError(t, err) set := newLimitedSet(2) - require.NoError(t, set.TryAdd(h1.ID())) - require.NoError(t, set.TryAdd(h2.ID())) + set.Add(h1.ID()) + set.Add(h2.ID()) ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) t.Cleanup(cancel) @@ -71,7 +59,7 @@ func TestSet_WaitPeers(t *testing.T) { set := newLimitedSet(2) go func() { time.Sleep(time.Millisecond * 500) - set.TryAdd(h1.ID()) //nolint:errcheck + set.Add(h1.ID()) }() ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) @@ -91,9 +79,9 @@ func TestSet_Size(t *testing.T) { require.NoError(t, err) set := newLimitedSet(2) - require.NoError(t, set.TryAdd(h1.ID())) - require.NoError(t, set.TryAdd(h2.ID())) - require.Equal(t, 2, set.Size()) + set.Add(h1.ID()) + set.Add(h2.ID()) + require.EqualValues(t, 2, set.Size()) set.Remove(h2.ID()) - require.Equal(t, 1, set.Size()) + require.EqualValues(t, 1, set.Size()) } diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index ca5d0f10c5..df4561a8eb 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -37,6 +37,10 @@ func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { } func TestAvailability(getter share.Getter) *ShareAvailability { - disc := discovery.NewDiscovery(nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + params := discovery.DefaultParameters() + params.AdvertiseInterval = time.Second + params.PeersLimit = 10 + disc := discovery.NewDiscovery(nil, + routing.NewRoutingDiscovery(routinghelpers.Null{}), params) return NewShareAvailability(nil, getter, disc) } diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 7578195639..b93a40d488 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -158,7 +158,11 @@ func testManager(ctx context.Context, host host.Host, headerSub libhead.Subscrib } disc := discovery.NewDiscovery(nil, - routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), discovery.Parameters{ + PeersLimit: 10, + AdvertiseInterval: time.Second, + }, + ) connGater, err := conngater.NewBasicConnectionGater(ds_sync.MutexWrap(datastore.NewMapDatastore())) if err != nil { return nil, err diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 05c1f5b33e..804dd4b673 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -365,7 +365,7 @@ func TestIntegration(t *testing.T) { t.Run("get peer from discovery", func(t *testing.T) { nw, err := mocknet.FullMeshConnected(3) require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) // set up bootstrapper @@ -393,9 +393,11 @@ func TestIntegration(t *testing.T) { bnDisc := discovery.NewDiscovery( nw.Hosts()[0], routingdisc.NewRoutingDiscovery(router1), - 10, - time.Second, - time.Second) + discovery.Parameters{ + PeersLimit: 0, + AdvertiseInterval: time.Second, + }, + ) // set up full node / receiver node fnHost := nw.Hosts()[0] @@ -404,9 +406,10 @@ func TestIntegration(t *testing.T) { fnDisc := discovery.NewDiscovery( nw.Hosts()[1], routingdisc.NewRoutingDiscovery(router2), - 10, - time.Second, - time.Second, + discovery.Parameters{ + PeersLimit: 10, + AdvertiseInterval: time.Second, + }, ) err = fnDisc.Start(ctx) require.NoError(t, err) @@ -458,8 +461,12 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten if err != nil { return nil, err } + disc := discovery.NewDiscovery(nil, - routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), 0, time.Second, time.Second) + routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), discovery.Parameters{ + PeersLimit: 0, + AdvertiseInterval: time.Second, + }) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) if err != nil { return nil, err From 4d31ad4a537e41b5ac588e14cf807904abbb8d8a Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 3 May 2023 11:48:53 +0200 Subject: [PATCH 0576/1008] feat(shrex): metrics (#2095) Closes #2143 Adds metrics to ShrexGetter, and ShrexEDS/ShrexND Client and Server (including shrex middleware) --- nodebuilder/settings.go | 21 +++++++-- nodebuilder/share/config.go | 1 - nodebuilder/share/opts.go | 25 ++++++++++ share/getters/shrex.go | 65 ++++++++++++++++++++++++++ share/p2p/metrics.go | 72 +++++++++++++++++++++++++++++ share/p2p/middleware.go | 33 ++++++++++--- share/p2p/shrexeds/client.go | 10 +++- share/p2p/shrexeds/exchange_test.go | 3 +- share/p2p/shrexeds/params.go | 18 ++++++++ share/p2p/shrexeds/server.go | 18 +++++++- share/p2p/shrexnd/client.go | 12 +++-- share/p2p/shrexnd/exchange_test.go | 12 +++-- share/p2p/shrexnd/params.go | 20 ++++++++ share/p2p/shrexnd/server.go | 29 ++++++++++-- 14 files changed, 311 insertions(+), 28 deletions(-) create mode 100644 share/p2p/metrics.go diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 0ef19d8fba..67a30793f8 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -72,19 +72,30 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti fx.Invoke(modheader.WithMetrics), ) + samplingMetrics := fx.Options( + fx.Invoke(das.WithMetrics), + fx.Invoke(share.WithPeerManagerMetrics), + fx.Invoke(share.WithShrexClientMetrics), + fx.Invoke(share.WithShrexGetterMetrics), + ) + var opts fx.Option switch nodeType { - case node.Full, node.Light: + case node.Full: + opts = fx.Options( + baseComponents, + fx.Invoke(share.WithShrexServerMetrics), + samplingMetrics, + ) + case node.Light: opts = fx.Options( baseComponents, - fx.Invoke(das.WithMetrics), - fx.Invoke(share.WithPeerManagerMetrics), - // add more monitoring here + samplingMetrics, ) case node.Bridge: opts = fx.Options( baseComponents, - // add more monitoring here + fx.Invoke(share.WithShrexServerMetrics), ) default: panic("invalid node type") diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 179035e21d..d843e78dd2 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -9,7 +9,6 @@ import ( type Config struct { // UseShareExchange is a flag toggling the usage of shrex protocols for blocksync. - // NOTE: This config variable only has an effect on full and bridge nodes. UseShareExchange bool // ShrExEDSParams sets shrexeds client and server configuration parameters ShrExEDSParams *shrexeds.Parameters diff --git a/nodebuilder/share/opts.go b/nodebuilder/share/opts.go index 93a9f4c4d5..dc63a4ca1e 100644 --- a/nodebuilder/share/opts.go +++ b/nodebuilder/share/opts.go @@ -1,7 +1,10 @@ package share import ( + "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/p2p/peers" + "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" + "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" ) // WithPeerManagerMetrics is a utility function that is expected to be @@ -9,3 +12,25 @@ import ( func WithPeerManagerMetrics(m *peers.Manager) error { return m.WithMetrics() } + +func WithShrexClientMetrics(edsClient *shrexeds.Client, ndClient *shrexnd.Client) error { + err := edsClient.WithMetrics() + if err != nil { + return err + } + + return ndClient.WithMetrics() +} + +func WithShrexServerMetrics(edsServer *shrexeds.Server, ndServer *shrexnd.Server) error { + err := edsServer.WithMetrics() + if err != nil { + return err + } + + return ndServer.WithMetrics() +} + +func WithShrexGetterMetrics(sg *getters.ShrexGetter) error { + return sg.WithMetrics() +} diff --git a/share/getters/shrex.go b/share/getters/shrex.go index c93a138ef7..3fd7c4846c 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -6,6 +6,12 @@ import ( "fmt" "time" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric/unit" + "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -23,8 +29,61 @@ const ( // serve getEDS request for block size 256 defaultMinRequestTimeout = time.Minute // should be >= shrexeds server write timeout defaultMinAttemptsCount = 3 + metricObservationTimeout = 100 * time.Millisecond ) +var meter = global.MeterProvider().Meter("shrex/getter") + +type metrics struct { + edsAttempts syncint64.Histogram + ndAttempts syncint64.Histogram +} + +func (m *metrics) recordEDSAttempt(attemptCount int, success bool) { + if m == nil { + return + } + ctx, cancel := context.WithTimeout(context.Background(), metricObservationTimeout) + defer cancel() + m.edsAttempts.Record(ctx, int64(attemptCount), attribute.Bool("success", success)) +} + +func (m *metrics) recordNDAttempt(attemptCount int, success bool) { + if m == nil { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), metricObservationTimeout) + defer cancel() + m.ndAttempts.Record(ctx, int64(attemptCount), attribute.Bool("success", success)) +} + +func (sg *ShrexGetter) WithMetrics() error { + edsAttemptHistogram, err := meter.SyncInt64().Histogram( + "getters_shrex_eds_attempts_per_request", + instrument.WithUnit(unit.Dimensionless), + instrument.WithDescription("Number of attempts per shrex/eds request"), + ) + if err != nil { + return err + } + + ndAttemptHistogram, err := meter.SyncInt64().Histogram( + "getters_shrex_nd_attempts_per_request", + instrument.WithUnit(unit.Dimensionless), + instrument.WithDescription("Number of attempts per shrex/nd request"), + ) + if err != nil { + return err + } + + sg.metrics = &metrics{ + edsAttempts: edsAttemptHistogram, + ndAttempts: ndAttemptHistogram, + } + return nil +} + // ShrexGetter is a share.Getter that uses the shrex/eds and shrex/nd protocol to retrieve shares. type ShrexGetter struct { edsClient *shrexeds.Client @@ -37,6 +96,8 @@ type ShrexGetter struct { // minAttemptsCount will be used to split request timeout into multiple attempts. It will allow to // attempt multiple peers in scope of one request before context timeout is reached minAttemptsCount int + + metrics *metrics } func NewShrexGetter(edsClient *shrexeds.Client, ndClient *shrexnd.Client, peerManager *peers.Manager) *ShrexGetter { @@ -79,6 +140,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) + sg.metrics.recordEDSAttempt(attempt, false) return nil, fmt.Errorf("getter/shrex: %w", err) } @@ -89,6 +151,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex switch { case getErr == nil: setStatus(peers.ResultSynced) + sg.metrics.recordEDSAttempt(attempt, true) return eds, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): @@ -135,6 +198,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) + sg.metrics.recordNDAttempt(attempt, false) return nil, fmt.Errorf("getter/shrex: %w", err) } @@ -145,6 +209,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( switch { case getErr == nil: setStatus(peers.ResultNoop) + sg.metrics.recordNDAttempt(attempt, true) return nd, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): diff --git a/share/p2p/metrics.go b/share/p2p/metrics.go new file mode 100644 index 0000000000..4b94d8c8d5 --- /dev/null +++ b/share/p2p/metrics.go @@ -0,0 +1,72 @@ +package p2p + +import ( + "context" + "fmt" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric/unit" +) + +var meter = global.MeterProvider().Meter("shrex/eds") + +var observationTimeout = 100 * time.Millisecond + +type status string + +const ( + StatusInternalErr status = "internal_err" + StatusNotFound status = "not_found" + StatusTimeout status = "timeout" + StatusSuccess status = "success" + StatusRateLimited status = "rate_limited" +) + +type Metrics struct { + totalRequestCounter syncint64.Counter +} + +// ObserveRequests increments the total number of requests sent with the given status as an attribute. +func (m *Metrics) ObserveRequests(count int64, status status) { + if m == nil { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), observationTimeout) + defer cancel() + m.totalRequestCounter.Add(ctx, count, attribute.String("status", string(status))) +} + +func InitClientMetrics(protocol string) (*Metrics, error) { + totalRequestCounter, err := meter.SyncInt64().Counter( + fmt.Sprintf("shrex_%s_client_total_requests", protocol), + instrument.WithUnit(unit.Dimensionless), + instrument.WithDescription(fmt.Sprintf("Total count of sent shrex/%s requests", protocol)), + ) + if err != nil { + return nil, err + } + + return &Metrics{ + totalRequestCounter: totalRequestCounter, + }, nil +} + +func InitServerMetrics(protocol string) (*Metrics, error) { + totalRequestCounter, err := meter.SyncInt64().Counter( + fmt.Sprintf("shrex_%s_server_total_responses", protocol), + instrument.WithUnit(unit.Dimensionless), + instrument.WithDescription(fmt.Sprintf("Total count of sent shrex/%s responses", protocol)), + ) + if err != nil { + return nil, err + } + + return &Metrics{ + totalRequestCounter: totalRequestCounter, + }, nil +} diff --git a/share/p2p/middleware.go b/share/p2p/middleware.go index 25d733f43b..df0a690af7 100644 --- a/share/p2p/middleware.go +++ b/share/p2p/middleware.go @@ -9,14 +9,33 @@ import ( var log = logging.Logger("shrex/middleware") -func RateLimitMiddleware(inner network.StreamHandler, concurrencyLimit int) network.StreamHandler { - var parallelRequests int64 - limit := int64(concurrencyLimit) +type Middleware struct { + // concurrencyLimit is the maximum number of requests that can be processed at once. + concurrencyLimit int64 + // parallelRequests is the number of requests currently being processed. + parallelRequests atomic.Int64 + // numRateLimited is the number of requests that were rate limited. + numRateLimited atomic.Int64 +} + +func NewMiddleware(concurrencyLimit int) *Middleware { + return &Middleware{ + concurrencyLimit: int64(concurrencyLimit), + } +} + +// DrainCounter returns the current value of the rate limit counter and resets it to 0. +func (m *Middleware) DrainCounter() int64 { + return m.numRateLimited.Swap(0) +} + +func (m *Middleware) RateLimitHandler(handler network.StreamHandler) network.StreamHandler { return func(stream network.Stream) { - current := atomic.AddInt64(¶llelRequests, 1) - defer atomic.AddInt64(¶llelRequests, -1) + current := m.parallelRequests.Add(1) + defer m.parallelRequests.Add(-1) - if current > limit { + if current > m.concurrencyLimit { + m.numRateLimited.Add(1) log.Debug("concurrency limit reached") err := stream.Close() if err != nil { @@ -24,6 +43,6 @@ func RateLimitMiddleware(inner network.StreamHandler, concurrencyLimit int) netw } return } - inner(stream) + handler(stream) } } diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index f6ad9d2992..dccb5ce06a 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -26,8 +26,9 @@ import ( type Client struct { params *Parameters protocolID protocol.ID + host host.Host - host host.Host + metrics *p2p.Metrics } // NewClient creates a new ShrEx/EDS client. @@ -53,7 +54,9 @@ func (c *Client) RequestEDS( if err == nil { return eds, nil } + log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String(), "error", err) if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + c.metrics.ObserveRequests(1, p2p.StatusTimeout) return nil, ctx.Err() } // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not @@ -61,6 +64,7 @@ func (c *Client) RequestEDS( var ne net.Error if errors.As(err, &ne) && ne.Timeout() { if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { + c.metrics.ObserveRequests(1, p2p.StatusTimeout) return nil, context.DeadlineExceeded } } @@ -106,6 +110,7 @@ func (c *Client) doRequest( if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { + c.metrics.ObserveRequests(1, p2p.StatusRateLimited) return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck @@ -119,8 +124,10 @@ func (c *Client) doRequest( if err != nil { return nil, fmt.Errorf("failed to read eds from ods bytes: %w", err) } + c.metrics.ObserveRequests(1, p2p.StatusSuccess) return eds, nil case pb.Status_NOT_FOUND: + c.metrics.ObserveRequests(1, p2p.StatusNotFound) return nil, p2p.ErrNotFound case pb.Status_INVALID: log.Debug("client: invalid request") @@ -128,6 +135,7 @@ func (c *Client) doRequest( case pb.Status_INTERNAL: fallthrough default: + c.metrics.ObserveRequests(1, p2p.StatusInternalErr) return nil, p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 6bd971c535..b0e11e3587 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -107,8 +107,9 @@ func TestExchange_RequestEDS(t *testing.T) { t.Fatal("timeout") } } + middleware := p2p.NewMiddleware(rateLimit) server.host.SetStreamHandler(server.protocolID, - p2p.RateLimitMiddleware(mockHandler, rateLimit)) + middleware.RateLimitHandler(mockHandler)) // take server concurrency slots with blocked requests for i := 0; i < rateLimit; i++ { diff --git a/share/p2p/shrexeds/params.go b/share/p2p/shrexeds/params.go index a6ed42bd83..795cb313ed 100644 --- a/share/p2p/shrexeds/params.go +++ b/share/p2p/shrexeds/params.go @@ -34,3 +34,21 @@ func (p *Parameters) Validate() error { return p.Parameters.Validate() } + +func (c *Client) WithMetrics() error { + metrics, err := p2p.InitClientMetrics("eds") + if err != nil { + return fmt.Errorf("shrex/eds: init Metrics: %w", err) + } + c.metrics = metrics + return nil +} + +func (s *Server) WithMetrics() error { + metrics, err := p2p.InitServerMetrics("eds") + if err != nil { + return fmt.Errorf("shrex/eds: init Metrics: %w", err) + } + s.metrics = metrics + return nil +} diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index 8b0b674b88..ac246c00c1 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -30,7 +30,9 @@ type Server struct { store *eds.Store - params *Parameters + params *Parameters + middleware *p2p.Middleware + metrics *p2p.Metrics } // NewServer creates a new ShrEx/EDS server. @@ -44,12 +46,13 @@ func NewServer(params *Parameters, host host.Host, store *eds.Store) (*Server, e store: store, protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), params: params, + middleware: p2p.NewMiddleware(params.ConcurrencyLimit), }, nil } func (s *Server) Start(context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) - s.host.SetStreamHandler(s.protocolID, p2p.RateLimitMiddleware(s.handleStream, s.params.ConcurrencyLimit)) + s.host.SetStreamHandler(s.protocolID, s.middleware.RateLimitHandler(s.handleStream)) return nil } @@ -59,10 +62,19 @@ func (s *Server) Stop(context.Context) error { return nil } +func (s *Server) observeRateLimitedRequests() { + numRateLimited := s.middleware.DrainCounter() + if numRateLimited > 0 { + s.metrics.ObserveRequests(numRateLimited, p2p.StatusRateLimited) + } +} + func (s *Server) handleStream(stream network.Stream) { logger := log.With("peer", stream.Conn().RemotePeer()) logger.Debug("server: handling eds request") + s.observeRateLimitedRequests() + // read request from stream to get the dataHash for store lookup req, err := s.readRequest(logger, stream) if err != nil { @@ -91,6 +103,7 @@ func (s *Server) handleStream(stream network.Stream) { status := p2p_pb.Status_OK switch { case errors.Is(err, eds.ErrNotFound): + s.metrics.ObserveRequests(1, p2p.StatusNotFound) status = p2p_pb.Status_NOT_FOUND case err != nil: logger.Errorw("server: get CAR", "err", err) @@ -121,6 +134,7 @@ func (s *Server) handleStream(stream network.Stream) { return } + s.metrics.ObserveRequests(1, p2p.StatusSuccess) err = stream.Close() if err != nil { logger.Debugw("server: closing stream", "err", err) diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 89e9e76e7d..47eb742aa7 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -29,7 +29,8 @@ type Client struct { params *Parameters protocolID protocol.ID - host host.Host + host host.Host + metrics *p2p.Metrics } // NewClient creates a new shrEx/nd client @@ -58,6 +59,7 @@ func (c *Client) RequestND( return shares, err } if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + c.metrics.ObserveRequests(1, p2p.StatusTimeout) return nil, err } // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not @@ -65,6 +67,7 @@ func (c *Client) RequestND( var ne net.Error if errors.As(err, &ne) && ne.Timeout() { if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { + c.metrics.ObserveRequests(1, p2p.StatusTimeout) return nil, context.DeadlineExceeded } } @@ -109,13 +112,14 @@ func (c *Client) doRequest( if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { + c.metrics.ObserveRequests(1, p2p.StatusRateLimited) return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck return nil, fmt.Errorf("client-nd: reading response: %w", err) } - if err = statusToErr(resp.Status); err != nil { + if err = c.statusToErr(resp.Status); err != nil { return nil, fmt.Errorf("client-nd: response code is not OK: %w", err) } @@ -183,11 +187,13 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) } } -func statusToErr(code pb.StatusCode) error { +func (c *Client) statusToErr(code pb.StatusCode) error { switch code { case pb.StatusCode_OK: + c.metrics.ObserveRequests(1, p2p.StatusSuccess) return nil case pb.StatusCode_NOT_FOUND: + c.metrics.ObserveRequests(1, p2p.StatusNotFound) return p2p.ErrNotFound case pb.StatusCode_INVALID: log.Debug("client-nd: invalid request") diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 8542992e0e..05bca67237 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -83,8 +83,9 @@ func TestExchange_RequestND(t *testing.T) { t.Fatal("timeout") } } + middleware := p2p.NewMiddleware(rateLimit) server.host.SetStreamHandler(server.protocolID, - p2p.RateLimitMiddleware(mockHandler, rateLimit)) + middleware.RateLimitHandler(mockHandler)) // take server concurrency slots with blocked requests for i := 0; i < rateLimit; i++ { @@ -102,17 +103,20 @@ func TestExchange_RequestND(t *testing.T) { type notFoundGetter struct{} -func (m notFoundGetter) GetShare(_ context.Context, _ *share.Root, _, _ int, +func (m notFoundGetter) GetShare( + _ context.Context, _ *share.Root, _, _ int, ) (share.Share, error) { return nil, share.ErrNotFound } -func (m notFoundGetter) GetEDS(_ context.Context, _ *share.Root, +func (m notFoundGetter) GetEDS( + _ context.Context, _ *share.Root, ) (*rsmt2d.ExtendedDataSquare, error) { return nil, share.ErrNotFound } -func (m notFoundGetter) GetSharesByNamespace(_ context.Context, _ *share.Root, _ namespace.ID, +func (m notFoundGetter) GetSharesByNamespace( + _ context.Context, _ *share.Root, _ namespace.ID, ) (share.NamespacedShares, error) { return nil, share.ErrNotFound } diff --git a/share/p2p/shrexnd/params.go b/share/p2p/shrexnd/params.go index 8f2c999dfe..1acf65ba96 100644 --- a/share/p2p/shrexnd/params.go +++ b/share/p2p/shrexnd/params.go @@ -1,6 +1,8 @@ package shrexnd import ( + "fmt" + logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/share/p2p" @@ -16,3 +18,21 @@ type Parameters = p2p.Parameters func DefaultParameters() *Parameters { return p2p.DefaultParameters() } + +func (c *Client) WithMetrics() error { + metrics, err := p2p.InitClientMetrics("nd") + if err != nil { + return fmt.Errorf("shrex/nd: init Metrics: %w", err) + } + c.metrics = metrics + return nil +} + +func (srv *Server) WithMetrics() error { + metrics, err := p2p.InitServerMetrics("nd") + if err != nil { + return fmt.Errorf("shrex/nd: init Metrics: %w", err) + } + srv.metrics = metrics + return nil +} diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index cb71ad3053..83bbcd6b40 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -24,14 +24,17 @@ import ( // Server implements server side of shrex/nd protocol to serve namespaced share to remote // peers. type Server struct { - params *Parameters + cancel context.CancelFunc + + host host.Host protocolID protocol.ID getter share.Getter store *eds.Store - host host.Host - cancel context.CancelFunc + params *Parameters + middleware *p2p.Middleware + metrics *p2p.Metrics } // NewServer creates new Server @@ -46,6 +49,7 @@ func NewServer(params *Parameters, host host.Host, store *eds.Store, getter shar host: host, params: params, protocolID: p2p.ProtocolID(params.NetworkID(), protocolString), + middleware: p2p.NewMiddleware(params.ConcurrencyLimit), } return srv, nil @@ -59,7 +63,7 @@ func (srv *Server) Start(context.Context) error { handler := func(s network.Stream) { srv.handleNamespacedData(ctx, s) } - srv.host.SetStreamHandler(srv.protocolID, p2p.RateLimitMiddleware(handler, srv.params.ConcurrencyLimit)) + srv.host.SetStreamHandler(srv.protocolID, srv.middleware.RateLimitHandler(handler)) return nil } @@ -70,10 +74,19 @@ func (srv *Server) Stop(context.Context) error { return nil } +func (srv *Server) observeRateLimitedRequests() { + numRateLimited := srv.middleware.DrainCounter() + if numRateLimited > 0 { + srv.metrics.ObserveRequests(numRateLimited, p2p.StatusRateLimited) + } +} + func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { logger := log.With("peer", stream.Conn().RemotePeer()) logger.Debug("server: handling nd request") + srv.observeRateLimitedRequests() + err := stream.SetReadDeadline(time.Now().Add(srv.params.ServerReadTimeout)) if err != nil { logger.Debugw("server: setting read deadline", "err", err) @@ -195,6 +208,14 @@ func (srv *Server) respond(logger *zap.SugaredLogger, stream network.Stream, res return } + switch { + case resp.Status == pb.StatusCode_OK: + srv.metrics.ObserveRequests(1, p2p.StatusSuccess) + case resp.Status == pb.StatusCode_NOT_FOUND: + srv.metrics.ObserveRequests(1, p2p.StatusNotFound) + case resp.Status == pb.StatusCode_INTERNAL: + srv.metrics.ObserveRequests(1, p2p.StatusInternalErr) + } if err = stream.Close(); err != nil { logger.Debugw("server: closing stream", "err", err) } From e8ae7994fc313aec04a0cc3040cc092b3d3a5a09 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 3 May 2023 19:05:02 +0200 Subject: [PATCH 0577/1008] fix(nodebuilder): fix error log and shut the fx (#2160) --- logs/logs.go | 1 + nodebuilder/node.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/logs/logs.go b/logs/logs.go index bedb74cc58..4ae1cddd3f 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -21,6 +21,7 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("net/identify", "ERROR") _ = logging.SetLogLevel("shrex/nd", "WARN") _ = logging.SetLogLevel("shrex/eds", "WARN") + _ = logging.SetLogLevel("fx", "FATAL") } func SetDebugLogging() { diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 95f5d02116..90caac5dc9 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -30,7 +30,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -var Timeout = time.Second * 30 +var Timeout = time.Minute * 2 var ( log = logging.Logger("node") @@ -100,7 +100,7 @@ func (n *Node) Start(ctx context.Context) error { if err != nil { log.Debugf("error starting %s Node: %s", n.Type, err) if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("node: failed to start within timeout(%s): %w", Timeout, errors.Unwrap(err)) + return fmt.Errorf("node: failed to start within timeout(%s): %w", Timeout, err) } return fmt.Errorf("node: failed to start: %w", err) } @@ -145,7 +145,7 @@ func (n *Node) Stop(ctx context.Context) error { if err != nil { log.Debugf("error stopping %s Node: %s", n.Type, err) if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("node: failed to stop within timeout(%s): %w", Timeout, errors.Unwrap(err)) + return fmt.Errorf("node: failed to stop within timeout(%s): %w", Timeout, err) } return fmt.Errorf("node: failed to stop: %w", err) } From 0100141b992cdc148c8f4fc4351cea5b4a0c60a0 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 4 May 2023 15:34:29 +0200 Subject: [PATCH 0578/1008] fix(share/discovery): fixes from Advertise logic audit (#2163) * Use only TTL provided by us. * Don't wait 8hs upon error * Increase the advertise timeout --- share/availability/discovery/discovery.go | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 0a2deb7858..cd1ccbb8f8 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -35,11 +35,6 @@ const ( defaultRetryTimeout = time.Second ) -// waitF calculates time to restart announcing. -var waitF = func(ttl time.Duration) time.Duration { - return 7 * ttl / 8 -} - type Parameters struct { // PeersLimit defines the soft limit of FNs to connect to via discovery. // Set 0 to disable. @@ -67,8 +62,9 @@ func (p Parameters) withDefaults() Parameters { func DefaultParameters() Parameters { return Parameters{ - PeersLimit: 5, - AdvertiseInterval: time.Hour * 8, + PeersLimit: 5, + // based on https://github.com/libp2p/go-libp2p-kad-dht/pull/793 + AdvertiseInterval: time.Hour * 22, } } @@ -151,32 +147,41 @@ func (d *Discovery) Peers(ctx context.Context) ([]peer.ID, error) { // TODO: Start advertising only after the reachability is confirmed by AutoNAT func (d *Discovery) Advertise(ctx context.Context) { if d.params.AdvertiseInterval == -1 { + log.Warn("AdvertiseInterval is set to -1. Skipping advertising...") return } timer := time.NewTimer(d.params.AdvertiseInterval) defer timer.Stop() for { - ttl, err := d.disc.Advertise(ctx, rendezvousPoint) + _, err := d.disc.Advertise(ctx, rendezvousPoint) if err != nil { - log.Debugf("Error advertising %s: %s", rendezvousPoint, err.Error()) if ctx.Err() != nil { return } + log.Warn("error advertising %s: %s", rendezvousPoint, err.Error()) + errTimer := time.NewTimer(time.Minute) select { - case <-timer.C: - timer.Reset(d.params.AdvertiseInterval) + case <-errTimer.C: + errTimer.Stop() + if !timer.Stop() { + <-timer.C + } continue case <-ctx.Done(): + errTimer.Stop() return } } log.Debugf("advertised") + if !timer.Stop() { + <-timer.C + } + timer.Reset(d.params.AdvertiseInterval) select { case <-timer.C: - timer.Reset(waitF(ttl)) case <-ctx.Done(): return } From 929a334e90b0a81e890a113beccb43caa2dee485 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 5 May 2023 18:32:42 +0300 Subject: [PATCH 0579/1008] feat(metrics): set build info as an attribute in metrics (#2165) --- cmd/celestia/util.go | 6 ++++++ cmd/celestia/version.go | 7 +++++-- cmd/env.go | 11 +++++++++++ cmd/flags_misc.go | 2 +- nodebuilder/node/buildInfo.go | 9 +++++++++ nodebuilder/node_test.go | 1 + nodebuilder/settings.go | 10 +++++----- 7 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 nodebuilder/node/buildInfo.go diff --git a/cmd/celestia/util.go b/cmd/celestia/util.go index a38860d1f7..85505c3a60 100644 --- a/cmd/celestia/util.go +++ b/cmd/celestia/util.go @@ -26,6 +26,12 @@ func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) err return err } ctx = cmdnode.WithNetwork(ctx, parsedNetwork) + ctx = cmdnode.WithNodeBuildInfo(ctx, &node.BuildInfo{ + LastCommit: lastCommit, + SemanticVersion: semanticVersion, + SystemVersion: systemVersion, + GolangVersion: golangVersion, + }) // loads existing config into the environment ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) diff --git a/cmd/celestia/version.go b/cmd/celestia/version.go index f6fba4a007..462f17b474 100644 --- a/cmd/celestia/version.go +++ b/cmd/celestia/version.go @@ -11,6 +11,9 @@ var ( buildTime string lastCommit string semanticVersion string + + systemVersion = fmt.Sprintf("%s/%s", runtime.GOARCH, runtime.GOOS) + golangVersion = runtime.Version() ) var versionCmd = &cobra.Command{ @@ -24,6 +27,6 @@ func printBuildInfo(_ *cobra.Command, _ []string) { fmt.Printf("Semantic version: %s\n", semanticVersion) fmt.Printf("Commit: %s\n", lastCommit) fmt.Printf("Build Date: %s\n", buildTime) - fmt.Printf("System version: %s/%s\n", runtime.GOARCH, runtime.GOOS) - fmt.Printf("Golang version: %s\n", runtime.Version()) + fmt.Printf("System version: %s\n", systemVersion) + fmt.Printf("Golang version: %s\n", golangVersion) } diff --git a/cmd/env.go b/cmd/env.go index f9860a2de8..ca915d884f 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -38,6 +38,11 @@ func NodeConfig(ctx context.Context) nodebuilder.Config { return cfg } +// NodeInfo reads the node build inforamtion from the context. +func NodeInfo(ctx context.Context) node.BuildInfo { + return ctx.Value(buildInfo{}).(node.BuildInfo) +} + // WithNodeType sets the node type in the given context. func WithNodeType(ctx context.Context, tp node.Type) context.Context { return context.WithValue(ctx, nodeTypeKey{}, tp) @@ -73,10 +78,16 @@ func WithNodeConfig(ctx context.Context, config *nodebuilder.Config) context.Con return context.WithValue(ctx, configKey{}, *config) } +// WithNodeConfig sets the node config build information. +func WithNodeBuildInfo(ctx context.Context, info *node.BuildInfo) context.Context { + return context.WithValue(ctx, buildInfo{}, *info) +} + type ( optionsKey struct{} configKey struct{} storePathKey struct{} nodeTypeKey struct{} networkKey struct{} + buildInfo struct{} ) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index f671840eab..4483e17201 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -262,7 +262,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e opts = append(opts, otlpmetrichttp.WithInsecure()) } - ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx))) + ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx), NodeInfo(ctx))) } ok, err = cmd.Flags().GetBool(p2pMetrics) diff --git a/nodebuilder/node/buildInfo.go b/nodebuilder/node/buildInfo.go new file mode 100644 index 0000000000..5f5bdde28e --- /dev/null +++ b/nodebuilder/node/buildInfo.go @@ -0,0 +1,9 @@ +package node + +// BuildInfo stores all necessary information for the current build. +type BuildInfo struct { + LastCommit string + SemanticVersion string + SystemVersion string + GolangVersion string +} diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index aa22b0fcc7..11b27b076a 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -80,6 +80,7 @@ func TestLifecycle_WithMetrics(t *testing.T) { otlpmetrichttp.WithInsecure(), }, tt.tp, + node.BuildInfo{}, ), ) require.NotNil(t, node) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 67a30793f8..8da9f90da6 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -62,9 +62,10 @@ func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { } // WithMetrics enables metrics exporting for the node. -func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Option { +func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type, buildInfo node.BuildInfo) fx.Option { baseComponents := fx.Options( fx.Supply(metricOpts), + fx.Supply(buildInfo), fx.Invoke(initializeMetrics), fx.Invoke(state.WithMetrics), fx.Invoke(fraud.WithMetrics), @@ -109,6 +110,7 @@ func initializeMetrics( lc fx.Lifecycle, peerID peer.ID, nodeType node.Type, + buildInfo node.BuildInfo, opts []otlpmetrichttp.Option, ) error { exp, err := otlpmetrichttp.New(ctx, opts...) @@ -120,17 +122,15 @@ func initializeMetrics( metric.WithReader(metric.NewPeriodicReader(exp, metric.WithTimeout(2*time.Second))), metric.WithResource(resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", nodeType.String())), - // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey + semconv.ServiceNamespaceKey.String(fmt.Sprintf("Celestia-%s", nodeType.String())), + semconv.ServiceNameKey.String(fmt.Sprintf("semver-%s", buildInfo.SemanticVersion)), semconv.ServiceInstanceIDKey.String(peerID.String()), ))) - lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { return provider.Shutdown(ctx) }, }) global.SetMeterProvider(provider) - return nil } From ff65dff4c36412ee5934a53951787f197c3c08a2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 May 2023 10:03:17 +0200 Subject: [PATCH 0580/1008] deps: Bump celestia-app version to 0.13.0 (#2164) Hardfork version bump. --- core/testing.go | 1 + go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/testing.go b/core/testing.go index 8b483b40a2..88154dc7d9 100644 --- a/core/testing.go +++ b/core/testing.go @@ -79,6 +79,7 @@ func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { state, kr, "private", + nil, ) require.NoError(t, err) diff --git a/go.mod b/go.mod index a7221327bd..25bb89586e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 - github.com/celestiaorg/celestia-app v0.12.2 + github.com/celestiaorg/celestia-app v0.13.0 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 diff --git a/go.sum b/go.sum index e04d77814d..76ec8166ce 100644 --- a/go.sum +++ b/go.sum @@ -340,8 +340,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.12.2 h1:Mlxzz2SS+uvE4RnbSuXAh4MagiJBW7GnhDa+8kVbzKE= -github.com/celestiaorg/celestia-app v0.12.2/go.mod h1:lKhL1Oxk4Z29M+GQ25luTHBgwSvgiT4puPeBrjdsgXc= +github.com/celestiaorg/celestia-app v0.13.0 h1:NPOR1P98YCCv+E2I9TdqsO1/UnGyzTHW5CBhctWHaOY= +github.com/celestiaorg/celestia-app v0.13.0/go.mod h1:OcPBfWDyowJgoEQ89NB2LgLOm9LSwloCgCzdZKjmi78= github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/xBFIHj93llcw9ZQfNxyVRlI= github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= From 7f556f06e175267e0dd60b444a68554f592710a0 Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Mon, 8 May 2023 10:17:51 +0100 Subject: [PATCH 0581/1008] feat(share)!: add functional options to share pkg (#1798) ## Overview This PR introduces functional options to the share package and deprecates usage of default values directly in code, in favor of using parameterized values. ## Breaking This PR breaks the configuration. The on-disk configuration becomes (_note that `Share.Availability` is only available for light nodes_): ``` [Share] [Share.Availability] SampleAmount = 16 [Share.Discovery] PeersLimit = 5 AdvertiseInterval = "8h0m0s" ``` ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --- nodebuilder/config.go | 2 +- nodebuilder/share/config.go | 51 ++++++++++++---- nodebuilder/share/constructors.go | 5 +- nodebuilder/share/module.go | 9 ++- share/availability.go | 6 +- share/availability/cache/availability.go | 20 +++---- share/availability/discovery/discovery.go | 59 ++++++------------- .../availability/discovery/discovery_test.go | 21 +++---- share/availability/discovery/options.go | 59 +++++++++++++++++++ share/availability/full/availability.go | 6 +- share/availability/full/testing.go | 11 ++-- share/availability/light/availability.go | 22 +++++-- share/availability/light/options.go | 50 ++++++++++++++++ share/availability/light/sample.go | 4 -- share/getters/shrex_test.go | 7 +-- share/p2p/peers/manager_test.go | 20 +++---- 16 files changed, 235 insertions(+), 117 deletions(-) create mode 100644 share/availability/discovery/options.go create mode 100644 share/availability/light/options.go diff --git a/nodebuilder/config.go b/nodebuilder/config.go index d4df75d2f6..3607aa593e 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -44,7 +44,7 @@ func DefaultConfig(tp node.Type) *Config { P2P: p2p.DefaultConfig(tp), RPC: rpc.DefaultConfig(), Gateway: gateway.DefaultConfig(), - Share: share.DefaultConfig(), + Share: share.DefaultConfig(tp), Header: header.DefaultConfig(tp), } diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index d843e78dd2..cd9514fb75 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -1,14 +1,18 @@ package share import ( - disc "github.com/celestiaorg/celestia-node/share/availability/discovery" + "fmt" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" ) +// TODO: some params are pointers and other are not, Let's fix this. type Config struct { - // UseShareExchange is a flag toggling the usage of shrex protocols for blocksync. UseShareExchange bool // ShrExEDSParams sets shrexeds client and server configuration parameters ShrExEDSParams *shrexeds.Parameters @@ -16,27 +20,50 @@ type Config struct { ShrExNDParams *shrexnd.Parameters // PeerManagerParams sets peer-manager configuration parameters PeerManagerParams peers.Parameters - // Discovery sets peer discovery configuration parameters. - Discovery disc.Parameters + + LightAvailability light.Parameters `toml:",omitempty"` + Discovery discovery.Parameters } -func DefaultConfig() Config { - return Config{ - UseShareExchange: true, +func DefaultConfig(tp node.Type) Config { + cfg := Config{ + Discovery: discovery.DefaultParameters(), ShrExEDSParams: shrexeds.DefaultParameters(), ShrExNDParams: shrexnd.DefaultParameters(), + UseShareExchange: true, PeerManagerParams: peers.DefaultParameters(), - Discovery: disc.DefaultParameters(), } + + if tp == node.Light { + cfg.LightAvailability = light.DefaultParameters() + } + + return cfg } // Validate performs basic validation of the config. -func (cfg *Config) Validate() error { +func (cfg *Config) Validate(tp node.Type) error { + if tp == node.Light { + if err := cfg.LightAvailability.Validate(); err != nil { + return fmt.Errorf("nodebuilder/share: %w", err) + } + } + + if err := cfg.Discovery.Validate(); err != nil { + return fmt.Errorf("nodebuilder/share: %w", err) + } + if err := cfg.ShrExNDParams.Validate(); err != nil { - return err + return fmt.Errorf("nodebuilder/share: %w", err) } + if err := cfg.ShrExEDSParams.Validate(); err != nil { - return err + return fmt.Errorf("nodebuilder/share: %w", err) } - return cfg.PeerManagerParams.Validate() + + if err := cfg.PeerManagerParams.Validate(); err != nil { + return fmt.Errorf("nodebuilder/share: %w", err) + } + + return nil } diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 59189243a5..a3fe691798 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -21,7 +21,7 @@ import ( "github.com/celestiaorg/celestia-node/share/getters" ) -func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { +func newDiscovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { return func( r routing.ContentRouting, h host.Host, @@ -29,7 +29,8 @@ func discovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discove return disc.NewDiscovery( h, routingdisc.NewRoutingDiscovery(r), - cfg.Discovery, + disc.WithPeersLimit(cfg.Discovery.PeersLimit), + disc.WithAdvertiseInterval(cfg.Discovery.AdvertiseInterval), ) } } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index a43194d53d..6cfcfdb475 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -24,7 +24,7 @@ import ( func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { // sanitize config values before constructing module - cfgErr := cfg.Validate() + cfgErr := cfg.Validate(tp) baseComponents := fx.Options( fx.Supply(*cfg), @@ -33,7 +33,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(newModule), fx.Invoke(func(disc *disc.Discovery) {}), fx.Provide(fx.Annotate( - discovery(*cfg), + newDiscovery(*cfg), fx.OnStart(func(ctx context.Context, d *disc.Discovery) error { return d.Start(ctx) }), @@ -170,6 +170,11 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, + fx.Provide(func() []light.Option { + return []light.Option{ + light.WithSampleAmount(cfg.LightAvailability.SampleAmount), + } + }), shrexGetterComponents, fx.Invoke(share.EnsureEmptySquareExists), fx.Provide(getters.NewIPLDGetter), diff --git a/share/availability.go b/share/availability.go index f02a9f55f2..9538573114 100644 --- a/share/availability.go +++ b/share/availability.go @@ -4,7 +4,7 @@ import ( "context" "errors" - "github.com/celestiaorg/celestia-app/pkg/da" + da "github.com/celestiaorg/celestia-app/pkg/da" ) // ErrNotAvailable is returned whenever DA sampling fails. @@ -15,11 +15,9 @@ var ErrNotAvailable = errors.New("share: data not available") type Root = da.DataAvailabilityHeader // Availability defines interface for validation of Shares' availability. -// -//go:generate mockgen -destination=availability/mocks/availability.go -package=mocks . Availability type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on - // the Network by requesting the EDS from the provided peers. + // the Network. SharesAvailable(context.Context, *Root) error // ProbabilityOfAvailability calculates the probability of the data square // being available based on the number of samples collected. diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index bd061b4e9b..d6496f7ff8 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -15,17 +15,12 @@ import ( "github.com/celestiaorg/celestia-node/share" ) -var log = logging.Logger("share/cache") - var ( - // DefaultWriteBatchSize defines the size of the batched header write. - // Headers are written in batches not to thrash the underlying Datastore with writes. - // TODO(@Wondertan, @renaynay): Those values must be configurable and proper defaults should be set - // for specific node type. (#709) - DefaultWriteBatchSize = 2048 - cacheAvailabilityPrefix = datastore.NewKey("sampling_result") - + log = logging.Logger("share/cache") minRoot = da.MinDataAvailabilityHeader() + + cacheAvailabilityPrefix = datastore.NewKey("sampling_result") + writeBatchSize = 2048 ) // ShareAvailability wraps a given share.Availability (whether it's light or full) @@ -42,9 +37,12 @@ type ShareAvailability struct { // NewShareAvailability wraps the given share.Availability with an additional datastore // for sampling result caching. -func NewShareAvailability(avail share.Availability, ds datastore.Batching) *ShareAvailability { +func NewShareAvailability( + avail share.Availability, + ds datastore.Batching, +) *ShareAvailability { ds = namespace.Wrap(ds, cacheAvailabilityPrefix) - autoDS := autobatch.NewAutoBatching(ds, DefaultWriteBatchSize) + autoDS := autobatch.NewAutoBatching(ds, writeBatchSize) return &ShareAvailability{ avail: avail, diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index cd1ccbb8f8..a9b0304dbc 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -35,53 +35,24 @@ const ( defaultRetryTimeout = time.Second ) -type Parameters struct { - // PeersLimit defines the soft limit of FNs to connect to via discovery. - // Set 0 to disable. - PeersLimit uint - // AdvertiseInterval is a interval between advertising sessions. - // Set -1 to disable. - // NOTE: only full and bridge can advertise themselves. - AdvertiseInterval time.Duration - // discoveryRetryTimeout is an interval between discovery attempts - // when we discovered lower than PeersLimit peers. - // Set -1 to disable. - discoveryRetryTimeout time.Duration -} - -func (p Parameters) withDefaults() Parameters { - def := DefaultParameters() - if p.AdvertiseInterval == 0 { - p.AdvertiseInterval = def.AdvertiseInterval - } - if p.discoveryRetryTimeout == 0 { - p.discoveryRetryTimeout = defaultRetryTimeout - } - return p -} - -func DefaultParameters() Parameters { - return Parameters{ - PeersLimit: 5, - // based on https://github.com/libp2p/go-libp2p-kad-dht/pull/793 - AdvertiseInterval: time.Hour * 22, - } -} +// defaultRetryTimeout defines time interval between discovery attempts. +var discoveryRetryTimeout = defaultRetryTimeout // Discovery combines advertise and discover services and allows to store discovered nodes. // TODO: The code here gets horribly hairy, so we should refactor this at some point type Discovery struct { - params Parameters - - set *limitedSet - host host.Host - disc discovery.Discovery - connector *backoffConnector + set *limitedSet + host host.Host + disc discovery.Discovery + connector *backoffConnector + // onUpdatedPeers will be called on peer set changes onUpdatedPeers OnUpdatedPeers triggerDisc chan struct{} cancel context.CancelFunc + + params Parameters } type OnUpdatedPeers func(peerID peer.ID, isAdded bool) @@ -90,15 +61,21 @@ type OnUpdatedPeers func(peerID peer.ID, isAdded bool) func NewDiscovery( h host.Host, d discovery.Discovery, - params Parameters, + opts ...Option, ) *Discovery { + params := DefaultParameters() + + for _, opt := range opts { + opt(¶ms) + } + return &Discovery{ - params: params.withDefaults(), set: newLimitedSet(params.PeersLimit), host: h, disc: d, connector: newBackoffConnector(h, defaultBackoffFactory), onUpdatedPeers: func(peer.ID, bool) {}, + params: params, triggerDisc: make(chan struct{}), } } @@ -191,7 +168,7 @@ func (d *Discovery) Advertise(ctx context.Context) { // discoveryLoop ensures we always have '~peerLimit' connected peers. // It starts peer discovery per request and restarts the process until the soft limit reached. func (d *Discovery) discoveryLoop(ctx context.Context) { - t := time.NewTicker(d.params.discoveryRetryTimeout) + t := time.NewTicker(discoveryRetryTimeout) defer t.Stop() for { // drain all previous ticks from channel diff --git a/share/availability/discovery/discovery_test.go b/share/availability/discovery/discovery_test.go index fd88a98586..eada732aa9 100644 --- a/share/availability/discovery/discovery_test.go +++ b/share/availability/discovery/discovery_test.go @@ -24,11 +24,11 @@ func TestDiscovery(t *testing.T) { tn := newTestnet(ctx, t) - peerA := tn.discovery(Parameters{ - PeersLimit: nodes, - discoveryRetryTimeout: time.Millisecond * 100, - AdvertiseInterval: -1, // we don't want to be found but only find - }) + peerA := tn.discovery( + WithPeersLimit(nodes), + WithAdvertiseInterval(-1), + ) + discoveryRetryTimeout = time.Millisecond * 100 // defined in discovery.go type peerUpdate struct { peerID peer.ID @@ -41,11 +41,8 @@ func TestDiscovery(t *testing.T) { discs := make([]*Discovery, nodes) for i := range discs { - discs[i] = tn.discovery(Parameters{ - PeersLimit: 0, - discoveryRetryTimeout: -1, - AdvertiseInterval: time.Millisecond * 100, - }) + discs[i] = tn.discovery(WithPeersLimit(0), WithAdvertiseInterval(time.Millisecond*100)) + discoveryRetryTimeout = -1 // defined in discovery.go select { case res := <-updateCh: @@ -98,9 +95,9 @@ func newTestnet(ctx context.Context, t *testing.T) *testnet { return &testnet{ctx: ctx, T: t, bootstrapper: *host.InfoFromHost(hst)} } -func (t *testnet) discovery(params Parameters) *Discovery { +func (t *testnet) discovery(opts ...Option) *Discovery { hst, routingDisc := t.peer() - disc := NewDiscovery(hst, routingDisc, params) + disc := NewDiscovery(hst, routingDisc, opts...) err := disc.Start(t.ctx) require.NoError(t.T, err) t.T.Cleanup(func() { diff --git a/share/availability/discovery/options.go b/share/availability/discovery/options.go new file mode 100644 index 0000000000..8a6a162e11 --- /dev/null +++ b/share/availability/discovery/options.go @@ -0,0 +1,59 @@ +package discovery + +import ( + "fmt" + "time" +) + +// Parameters is the set of Parameters that must be configured for the Discovery module +type Parameters struct { + // PeersLimit defines the soft limit of FNs to connect to via discovery. + // Set 0 to disable. + PeersLimit uint + // AdvertiseInterval is a interval between advertising sessions. + // Set -1 to disable. + // NOTE: only full and bridge can advertise themselves. + AdvertiseInterval time.Duration +} + +// Option is a function that configures Discovery Parameters +type Option func(*Parameters) + +// DefaultParameters returns the default Parameters' configuration values +// for the Discovery module +func DefaultParameters() Parameters { + return Parameters{ + PeersLimit: 5, + // based on https://github.com/libp2p/go-libp2p-kad-dht/pull/793 + AdvertiseInterval: time.Hour * 22, + } +} + +// Validate validates the values in Parameters +func (p *Parameters) Validate() error { + if p.AdvertiseInterval <= 0 { + return fmt.Errorf( + "discovery: invalid option: value AdvertiseInterval %s, %s", + "is 0 or negative.", + "value must be positive", + ) + } + + return nil +} + +// WithPeersLimit is a functional option that Discovery +// uses to set the PeersLimit configuration param +func WithPeersLimit(peersLimit uint) Option { + return func(p *Parameters) { + p.PeersLimit = peersLimit + } +} + +// WithAdvertiseInterval is a functional option that Discovery +// uses to set the AdvertiseInterval configuration param +func WithAdvertiseInterval(advInterval time.Duration) Option { + return func(p *Parameters) { + p.AdvertiseInterval = advInterval + } +} diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 9eef8d1372..b5d1d439a5 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -27,7 +27,11 @@ type ShareAvailability struct { } // NewShareAvailability creates a new full ShareAvailability. -func NewShareAvailability(store *eds.Store, getter share.Getter, disc *discovery.Discovery) *ShareAvailability { +func NewShareAvailability( + store *eds.Store, + getter share.Getter, + disc *discovery.Discovery, +) *ShareAvailability { return &ShareAvailability{ store: store, getter: getter, diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index df4561a8eb..1e86b1e381 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -37,10 +37,11 @@ func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { } func TestAvailability(getter share.Getter) *ShareAvailability { - params := discovery.DefaultParameters() - params.AdvertiseInterval = time.Second - params.PeersLimit = 10 - disc := discovery.NewDiscovery(nil, - routing.NewRoutingDiscovery(routinghelpers.Null{}), params) + disc := discovery.NewDiscovery( + nil, + routing.NewRoutingDiscovery(routinghelpers.Null{}), + discovery.WithAdvertiseInterval(time.Second), + discovery.WithPeersLimit(10), + ) return NewShareAvailability(nil, getter, disc) } diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index c947e65e63..07a3e801d9 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -20,14 +20,24 @@ var log = logging.Logger("share/light") // on the network doing sampling over the same Root to collectively verify its availability. type ShareAvailability struct { getter share.Getter + params Parameters } // NewShareAvailability creates a new light Availability. -func NewShareAvailability(getter share.Getter) *ShareAvailability { - return &ShareAvailability{getter} +func NewShareAvailability( + getter share.Getter, + opts ...Option, +) *ShareAvailability { + params := DefaultParameters() + + for _, opt := range opts { + opt(¶ms) + } + + return &ShareAvailability{getter, params} } -// SharesAvailable randomly samples DefaultSampleAmount amount of Shares committed to the given +// SharesAvailable randomly samples `params.SampleAmount` amount of Shares committed to the given // Root. This way SharesAvailable subjectively verifies that Shares are available. func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { log.Debugw("Validate availability", "root", dah.String()) @@ -38,7 +48,7 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo "err", err) panic(err) } - samples, err := SampleSquare(len(dah.RowsRoots), DefaultSampleAmount) + samples, err := SampleSquare(len(dah.RowsRoots), int(la.params.SampleAmount)) if err != nil { return err } @@ -90,9 +100,9 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo // ProbabilityOfAvailability calculates the probability that the // data square is available based on the amount of samples collected -// (DefaultSampleAmount). +// (params.SampleAmount). // // Formula: 1 - (0.75 ** amount of samples) func (la *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { - return 1 - math.Pow(0.75, float64(DefaultSampleAmount)) + return 1 - math.Pow(0.75, float64(la.params.SampleAmount)) } diff --git a/share/availability/light/options.go b/share/availability/light/options.go new file mode 100644 index 0000000000..80fd27acfd --- /dev/null +++ b/share/availability/light/options.go @@ -0,0 +1,50 @@ +package light + +import ( + "fmt" +) + +// SampleAmount specifies the minimum required amount of samples a light node must perform +// before declaring that a block is available +var ( + DefaultSampleAmount uint = 16 +) + +// Parameters is the set of Parameters that must be configured for the light +// availability implementation +type Parameters struct { + SampleAmount uint // The minimum required amount of samples to perform +} + +// Option is a function that configures light availability Parameters +type Option func(*Parameters) + +// DefaultParameters returns the default Parameters' configuration values +// for the light availability implementation +func DefaultParameters() Parameters { + return Parameters{ + SampleAmount: DefaultSampleAmount, + } +} + +// Validate validates the values in Parameters +func (p *Parameters) Validate() error { + if p.SampleAmount <= 0 { + return fmt.Errorf( + "light availability: invalid option: value %s was %s, where it should be %s", + "SampleAmount", + "<= 0", // current value + ">= 0", // what the value should be + ) + } + + return nil +} + +// WithSampleAmount is a functional option that the Availability interface +// implementers use to set the SampleAmount configuration param +func WithSampleAmount(sampleAmount uint) Option { + return func(p *Parameters) { + p.SampleAmount = sampleAmount + } +} diff --git a/share/availability/light/sample.go b/share/availability/light/sample.go index 12d8505397..e66ff9aafe 100644 --- a/share/availability/light/sample.go +++ b/share/availability/light/sample.go @@ -6,10 +6,6 @@ import ( "math/big" ) -// DefaultSampleAmount sets the default amount of samples to be sampled from the network by -// ShareAvailability. -var DefaultSampleAmount = 16 - // Sample is a point in 2D space over square. type Sample struct { Row, Col int diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index b93a40d488..01322b014c 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -158,10 +158,9 @@ func testManager(ctx context.Context, host host.Host, headerSub libhead.Subscrib } disc := discovery.NewDiscovery(nil, - routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), discovery.Parameters{ - PeersLimit: 10, - AdvertiseInterval: time.Second, - }, + routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), + discovery.WithPeersLimit(10), + discovery.WithAdvertiseInterval(time.Second), ) connGater, err := conngater.NewBasicConnectionGater(ds_sync.MutexWrap(datastore.NewMapDatastore())) if err != nil { diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 804dd4b673..cdcffef4db 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -393,10 +393,8 @@ func TestIntegration(t *testing.T) { bnDisc := discovery.NewDiscovery( nw.Hosts()[0], routingdisc.NewRoutingDiscovery(router1), - discovery.Parameters{ - PeersLimit: 0, - AdvertiseInterval: time.Second, - }, + discovery.WithPeersLimit(0), + discovery.WithAdvertiseInterval(time.Second), ) // set up full node / receiver node @@ -406,10 +404,8 @@ func TestIntegration(t *testing.T) { fnDisc := discovery.NewDiscovery( nw.Hosts()[1], routingdisc.NewRoutingDiscovery(router2), - discovery.Parameters{ - PeersLimit: 10, - AdvertiseInterval: time.Second, - }, + discovery.WithPeersLimit(10), + discovery.WithAdvertiseInterval(time.Second), ) err = fnDisc.Start(ctx) require.NoError(t, err) @@ -463,10 +459,10 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten } disc := discovery.NewDiscovery(nil, - routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), discovery.Parameters{ - PeersLimit: 0, - AdvertiseInterval: time.Second, - }) + routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), + discovery.WithPeersLimit(0), + discovery.WithAdvertiseInterval(time.Second), + ) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) if err != nil { return nil, err From a57ad562757815958b749f7d5068b53090509afb Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Wed, 10 May 2023 07:19:38 +0100 Subject: [PATCH 0582/1008] fix(share/p2p/shrexeds): return correct err on context error (#2177) Resolves item 7 on https://github.com/celestiaorg/celestia-node/issues/2176 --- share/p2p/shrexeds/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index dccb5ce06a..5f3470179e 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -57,7 +57,7 @@ func (c *Client) RequestEDS( log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String(), "error", err) if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { c.metrics.ObserveRequests(1, p2p.StatusTimeout) - return nil, ctx.Err() + return nil, err } // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not // unwrap to a ctx err From 267b8ffd7cabaf7d3732c04ed2a4ea022d1337e4 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 10 May 2023 17:06:13 +0800 Subject: [PATCH 0583/1008] fix(discovery): race in discovery tests (#2179) ## Overview Fix for error: ``` WARNING: DATA RACE Read at 0x000001ead5c8 by goroutine 282: github.com/celestiaorg/celestia-node/share/availability/discovery.(*Discovery).discoveryLoop() /home/runner/work/celestia-node/celestia-node/share/availability/discovery/discovery.go:174 +0x59 github.com/celestiaorg/celestia-node/share/availability/discovery.(*Discovery).Start.func2() /home/runner/work/celestia-node/celestia-node/share/availability/discovery/discovery.go:99 +0x58 Previous write at 0x000001ead5c8 by goroutine 172: github.com/celestiaorg/celestia-node/share/availability/discovery.TestDiscovery() /home/runner/work/celestia-node/celestia-node/share/availability/discovery/discovery_test.go:31 +0x14c testing.tRunner() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1576 +0x216 testing.(*T).Run.func1() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1629 +0x47 Goroutine 282 (running) created at: github.com/celestiaorg/celestia-node/share/availability/discovery.(*Discovery).Start() /home/runner/work/celestia-node/celestia-node/share/availability/discovery/discovery.go:99 +0x3ad github.com/celestiaorg/celestia-node/share/availability/discovery.(*testnet).discovery() /home/runner/work/celestia-node/celestia-node/share/availability/discovery/discovery_test.go:101 +0xb7 github.com/celestiaorg/celestia-node/share/availability/discovery.TestDiscovery() /home/runner/work/celestia-node/celestia-node/share/availability/discovery/discovery_test.go:27 +0x138 testing.tRunner() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1576 +0x216 testing.(*T).Run.func1() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1629 +0x47 Goroutine 172 (running) created at: testing.(*T).Run() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1629 +0x805 testing.runTests.func1() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:2036 +0x8d testing.tRunner() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1576 +0x216 testing.runTests() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:2034 +0x87c testing.(*M).Run() /opt/hostedtoolcache/go/1.20.3/x64/src/testing/testing.go:1906 +0xb44 main.main() _testmain.go:63 +0x2e9 ``` --- share/availability/discovery/discovery_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/availability/discovery/discovery_test.go b/share/availability/discovery/discovery_test.go index eada732aa9..f0935086ef 100644 --- a/share/availability/discovery/discovery_test.go +++ b/share/availability/discovery/discovery_test.go @@ -19,6 +19,8 @@ import ( func TestDiscovery(t *testing.T) { const nodes = 10 // higher number brings higher coverage + discoveryRetryTimeout = time.Millisecond * 100 // defined in discovery.go + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) t.Cleanup(cancel) @@ -28,7 +30,6 @@ func TestDiscovery(t *testing.T) { WithPeersLimit(nodes), WithAdvertiseInterval(-1), ) - discoveryRetryTimeout = time.Millisecond * 100 // defined in discovery.go type peerUpdate struct { peerID peer.ID @@ -42,7 +43,6 @@ func TestDiscovery(t *testing.T) { discs := make([]*Discovery, nodes) for i := range discs { discs[i] = tn.discovery(WithPeersLimit(0), WithAdvertiseInterval(time.Millisecond*100)) - discoveryRetryTimeout = -1 // defined in discovery.go select { case res := <-updateCh: From e632a471e6ab79003ba4f4efd54a92417eb276f2 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 10 May 2023 17:17:01 +0800 Subject: [PATCH 0584/1008] fix(daser): do not spawn retry job twice (#2178) ## Overview In some rare cases when header height was not available for sampling and also picked up by both `recent` and `catchup` workers it could result in two parallel `retry` workers spawned for same header height. This PR fixes given case. --- das/state.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/das/state.go b/das/state.go index 12de2a0b01..894bf742ba 100644 --- a/das/state.go +++ b/das/state.go @@ -96,6 +96,10 @@ func (s *coordinatorState) handleResult(res result) { // if job was already in retry and failed again, carry over attempt count lastRetry, ok := s.inRetry[h] if ok { + if res.job.jobType != retryJob { + // retry job has been already created by another worker (recent or catchup) + continue + } delete(s.inRetry, h) } From ddf2f93ec188e8517b24fbe1418f812fa7688032 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 10 May 2023 21:39:20 +0800 Subject: [PATCH 0585/1008] fix(discovery): findCtx is canceled too early by defer (#2180) ## Overview If underlying discovery.FindPeers closes the channel, `discover` call would return calling the defer stack and canceling `fincCtx` too early. This would result in all routines trying to establish connection being canceled before having time to succeed. Fixed by changing defer stack order of `wg.Wait` and `context.Cancel` calls. The change also fixes flaky `get_peer_from_discovery` test, since flakiness was actually a bug. --- share/availability/discovery/discovery.go | 7 +++++-- share/p2p/peers/manager_test.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index a9b0304dbc..913b66d328 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -245,11 +245,14 @@ func (d *Discovery) discover(ctx context.Context) bool { var wg errgroup.Group // limit to minimize chances of overreaching the limit wg.SetLimit(int(d.set.Limit())) - defer wg.Wait() //nolint:errcheck // stop discovery when we are done findCtx, findCancel := context.WithCancel(ctx) - defer findCancel() + defer func() { + // some workers could still be running, wait them to finish before canceling findCtx + wg.Wait() //nolint:errcheck + findCancel() + }() peers, err := d.disc.FindPeers(findCtx, rendezvousPoint) if err != nil { diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index cdcffef4db..b35ef70e6b 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -223,7 +223,7 @@ func TestManager(t *testing.T) { stopManager(t, manager) }) - t.Run("get peer from discovery", func(t *testing.T) { + t.Run("mark pool synced", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) From a8d02a2f839cfffd4a5a2353280fd5277bf53f67 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 11 May 2023 17:15:33 +0800 Subject: [PATCH 0586/1008] refactor(metrics): use user context for metrics observation (#2175) ## Overview Use same approach as discussed in https://github.com/celestiaorg/celestia-node/pull/2155#discussion_r1188313387 --- das/metrics.go | 9 +++++++++ share/getters/shrex.go | 24 ++++++++++++------------ share/p2p/metrics.go | 14 ++++++-------- share/p2p/peers/manager.go | 11 ++++++----- share/p2p/peers/metrics.go | 20 ++++++++++---------- share/p2p/shrexeds/client.go | 12 ++++++------ share/p2p/shrexeds/server.go | 6 +++--- share/p2p/shrexnd/client.go | 14 +++++++------- share/p2p/shrexnd/server.go | 31 +++++++++++++++++-------------- 9 files changed, 76 insertions(+), 65 deletions(-) diff --git a/das/metrics.go b/das/metrics.go index 5a6262969b..fd72fe5e4e 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -149,6 +149,9 @@ func (m *metrics) observeSample( if m == nil { return } + if ctx.Err() != nil { + ctx = context.Background() + } m.sampleTime.Record(ctx, sampleTime.Seconds(), attribute.Bool(failedLabel, err != nil), attribute.Int(headerWidthLabel, len(h.DAH.RowsRoots)), @@ -169,6 +172,9 @@ func (m *metrics) observeGetHeader(ctx context.Context, d time.Duration) { if m == nil { return } + if ctx.Err() != nil { + ctx = context.Background() + } m.getHeaderTime.Record(ctx, d.Seconds()) } @@ -177,5 +183,8 @@ func (m *metrics) observeNewHead(ctx context.Context) { if m == nil { return } + if ctx.Err() != nil { + ctx = context.Background() + } m.newHead.Add(ctx, 1) } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 3fd7c4846c..99a94048f0 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -29,7 +29,6 @@ const ( // serve getEDS request for block size 256 defaultMinRequestTimeout = time.Minute // should be >= shrexeds server write timeout defaultMinAttemptsCount = 3 - metricObservationTimeout = 100 * time.Millisecond ) var meter = global.MeterProvider().Meter("shrex/getter") @@ -39,22 +38,23 @@ type metrics struct { ndAttempts syncint64.Histogram } -func (m *metrics) recordEDSAttempt(attemptCount int, success bool) { +func (m *metrics) recordEDSAttempt(ctx context.Context, attemptCount int, success bool) { if m == nil { return } - ctx, cancel := context.WithTimeout(context.Background(), metricObservationTimeout) - defer cancel() + if ctx.Err() != nil { + ctx = context.Background() + } m.edsAttempts.Record(ctx, int64(attemptCount), attribute.Bool("success", success)) } -func (m *metrics) recordNDAttempt(attemptCount int, success bool) { +func (m *metrics) recordNDAttempt(ctx context.Context, attemptCount int, success bool) { if m == nil { return } - - ctx, cancel := context.WithTimeout(context.Background(), metricObservationTimeout) - defer cancel() + if ctx.Err() != nil { + ctx = context.Background() + } m.ndAttempts.Record(ctx, int64(attemptCount), attribute.Bool("success", success)) } @@ -140,7 +140,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) - sg.metrics.recordEDSAttempt(attempt, false) + sg.metrics.recordEDSAttempt(ctx, attempt, false) return nil, fmt.Errorf("getter/shrex: %w", err) } @@ -151,7 +151,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex switch { case getErr == nil: setStatus(peers.ResultSynced) - sg.metrics.recordEDSAttempt(attempt, true) + sg.metrics.recordEDSAttempt(ctx, attempt, true) return eds, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): @@ -198,7 +198,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) - sg.metrics.recordNDAttempt(attempt, false) + sg.metrics.recordNDAttempt(ctx, attempt, false) return nil, fmt.Errorf("getter/shrex: %w", err) } @@ -209,7 +209,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( switch { case getErr == nil: setStatus(peers.ResultNoop) - sg.metrics.recordNDAttempt(attempt, true) + sg.metrics.recordNDAttempt(ctx, attempt, true) return nd, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): diff --git a/share/p2p/metrics.go b/share/p2p/metrics.go index 4b94d8c8d5..87c1e2eeb0 100644 --- a/share/p2p/metrics.go +++ b/share/p2p/metrics.go @@ -3,7 +3,6 @@ package p2p import ( "context" "fmt" - "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/global" @@ -14,8 +13,6 @@ import ( var meter = global.MeterProvider().Meter("shrex/eds") -var observationTimeout = 100 * time.Millisecond - type status string const ( @@ -30,14 +27,15 @@ type Metrics struct { totalRequestCounter syncint64.Counter } -// ObserveRequests increments the total number of requests sent with the given status as an attribute. -func (m *Metrics) ObserveRequests(count int64, status status) { +// ObserveRequests increments the total number of requests sent with the given status as an +// attribute. +func (m *Metrics) ObserveRequests(ctx context.Context, count int64, status status) { if m == nil { return } - - ctx, cancel := context.WithTimeout(context.Background(), observationTimeout) - defer cancel() + if ctx.Err() != nil { + ctx = context.Background() + } m.totalRequestCounter.Add(ctx, count, attribute.String("status", string(status))) } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 7a2dcc6dc3..35aa67c6e6 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -189,29 +189,30 @@ func (m *Manager) Peer( p.remove(peerID) return m.Peer(ctx, datahash) } - return m.newPeer(datahash, peerID, sourceShrexSub, p.len(), 0) + return m.newPeer(ctx, datahash, peerID, sourceShrexSub, p.len(), 0) } // if no peer for datahash is currently available, try to use full node // obtained from discovery peerID, ok = m.fullNodes.tryGet() if ok { - return m.newPeer(datahash, peerID, sourceFullNodes, m.fullNodes.len(), 0) + return m.newPeer(ctx, datahash, peerID, sourceFullNodes, m.fullNodes.len(), 0) } // no peers are available right now, wait for the first one start := time.Now() select { case peerID = <-p.next(ctx): - return m.newPeer(datahash, peerID, sourceShrexSub, p.len(), time.Since(start)) + return m.newPeer(ctx, datahash, peerID, sourceShrexSub, p.len(), time.Since(start)) case peerID = <-m.fullNodes.next(ctx): - return m.newPeer(datahash, peerID, sourceFullNodes, m.fullNodes.len(), time.Since(start)) + return m.newPeer(ctx, datahash, peerID, sourceFullNodes, m.fullNodes.len(), time.Since(start)) case <-ctx.Done(): return "", nil, ctx.Err() } } func (m *Manager) newPeer( + ctx context.Context, datahash share.DataHash, peerID peer.ID, source peerSource, @@ -223,7 +224,7 @@ func (m *Manager) newPeer( "source", source, "pool_size", poolSize, "wait (s)", waitTime) - m.metrics.observeGetPeer(source, poolSize, waitTime) + m.metrics.observeGetPeer(ctx, source, poolSize, waitTime) return peerID, m.doneFunc(datahash, peerID, source), nil } diff --git a/share/p2p/peers/metrics.go b/share/p2p/peers/metrics.go index 412cc01996..ab5bfc97b2 100644 --- a/share/p2p/peers/metrics.go +++ b/share/p2p/peers/metrics.go @@ -18,8 +18,6 @@ import ( ) const ( - observeTimeout = 100 * time.Millisecond - isInstantKey = "is_instant" doneResultKey = "done_result" @@ -171,12 +169,14 @@ func initMetrics(manager *Manager) (*metrics, error) { return metrics, nil } -func (m *metrics) observeGetPeer(source peerSource, poolSize int, waitTime time.Duration) { +func (m *metrics) observeGetPeer(ctx context.Context, + source peerSource, poolSize int, waitTime time.Duration) { if m == nil { return } - ctx, cancel := context.WithTimeout(context.Background(), observeTimeout) - defer cancel() + if ctx.Err() != nil { + ctx = context.Background() + } m.getPeer.Add(ctx, 1, attribute.String(sourceKey, string(source)), attribute.Bool(isInstantKey, waitTime == 0)) @@ -196,9 +196,8 @@ func (m *metrics) observeDoneResult(source peerSource, result result) { if m == nil { return } - ctx, cancel := context.WithTimeout(context.Background(), observeTimeout) - defer cancel() + ctx := context.Background() m.doneResult.Add(ctx, 1, attribute.String(sourceKey, string(source)), attribute.String(doneResultKey, string(result))) @@ -224,10 +223,11 @@ func (m *metrics) validationObserver(validator shrexsub.ValidatorFn) shrexsub.Va resStr = "unknown" } - observeCtx, cancel := context.WithTimeout(context.Background(), observeTimeout) - defer cancel() + if ctx.Err() != nil { + ctx = context.Background() + } - m.validationResult.Add(observeCtx, 1, + m.validationResult.Add(ctx, 1, attribute.String(validationResultKey, resStr)) return res } diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 5f3470179e..daff83a07b 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -56,7 +56,7 @@ func (c *Client) RequestEDS( } log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String(), "error", err) if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - c.metrics.ObserveRequests(1, p2p.StatusTimeout) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) return nil, err } // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not @@ -64,7 +64,7 @@ func (c *Client) RequestEDS( var ne net.Error if errors.As(err, &ne) && ne.Timeout() { if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { - c.metrics.ObserveRequests(1, p2p.StatusTimeout) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) return nil, context.DeadlineExceeded } } @@ -110,7 +110,7 @@ func (c *Client) doRequest( if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { - c.metrics.ObserveRequests(1, p2p.StatusRateLimited) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusRateLimited) return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck @@ -124,10 +124,10 @@ func (c *Client) doRequest( if err != nil { return nil, fmt.Errorf("failed to read eds from ods bytes: %w", err) } - c.metrics.ObserveRequests(1, p2p.StatusSuccess) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) return eds, nil case pb.Status_NOT_FOUND: - c.metrics.ObserveRequests(1, p2p.StatusNotFound) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) return nil, p2p.ErrNotFound case pb.Status_INVALID: log.Debug("client: invalid request") @@ -135,7 +135,7 @@ func (c *Client) doRequest( case pb.Status_INTERNAL: fallthrough default: - c.metrics.ObserveRequests(1, p2p.StatusInternalErr) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusInternalErr) return nil, p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index ac246c00c1..cecf0c5cb8 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -65,7 +65,7 @@ func (s *Server) Stop(context.Context) error { func (s *Server) observeRateLimitedRequests() { numRateLimited := s.middleware.DrainCounter() if numRateLimited > 0 { - s.metrics.ObserveRequests(numRateLimited, p2p.StatusRateLimited) + s.metrics.ObserveRequests(context.Background(), numRateLimited, p2p.StatusRateLimited) } } @@ -103,7 +103,7 @@ func (s *Server) handleStream(stream network.Stream) { status := p2p_pb.Status_OK switch { case errors.Is(err, eds.ErrNotFound): - s.metrics.ObserveRequests(1, p2p.StatusNotFound) + s.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) status = p2p_pb.Status_NOT_FOUND case err != nil: logger.Errorw("server: get CAR", "err", err) @@ -134,7 +134,7 @@ func (s *Server) handleStream(stream network.Stream) { return } - s.metrics.ObserveRequests(1, p2p.StatusSuccess) + s.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) err = stream.Close() if err != nil { logger.Debugw("server: closing stream", "err", err) diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 47eb742aa7..b5ae66f51f 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -59,7 +59,7 @@ func (c *Client) RequestND( return shares, err } if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { - c.metrics.ObserveRequests(1, p2p.StatusTimeout) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) return nil, err } // some net.Errors also mean the context deadline was exceeded, but yamux/mocknet do not @@ -67,7 +67,7 @@ func (c *Client) RequestND( var ne net.Error if errors.As(err, &ne) && ne.Timeout() { if deadline, _ := ctx.Deadline(); deadline.Before(time.Now()) { - c.metrics.ObserveRequests(1, p2p.StatusTimeout) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) return nil, context.DeadlineExceeded } } @@ -112,14 +112,14 @@ func (c *Client) doRequest( if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { - c.metrics.ObserveRequests(1, p2p.StatusRateLimited) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusRateLimited) return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck return nil, fmt.Errorf("client-nd: reading response: %w", err) } - if err = c.statusToErr(resp.Status); err != nil { + if err = c.statusToErr(ctx, resp.Status); err != nil { return nil, fmt.Errorf("client-nd: response code is not OK: %w", err) } @@ -187,13 +187,13 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) } } -func (c *Client) statusToErr(code pb.StatusCode) error { +func (c *Client) statusToErr(ctx context.Context, code pb.StatusCode) error { switch code { case pb.StatusCode_OK: - c.metrics.ObserveRequests(1, p2p.StatusSuccess) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) return nil case pb.StatusCode_NOT_FOUND: - c.metrics.ObserveRequests(1, p2p.StatusNotFound) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) return p2p.ErrNotFound case pb.StatusCode_INVALID: log.Debug("client-nd: invalid request") diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 83bbcd6b40..3355bdd759 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -77,7 +77,7 @@ func (srv *Server) Stop(context.Context) error { func (srv *Server) observeRateLimitedRequests() { numRateLimited := srv.middleware.DrainCounter() if numRateLimited > 0 { - srv.metrics.ObserveRequests(numRateLimited, p2p.StatusRateLimited) + srv.metrics.ObserveRequests(context.Background(), numRateLimited, p2p.StatusRateLimited) } } @@ -120,27 +120,27 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre dah, err := srv.store.GetDAH(ctx, req.RootHash) if err != nil { if errors.Is(err, eds.ErrNotFound) { - srv.respondNotFoundError(logger, stream) + srv.respondNotFoundError(ctx, logger, stream) return } logger.Errorw("server: retrieving DAH", "err", err) - srv.respondInternalError(logger, stream) + srv.respondInternalError(ctx, logger, stream) return } shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) if errors.Is(err, share.ErrNotFound) { - srv.respondNotFoundError(logger, stream) + srv.respondNotFoundError(ctx, logger, stream) return } if err != nil { logger.Errorw("server: retrieving shares", "err", err) - srv.respondInternalError(logger, stream) + srv.respondInternalError(ctx, logger, stream) return } resp := namespacedSharesToResponse(shares) - srv.respond(logger, stream, resp) + srv.respond(ctx, logger, stream, resp) } // validateRequest checks correctness of the request @@ -156,19 +156,21 @@ func validateRequest(req pb.GetSharesByNamespaceRequest) error { } // respondNotFoundError sends internal error response to client -func (srv *Server) respondNotFoundError(logger *zap.SugaredLogger, stream network.Stream) { +func (srv *Server) respondNotFoundError(ctx context.Context, + logger *zap.SugaredLogger, stream network.Stream) { resp := &pb.GetSharesByNamespaceResponse{ Status: pb.StatusCode_NOT_FOUND, } - srv.respond(logger, stream, resp) + srv.respond(ctx, logger, stream, resp) } // respondInternalError sends internal error response to client -func (srv *Server) respondInternalError(logger *zap.SugaredLogger, stream network.Stream) { +func (srv *Server) respondInternalError(ctx context.Context, + logger *zap.SugaredLogger, stream network.Stream) { resp := &pb.GetSharesByNamespaceResponse{ Status: pb.StatusCode_INTERNAL, } - srv.respond(logger, stream, resp) + srv.respond(ctx, logger, stream, resp) } // namespacedSharesToResponse encodes shares into proto and sends it to client with OK status code @@ -195,7 +197,8 @@ func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNa } } -func (srv *Server) respond(logger *zap.SugaredLogger, stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { +func (srv *Server) respond(ctx context.Context, + logger *zap.SugaredLogger, stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { err := stream.SetWriteDeadline(time.Now().Add(srv.params.ServerWriteTimeout)) if err != nil { logger.Debugw("server: setting write deadline", "err", err) @@ -210,11 +213,11 @@ func (srv *Server) respond(logger *zap.SugaredLogger, stream network.Stream, res switch { case resp.Status == pb.StatusCode_OK: - srv.metrics.ObserveRequests(1, p2p.StatusSuccess) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) case resp.Status == pb.StatusCode_NOT_FOUND: - srv.metrics.ObserveRequests(1, p2p.StatusNotFound) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) case resp.Status == pb.StatusCode_INTERNAL: - srv.metrics.ObserveRequests(1, p2p.StatusInternalErr) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusInternalErr) } if err = stream.Close(); err != nil { logger.Debugw("server: closing stream", "err", err) From 687704e2038b256f249e9cb565d62a47ed7727ba Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 11 May 2023 17:52:53 +0800 Subject: [PATCH 0587/1008] feat(share/discovery): add discovery metrics (#2155) ## Overview Adds metrics for discovery component --- nodebuilder/settings.go | 1 + nodebuilder/share/opts.go | 11 +- share/availability/discovery/discovery.go | 16 +- share/availability/discovery/metrics.go | 184 ++++++++++++++++++++++ 4 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 share/availability/discovery/metrics.go diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 8da9f90da6..bea3c78ad2 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -71,6 +71,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type, buildIn fx.Invoke(fraud.WithMetrics), fx.Invoke(node.WithMetrics), fx.Invoke(modheader.WithMetrics), + fx.Invoke(share.WithDiscoveryMetrics), ) samplingMetrics := fx.Options( diff --git a/nodebuilder/share/opts.go b/nodebuilder/share/opts.go index dc63a4ca1e..cba949882b 100644 --- a/nodebuilder/share/opts.go +++ b/nodebuilder/share/opts.go @@ -1,18 +1,25 @@ package share import ( + disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" ) -// WithPeerManagerMetrics is a utility function that is expected to be -// "invoked" by the fx lifecycle. +// WithPeerManagerMetrics is a utility function to turn on peer manager metrics and that is +// expected to be "invoked" by the fx lifecycle. func WithPeerManagerMetrics(m *peers.Manager) error { return m.WithMetrics() } +// WithDiscoveryMetrics is a utility function to turn on discovery metrics and that is expected to +// be "invoked" by the fx lifecycle. +func WithDiscoveryMetrics(d *disc.Discovery) error { + return d.WithMetrics() +} + func WithShrexClientMetrics(edsClient *shrexeds.Client, ndClient *shrexnd.Client) error { err := edsClient.WithMetrics() if err != nil { diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 913b66d328..f1db6e0797 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -50,6 +50,8 @@ type Discovery struct { triggerDisc chan struct{} + metrics *metrics + cancel context.CancelFunc params Parameters @@ -132,6 +134,7 @@ func (d *Discovery) Advertise(ctx context.Context) { defer timer.Stop() for { _, err := d.disc.Advertise(ctx, rendezvousPoint) + d.metrics.observeAdvertise(ctx, err) if err != nil { if ctx.Err() != nil { return @@ -268,14 +271,18 @@ func (d *Discovery) discover(ctx context.Context) bool { drainChannel(ticker.C) select { case <-findCtx.Done(): + d.metrics.observeFindPeers(ctx, true, true) return true case <-ticker.C: + d.metrics.observeDiscoveryStuck(ctx) log.Warn("wasn't able to find new peers for long time") continue case p, ok := <-peers: if !ok { + isEnoughPeers := d.set.Size() >= d.set.Limit() + d.metrics.observeFindPeers(ctx, ctx.Err() != nil, isEnoughPeers) log.Debugw("discovery channel closed", "find_is_canceled", findCtx.Err() != nil) - return d.set.Size() >= d.set.Limit() + return isEnoughPeers } peer := p @@ -311,15 +318,19 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo logger := log.With("peer", peer.ID) switch { case peer.ID == d.host.ID(): + d.metrics.observeHandlePeer(ctx, handlePeerSkipSelf) logger.Debug("skip handle: self discovery") return false case len(peer.Addrs) == 0: + d.metrics.observeHandlePeer(ctx, handlePeerEmptyAddrs) logger.Debug("skip handle: empty address list") return false case d.set.Size() >= d.set.Limit(): + d.metrics.observeHandlePeer(ctx, handlePeerEnoughPeers) logger.Debug("skip handle: enough peers found") return false case d.connector.HasBackoff(peer.ID): + d.metrics.observeHandlePeer(ctx, handlePeerBackoff) logger.Debug("skip handle: backoff") return false } @@ -330,6 +341,7 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo case network.NotConnected: err := d.connector.Connect(ctx, peer) if err != nil { + d.metrics.observeHandlePeer(ctx, handlePeerConnErr) logger.Debugw("unable to connect", "err", err) return false } @@ -338,10 +350,12 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo } if !d.set.Add(peer.ID) { + d.metrics.observeHandlePeer(ctx, handlePeerInSet) logger.Debug("peer is already in discovery set") return false } d.onUpdatedPeers(peer.ID, true) + d.metrics.observeHandlePeer(ctx, handlePeerConnected) logger.Debug("added peer to set") // tag to protect peer from being killed by ConnManager diff --git a/share/availability/discovery/metrics.go b/share/availability/discovery/metrics.go new file mode 100644 index 0000000000..9edd364185 --- /dev/null +++ b/share/availability/discovery/metrics.go @@ -0,0 +1,184 @@ +package discovery + +import ( + "context" + "fmt" + + "github.com/libp2p/go-libp2p/core/peer" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncint64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" +) + +const ( + discoveryEnougPeersKey = "enough_peers" + discoveryFindCancledKey = "is_canceled" + + handlePeerResultKey = "result" + handlePeerSkipSelf handlePeerResult = "skip_self" + handlePeerEmptyAddrs handlePeerResult = "skip_empty_addresses" + handlePeerEnoughPeers handlePeerResult = "skip_enough_peers" + handlePeerBackoff handlePeerResult = "skip_backoff" + handlePeerConnected handlePeerResult = "connected" + handlePeerConnErr handlePeerResult = "conn_err" + handlePeerInSet handlePeerResult = "in_set" + + advertiseFailedKey = "failed" +) + +var ( + meter = global.MeterProvider().Meter("share_discovery") +) + +type handlePeerResult string + +type metrics struct { + peersAmount asyncint64.Gauge + discoveryResult syncint64.Counter // attributes: enough_peers[bool],is_canceled[bool] + discoveryStuck syncint64.Counter + handlePeerResult syncint64.Counter // attributes: result[string] + advertise syncint64.Counter // attributes: failed[bool] + peerAdded syncint64.Counter + peerRemoved syncint64.Counter +} + +// WithMetrics turns on metric collection in discoery. +func (d *Discovery) WithMetrics() error { + metrics, err := initMetrics(d) + if err != nil { + return fmt.Errorf("discovery: init metrics: %w", err) + } + d.metrics = metrics + d.WithOnPeersUpdate(metrics.observeOnPeersUpdate) + return nil +} + +func initMetrics(d *Discovery) (*metrics, error) { + peersAmount, err := meter.AsyncInt64().Gauge("discovery_amount_of_peers", + instrument.WithDescription("amount of peers in discovery set")) + if err != nil { + return nil, err + } + + discoveryResult, err := meter.SyncInt64().Counter("discovery_find_peers_result", + instrument.WithDescription("result of find peers run")) + if err != nil { + return nil, err + } + + discoveryStuck, err := meter.SyncInt64().Counter("discovery_lookup_is_stuck", + instrument.WithDescription("indicates discovery wasn't able to find peers for more than 1 min")) + if err != nil { + return nil, err + } + + handlePeerResultCounter, err := meter.SyncInt64().Counter("discovery_handler_peer_result", + instrument.WithDescription("result handling found peer")) + if err != nil { + return nil, err + } + + advertise, err := meter.SyncInt64().Counter("discovery_advertise_event", + instrument.WithDescription("advertise events counter")) + if err != nil { + return nil, err + } + + peerAdded, err := meter.SyncInt64().Counter("discovery_add_peer", + instrument.WithDescription("add peer to discovery set counter")) + if err != nil { + return nil, err + } + + peerRemoved, err := meter.SyncInt64().Counter("discovery_remove_peer", + instrument.WithDescription("remove peer from discovery set counter")) + if err != nil { + return nil, err + } + + metrics := &metrics{ + peersAmount: peersAmount, + discoveryResult: discoveryResult, + discoveryStuck: discoveryStuck, + handlePeerResult: handlePeerResultCounter, + advertise: advertise, + peerAdded: peerAdded, + peerRemoved: peerRemoved, + } + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + peersAmount, + }, + func(ctx context.Context) { + peersAmount.Observe(ctx, int64(d.set.Size())) + }, + ) + if err != nil { + return nil, fmt.Errorf("registering metrics callback: %w", err) + } + return metrics, nil +} + +func (m *metrics) observeFindPeers(ctx context.Context, canceled, isEnoughPeers bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.discoveryResult.Add(ctx, 1, + attribute.Bool(discoveryFindCancledKey, canceled), + attribute.Bool(discoveryEnougPeersKey, isEnoughPeers)) +} + +func (m *metrics) observeHandlePeer(ctx context.Context, result handlePeerResult) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.handlePeerResult.Add(ctx, 1, + attribute.String(handlePeerResultKey, string(result))) +} + +func (m *metrics) observeAdvertise(ctx context.Context, err error) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.advertise.Add(ctx, 1, + attribute.Bool(advertiseFailedKey, err != nil)) +} + +func (m *metrics) observeOnPeersUpdate(_ peer.ID, isAdded bool) { + if m == nil { + return + } + ctx := context.Background() + + if isAdded { + m.peerAdded.Add(ctx, 1) + return + } + m.peerRemoved.Add(ctx, 1) +} + +func (m *metrics) observeDiscoveryStuck(ctx context.Context) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.discoveryStuck.Add(ctx, 1) +} From 876c6d04db110d52fe6ca1fd253fed5a402a8715 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 11 May 2023 23:00:13 +0800 Subject: [PATCH 0588/1008] fix(daser): persist retry count after restart (#2185) If job was being handled by retry worker on node shutdown the retry backoff counter would be lost after restart. --- das/checkpoint.go | 4 ++++ das/coordinator_test.go | 46 +++++++++++++++++++++++++++++++++++++++++ das/state.go | 42 ++++++++++++++++++++++++++++--------- das/worker.go | 5 ++--- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/das/checkpoint.go b/das/checkpoint.go index 59685de413..a38eca828c 100644 --- a/das/checkpoint.go +++ b/das/checkpoint.go @@ -23,6 +23,10 @@ type workerCheckpoint struct { func newCheckpoint(stats SamplingStats) checkpoint { workers := make([]workerCheckpoint, 0, len(stats.Workers)) for _, w := range stats.Workers { + // no need to store retry jobs, since they will resume from failed heights map + if w.JobType == retryJob { + continue + } workers = append(workers, workerCheckpoint{ From: w.Curr, To: w.To, diff --git a/das/coordinator_test.go b/das/coordinator_test.go index d10ff8848d..e7e2d5ceb2 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/header" @@ -252,6 +253,51 @@ func TestCoordinator(t *testing.T) { expectedState.Failed = make(map[uint64]int) assert.Equal(t, expectedState, newCheckpoint(coordinator.state.unsafeStats())) }) + + t.Run("persist retry count after on restart", func(t *testing.T) { + testParams := defaultTestParams() + testParams.dasParams.ConcurrencyLimit = 5 + ctx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) + + ch := checkpoint{ + SampleFrom: testParams.sampleFrom, + NetworkHead: testParams.networkHead, + Failed: map[uint64]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}, + Workers: []workerCheckpoint{}, + } + + waitCh := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(testParams.dasParams.ConcurrencyLimit) + sampleFn := func(ctx context.Context, h *header.ExtendedHeader) error { + wg.Done() + select { + case <-ctx.Done(): + return ctx.Err() + case <-waitCh: + return nil + } + } + + coordinator := newSamplingCoordinator( + testParams.dasParams, + getterStub{}, + sampleFn, + newBroadcastMock(1), + ) + + go coordinator.run(ctx, ch) + cancel() + wg.Wait() + close(waitCh) + + stopCtx, cancel := context.WithTimeout(context.Background(), testParams.timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + + st := coordinator.state.unsafeStats() + require.Equal(t, ch, newCheckpoint(st)) + }) } func BenchmarkCoordinator(b *testing.B) { diff --git a/das/state.go b/das/state.go index 894bf742ba..6af0b7d8d8 100644 --- a/das/state.go +++ b/das/state.go @@ -71,8 +71,10 @@ func (s *coordinatorState) resumeFromCheckpoint(c checkpoint) { s.networkHead = c.NetworkHead for h, count := range c.Failed { + // resumed retries should start without backoff delay s.failed[h] = retryAttempt{ count: count, + after: time.Now(), } } } @@ -80,6 +82,17 @@ func (s *coordinatorState) resumeFromCheckpoint(c checkpoint) { func (s *coordinatorState) handleResult(res result) { delete(s.inProgress, res.id) + switch res.jobType { + case recentJob, catchupJob: + s.handleRecentOrCatchupResult(res) + case retryJob: + s.handleRetryResult(res) + } + + s.checkDone() +} + +func (s *coordinatorState) handleRecentOrCatchupResult(res result) { // check if the worker retried any of the previously failed heights for h := range s.failed { if h < res.from || h > res.to { @@ -93,16 +106,17 @@ func (s *coordinatorState) handleResult(res result) { // update failed heights for h := range res.failed { - // if job was already in retry and failed again, carry over attempt count - lastRetry, ok := s.inRetry[h] - if ok { - if res.job.jobType != retryJob { - // retry job has been already created by another worker (recent or catchup) - continue - } - delete(s.inRetry, h) - } + nextRetry, _ := s.retryStrategy.nextRetry(retryAttempt{}, time.Now()) + s.failed[h] = nextRetry + } +} +func (s *coordinatorState) handleRetryResult(res result) { + // move heights that has failed again to failed with keeping retry count, they will be picked up by + // retry workers later + for h := range res.failed { + lastRetry := s.inRetry[h] + // height will be retried after backoff nextRetry, retryExceeded := s.retryStrategy.nextRetry(lastRetry, time.Now()) if retryExceeded { log.Warnw("header exceeded maximum amount of sampling attempts", @@ -111,7 +125,11 @@ func (s *coordinatorState) handleResult(res result) { } s.failed[h] = nextRetry } - s.checkDone() + + // processed height are either already moved to failed map or succeeded, cleanup inRetry + for h := res.from; h <= res.to; h++ { + delete(s.inRetry, h) + } } func (s *coordinatorState) isNewHead(newHead int64) bool { @@ -249,6 +267,10 @@ func (s *coordinatorState) unsafeStats() SamplingStats { } } + for h, retry := range s.inRetry { + failed[h] += retry.count + } + return SamplingStats{ SampledChainHead: lowestFailedOrInProgress - 1, CatchupHead: s.next - 1, diff --git a/das/worker.go b/das/worker.go index 4319928b1f..cb80333f4a 100644 --- a/das/worker.go +++ b/das/worker.go @@ -77,14 +77,13 @@ func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- for curr := w.state.from; curr <= w.state.to; curr++ { err := w.sample(ctx, timeout, curr) - w.setResult(curr, err) if errors.Is(err, context.Canceled) { // sampling worker will resume upon restart - break + return } + w.setResult(curr, err) } - log.With() log.Infow( "finished sampling headers", "from", w.state.from, From 060bced067c0cef9b59f54e8808cbd6e86c95c50 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 11 May 2023 18:09:38 +0300 Subject: [PATCH 0589/1008] refactor(header/service): ensure that the requested height is less or equal than the network head (#2173) --- nodebuilder/header/service.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 7947b8975f..7310940ab1 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -2,6 +2,7 @@ package header import ( "context" + "fmt" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/p2p" @@ -51,7 +52,28 @@ func (s *Service) GetVerifiedRangeByHeight( } func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { - return s.store.GetByHeight(ctx, height) + head, err := s.syncer.Head(ctx) + switch { + case uint64(head.Height()) == height, err != nil: + return head, err + case uint64(head.Height()) < height: + return nil, fmt.Errorf("header: given height is from the future: "+ + "networkHeight: %d, requestedHeight: %d", head.Height(), height) + } + + // TODO(vgonkivs): remove after https://github.com/celestiaorg/go-header/issues/32 will be + // implemented + head, err = s.store.Head(ctx) + switch { + case uint64(head.Height()) == height, err != nil: + return head, err + // `+1` allows for one header network lag, e.g. user request header that is milliseconds away + case uint64(head.Height())+1 < height: + return nil, fmt.Errorf("header: syncing in progress: "+ + "localHeadHeight: %d, requestedHeight: %d", head.Height(), height) + default: + return s.store.GetByHeight(ctx, height) + } } func (s *Service) LocalHead(ctx context.Context) (*header.ExtendedHeader, error) { From 9baade4bd0300dabcd160c185e145a824c128e30 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Sat, 13 May 2023 10:11:22 +0200 Subject: [PATCH 0590/1008] fix(share/discovery): deadlock in limitedSet (#2190) The deadlock eventually blocks the disconnect event processing in Discovery, causing libp2p connection processing to stall. It was discovered via libp2p metrics --- share/availability/discovery/discovery.go | 7 ++----- share/availability/discovery/set.go | 7 +++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index f1db6e0797..9d3c69607d 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -210,11 +210,8 @@ func (d *Discovery) disconnectsLoop(ctx context.Context, sub event.Subscription) return } - if evnt := e.(event.EvtPeerConnectednessChanged); evnt.Connectedness == network.NotConnected { - if !d.set.Contains(evnt.Peer) { - continue - } - + evnt := e.(event.EvtPeerConnectednessChanged) + if evnt.Connectedness == network.NotConnected && d.set.Contains(evnt.Peer) { d.host.ConnManager().Unprotect(evnt.Peer, rendezvousPoint) d.connector.Backoff(evnt.Peer) d.set.Remove(evnt.Peer) diff --git a/share/availability/discovery/set.go b/share/availability/discovery/set.go index 37b3851bdf..a22e10f06e 100644 --- a/share/availability/discovery/set.go +++ b/share/availability/discovery/set.go @@ -47,6 +47,7 @@ func (ps *limitedSet) Size() uint { func (ps *limitedSet) Add(p peer.ID) (added bool) { ps.lk.Lock() if _, ok := ps.ps[p]; ok { + ps.lk.Unlock() return false } ps.ps[p] = struct{}{} @@ -65,10 +66,8 @@ func (ps *limitedSet) Add(p peer.ID) (added bool) { func (ps *limitedSet) Remove(id peer.ID) { ps.lk.Lock() - defer ps.lk.Unlock() - if ps.limit > 0 { - delete(ps.ps, id) - } + delete(ps.ps, id) + ps.lk.Unlock() } // Peers returns all discovered peers from the set. From 9ecf08fd52b9dde2f77ca64c138a2e7f82b59060 Mon Sep 17 00:00:00 2001 From: John Adler Date: Sun, 14 May 2023 04:19:24 -0400 Subject: [PATCH 0591/1008] Fix Makefile docstrings and phony targets. (#2194) ## Overview Some docstrings and phony targets were outdated/incomplete. Fix. ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- Makefile | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index eb3e9cce4f..1a06e254d2 100644 --- a/Makefile +++ b/Makefile @@ -10,11 +10,11 @@ help: Makefile @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' .PHONY: help -## install: Install git-hooks from .githooks directory. +## install-hooks: Install git-hooks from .githooks directory. install-hooks: @echo "--> Installing git hooks" @git config core.hooksPath .githooks -.PHONY: init-hooks +.PHONY: install-hooks ## build: Build celestia-node binary. build: @@ -26,6 +26,7 @@ build: clean: @echo "--> Cleaning up ./build" @rm -rf build/* +.PHONY: clean ## cover: generate to code coverage report. cover: @@ -52,7 +53,7 @@ go-install: @go install ${LDFLAGS} ./cmd/celestia .PHONY: go-install -## shed: Build cel-shed binary. +## cel-shed: Build cel-shed binary. cel-shed: @echo "--> Building cel-shed" @go build ./cmd/cel-shed @@ -83,9 +84,9 @@ fmt: sort-imports @go mod tidy -compat=1.17 @cfmt -w -m=100 ./... @markdownlint --fix --quiet --config .markdownlint.yaml . -.PHONY: sort-imports +.PHONY: fmt -## lint: Linting *.go files using golangci-lint. Look for .golangci.yml for the list of linters. +## lint: Linting *.go files using golangci-lint. Look for .golangci.yml for the list of linters. Also lint *.md files using markdownlint. lint: lint-imports @echo "--> Running linter" @golangci-lint run @@ -111,13 +112,13 @@ test-swamp: @go test ./nodebuilder/tests .PHONY: test-swamp -## test-swamp: Running swamp tests with data race detector located in node/tests +## test-swamp-race: Running swamp tests with data race detector located in node/tests test-swamp-race: @echo "--> Running swamp tests with data race detector" @go test -race ./nodebuilder/tests .PHONY: test-swamp-race -## test-all: Running both unit and swamp tests +## test: Running both unit and swamp tests test: @echo "--> Running all tests without data race detector" @go test ./... @@ -154,6 +155,7 @@ openrpc-gen: @go run ./cmd/docgen fraud header state share das p2p node .PHONY: openrpc-gen +## lint-imports: Lint only Go imports. lint-imports: @echo "--> Running imports linter" @for file in `find . -type f -name '*.go'`; \ @@ -162,11 +164,13 @@ lint-imports: done; .PHONY: lint-imports +## sort-imports: Sort Go imports. sort-imports: @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... .PHONY: sort-imports +## adr-gen: Generate ADR from template. Must set NUM and TITLE parameters. adr-gen: - @echo "--> Generating ADRs" + @echo "--> Generating ADR" @curl -sSL https://raw.githubusercontent.com/celestiaorg/.github/main/adr-template.md > docs/architecture/adr-$(NUM)-$(TITLE).md .PHONY: adr-gen From 955f1842dcb8bffcdd5aa6111f8f645b77c5e8e0 Mon Sep 17 00:00:00 2001 From: John Adler Date: Mon, 15 May 2023 02:31:56 -0400 Subject: [PATCH 0592/1008] Remove docstring for removed function IsBefore. (#2195) ## Overview Remove docstring for removed function `IsBefore`. `IsBefore` was removed in #1304: > `IsBefore` inlined, so we don't need to include this method in interface (it's a oneliner used once) ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- header/header.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/header/header.go b/header/header.go index ee757f6ab4..dd70859155 100644 --- a/header/header.go +++ b/header/header.go @@ -103,8 +103,6 @@ func (eh *ExtendedHeader) LastHeader() libhead.Hash { return libhead.Hash(eh.RawHeader.LastBlockID.Hash) } -// IsBefore returns whether the given header is of a higher height. - // Equals returns whether the hash and height of the given header match. func (eh *ExtendedHeader) Equals(header *ExtendedHeader) bool { return eh.Height() == header.Height() && bytes.Equal(eh.Hash(), header.Hash()) From e06aa7f50771c1e57f9a73d90164e53577395514 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 15 May 2023 15:00:00 +0200 Subject: [PATCH 0593/1008] fix(share/p2p/shrexeds): hotfix shrex contexts for bsr (#2192) Currently we are timing out in shrex for the full context length (2.5 minutes) because we fail to 1. Open streams 2. Read status from streams This PR limits stream open to 5s, and reading the status from the stream to 5s (via read Deadline config param). We also put peers on timeout that return a context deadline. It is a bad fix and the correct way to do this can be discussed once we can look deeper into the problems - but for now, we need to fix the test network before it ends next week. This fix does not appear to be enough to fully fix syncing stall/syncing speed (the only solution that has done that so far is a new `ResultDropPeer` in peerman), but it is simple and helps a bit. Related: https://github.com/celestiaorg/celestia-node/discussions/2191 --- share/getters/shrex.go | 4 ++++ share/p2p/shrexeds/client.go | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 99a94048f0..dc26d6ed7a 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -129,6 +129,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex ) for { if ctx.Err() != nil { + sg.metrics.recordEDSAttempt(ctx, attempt, false) return nil, ctx.Err() } attempt++ @@ -155,6 +156,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex return eds, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): + setStatus(peers.ResultCooldownPeer) case errors.Is(getErr, p2p.ErrNotFound): getErr = share.ErrNotFound setStatus(peers.ResultCooldownPeer) @@ -187,6 +189,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( ) for { if ctx.Err() != nil { + sg.metrics.recordNDAttempt(ctx, attempt, false) return nil, ctx.Err() } attempt++ @@ -213,6 +216,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( return nd, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): + setStatus(peers.ResultCooldownPeer) case errors.Is(getErr, p2p.ErrNotFound): getErr = share.ErrNotFound setStatus(peers.ResultCooldownPeer) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index daff83a07b..af408d130c 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -83,7 +83,9 @@ func (c *Client) doRequest( dataHash share.DataHash, to peer.ID, ) (*rsmt2d.ExtendedDataSquare, error) { - stream, err := c.host.NewStream(ctx, to, c.protocolID) + streamOpenCtx, cancel := context.WithTimeout(ctx, c.params.ServerReadTimeout) + defer cancel() + stream, err := c.host.NewStream(streamOpenCtx, to, c.protocolID) if err != nil { return nil, fmt.Errorf("failed to open stream: %w", err) } @@ -106,11 +108,15 @@ func (c *Client) doRequest( // read and parse status from peer resp := new(pb.EDSResponse) + err = stream.SetReadDeadline(time.Now().Add(c.params.ServerReadTimeout)) + if err != nil { + log.Debugw("client: failed to set read deadline for reading status", "err", err) + } _, err = serde.Read(stream, resp) if err != nil { - // server is overloaded and closed the stream + // server closes the stream after returning a non-successful status if errors.Is(err, io.EOF) { - c.metrics.ObserveRequests(ctx, 1, p2p.StatusRateLimited) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck @@ -119,6 +125,8 @@ func (c *Client) doRequest( switch resp.Status { case pb.Status_OK: + // reset stream deadlines to original values, since read deadline was changed during status read + c.setStreamDeadlines(ctx, stream) // use header and ODS bytes to construct EDS and verify it against dataHash eds, err := eds.ReadEDS(ctx, stream, dataHash) if err != nil { From 2fa72c7199e5b93772a2c7e25141cfbd28f16a8e Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 15 May 2023 21:08:47 +0800 Subject: [PATCH 0594/1008] refactor(shrex/server): add warn logs to shrex server (#2197) ## Overview Add some warn logs for shrex server for to improve edge cases visibility. --- share/availability/discovery/discovery.go | 2 +- share/p2p/shrexeds/server.go | 3 ++- share/p2p/shrexnd/server.go | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 9d3c69607d..727022fead 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -139,7 +139,7 @@ func (d *Discovery) Advertise(ctx context.Context) { if ctx.Err() != nil { return } - log.Warn("error advertising %s: %s", rendezvousPoint, err.Error()) + log.Warnw("error advertising", "rendezvous", rendezvousPoint, "err", err) errTimer := time.NewTimer(time.Minute) select { diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index cecf0c5cb8..60e4198842 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -87,7 +87,7 @@ func (s *Server) handleStream(stream network.Stream) { hash := share.DataHash(req.Hash) err = hash.Validate() if err != nil { - logger.Debugw("server: invalid request", "err", err) + logger.Warnw("server: invalid request", "err", err) stream.Reset() //nolint:errcheck return } @@ -103,6 +103,7 @@ func (s *Server) handleStream(stream network.Stream) { status := p2p_pb.Status_OK switch { case errors.Is(err, eds.ErrNotFound): + logger.Warnw("server: request hash not found") s.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) status = p2p_pb.Status_NOT_FOUND case err != nil: diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 3355bdd759..5c1fe80f52 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -109,7 +109,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre err = validateRequest(req) if err != nil { - logger.Debugw("server: invalid request", "err", err) + logger.Warnw("server: invalid request", "err", err) stream.Reset() //nolint:errcheck return } @@ -120,6 +120,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre dah, err := srv.store.GetDAH(ctx, req.RootHash) if err != nil { if errors.Is(err, eds.ErrNotFound) { + logger.Warn("server: DAH not found") srv.respondNotFoundError(ctx, logger, stream) return } @@ -130,6 +131,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) if errors.Is(err, share.ErrNotFound) { + logger.Warn("server: nd not found") srv.respondNotFoundError(ctx, logger, stream) return } From 3b62c6b310d5df181b2fffaf2c337aec0b3280c6 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 May 2023 10:26:02 +0200 Subject: [PATCH 0595/1008] feat(nodebuilder/header): Add `WaitForHeight` endpoint (#2201) Adds`WaitForHeight` endpoint which was formerly `GetByHeight` that just exposes store.GetByHeight --- docs/adr/adr-009-public-api.md | 8 +++++--- nodebuilder/header/header.go | 22 +++++++++++++++------- nodebuilder/header/service.go | 4 ++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 0eb3ec3b7f..76bb1c5ab9 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -95,10 +95,11 @@ type HeaderModule interface { LocalHead(ctx context.Context) (*header.ExtendedHeader, error) // GetByHash returns the header of the given hash from the node's header store. GetByHash(ctx context.Context, hash tmbytes.HexBytes) (*header.ExtendedHeader, error) -// GetByHeight returns the header of the given height from the node's header store. -// If the header of the given height is not yet available, the request will hang -// until it becomes available in the node's header store. +// GetByHeight returns the header of the given height if it is available. GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) +// WaitForHeight blocks until the header at the given height has been processed +// by the node's header store or until context deadline is exceeded. +WaitForHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) // GetVerifiedRangeByHeight returns the given range [from:to) of ExtendedHeaders // from the node's header store and verifies that the returned headers are // adjacent to each other. @@ -113,6 +114,7 @@ SyncWait(ctx context.Context) error // NetworkHead provides the Syncer's view of the current network head. NetworkHead(ctx context.Context) (*header.ExtendedHeader, error) } + ``` ##### Shares diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 5551df9c8f..d7eb7b5b46 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -27,9 +27,12 @@ type Module interface { from *header.ExtendedHeader, to uint64, ) ([]*header.ExtendedHeader, error) - // GetByHeight returns the ExtendedHeader at the given height, blocking - // until header has been processed by the store or context deadline is exceeded. + // GetByHeight returns the ExtendedHeader at the given height if it is + // currently available. GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) + // WaitForHeight blocks until the header at the given height has been processed + // by the store or context deadline is exceeded. + WaitForHeight(context.Context, uint64) (*header.ExtendedHeader, error) // SyncState returns the current state of the header Syncer. SyncState(context.Context) (sync.State, error) @@ -56,11 +59,12 @@ type API struct { *header.ExtendedHeader, uint64, ) ([]*header.ExtendedHeader, error) `perm:"public"` - GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` - SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` - SyncWait func(ctx context.Context) error `perm:"read"` - NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"public"` - Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"public"` + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` + WaitForHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` + SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` + SyncWait func(ctx context.Context) error `perm:"read"` + NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"public"` + Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"public"` } } @@ -80,6 +84,10 @@ func (api *API) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHead return api.Internal.GetByHeight(ctx, u) } +func (api *API) WaitForHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { + return api.Internal.WaitForHeight(ctx, u) +} + func (api *API) LocalHead(ctx context.Context) (*header.ExtendedHeader, error) { return api.Internal.LocalHead(ctx) } diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 7310940ab1..8c68f69f9c 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -76,6 +76,10 @@ func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.Exten } } +func (s *Service) WaitForHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + return s.store.GetByHeight(ctx, height) +} + func (s *Service) LocalHead(ctx context.Context) (*header.ExtendedHeader, error) { return s.store.Head(ctx) } From ac0f0e31084c23e1a1352092211495632fdd9193 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 16 May 2023 12:57:35 +0200 Subject: [PATCH 0596/1008] feat(share/discovery): Discard method (#2207) Fixes #2204 Regarding the case where we exhausted all the peers on the network and backed them all off. It can happen on the smaller network, and in the worst case, the node will wait 10 minutes(the default backoff time), which is acceptable considering peers from shrexsub #2105 --- share/availability/discovery/discovery.go | 42 ++++++++++++++--------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index 727022fead..e8e5594e31 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -122,6 +122,30 @@ func (d *Discovery) Peers(ctx context.Context) ([]peer.ID, error) { return d.set.Peers(ctx) } +// Discard removes the peer from the peer set and rediscovers more if soft peer limit is not +// reached. Reports whether peer was removed with bool. +func (d *Discovery) Discard(id peer.ID) bool { + if !d.set.Contains(id) { + return false + } + + d.host.ConnManager().Unprotect(id, rendezvousPoint) + d.connector.Backoff(id) + d.set.Remove(id) + d.onUpdatedPeers(id, false) + log.Debugw("removed peer from the peer set", "peer", id) + + if d.set.Size() < d.set.Limit() { + // trigger discovery + select { + case d.triggerDisc <- struct{}{}: + default: + } + } + + return true +} + // Advertise is a utility function that persistently advertises a service through an Advertiser. // TODO: Start advertising only after the reachability is confirmed by AutoNAT func (d *Discovery) Advertise(ctx context.Context) { @@ -210,22 +234,8 @@ func (d *Discovery) disconnectsLoop(ctx context.Context, sub event.Subscription) return } - evnt := e.(event.EvtPeerConnectednessChanged) - if evnt.Connectedness == network.NotConnected && d.set.Contains(evnt.Peer) { - d.host.ConnManager().Unprotect(evnt.Peer, rendezvousPoint) - d.connector.Backoff(evnt.Peer) - d.set.Remove(evnt.Peer) - d.onUpdatedPeers(evnt.Peer, false) - log.Debugw("removed peer from the peer set", - "peer", evnt.Peer, "status", evnt.Connectedness.String()) - - if d.set.Size() < d.set.Limit() { - // trigger discovery - select { - case d.triggerDisc <- struct{}{}: - default: - } - } + if evnt := e.(event.EvtPeerConnectednessChanged); evnt.Connectedness == network.NotConnected { + d.Discard(evnt.Peer) } } } From ecf7956b56acf6ca72155472dcf2d853336723c7 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 May 2023 14:07:39 +0200 Subject: [PATCH 0597/1008] fix(nodebuilder/tests): swamp uses `WaitForHeight` instead of `GetByHeight` (#2214) Uses `WaitForHeight` which was formerly `GetByHeight` Resolves #2196 without having to touch block time. --- nodebuilder/tests/fraud_test.go | 2 +- nodebuilder/tests/reconstruct_test.go | 4 ++-- nodebuilder/tests/sync_test.go | 26 +++++++++++++------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index ac6858d1f9..cedb81f33c 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -78,7 +78,7 @@ func TestFraudProofBroadcasting(t *testing.T) { // FIXME: Eventually, this should be a check on service registry managing and keeping // lifecycles of each Module. syncCtx, syncCancel := context.WithTimeout(context.Background(), btime) - _, err = full.HeaderServ.GetByHeight(syncCtx, 100) + _, err = full.HeaderServ.WaitForHeight(syncCtx, 100) require.ErrorIs(t, err, context.DeadlineExceeded) syncCancel() diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 2c7d67a7e5..a4e3296c2f 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -64,7 +64,7 @@ func TestFullReconstructFromBridge(t *testing.T) { for i := 1; i <= blocks+1; i++ { i := i errg.Go(func() error { - h, err := full.HeaderServ.GetByHeight(bctx, uint64(i)) + h, err := full.HeaderServ.WaitForHeight(bctx, uint64(i)) if err != nil { return err } @@ -167,7 +167,7 @@ func TestFullReconstructFromLights(t *testing.T) { for i := 1; i <= blocks+1; i++ { i := i errg.Go(func() error { - h, err := full.HeaderServ.GetByHeight(bctx, uint64(i)) + h, err := full.HeaderServ.WaitForHeight(bctx, uint64(i)) if err != nil { return err } diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index adaf788884..68f33bc61b 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -46,7 +46,7 @@ func TestSyncLightWithBridge(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.GetByHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) @@ -61,7 +61,7 @@ func TestSyncLightWithBridge(t *testing.T) { err = light.Start(ctx) require.NoError(t, err) - h, err = light.HeaderServ.GetByHeight(ctx, 30) + h, err = light.HeaderServ.WaitForHeight(ctx, 30) require.NoError(t, err) err = light.ShareServ.SharesAvailable(ctx, h.DAH) @@ -103,7 +103,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.GetByHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) @@ -116,7 +116,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { light := sw.NewNodeWithConfig(node.Light, cfg) require.NoError(t, light.Start(ctx)) - h, err = light.HeaderServ.GetByHeight(ctx, 30) + h, err = light.HeaderServ.WaitForHeight(ctx, 30) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) @@ -129,7 +129,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { light = sw.NewNodeWithConfig(node.Light, cfg) require.NoError(t, light.Start(ctx)) - h, err = light.HeaderServ.GetByHeight(ctx, 40) + h, err = light.HeaderServ.WaitForHeight(ctx, 40) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 40)) @@ -159,7 +159,7 @@ func TestSyncFullWithBridge(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.GetByHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) @@ -173,7 +173,7 @@ func TestSyncFullWithBridge(t *testing.T) { full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) - h, err = full.HeaderServ.GetByHeight(ctx, 30) + h, err = full.HeaderServ.WaitForHeight(ctx, 30) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) @@ -217,7 +217,7 @@ func TestSyncLightWithFull(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.GetByHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) @@ -230,7 +230,7 @@ func TestSyncLightWithFull(t *testing.T) { full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) - h, err = full.HeaderServ.GetByHeight(ctx, 30) + h, err = full.HeaderServ.WaitForHeight(ctx, 30) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) @@ -248,7 +248,7 @@ func TestSyncLightWithFull(t *testing.T) { err = light.Start(ctx) require.NoError(t, err) - h, err = light.HeaderServ.GetByHeight(ctx, 50) + h, err = light.HeaderServ.WaitForHeight(ctx, 50) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 50)) @@ -283,7 +283,7 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.GetByHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) @@ -296,7 +296,7 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) - h, err = full.HeaderServ.GetByHeight(ctx, 30) + h, err = full.HeaderServ.WaitForHeight(ctx, 30) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) @@ -311,7 +311,7 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { err = light.Start(ctx) require.NoError(t, err) - h, err = light.HeaderServ.GetByHeight(ctx, 50) + h, err = light.HeaderServ.WaitForHeight(ctx, 50) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 50)) From 84eccbfb631423c9d7f8e430770c68abc255f367 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 16 May 2023 15:37:33 +0200 Subject: [PATCH 0598/1008] fix(header): check that Commit corresponds to Header (#2215) During the BEFP discussion on TG with @vgonkivs, we figured out that with have CRITICAL vulnerability in the header validation process. The funniest part is that we abuse this vulnerability to make our BEFP test in Swamp work, and the test represents the exact scenario of such vulnerability: a malicious BN/FN can substitute any value in RawHeader it wants like DAH and the verification on the client side still passes. This is because we don't check that BlockID(Hash) in Commit is aligned with recomputed RawHeader hash. Unfortunately, this breaks our BEFP testing suite, and the only way to reliably produce FPs now is by modifying the Core/App node. --- header/header.go | 9 ++++++++- nodebuilder/tests/fraud_test.go | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/header/header.go b/header/header.go index dd70859155..36dbe1a5d6 100644 --- a/header/header.go +++ b/header/header.go @@ -118,7 +118,6 @@ func (eh *ExtendedHeader) Validate() error { err = eh.Commit.ValidateBasic() if err != nil { return fmt.Errorf("ValidateBasic error on Commit at height %d: %w", eh.Height(), err) - } err = eh.ValidatorSet.ValidateBasic() @@ -133,6 +132,14 @@ func (eh *ExtendedHeader) Validate() error { ) } + // Make sure the header is consistent with the commit. + if eh.Commit.Height != eh.RawHeader.Height { + return fmt.Errorf("header and commit height mismatch: %d vs %d", eh.RawHeader.Height, eh.Commit.Height) + } + if hhash, chash := eh.RawHeader.Hash(), eh.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) { + return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash) + } + if err := eh.ValidatorSet.VerifyCommitLight(eh.ChainID(), eh.Commit.BlockID, eh.Height(), eh.Commit); err != nil { return fmt.Errorf("VerifyCommitLight error at height %d: %w", eh.Height(), err) } diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index cedb81f33c..2022107cb7 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -33,6 +33,8 @@ Note: 15 is not available because DASer will be stopped before reaching this hei Another note: this test disables share exchange to speed up test results. */ func TestFraudProofBroadcasting(t *testing.T) { + t.Skip("requires BEFP generation on app side to work") + const ( blocks = 15 bsize = 2 @@ -109,6 +111,8 @@ Steps: Note: this test disables share exchange to speed up test results. */ func TestFraudProofSyncing(t *testing.T) { + t.Skip("requires BEFP generation on app side to work") + const ( blocks = 15 bsize = 2 From d0db7a9b4f3af753fdc025f9b20c90da08c66304 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 16 May 2023 16:02:24 +0200 Subject: [PATCH 0599/1008] chore(das): improve logging (#2209) Every new head now produces fewer logs + makes the more informative --- das/subscriber.go | 2 +- das/worker.go | 39 ++++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/das/subscriber.go b/das/subscriber.go index 18cc891c90..6af894d2dc 100644 --- a/das/subscriber.go +++ b/das/subscriber.go @@ -32,7 +32,7 @@ func (s *subscriber) run(ctx context.Context, sub libhead.Subscription[*header.E log.Errorw("failed to get next header", "err", err) continue } - log.Infow("new header received via subscription", "height", h.Height()) + log.Debugw("new header received via subscription", "height", h.Height()) emit(ctx, h) } diff --git a/das/worker.go b/das/worker.go index cb80333f4a..06585e49b0 100644 --- a/das/worker.go +++ b/das/worker.go @@ -84,13 +84,16 @@ func (w *worker) run(ctx context.Context, timeout time.Duration, resultCh chan<- w.setResult(curr, err) } - log.Infow( - "finished sampling headers", - "from", w.state.from, - "to", w.state.curr, - "errors", len(w.state.failed), - "finished (s)", time.Since(jobStart), - ) + if w.state.jobType != recentJob { + log.Infow( + "finished sampling headers", + "type", w.state.jobType, + "from", w.state.from, + "to", w.state.curr, + "errors", len(w.state.failed), + "finished (s)", time.Since(jobStart), + ) + } select { case resultCh <- w.state.result: @@ -114,6 +117,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 if !errors.Is(err, context.Canceled) { log.Debugw( "failed to sample header", + "type", w.state.jobType, "height", h.Height(), "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), @@ -125,14 +129,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 return err } - log.Debugw( - "sampled header", - "height", h.Height(), - "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), - "data root", h.DAH.String(), - "finished (s)", time.Since(start), - ) + logout := log.Debugw // notify network about availability of new block data (note: only full nodes can notify) if w.state.job.jobType == recentJob { @@ -144,7 +141,19 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 log.Warn("failed to broadcast availability message", "height", h.Height(), "hash", h.Hash(), "err", err) } + + logout = log.Infow } + + logout( + "sampled header", + "type", w.state.jobType, + "height", h.Height(), + "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), + "data root", h.DAH.String(), + "finished (s)", time.Since(start), + ) return nil } From 8c34b7daa3e7bf7e36df78afe8bdb9c6bb4d9292 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 16 May 2023 18:59:09 +0200 Subject: [PATCH 0600/1008] fix(header): fix testing utilities and tests (#2219) #2215 was approved and merged too quickly, making some tests fail. This PR fixes that. Expect further a new PR to clean up Validate func and increase coverage for it. --- header/header.go | 12 ++++++------ header/headertest/testing.go | 8 ++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/header/header.go b/header/header.go index 36dbe1a5d6..25c3b0e5cf 100644 --- a/header/header.go +++ b/header/header.go @@ -132,6 +132,12 @@ func (eh *ExtendedHeader) Validate() error { ) } + // ensure data root from raw header matches computed root + if !bytes.Equal(eh.DAH.Hash(), eh.DataHash) { + panic(fmt.Sprintf("mismatch between data hash commitment from core header and computed data root "+ + "at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash())) + } + // Make sure the header is consistent with the commit. if eh.Commit.Height != eh.RawHeader.Height { return fmt.Errorf("header and commit height mismatch: %d vs %d", eh.RawHeader.Height, eh.Commit.Height) @@ -144,12 +150,6 @@ func (eh *ExtendedHeader) Validate() error { return fmt.Errorf("VerifyCommitLight error at height %d: %w", eh.Height(), err) } - // ensure data root from raw header matches computed root - if !bytes.Equal(eh.DAH.Hash(), eh.DataHash) { - panic(fmt.Sprintf("mismatch between data hash commitment from core header and computed data root "+ - "at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash())) - } - err = eh.DAH.ValidateBasic() if err != nil { return fmt.Errorf("ValidateBasic error on DAH at height %d: %w", eh.RawHeader.Height, err) diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 11abc5f2d6..76bda50db5 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -67,7 +67,9 @@ func (s *TestSuite) genesis() *header.ExtendedHeader { gen.NextValidatorsHash = s.valSet.Hash() gen.Height = 1 voteSet := types.NewVoteSet(gen.ChainID, gen.Height, 0, tmproto.PrecommitType, s.valSet) - commit, err := MakeCommit(RandBlockID(s.t), gen.Height, 0, voteSet, s.vals, time.Now()) + blockID := RandBlockID(s.t) + blockID.Hash = gen.Hash() + commit, err := MakeCommit(blockID, gen.Height, 0, voteSet, s.vals, time.Now()) require.NoError(s.t, err) eh := &header.ExtendedHeader{ @@ -215,7 +217,9 @@ func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { valSet, vals := RandValidatorSet(3, 1) rh.ValidatorsHash = valSet.Hash() voteSet := types.NewVoteSet(rh.ChainID, rh.Height, 0, tmproto.PrecommitType, valSet) - commit, err := MakeCommit(RandBlockID(t), rh.Height, 0, voteSet, vals, time.Now()) + blockID := RandBlockID(t) + blockID.Hash = rh.Hash() + commit, err := MakeCommit(blockID, rh.Height, 0, voteSet, vals, time.Now()) require.NoError(t, err) return &header.ExtendedHeader{ From e8ffd64f499384bfb4c974b06182559ee3e15a31 Mon Sep 17 00:00:00 2001 From: John Adler Date: Wed, 17 May 2023 04:34:46 -0400 Subject: [PATCH 0601/1008] Makefile: fix docstring for cel-key target. (#2221) ## Overview Missed one in #2194. ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1a06e254d2..c631112421 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ install-shed: @go install ./cmd/cel-shed .PHONY: install-shed -## key: Build cel-key binary. +## cel-key: Build cel-key binary. cel-key: @echo "--> Building cel-key" @go build ./cmd/cel-key From bd7049458adc5b25e883f515fc7e8266674963fa Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 17 May 2023 17:55:35 +0800 Subject: [PATCH 0602/1008] feat(share/discovery): add backoff metrics (#2212) ## Overview Add metrics to discovery. --- share/availability/discovery/backoff.go | 6 ++++++ share/availability/discovery/discovery.go | 10 ++++++---- share/availability/discovery/metrics.go | 8 ++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/share/availability/discovery/backoff.go b/share/availability/discovery/backoff.go index 5cd20e1497..70581bc519 100644 --- a/share/availability/discovery/backoff.go +++ b/share/availability/discovery/backoff.go @@ -106,3 +106,9 @@ func (b *backoffConnector) GC(ctx context.Context) { } } } + +func (b *backoffConnector) Size() int { + b.cacheLk.Lock() + defer b.cacheLk.Unlock() + return len(b.cacheData) +} diff --git a/share/availability/discovery/discovery.go b/share/availability/discovery/discovery.go index e8e5594e31..23489c3147 100644 --- a/share/availability/discovery/discovery.go +++ b/share/availability/discovery/discovery.go @@ -2,6 +2,7 @@ package discovery import ( "context" + "errors" "fmt" "time" @@ -336,10 +337,6 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo d.metrics.observeHandlePeer(ctx, handlePeerEnoughPeers) logger.Debug("skip handle: enough peers found") return false - case d.connector.HasBackoff(peer.ID): - d.metrics.observeHandlePeer(ctx, handlePeerBackoff) - logger.Debug("skip handle: backoff") - return false } switch d.host.Network().Connectedness(peer.ID) { @@ -347,6 +344,11 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo d.connector.Backoff(peer.ID) // we still have to backoff the connected peer case network.NotConnected: err := d.connector.Connect(ctx, peer) + if errors.Is(err, errBackoffNotEnded) { + d.metrics.observeHandlePeer(ctx, handlePeerBackoff) + logger.Debug("skip handle: backoff") + return false + } if err != nil { d.metrics.observeHandlePeer(ctx, handlePeerConnErr) logger.Debugw("unable to connect", "err", err) diff --git a/share/availability/discovery/metrics.go b/share/availability/discovery/metrics.go index 9edd364185..c147a2eeeb 100644 --- a/share/availability/discovery/metrics.go +++ b/share/availability/discovery/metrics.go @@ -98,6 +98,12 @@ func initMetrics(d *Discovery) (*metrics, error) { return nil, err } + backOffSize, err := meter.AsyncInt64().Gauge("discovery_backoff_amount", + instrument.WithDescription("amount of peers in backoff")) + if err != nil { + return nil, err + } + metrics := &metrics{ peersAmount: peersAmount, discoveryResult: discoveryResult, @@ -111,9 +117,11 @@ func initMetrics(d *Discovery) (*metrics, error) { err = meter.RegisterCallback( []instrument.Asynchronous{ peersAmount, + backOffSize, }, func(ctx context.Context) { peersAmount.Observe(ctx, int64(d.set.Size())) + backOffSize.Observe(ctx, int64(d.connector.Size())) }, ) if err != nil { From 62a0b97997b865efd76a2e8d02af8ee5d2a8e912 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 19 May 2023 14:10:35 +0200 Subject: [PATCH 0603/1008] fix(share/p2p)!: share.ErrNamespaceNotFound and integration into shrex-nd (#2230) This PR replaces #2156 , as gh was not showing the diff properly. It closes #2145 --------- Co-authored-by: Hlib Kanunnikov --- share/availability/light/availability_test.go | 2 +- share/getter.go | 9 ++- share/getters/cascade.go | 3 + share/getters/getter_test.go | 12 ++-- share/getters/shrex.go | 11 ++- share/getters/shrex_test.go | 28 +++++++- share/getters/store.go | 1 + share/getters/utils.go | 6 +- share/p2p/shrexnd/client.go | 4 +- share/p2p/shrexnd/exchange_test.go | 6 +- share/p2p/shrexnd/params.go | 2 +- share/p2p/shrexnd/pb/share.pb.go | 70 ++++++++++--------- share/p2p/shrexnd/pb/share.proto | 1 + share/p2p/shrexnd/server.go | 20 ++++-- 14 files changed, 117 insertions(+), 58 deletions(-) diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index a0586c12bc..146cb8554a 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -134,7 +134,7 @@ func TestService_GetSharesByNamespaceNotFound(t *testing.T) { root.RowsRoots = nil _, err := getter.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) - assert.ErrorIs(t, err, share.ErrNotFound) + assert.ErrorIs(t, err, share.ErrNamespaceNotFound) } func BenchmarkService_GetSharesByNamespace(b *testing.B) { diff --git a/share/getter.go b/share/getter.go index 325253e974..163741f925 100644 --- a/share/getter.go +++ b/share/getter.go @@ -12,8 +12,13 @@ import ( "github.com/celestiaorg/rsmt2d" ) -// ErrNotFound is used to indicated that requested data could not be found. -var ErrNotFound = errors.New("data not found") +var ( + // ErrNotFound is used to indicate that requested data could not be found. + ErrNotFound = errors.New("share: data not found") + // ErrNamespaceNotFound is returned by GetSharesByNamespace when data for requested root does + // not include any shares from the given namespace + ErrNamespaceNotFound = errors.New("share: namespace not found in data") +) // Getter interface provides a set of accessors for shares by the Root. // Automatically verifies integrity of shares(exceptions possible depending on the implementation). diff --git a/share/getters/cascade.go b/share/getters/cascade.go index cca1eab944..3dcb2a472b 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -124,6 +124,9 @@ func cascadeGetters[V any]( if getErr == nil { return val, nil } + if errors.Is(share.ErrNamespaceNotFound, getErr) { + return zero, getErr + } if !errors.Is(getErr, errOperationNotSupported) { err = errors.Join(err, getErr) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 6075f6678e..8751f49da0 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -138,7 +138,7 @@ func TestStoreGetter(t *testing.T) { // nid not found nID = make([]byte, 8) _, err = sg.GetSharesByNamespace(ctx, &dah, nID) - require.ErrorIs(t, err, share.ErrNotFound) + require.ErrorIs(t, err, share.ErrNamespaceNotFound) // root not found root := share.Root{} @@ -206,6 +206,7 @@ func TestIPLDGetter(t *testing.T) { err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) + // first check that shares are returned correctly if they exist shares, err := sg.GetSharesByNamespace(ctx, &dah, nID) require.NoError(t, err) require.NoError(t, shares.Verify(&dah, nID)) @@ -213,13 +214,14 @@ func TestIPLDGetter(t *testing.T) { // nid not found nID = make([]byte, 8) - _, err = sg.GetSharesByNamespace(ctx, &dah, nID) - require.ErrorIs(t, err, share.ErrNotFound) + emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + require.ErrorIs(t, err, share.ErrNamespaceNotFound) + require.Nil(t, emptyShares) - // root not found + // nid doesnt exist in root root := share.Root{} _, err = sg.GetSharesByNamespace(ctx, &root, nID) - require.ErrorIs(t, err, share.ErrNotFound) + require.ErrorIs(t, err, share.ErrNamespaceNotFound) }) } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index dc26d6ed7a..c860e01532 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -187,6 +187,13 @@ func (sg *ShrexGetter) GetSharesByNamespace( attempt int err error ) + + // verify that the namespace could exist inside the roots before starting network requests + roots := filterRootsByNamespace(root, id) + if len(roots) == 0 { + return nil, share.ErrNamespaceNotFound + } + for { if ctx.Err() != nil { sg.metrics.recordNDAttempt(ctx, attempt, false) @@ -210,10 +217,10 @@ func (sg *ShrexGetter) GetSharesByNamespace( nd, getErr := sg.ndClient.RequestND(reqCtx, root, id, peer) cancel() switch { - case getErr == nil: + case getErr == nil, errors.Is(getErr, share.ErrNamespaceNotFound): setStatus(peers.ResultNoop) sg.metrics.recordNDAttempt(ctx, attempt, true) - return nd, nil + return nd, getErr case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): setStatus(peers.ResultCooldownPeer) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 01322b014c..1e86d8255e 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -87,6 +87,25 @@ func TestShrexGetter(t *testing.T) { require.ErrorIs(t, err, share.ErrNotFound) }) + t.Run("ND_namespace_not_found", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + // generate test data + eds, dah, nID := generateTestEDS(t) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + // corrupt NID + nID[4]++ + + _, err := getter.GetSharesByNamespace(ctx, &dah, nID) + require.ErrorIs(t, err, share.ErrNamespaceNotFound) + }) + t.Run("EDS_Available", func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) @@ -150,7 +169,8 @@ func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabil return eds, dah, randNID } -func testManager(ctx context.Context, host host.Host, headerSub libhead.Subscriber[*header.ExtendedHeader], +func testManager( + ctx context.Context, host host.Host, headerSub libhead.Subscriber[*header.ExtendedHeader], ) (*peers.Manager, error) { shrexSub, err := shrexsub.NewPubSub(ctx, host, "test") if err != nil { @@ -177,7 +197,8 @@ func testManager(ctx context.Context, host host.Host, headerSub libhead.Subscrib return manager, err } -func newNDClientServer(ctx context.Context, t *testing.T, edsStore *eds.Store, srvHost, clHost host.Host, +func newNDClientServer( + ctx context.Context, t *testing.T, edsStore *eds.Store, srvHost, clHost host.Host, ) (*shrexnd.Client, *shrexnd.Server) { params := shrexnd.DefaultParameters() @@ -196,7 +217,8 @@ func newNDClientServer(ctx context.Context, t *testing.T, edsStore *eds.Store, s return client, server } -func newEDSClientServer(ctx context.Context, t *testing.T, edsStore *eds.Store, srvHost, clHost host.Host, +func newEDSClientServer( + ctx context.Context, t *testing.T, edsStore *eds.Store, srvHost, clHost host.Host, ) (*shrexeds.Client, *shrexeds.Server) { params := shrexeds.DefaultParameters() diff --git a/share/getters/store.go b/share/getters/store.go index 1c88206577..156c9cf2ee 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -128,5 +128,6 @@ func (sg *StoreGetter) GetSharesByNamespace( if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) } + return shares, nil } diff --git a/share/getters/utils.go b/share/getters/utils.go index 6c7c2ba7e6..a6fd4a7beb 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -59,7 +59,7 @@ func collectSharesByNamespace( rootCIDs := filterRootsByNamespace(root, nID) if len(rootCIDs) == 0 { - return nil, share.ErrNotFound + return nil, share.ErrNamespaceNotFound } errGroup, ctx := errgroup.WithContext(ctx) @@ -84,9 +84,9 @@ func collectSharesByNamespace( return nil, err } - // return ErrNotFound if no shares are found for namespaceID + // return ErrNamespaceNotFound if no shares are found for the namespace.ID if len(rootCIDs) == 1 && len(shares[0].Shares) == 0 { - return nil, share.ErrNotFound + return nil, share.ErrNamespaceNotFound } return shares, nil diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index b5ae66f51f..ff51ef1fe0 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -71,7 +71,7 @@ func (c *Client) RequestND( return nil, context.DeadlineExceeded } } - if err != p2p.ErrNotFound { + if err != p2p.ErrNotFound && err != share.ErrNamespaceNotFound { log.Warnw("client-nd: peer returned err", "err", err) } return nil, err @@ -195,6 +195,8 @@ func (c *Client) statusToErr(ctx context.Context, code pb.StatusCode) error { case pb.StatusCode_NOT_FOUND: c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) return p2p.ErrNotFound + case pb.StatusCode_NAMESPACE_NOT_FOUND: + return share.ErrNamespaceNotFound case pb.StatusCode_INVALID: log.Debug("client-nd: invalid request") fallthrough diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 05bca67237..944b763229 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -39,7 +39,7 @@ func TestExchange_RequestND_NotFound(t *testing.T) { require.ErrorIs(t, err, p2p.ErrNotFound) }) - t.Run("Getter_err_not_found", func(t *testing.T) { + t.Run("ErrNamespaceNotFound", func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) @@ -49,7 +49,7 @@ func TestExchange_RequestND_NotFound(t *testing.T) { randNID := dah.RowsRoots[(len(dah.RowsRoots)-1)/2][:8] _, err := client.RequestND(ctx, &dah, randNID, server.host.ID()) - require.ErrorIs(t, err, p2p.ErrNotFound) + require.ErrorIs(t, err, share.ErrNamespaceNotFound) }) } @@ -118,7 +118,7 @@ func (m notFoundGetter) GetEDS( func (m notFoundGetter) GetSharesByNamespace( _ context.Context, _ *share.Root, _ namespace.ID, ) (share.NamespacedShares, error) { - return nil, share.ErrNotFound + return nil, share.ErrNamespaceNotFound } func newStore(t *testing.T) *eds.Store { diff --git a/share/p2p/shrexnd/params.go b/share/p2p/shrexnd/params.go index 1acf65ba96..a645267962 100644 --- a/share/p2p/shrexnd/params.go +++ b/share/p2p/shrexnd/params.go @@ -8,7 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p" ) -const protocolString = "/shrex/nd/0.0.1" +const protocolString = "/shrex/nd/v0.0.2" var log = logging.Logger("shrex/nd") diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go index bb9d1e4d40..d902570410 100644 --- a/share/p2p/shrexnd/pb/share.pb.go +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -25,10 +25,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type StatusCode int32 const ( - StatusCode_INVALID StatusCode = 0 - StatusCode_OK StatusCode = 1 - StatusCode_NOT_FOUND StatusCode = 2 - StatusCode_INTERNAL StatusCode = 3 + StatusCode_INVALID StatusCode = 0 + StatusCode_OK StatusCode = 1 + StatusCode_NOT_FOUND StatusCode = 2 + StatusCode_INTERNAL StatusCode = 3 + StatusCode_NAMESPACE_NOT_FOUND StatusCode = 4 ) var StatusCode_name = map[int32]string{ @@ -36,13 +37,15 @@ var StatusCode_name = map[int32]string{ 1: "OK", 2: "NOT_FOUND", 3: "INTERNAL", + 4: "NAMESPACE_NOT_FOUND", } var StatusCode_value = map[string]int32{ - "INVALID": 0, - "OK": 1, - "NOT_FOUND": 2, - "INTERNAL": 3, + "INVALID": 0, + "OK": 1, + "NOT_FOUND": 2, + "INTERNAL": 3, + "NAMESPACE_NOT_FOUND": 4, } func (x StatusCode) String() string { @@ -280,31 +283,32 @@ func init() { func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } var fileDescriptor_ed9f13149b0de397 = []byte{ - // 374 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xc1, 0x6a, 0xdb, 0x40, - 0x10, 0x86, 0x25, 0x6d, 0xad, 0xda, 0x23, 0xb5, 0x88, 0xa5, 0xb4, 0x2a, 0x2e, 0xc2, 0xd5, 0xc9, - 0xb4, 0x20, 0x81, 0x0a, 0x3d, 0x16, 0xec, 0xda, 0x6d, 0x45, 0xcd, 0xba, 0xac, 0xdd, 0xdc, 0x82, - 0x91, 0xa3, 0x0d, 0xca, 0x21, 0xda, 0x8d, 0x76, 0x8d, 0x93, 0x73, 0x5e, 0x20, 0x8f, 0x95, 0xa3, - 0x8f, 0x39, 0x06, 0xfb, 0x45, 0x82, 0x56, 0x4e, 0x72, 0x88, 0x6f, 0xfa, 0xff, 0xf9, 0xe6, 0x9f, - 0x19, 0x2d, 0xf4, 0x64, 0x91, 0x55, 0x2c, 0x16, 0x89, 0x88, 0x65, 0x51, 0xb1, 0xcb, 0x32, 0x8f, - 0xc5, 0x32, 0xd6, 0x66, 0x24, 0x2a, 0xae, 0x38, 0xc6, 0x7b, 0x91, 0x88, 0x48, 0x13, 0x51, 0x99, - 0x87, 0xc7, 0xd0, 0xfd, 0xcd, 0xd4, 0xac, 0x2e, 0xc8, 0xe1, 0x15, 0xc9, 0xce, 0x99, 0x14, 0xd9, - 0x09, 0xa3, 0xec, 0x62, 0xc5, 0xa4, 0xc2, 0x5d, 0xe8, 0x54, 0x9c, 0xab, 0x45, 0x91, 0xc9, 0xc2, - 0x37, 0x7b, 0x66, 0xdf, 0xa5, 0xed, 0xda, 0xf8, 0x93, 0xc9, 0x02, 0x7f, 0x06, 0xb7, 0x7c, 0x6c, - 0x58, 0x9c, 0xe5, 0xbe, 0xa5, 0xeb, 0xce, 0x93, 0x97, 0xe6, 0xe1, 0xb5, 0x09, 0x9f, 0x0e, 0xe7, - 0x4b, 0xc1, 0x4b, 0xc9, 0xf0, 0x77, 0xb0, 0xa5, 0xca, 0xd4, 0x4a, 0xea, 0xf4, 0xb7, 0x49, 0x10, - 0xbd, 0x5c, 0x32, 0x9a, 0x69, 0xe2, 0x27, 0xcf, 0x19, 0xdd, 0xd3, 0xf8, 0x2b, 0xbc, 0xaa, 0xf8, - 0x5a, 0xfa, 0x56, 0x0f, 0xf5, 0x9d, 0xe4, 0xc3, 0xa1, 0x2e, 0xca, 0xd7, 0x54, 0x43, 0x21, 0x01, - 0x44, 0xf9, 0x1a, 0xbf, 0x07, 0x5b, 0x63, 0xf5, 0x2c, 0xd4, 0x77, 0xe9, 0x5e, 0xe1, 0x18, 0x5a, - 0xa2, 0xe2, 0xfc, 0x54, 0x1f, 0xe0, 0x24, 0x1f, 0x0f, 0x85, 0xfd, 0xab, 0x01, 0xda, 0x70, 0xe1, - 0x18, 0x5a, 0x5a, 0xe3, 0x77, 0xd0, 0x92, 0x2a, 0xab, 0x94, 0x5e, 0x1e, 0xd1, 0x46, 0x60, 0x0f, - 0x10, 0x2b, 0x9b, 0xdf, 0x81, 0x68, 0xfd, 0x59, 0x73, 0x84, 0xe7, 0x4c, 0xfa, 0x48, 0x0f, 0x6e, - 0xc4, 0x97, 0x1f, 0x00, 0xcf, 0x97, 0x61, 0x07, 0x5e, 0xa7, 0xe4, 0x68, 0x30, 0x49, 0x47, 0x9e, - 0x81, 0x6d, 0xb0, 0xa6, 0x7f, 0x3d, 0x13, 0xbf, 0x81, 0x0e, 0x99, 0xce, 0x17, 0xbf, 0xa6, 0xff, - 0xc9, 0xc8, 0xb3, 0xb0, 0x0b, 0xed, 0x94, 0xcc, 0xc7, 0x94, 0x0c, 0x26, 0x1e, 0x1a, 0xfa, 0xb7, - 0xdb, 0xc0, 0xdc, 0x6c, 0x03, 0xf3, 0x7e, 0x1b, 0x98, 0x37, 0xbb, 0xc0, 0xd8, 0xec, 0x02, 0xe3, - 0x6e, 0x17, 0x18, 0x4b, 0x5b, 0x3f, 0xf8, 0xb7, 0x87, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x23, - 0xba, 0xf9, 0x14, 0x02, 0x00, 0x00, + // 386 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xc1, 0xae, 0x93, 0x40, + 0x14, 0x86, 0x81, 0xb9, 0xc5, 0x7b, 0x0f, 0x68, 0xc8, 0x68, 0xbc, 0x98, 0x6b, 0x48, 0x65, 0xd5, + 0x68, 0x02, 0x09, 0x26, 0xee, 0x69, 0x8b, 0x4a, 0xac, 0xd3, 0x66, 0x5a, 0x75, 0x65, 0x08, 0x95, + 0x31, 0xb8, 0x90, 0x19, 0x99, 0x69, 0xaa, 0x6b, 0x5f, 0xc0, 0xc7, 0x72, 0xd9, 0xa5, 0x4b, 0xd3, + 0xbe, 0x88, 0x61, 0xa8, 0x76, 0x61, 0x77, 0xfc, 0xff, 0xf9, 0xce, 0x7f, 0xce, 0x21, 0x03, 0x43, + 0x59, 0x97, 0x2d, 0x8b, 0x45, 0x22, 0x62, 0x59, 0xb7, 0xec, 0x6b, 0x53, 0xc5, 0x62, 0x1d, 0x6b, + 0x33, 0x12, 0x2d, 0x57, 0x1c, 0xe3, 0xa3, 0x48, 0x44, 0xa4, 0x89, 0xa8, 0xa9, 0xc2, 0xf7, 0x70, + 0xf3, 0x82, 0xa9, 0x65, 0x57, 0x90, 0xe3, 0x6f, 0xa4, 0xfc, 0xcc, 0xa4, 0x28, 0x3f, 0x30, 0xca, + 0xbe, 0x6c, 0x98, 0x54, 0xf8, 0x06, 0xae, 0x5a, 0xce, 0x55, 0x51, 0x97, 0xb2, 0xf6, 0xcd, 0xa1, + 0x39, 0x72, 0xe9, 0x65, 0x67, 0xbc, 0x2c, 0x65, 0x8d, 0x1f, 0x81, 0xdb, 0xfc, 0x6d, 0x28, 0x3e, + 0x55, 0xbe, 0xa5, 0xeb, 0xce, 0x3f, 0x2f, 0xaf, 0xc2, 0xef, 0x26, 0x3c, 0x3c, 0x9f, 0x2f, 0x05, + 0x6f, 0x24, 0xc3, 0xcf, 0xc0, 0x96, 0xaa, 0x54, 0x1b, 0xa9, 0xd3, 0xef, 0x24, 0x41, 0xf4, 0xff, + 0x92, 0xd1, 0x52, 0x13, 0x13, 0x5e, 0x31, 0x7a, 0xa4, 0xf1, 0x13, 0xb8, 0x68, 0xf9, 0x56, 0xfa, + 0xd6, 0x10, 0x8d, 0x9c, 0xe4, 0xfa, 0x5c, 0x17, 0xe5, 0x5b, 0xaa, 0xa1, 0x90, 0x00, 0xa2, 0x7c, + 0x8b, 0xef, 0x83, 0xad, 0xb1, 0x6e, 0x16, 0x1a, 0xb9, 0xf4, 0xa8, 0x70, 0x0c, 0x03, 0xd1, 0x72, + 0xfe, 0x51, 0x1f, 0xe0, 0x24, 0x0f, 0xce, 0x85, 0x2d, 0x3a, 0x80, 0xf6, 0x5c, 0x98, 0xc1, 0x40, + 0x6b, 0x7c, 0x0f, 0x06, 0x52, 0x95, 0xad, 0xd2, 0xcb, 0x23, 0xda, 0x0b, 0xec, 0x01, 0x62, 0x4d, + 0xff, 0x3b, 0x10, 0xed, 0x3e, 0x3b, 0x8e, 0xf0, 0x8a, 0x49, 0x1f, 0xe9, 0xc1, 0xbd, 0x78, 0xfc, + 0x0e, 0xe0, 0x74, 0x19, 0x76, 0xe0, 0x56, 0x4e, 0xde, 0xa6, 0xb3, 0x7c, 0xea, 0x19, 0xd8, 0x06, + 0x6b, 0xfe, 0xca, 0x33, 0xf1, 0x6d, 0xb8, 0x22, 0xf3, 0x55, 0xf1, 0x7c, 0xfe, 0x86, 0x4c, 0x3d, + 0x0b, 0xbb, 0x70, 0x99, 0x93, 0x55, 0x46, 0x49, 0x3a, 0xf3, 0x10, 0xbe, 0x86, 0xbb, 0x24, 0x7d, + 0x9d, 0x2d, 0x17, 0xe9, 0x24, 0x2b, 0x4e, 0xd8, 0xc5, 0xd8, 0xff, 0xb9, 0x0f, 0xcc, 0xdd, 0x3e, + 0x30, 0x7f, 0xef, 0x03, 0xf3, 0xc7, 0x21, 0x30, 0x76, 0x87, 0xc0, 0xf8, 0x75, 0x08, 0x8c, 0xb5, + 0xad, 0x5f, 0xc2, 0xd3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xba, 0xc1, 0x4e, 0xec, 0x2d, 0x02, + 0x00, 0x00, } func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto index d181dd7010..3d6a896641 100644 --- a/share/p2p/shrexnd/pb/share.proto +++ b/share/p2p/shrexnd/pb/share.proto @@ -17,6 +17,7 @@ enum StatusCode { OK = 1; NOT_FOUND = 2; INTERNAL = 3; + NAMESPACE_NOT_FOUND = 4; }; message Row { diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 5c1fe80f52..748d9fbd10 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -130,12 +130,15 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre } shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) - if errors.Is(err, share.ErrNotFound) { + switch { + case errors.Is(err, share.ErrNotFound): logger.Warn("server: nd not found") srv.respondNotFoundError(ctx, logger, stream) return - } - if err != nil { + case errors.Is(err, share.ErrNamespaceNotFound): + srv.respondNamespaceNotFoundError(ctx, logger, stream) + return + case err != nil: logger.Errorw("server: retrieving shares", "err", err) srv.respondInternalError(ctx, logger, stream) return @@ -157,7 +160,7 @@ func validateRequest(req pb.GetSharesByNamespaceRequest) error { return nil } -// respondNotFoundError sends internal error response to client +// respondNotFoundError sends a not found response to client func (srv *Server) respondNotFoundError(ctx context.Context, logger *zap.SugaredLogger, stream network.Stream) { resp := &pb.GetSharesByNamespaceResponse{ @@ -166,6 +169,15 @@ func (srv *Server) respondNotFoundError(ctx context.Context, srv.respond(ctx, logger, stream, resp) } +// respondNamespaceNotFoundError sends a namespace not found response to client +func (srv *Server) respondNamespaceNotFoundError(ctx context.Context, + logger *zap.SugaredLogger, stream network.Stream) { + resp := &pb.GetSharesByNamespaceResponse{ + Status: pb.StatusCode_NAMESPACE_NOT_FOUND, + } + srv.respond(ctx, logger, stream, resp) +} + // respondInternalError sends internal error response to client func (srv *Server) respondInternalError(ctx context.Context, logger *zap.SugaredLogger, stream network.Stream) { From be937f2073725735eba8ccc065f3f15ccc3ad570 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 19 May 2023 15:36:24 +0300 Subject: [PATCH 0604/1008] improvement(test/swamp): set core IP and Port to nodes (#2226) --- nodebuilder/tests/swamp/swamp.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index c36e59b742..58ff807a4d 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -18,6 +18,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-app/testutil/testnode" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/core" @@ -175,10 +176,22 @@ func (s *Swamp) setupGenesis(ctx context.Context) { s.genesis = h } +// DefaultTestConfig creates a test config with the access to the core node for the tp +func (s *Swamp) DefaultTestConfig(tp node.Type) *nodebuilder.Config { + cfg := nodebuilder.DefaultConfig(tp) + + ip, port, err := net.SplitHostPort(s.comps.App.GRPC.Address) + require.NoError(s.t, err) + + cfg.Core.IP = ip + cfg.Core.GRPCPort = port + return cfg +} + // NewBridgeNode creates a new instance of a BridgeNode providing a default config // and a mockstore to the NewNodeWithStore method func (s *Swamp) NewBridgeNode(options ...fx.Option) *nodebuilder.Node { - cfg := nodebuilder.DefaultConfig(node.Bridge) + cfg := s.DefaultTestConfig(node.Bridge) store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Bridge, store, options...) @@ -187,7 +200,7 @@ func (s *Swamp) NewBridgeNode(options ...fx.Option) *nodebuilder.Node { // NewFullNode creates a new instance of a FullNode providing a default config // and a mockstore to the NewNodeWithStore method func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { - cfg := nodebuilder.DefaultConfig(node.Full) + cfg := s.DefaultTestConfig(node.Full) cfg.Header.TrustedPeers = []string{ "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", } @@ -199,7 +212,7 @@ func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { // NewLightNode creates a new instance of a LightNode providing a default config // and a mockstore to the NewNodeWithStore method func (s *Swamp) NewLightNode(options ...fx.Option) *nodebuilder.Node { - cfg := nodebuilder.DefaultConfig(node.Light) + cfg := s.DefaultTestConfig(node.Light) cfg.Header.TrustedPeers = []string{ "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", } @@ -224,11 +237,9 @@ func (s *Swamp) NewNodeWithStore( ) *nodebuilder.Node { var n *nodebuilder.Node - ks, err := store.Keystore() - require.NoError(s.t, err) - + signer := apptypes.NewKeyringSigner(s.ClientContext.Keyring, s.Accounts[0], s.ClientContext.ChainID) options = append(options, - state.WithKeyringSigner(nodebuilder.TestKeyringSigner(s.t, ks.Keyring())), + state.WithKeyringSigner(signer), ) switch t { From d4e70e233aff2adfb76c89e8f4b1d01e65f250b1 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 22 May 2023 17:40:21 +0800 Subject: [PATCH 0605/1008] feat(share/p2p/peer-manager): use shrexSub peers as full nodes (#2105) ## Problem https://github.com/celestiaorg/celestia-node/issues/2107 ## Overview Could hotfix the problem by allowing peer manager to save nodes discovered through shrexSub to be used in full nodes pool. The solution would also benefit shrex performance by allowing distribution of request to more peers. --------- Co-authored-by: Ryan Co-authored-by: Hlib Kanunnikov --- share/p2p/peers/manager.go | 134 ++++++++++++++++++++++++++--------- share/p2p/peers/metrics.go | 6 +- share/p2p/peers/pool.go | 21 ++++++ share/p2p/shrexeds/client.go | 4 +- 4 files changed, 128 insertions(+), 37 deletions(-) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 35aa67c6e6..46cb9123f6 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -11,8 +11,11 @@ import ( logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/host/eventbus" "github.com/libp2p/go-libp2p/p2p/net/conngater" libhead "github.com/celestiaorg/go-header" @@ -34,6 +37,10 @@ const ( // ResultBlacklistPeer will blacklist peer. Blacklisted peers will be disconnected and blocked from // any p2p communication in future by libp2p Gater ResultBlacklistPeer = "result_blacklist_peer" + + // eventbusBufSize is the size of the buffered channel to handle + // events in libp2p + eventbusBufSize = 32 ) type result string @@ -48,6 +55,7 @@ type Manager struct { // header subscription is necessary in order to Validate the inbound eds hash headerSub libhead.Subscriber[*header.ExtendedHeader] shrexSub *shrexsub.PubSub + disc *discovery.Discovery host host.Host connGater *conngater.BasicConnectionGater @@ -65,8 +73,9 @@ type Manager struct { metrics *metrics - cancel context.CancelFunc - done chan struct{} + headerSubDone chan struct{} + disconnectedPeersDone chan struct{} + cancel context.CancelFunc } // DoneFunc updates internal state depending on call results. Should be called once per returned @@ -101,14 +110,16 @@ func NewManager( } s := &Manager{ - params: params, - headerSub: headerSub, - shrexSub: shrexSub, - connGater: connGater, - host: host, - pools: make(map[string]*syncPool), - blacklistedHashes: make(map[string]bool), - done: make(chan struct{}), + params: params, + headerSub: headerSub, + shrexSub: shrexSub, + connGater: connGater, + disc: discovery, + host: host, + pools: make(map[string]*syncPool), + blacklistedHashes: make(map[string]bool), + headerSubDone: make(chan struct{}), + disconnectedPeersDone: make(chan struct{}), } s.fullNodes = newPool(s.params.PeerCooldown) @@ -120,8 +131,8 @@ func NewManager( log.Debugw("got blacklisted peer from discovery", "peer", peerID) return } - log.Debugw("added to full nodes", "peer", peerID) s.fullNodes.add(peerID) + log.Debugw("added to full nodes", "peer", peerID) return } @@ -152,6 +163,12 @@ func (m *Manager) Start(startCtx context.Context) error { return fmt.Errorf("subscribing to headersub: %w", err) } + sub, err := m.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}, eventbus.BufSize(eventbusBufSize)) + if err != nil { + return fmt.Errorf("subscribing to libp2p events: %w", err) + } + + go m.subscribeDisconnectedPeers(ctx, sub) go m.subscribeHeader(ctx, headerSub) go m.GC(ctx) @@ -162,11 +179,18 @@ func (m *Manager) Stop(ctx context.Context) error { m.cancel() select { - case <-m.done: - return nil + case <-m.headerSubDone: case <-ctx.Done(): return ctx.Err() } + + select { + case <-m.disconnectedPeersDone: + case <-ctx.Done(): + return ctx.Err() + } + + return nil } // Peer returns peer collected from shrex.Sub for given datahash if any available. @@ -182,11 +206,7 @@ func (m *Manager) Peer( // first, check if a peer is available for the given datahash peerID, ok := p.tryGet() if ok { - // some pools could still have blacklisted peers in storage - if m.isBlacklistedPeer(peerID) { - log.Debugw("removing blacklisted peer from pool", - "peer", peerID.String()) - p.remove(peerID) + if m.removeIfUnreachable(p, peerID) { return m.Peer(ctx, datahash) } return m.newPeer(ctx, datahash, peerID, sourceShrexSub, p.len(), 0) @@ -203,6 +223,9 @@ func (m *Manager) Peer( start := time.Now() select { case peerID = <-p.next(ctx): + if m.removeIfUnreachable(p, peerID) { + return m.Peer(ctx, datahash) + } return m.newPeer(ctx, datahash, peerID, sourceShrexSub, p.len(), time.Since(start)) case peerID = <-m.fullNodes.next(ctx): return m.newPeer(ctx, datahash, peerID, sourceFullNodes, m.fullNodes.len(), time.Since(start)) @@ -217,7 +240,8 @@ func (m *Manager) newPeer( peerID peer.ID, source peerSource, poolSize int, - waitTime time.Duration) (peer.ID, DoneFunc, error) { + waitTime time.Duration, +) (peer.ID, DoneFunc, error) { log.Debugw("got peer", "hash", datahash.String(), "peer", peerID.String(), @@ -241,10 +265,11 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, source peerS case ResultSynced: m.markPoolAsSynced(datahash.String()) case ResultCooldownPeer: - m.getOrCreatePool(datahash.String()).putOnCooldown(peerID) if source == sourceFullNodes { m.fullNodes.putOnCooldown(peerID) + return } + m.getOrCreatePool(datahash.String()).putOnCooldown(peerID) case ResultBlacklistPeer: m.blacklistPeers(reasonMisbehave, peerID) } @@ -253,7 +278,7 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, source peerS // subscribeHeader takes datahash from received header and validates corresponding peer pool. func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscription[*header.ExtendedHeader]) { - defer close(m.done) + defer close(m.headerSubDone) defer headerSub.Cancel() for { @@ -274,6 +299,33 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri } } +// subscribeDisconnectedPeers subscribes to libp2p connectivity events and removes disconnected +// peers from full nodes pool +func (m *Manager) subscribeDisconnectedPeers(ctx context.Context, sub event.Subscription) { + defer close(m.disconnectedPeersDone) + defer sub.Close() + for { + select { + case <-ctx.Done(): + return + case e, ok := <-sub.Out(): + if !ok { + log.Fatal("Subscription for connectedness events is closed.") //nolint:gocritic + return + } + // listen to disconnect event to remove peer from full nodes pool + connStatus := e.(event.EvtPeerConnectednessChanged) + if connStatus.Connectedness == network.NotConnected { + peer := connStatus.Peer + if m.fullNodes.has(peer) { + log.Debugw("peer disconnected, removing from full nodes", "peer", peer) + m.fullNodes.remove(peer) + } + } + } + } +} + // Validate will collect peer.ID into corresponding peer pool func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { logger := log.With("peer", peerID, "hash", msg.DataHash.String()) @@ -311,19 +363,14 @@ func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notif p := m.getOrCreatePool(msg.DataHash.String()) p.headerHeight.Store(msg.Height) - p.add(peerID) - logger.Debug("got hash from shrex-sub") - return pubsub.ValidationIgnore -} + logger.Debugw("got hash from shrex-sub") -func (m *Manager) validatedPool(datahash string) *syncPool { - p := m.getOrCreatePool(datahash) - if p.isValidatedDataHash.CompareAndSwap(false, true) { - log.Debugw("pool marked validated", - "hash", datahash, - "after (s)", time.Since(p.createdAt)) + p.add(peerID) + if p.isValidatedDataHash.Load() { + // add peer to full nodes pool only if datahash has been already validated + m.fullNodes.add(peerID) } - return p + return pubsub.ValidationIgnore } func (m *Manager) getOrCreatePool(datahash string) *syncPool { @@ -356,7 +403,7 @@ func (m *Manager) blacklistPeers(reason blacklistPeerReason, peerIDs ...peer.ID) // add peer to the blacklist, so we can't connect to it in the future. err := m.connGater.BlockPeer(peerID) if err != nil { - log.Warnw("failed tp block peer", "peer", peerID, "err", err) + log.Warnw("failed to block peer", "peer", peerID, "err", err) } // close connections to peer. err = m.host.Network().ClosePeer(peerID) @@ -376,6 +423,26 @@ func (m *Manager) isBlacklistedHash(hash share.DataHash) bool { return m.blacklistedHashes[hash.String()] } +func (m *Manager) validatedPool(hashStr string) *syncPool { + p := m.getOrCreatePool(hashStr) + if p.isValidatedDataHash.CompareAndSwap(false, true) { + log.Debugw("pool marked validated", "datahash", hashStr) + // if pool is proven to be valid, add all collected peers to full nodes + m.fullNodes.add(p.peers()...) + } + return p +} + +// removeIfUnreachable removes peer from some pool if it is blacklisted or disconnected +func (m *Manager) removeIfUnreachable(pool *syncPool, peerID peer.ID) bool { + if m.isBlacklistedPeer(peerID) || !m.fullNodes.has(peerID) { + log.Debugw("removing outdated peer from pool", "peer", peerID.String()) + pool.remove(peerID) + return true + } + return false +} + func (m *Manager) GC(ctx context.Context) { ticker := time.NewTicker(m.params.GcInterval) defer ticker.Stop() @@ -441,6 +508,7 @@ func (m *Manager) markPoolAsSynced(datahash string) { atomic.StorePointer(old, unsafe.Pointer(newPool(time.Second))) } } + func (p *syncPool) add(peers ...peer.ID) { if !p.isSynced.Load() { p.pool.add(peers...) diff --git a/share/p2p/peers/metrics.go b/share/p2p/peers/metrics.go index ab5bfc97b2..bf4d544d9f 100644 --- a/share/p2p/peers/metrics.go +++ b/share/p2p/peers/metrics.go @@ -169,8 +169,10 @@ func initMetrics(manager *Manager) (*metrics, error) { return metrics, nil } -func (m *metrics) observeGetPeer(ctx context.Context, - source peerSource, poolSize int, waitTime time.Duration) { +func (m *metrics) observeGetPeer( + ctx context.Context, + source peerSource, poolSize int, waitTime time.Duration, +) { if m == nil { return } diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index 4eae614ca1..c43bbc963b 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -139,6 +139,27 @@ func (p *pool) remove(peers ...peer.ID) { p.checkHasPeers() } +func (p *pool) has(peer peer.ID) bool { + p.m.RLock() + defer p.m.RUnlock() + + status, ok := p.statuses[peer] + return ok && status != removed +} + +func (p *pool) peers() []peer.ID { + p.m.RLock() + defer p.m.RUnlock() + + peers := make([]peer.ID, 0, len(p.peersList)) + for peer, status := range p.statuses { + if status != removed { + peers = append(peers, peer) + } + } + return peers +} + // cleanup will reduce memory footprint of pool. func (p *pool) cleanup() { newList := make([]peer.ID, 0, p.activeCount) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index af408d130c..7c2591bcbd 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -114,9 +114,9 @@ func (c *Client) doRequest( } _, err = serde.Read(stream, resp) if err != nil { - // server closes the stream after returning a non-successful status + // server closes the stream here if we are rate limited if errors.Is(err, io.EOF) { - c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) + c.metrics.ObserveRequests(ctx, 1, p2p.StatusRateLimited) return nil, p2p.ErrNotFound } stream.Reset() //nolint:errcheck From 301cdbe17490be17ccd2f4dca1d93a1f7aea22c6 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 22 May 2023 18:43:01 +0800 Subject: [PATCH 0606/1008] refactor(nodebuilder/share): don't wrap store getter with tee getter (#2229) ## Overview The change will save extra dagstore lookup operation on every call to store getter resulting with success. For free. --- nodebuilder/share/constructors.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index a3fe691798..3ed57d325d 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -83,12 +83,8 @@ func fullGetter( var cascade []share.Getter cascade = append(cascade, storeGetter) if cfg.UseShareExchange { - cascade = append(cascade, shrexGetter) + cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) } - cascade = append(cascade, ipldGetter) - - return getters.NewTeeGetter( - getters.NewCascadeGetter(cascade), - store, - ) + cascade = append(cascade, getters.NewTeeGetter(ipldGetter, store)) + return getters.NewCascadeGetter(cascade) } From cd8d0b9afd6dd982c43ab306cb9b160e985c6da1 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 22 May 2023 19:17:27 +0800 Subject: [PATCH 0607/1008] fix(utils): splitCtx return original timeout when it is less than min timeout (#2228) ## Overview Handle case when timeout left in original context is less than min timeout. Also refactored one more if because it is nicer this way. --- share/getters/utils.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/share/getters/utils.go b/share/getters/utils.go index a6fd4a7beb..b80478af15 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -115,11 +115,16 @@ func ctxWithSplitTimeout( return context.WithTimeout(ctx, minTimeout) } - timeout := time.Until(deadline) / time.Duration(splitFactor) - if minTimeout == 0 || timeout > minTimeout { - return context.WithTimeout(ctx, timeout) + timeout := time.Until(deadline) + if timeout < minTimeout { + return context.WithCancel(ctx) } - return context.WithTimeout(ctx, minTimeout) + + splitTimeout := timeout / time.Duration(splitFactor) + if splitTimeout < minTimeout { + return context.WithTimeout(ctx, minTimeout) + } + return context.WithTimeout(ctx, splitTimeout) } // ErrorContains reports whether any error in err's tree matches any error in targets tree. From 415583d4f420e542eb4091aa8fa81d5b90cba046 Mon Sep 17 00:00:00 2001 From: Chirag <56735482+Chirag018@users.noreply.github.com> Date: Tue, 23 May 2023 19:23:14 +0530 Subject: [PATCH 0608/1008] share/discovery: moving the package closer to shrex (#2239) Closes #2206 --- nodebuilder/share/config.go | 2 +- nodebuilder/share/constructors.go | 2 +- nodebuilder/share/module.go | 2 +- nodebuilder/share/opts.go | 2 +- share/availability/full/availability.go | 2 +- share/availability/full/testing.go | 2 +- share/getters/shrex_test.go | 2 +- share/{availability => p2p}/discovery/backoff.go | 0 share/{availability => p2p}/discovery/backoff_test.go | 0 share/{availability => p2p}/discovery/discovery.go | 0 share/{availability => p2p}/discovery/discovery_test.go | 0 share/{availability => p2p}/discovery/metrics.go | 0 share/{availability => p2p}/discovery/options.go | 0 share/{availability => p2p}/discovery/set.go | 0 share/{availability => p2p}/discovery/set_test.go | 0 share/p2p/peers/manager.go | 2 +- share/p2p/peers/manager_test.go | 2 +- 17 files changed, 9 insertions(+), 9 deletions(-) rename share/{availability => p2p}/discovery/backoff.go (100%) rename share/{availability => p2p}/discovery/backoff_test.go (100%) rename share/{availability => p2p}/discovery/discovery.go (100%) rename share/{availability => p2p}/discovery/discovery_test.go (100%) rename share/{availability => p2p}/discovery/metrics.go (100%) rename share/{availability => p2p}/discovery/options.go (100%) rename share/{availability => p2p}/discovery/set.go (100%) rename share/{availability => p2p}/discovery/set_test.go (100%) diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index cd9514fb75..7fd845a672 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 3ed57d325d..1913b3d576 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -15,10 +15,10 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/cache" - disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" + disc "github.com/celestiaorg/celestia-node/share/p2p/discovery" ) func newDiscovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 6cfcfdb475..6dfb155bb0 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -11,11 +11,11 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" - disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" + disc "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" diff --git a/nodebuilder/share/opts.go b/nodebuilder/share/opts.go index cba949882b..20ba0ce58c 100644 --- a/nodebuilder/share/opts.go +++ b/nodebuilder/share/opts.go @@ -1,8 +1,8 @@ package share import ( - disc "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/getters" + disc "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index b5d1d439a5..cfa5bd0c39 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -8,9 +8,9 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/byzantine" + "github.com/celestiaorg/celestia-node/share/p2p/discovery" ) var log = logging.Logger("share/full") diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index 1e86b1e381..680eb7abaf 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -9,9 +9,9 @@ import ( "github.com/libp2p/go-libp2p/p2p/discovery/routing" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/p2p/discovery" ) // GetterWithRandSquare provides a share.Getter filled with 'n' NMT diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 1e86d8255e..9d727635a8 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -22,8 +22,8 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" diff --git a/share/availability/discovery/backoff.go b/share/p2p/discovery/backoff.go similarity index 100% rename from share/availability/discovery/backoff.go rename to share/p2p/discovery/backoff.go diff --git a/share/availability/discovery/backoff_test.go b/share/p2p/discovery/backoff_test.go similarity index 100% rename from share/availability/discovery/backoff_test.go rename to share/p2p/discovery/backoff_test.go diff --git a/share/availability/discovery/discovery.go b/share/p2p/discovery/discovery.go similarity index 100% rename from share/availability/discovery/discovery.go rename to share/p2p/discovery/discovery.go diff --git a/share/availability/discovery/discovery_test.go b/share/p2p/discovery/discovery_test.go similarity index 100% rename from share/availability/discovery/discovery_test.go rename to share/p2p/discovery/discovery_test.go diff --git a/share/availability/discovery/metrics.go b/share/p2p/discovery/metrics.go similarity index 100% rename from share/availability/discovery/metrics.go rename to share/p2p/discovery/metrics.go diff --git a/share/availability/discovery/options.go b/share/p2p/discovery/options.go similarity index 100% rename from share/availability/discovery/options.go rename to share/p2p/discovery/options.go diff --git a/share/availability/discovery/set.go b/share/p2p/discovery/set.go similarity index 100% rename from share/availability/discovery/set.go rename to share/p2p/discovery/set.go diff --git a/share/availability/discovery/set_test.go b/share/p2p/discovery/set_test.go similarity index 100% rename from share/availability/discovery/set_test.go rename to share/p2p/discovery/set_test.go diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 46cb9123f6..313cca8db0 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -22,7 +22,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index b35ef70e6b..e10e820e84 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -22,7 +22,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/discovery" + "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) From ec41fcb0bf36c13f3212d8d644f9e9cb28d7bbb1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 24 May 2023 16:38:12 +0200 Subject: [PATCH 0609/1008] chore(.github): Fix coverage run to separate unit test and coverage upload to codecov (#2249) Separate unit test coverage run to two separate steps: unit test and then upload code coverage report. Adds the token so that code coverage actually gets pushed to codecov. --- .github/workflows/go-ci.yml | 9 ++++++--- Makefile | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index f4c5626246..8f4729c7c8 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -55,10 +55,13 @@ jobs: with: go-version: ${{ env.GO_VERSION }} - - name: Test & Coverage - run: make cover - - uses: codecov/codecov-action@v3.1.3 + - name: run unit tests + run: make test-unit + + - name: upload coverage + uses: codecov/codecov-action@v3.1.3 with: + token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.txt unit_race_test: diff --git a/Makefile b/Makefile index c631112421..79c45d6597 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ lint: lint-imports ## test-unit: Running unit tests test-unit: @echo "--> Running unit tests" - @go test `go list ./... | grep -v nodebuilder/tests` -covermode=atomic -coverprofile=coverage.out + @go test -covermode=atomic -coverprofile=coverage.txt `go list ./... | grep -v nodebuilder/tests` .PHONY: test-unit ## test-unit-race: Running unit tests with data race detector From 5fa02442ba2e77fa4271a17b27695df4c7dd3495 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 25 May 2023 17:53:47 +0800 Subject: [PATCH 0610/1008] fix(share/shrex): return aggregated error if context Deadline reached (#2243) ## Overview Errors were lost on context deadline --- share/getters/shrex.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index c860e01532..170cd53009 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -130,19 +130,18 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex for { if ctx.Err() != nil { sg.metrics.recordEDSAttempt(ctx, attempt, false) - return nil, ctx.Err() + return nil, errors.Join(err, ctx.Err()) } attempt++ start := time.Now() peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) if getErr != nil { - err = errors.Join(err, getErr) log.Debugw("eds: couldn't find peer", "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) sg.metrics.recordEDSAttempt(ctx, attempt, false) - return nil, fmt.Errorf("getter/shrex: %w", err) + return nil, errors.Join(err, getErr) } reqStart := time.Now() @@ -197,19 +196,18 @@ func (sg *ShrexGetter) GetSharesByNamespace( for { if ctx.Err() != nil { sg.metrics.recordNDAttempt(ctx, attempt, false) - return nil, ctx.Err() + return nil, errors.Join(err, ctx.Err()) } attempt++ start := time.Now() peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) if getErr != nil { - err = errors.Join(err, getErr) log.Debugw("nd: couldn't find peer", "hash", root.String(), "err", getErr, "finished (s)", time.Since(start)) sg.metrics.recordNDAttempt(ctx, attempt, false) - return nil, fmt.Errorf("getter/shrex: %w", err) + return nil, errors.Join(err, getErr) } reqStart := time.Now() From cf1c2ca82a4aec0fc49b3463f8548de2c1672b22 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 25 May 2023 18:01:47 +0800 Subject: [PATCH 0611/1008] refactor(share/shrex-nd): blacklist peers that send invalid data (#2231) ## Overview Shrex should blacklist peers that send bad data. --- share/getters/shrex.go | 11 ++++++++++- share/p2p/shrexnd/client.go | 6 ------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 170cd53009..a8dda90045 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -215,7 +215,16 @@ func (sg *ShrexGetter) GetSharesByNamespace( nd, getErr := sg.ndClient.RequestND(reqCtx, root, id, peer) cancel() switch { - case getErr == nil, errors.Is(getErr, share.ErrNamespaceNotFound): + case getErr == nil: + if getErr = nd.Verify(root, id); getErr != nil { + setStatus(peers.ResultBlacklistPeer) + break + } + setStatus(peers.ResultNoop) + sg.metrics.recordNDAttempt(ctx, attempt, true) + return nd, getErr + case errors.Is(getErr, share.ErrNamespaceNotFound): + // TODO: will be merged with first case once non-inclusion proofs are ready setStatus(peers.ResultNoop) sg.metrics.recordNDAttempt(ctx, attempt, true) return nd, getErr diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index ff51ef1fe0..ab407126de 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -127,12 +127,6 @@ func (c *Client) doRequest( if err != nil { return nil, fmt.Errorf("client-nd: converting response to shares: %w", err) } - - err = shares.Verify(root, nID) - if err != nil { - return nil, fmt.Errorf("client-nd: verifying response: %w", err) - } - return shares, nil } From 7efdbbe9ffcb5cb5b5cde6c9dc461bebe6b46cc6 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 25 May 2023 12:18:28 +0200 Subject: [PATCH 0612/1008] fix(modheader): check err first in switch (#2246) Fixes https://github.com/celestiaorg/celestia-node/issues/2244 Otherwise, we call Height on the nil header --- nodebuilder/header/service.go | 26 ++++++++++++++----- nodebuilder/header/service_test.go | 41 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 nodebuilder/header/service_test.go diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 8c68f69f9c..e6d7d46b8f 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -17,12 +17,20 @@ import ( type Service struct { ex libhead.Exchange[*header.ExtendedHeader] - syncer *sync.Syncer[*header.ExtendedHeader] + syncer syncer sub libhead.Subscriber[*header.ExtendedHeader] p2pServer *p2p.ExchangeServer[*header.ExtendedHeader] store libhead.Store[*header.ExtendedHeader] } +// syncer bare minimum Syncer interface for testing +type syncer interface { + libhead.Head[*header.ExtendedHeader] + + State() sync.State + SyncWait(ctx context.Context) error +} + // newHeaderService creates a new instance of header Service. func newHeaderService( syncer *sync.Syncer[*header.ExtendedHeader], @@ -54,19 +62,23 @@ func (s *Service) GetVerifiedRangeByHeight( func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { head, err := s.syncer.Head(ctx) switch { - case uint64(head.Height()) == height, err != nil: - return head, err + case err != nil: + return nil, err + case uint64(head.Height()) == height: + return head, nil case uint64(head.Height()) < height: return nil, fmt.Errorf("header: given height is from the future: "+ "networkHeight: %d, requestedHeight: %d", head.Height(), height) } - // TODO(vgonkivs): remove after https://github.com/celestiaorg/go-header/issues/32 will be - // implemented + // TODO(vgonkivs): remove after https://github.com/celestiaorg/go-header/issues/32 is + // implemented and fetch header from HeaderEx if missing locally head, err = s.store.Head(ctx) switch { - case uint64(head.Height()) == height, err != nil: - return head, err + case err != nil: + return nil, err + case uint64(head.Height()) == height: + return head, nil // `+1` allows for one header network lag, e.g. user request header that is milliseconds away case uint64(head.Height())+1 < height: return nil, fmt.Errorf("header: syncing in progress: "+ diff --git a/nodebuilder/header/service_test.go b/nodebuilder/header/service_test.go new file mode 100644 index 0000000000..6493d3d51d --- /dev/null +++ b/nodebuilder/header/service_test.go @@ -0,0 +1,41 @@ +package header + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/sync" + + "github.com/celestiaorg/celestia-node/header" +) + +func TestGetByHeightHandlesError(t *testing.T) { + serv := Service{ + syncer: &errorSyncer[*header.ExtendedHeader]{}, + } + + assert.NotPanics(t, func() { + h, err := serv.GetByHeight(context.Background(), 100) + assert.Error(t, err) + assert.Nil(t, h) + }) +} + +type errorSyncer[H libhead.Header] struct{} + +func (d *errorSyncer[H]) Head(context.Context) (H, error) { + var zero H + return zero, fmt.Errorf("dummy error") +} + +func (d *errorSyncer[H]) State() sync.State { + return sync.State{} +} + +func (d *errorSyncer[H]) SyncWait(context.Context) error { + return fmt.Errorf("dummy error") +} From e5efcb0c868154fe508bd0240cfb44ca6e3ca03f Mon Sep 17 00:00:00 2001 From: Rootul P Date: Fri, 26 May 2023 10:33:11 -0400 Subject: [PATCH 0613/1008] fix: bump to celestia-app v0.13.3 (#2257) Closes https://github.com/celestiaorg/celestia-app/issues/1816 which was originally reported by @jcstein. ## Testing Before (with celestia-node v0.10.0) ``` $ curl -X GET http://0.0.0.0:26659/namespaced_data/e8e5f679bf7116cb/height/559108 "share sequence has 1 shares but needed 0 shares"% ``` After ``` $ curl -X GET http://0.0.0.0:26659/namespaced_data/e8e5f679bf7116cb/height/559108 {"data":["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"],"height":559108}% ``` --- api/gateway/share_test.go | 36 ++++++++++++++++++++++++++ api/gateway/testdata/sharesBase64.json | 8 ++++++ go.mod | 2 +- go.sum | 4 +-- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 api/gateway/testdata/sharesBase64.json diff --git a/api/gateway/share_test.go b/api/gateway/share_test.go index aeccc9eb02..92b7a2c853 100644 --- a/api/gateway/share_test.go +++ b/api/gateway/share_test.go @@ -2,6 +2,9 @@ package gateway import ( "bytes" + _ "embed" + "encoding/base64" + "encoding/json" "testing" "github.com/stretchr/testify/assert" @@ -106,6 +109,7 @@ func Test_dataFromShares(t *testing.T) { assert.Error(t, err) return } + assert.NoError(t, err) assert.Equal(t, tc.want, got) }) } @@ -121,3 +125,35 @@ func padShare(share []byte) (paddedShare []byte) { func fillShare(share []byte, filler byte) (paddedShare []byte) { return append(share, bytes.Repeat([]byte{filler}, appconsts.ShareSize-len(share))...) } + +// sharesBase64JSON is the base64 encoded share data from Blockspace Race +// block height 559108 and namespace e8e5f679bf7116cb. +// +//go:embed "testdata/sharesBase64.json" +var sharesBase64JSON string + +// Test_dataFromSharesBSR reproduces an error that occurred when parsing shares +// on Blockspace Race block height 559108 namespace e8e5f679bf7116cb. +// +// https://github.com/celestiaorg/celestia-app/issues/1816 +func Test_dataFromSharesBSR(t *testing.T) { + var sharesBase64 []string + err := json.Unmarshal([]byte(sharesBase64JSON), &sharesBase64) + assert.NoError(t, err) + input := decode(sharesBase64) + + _, err = dataFromShares(input) + assert.NoError(t, err) +} + +// decode returns the raw shares from base64Encoded. +func decode(base64Encoded []string) (rawShares [][]byte) { + for _, share := range base64Encoded { + rawShare, err := base64.StdEncoding.DecodeString(share) + if err != nil { + panic(err) + } + rawShares = append(rawShares, rawShare) + } + return rawShares +} diff --git a/api/gateway/testdata/sharesBase64.json b/api/gateway/testdata/sharesBase64.json new file mode 100644 index 0000000000..e73d69f420 --- /dev/null +++ b/api/gateway/testdata/sharesBase64.json @@ -0,0 +1,8 @@ +[ + "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" +] diff --git a/go.mod b/go.mod index 25bb89586e..431a78f519 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 - github.com/celestiaorg/celestia-app v0.13.0 + github.com/celestiaorg/celestia-app v0.13.3 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 diff --git a/go.sum b/go.sum index 76ec8166ce..a7feaf83ab 100644 --- a/go.sum +++ b/go.sum @@ -340,8 +340,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.13.0 h1:NPOR1P98YCCv+E2I9TdqsO1/UnGyzTHW5CBhctWHaOY= -github.com/celestiaorg/celestia-app v0.13.0/go.mod h1:OcPBfWDyowJgoEQ89NB2LgLOm9LSwloCgCzdZKjmi78= +github.com/celestiaorg/celestia-app v0.13.3 h1:lJQLBzKALrKe0lLe3Gf8Si7+CF0eqRuNhjIjj7pM2fk= +github.com/celestiaorg/celestia-app v0.13.3/go.mod h1:OcPBfWDyowJgoEQ89NB2LgLOm9LSwloCgCzdZKjmi78= github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/xBFIHj93llcw9ZQfNxyVRlI= github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= From 9359b65cf8184d34f246bf989a3bf3c949477bce Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Mon, 29 May 2023 05:06:27 -0500 Subject: [PATCH 0614/1008] feat!: bump app v1.0.0-rc0 (#2253) Co-authored-by: Rootul Patel Co-authored-by: Wondertan --- api/gateway/share.go | 6 +- api/gateway/share_test.go | 133 +++-------- core/eds.go | 13 +- core/eds_test.go | 12 +- core/exchange_test.go | 2 +- core/testing.go | 3 +- core/testing_grpc.go | 2 +- das/coordinator_test.go | 2 +- das/daser_test.go | 4 +- das/metrics.go | 4 +- das/worker.go | 6 +- go.mod | 56 ++--- go.sum | 116 +++++----- nodebuilder/tests/swamp/config.go | 6 +- nodebuilder/tests/swamp/swamp.go | 2 +- nodebuilder/tests/swamp/swamp_tx.go | 2 +- share/availability/cache/availability_test.go | 2 +- share/availability/light/availability.go | 2 +- share/availability/light/availability_test.go | 209 +++--------------- .../light/testdata/sample-block.json | 60 ----- share/eds/byzantine/bad_encoding.go | 18 +- share/eds/byzantine/bad_encoding_test.go | 2 +- share/eds/byzantine/byzantine.go | 2 +- share/eds/byzantine/share_proof_test.go | 2 +- share/eds/eds.go | 6 +- share/eds/eds_test.go | 65 ++++-- share/eds/retriever.go | 10 +- share/eds/retriever_quadrant.go | 2 +- share/eds/retriever_test.go | 2 +- share/eds/store.go | 2 +- share/eds/testdata/example-root.json | 32 +-- share/eds/testdata/example.car | Bin 55235 -> 74051 bytes share/empty.go | 10 +- share/get_test.go | 8 +- share/getter.go | 2 +- share/getters/getter_test.go | 19 +- share/getters/ipld.go | 2 +- share/getters/shrex_test.go | 7 +- share/getters/store.go | 2 +- share/getters/utils.go | 6 +- share/ipld/nmt.go | 14 +- share/ipld/nmt_test.go | 11 +- share/p2p/shrexnd/exchange_test.go | 9 +- share/share.go | 2 +- share/test_helpers.go | 15 +- state/integration_test.go | 4 +- 46 files changed, 337 insertions(+), 559 deletions(-) delete mode 100755 share/availability/light/testdata/sample-block.json diff --git a/api/gateway/share.go b/api/gateway/share.go index 13903fd14b..36a9778f1a 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -123,7 +123,11 @@ func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID } func dataFromShares(input []share.Share) (data [][]byte, err error) { - sequences, err := shares.ParseShares(input) + appShares, err := shares.FromBytes(input) + if err != nil { + return nil, err + } + sequences, err := shares.ParseShares(appShares, false) if err != nil { return nil, err } diff --git a/api/gateway/share_test.go b/api/gateway/share_test.go index 92b7a2c853..423d08682b 100644 --- a/api/gateway/share_test.go +++ b/api/gateway/share_test.go @@ -1,129 +1,52 @@ package gateway import ( - "bytes" _ "embed" "encoding/base64" "encoding/json" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + coretypes "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/celestiaorg/celestia-app/pkg/shares" ) func Test_dataFromShares(t *testing.T) { - type testCase struct { - name string - input [][]byte - want [][]byte - wantErr bool + testData := [][]byte{ + []byte("beep"), + []byte("beeap"), + []byte("BEEEEAHP"), } - smallTxInput := padShare([]uint8{ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // namespace id - 0x1, // info byte - 0x0, 0x0, 0x0, 0x2, // 1 byte (unit) + 1 byte (unit length) = 2 bytes sequence length - 0x0, 0x0, 0x0, 17, // reserved bytes - 0x1, // unit length of first transaction - 0xa, // data of first transaction - }) - smallTxData := []byte{0x1, 0xa} - - largeTxInput := [][]byte{ - fillShare([]uint8{ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // namespace id - 0x1, // info byte - 0x0, 0x0, 0x2, 0x2, // 512 (unit) + 2 (unit length) = 514 sequence length - 0x0, 0x0, 0x0, 17, // reserved bytes - 128, 4, // unit length of transaction is 512 - }, 0xc), // data of transaction - padShare(append([]uint8{ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // namespace id - 0x0, // info byte - 0x0, 0x0, 0x0, 0x0, // reserved bytes - }, bytes.Repeat([]byte{0xc}, 19)..., // continuation data of transaction - )), - } - largeTxData := []byte{128, 4} - largeTxData = append(largeTxData, bytes.Repeat([]byte{0xc}, 512)...) - - largePfbTxInput := [][]byte{ - fillShare([]uint8{ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, // namespace id - 0x1, // info byte - 0x0, 0x0, 0x2, 0x2, // 512 (unit) + 2 (unit length) = 514 sequence length - 0x0, 0x0, 0x0, 17, // reserved bytes - 128, 4, // unit length of transaction is 512 - }, 0xc), // data of transaction - padShare(append([]uint8{ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, // namespace id - 0x0, // info byte - 0x0, 0x0, 0x0, 0x0, // reserved bytes - }, bytes.Repeat([]byte{0xc}, 19)..., // continuation data of transaction - )), + ns := namespace.RandomBlobNamespace() + sss := shares.NewSparseShareSplitter() + for _, data := range testData { + b := coretypes.Blob{ + Data: data, + NamespaceID: ns.ID, + NamespaceVersion: ns.Version, + ShareVersion: appconsts.ShareVersionZero, + } + err := sss.Write(b) + require.NoError(t, err) } - largePfbTxData := []byte{128, 4} - largePfbTxData = append(largePfbTxData, bytes.Repeat([]byte{0xc}, 512)...) - testCases := []testCase{ - { - name: "empty", - input: [][]byte{}, - want: nil, - wantErr: false, - }, - { - name: "returns an error when shares contain two different namespaces", - input: [][]byte{ - {0, 0, 0, 0, 0, 0, 0, 1}, - {0, 0, 0, 0, 0, 0, 0, 2}, - }, - want: nil, - wantErr: true, - }, - { - name: "returns raw data of a single tx share", - input: [][]byte{smallTxInput}, - want: [][]byte{smallTxData}, - wantErr: false, - }, - { - name: "returns raw data of a large tx that spans two shares", - input: largeTxInput, - want: [][]byte{largeTxData}, - wantErr: false, - }, - { - name: "returns raw data of a large PFB tx that spans two shares", - input: largePfbTxInput, - want: [][]byte{largePfbTxData}, - wantErr: false, - }, - } + sssShares := sss.Export() - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, err := dataFromShares(tc.input) - if tc.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Equal(t, tc.want, got) - }) + rawSSSShares := make([][]byte, len(sssShares)) + for i := 0; i < len(sssShares); i++ { + d := sssShares[i].ToBytes() + rawSSSShares[i] = d } -} -// padShare returns a share padded with trailing zeros. -func padShare(share []byte) (paddedShare []byte) { - return fillShare(share, 0) -} + parsedSSSShares, err := dataFromShares(rawSSSShares) + require.NoError(t, err) -// fillShare returns a share filled with filler so that the share length -// is equal to appconsts.ShareSize. -func fillShare(share []byte, filler byte) (paddedShare []byte) { - return append(share, bytes.Repeat([]byte{filler}, appconsts.ShareSize-len(share))...) + require.Equal(t, testData, parsedSSSShares) } // sharesBase64JSON is the base64 encoded share data from Blockspace Race @@ -137,6 +60,8 @@ var sharesBase64JSON string // // https://github.com/celestiaorg/celestia-app/issues/1816 func Test_dataFromSharesBSR(t *testing.T) { + t.Skip("skip until sharesBase64JSON is regenerated with v1 compatibility") + var sharesBase64 []string err := json.Unmarshal([]byte(sharesBase64JSON), &sharesBase64) assert.NoError(t, err) diff --git a/core/eds.go b/core/eds.go index e37fa5d960..c435f0e649 100644 --- a/core/eds.go +++ b/core/eds.go @@ -7,8 +7,9 @@ import ( "github.com/filecoin-project/dagstore" "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" - appshares "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/celestia-app/pkg/square" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -22,12 +23,18 @@ func extendBlock(data types.Data) (*rsmt2d.ExtendedDataSquare, error) { if len(data.Txs) == 0 && data.SquareSize == uint64(1) { return nil, nil } - shares, err := appshares.Split(data, true) + + sqr, err := square.Construct(data.Txs.ToSliceOfBytes(), appconsts.MaxSquareSize) if err != nil { return nil, err } - return da.ExtendShares(data.SquareSize, appshares.ToBytes(shares)) + shares := make([][]byte, len(sqr)) + for i, s := range sqr { + shares[i] = s.ToBytes() + } + + return da.ExtendShares(shares) } // storeEDS will only store extended block if it is not empty and doesn't already exist. diff --git a/core/eds_test.go b/core/eds_test.go index d67f5005f9..6bc04c96c4 100644 --- a/core/eds_test.go +++ b/core/eds_test.go @@ -18,7 +18,6 @@ import ( func TestTrulyEmptySquare(t *testing.T) { data := types.Data{ Txs: []types.Tx{}, - Blobs: []types.Blob{}, SquareSize: 1, } @@ -27,17 +26,20 @@ func TestTrulyEmptySquare(t *testing.T) { assert.Nil(t, eds) } -// TestNonEmptySquareWithZeroTxs tests that a non-empty square with no -// transactions or blobs computes the correct data root (not the minimum DAH). +// TestNonZeroSquareSize tests that the DAH hash of a block with no transactions +// is equal to the DAH hash for an empty root even if SquareSize is set to +// something non-zero. Technically, this block data is invalid because the +// construction of the square is deterministic, and the rules which dictate the +// square size do not allow for empty block data. However, should that ever +// occur, we need to ensure that the correct data root is generated. func TestNonEmptySquareWithZeroTxs(t *testing.T) { data := types.Data{ Txs: []types.Tx{}, - Blobs: []types.Blob{}, SquareSize: 16, } eds, err := extendBlock(data) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(eds) - assert.NotEqual(t, share.EmptyRoot().Hash(), dah.Hash()) + assert.Equal(t, share.EmptyRoot().Hash(), dah.Hash()) } diff --git a/core/exchange_test.go b/core/exchange_test.go index 928d15a9e5..f6302b5742 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-app/test/util/testnode" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share/eds" diff --git a/core/testing.go b/core/testing.go index 88154dc7d9..393ec62c09 100644 --- a/core/testing.go +++ b/core/testing.go @@ -12,7 +12,7 @@ import ( tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-app/test/util/testnode" ) // TestConfig encompasses all the configs required to run test Tendermint + Celestia App tandem. @@ -79,7 +79,6 @@ func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { state, kr, "private", - nil, ) require.NoError(t, err) diff --git a/core/testing_grpc.go b/core/testing_grpc.go index 2306b61723..d831bc0724 100644 --- a/core/testing_grpc.go +++ b/core/testing_grpc.go @@ -14,7 +14,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-app/test/util/testnode" ) /* diff --git a/das/coordinator_test.go b/das/coordinator_test.go index e7e2d5ceb2..188cb0d222 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -431,7 +431,7 @@ func (m *mockSampler) discover(ctx context.Context, newHeight uint64, emit liste emit(ctx, &header.ExtendedHeader{ Commit: &types.Commit{}, RawHeader: header.RawHeader{Height: int64(newHeight)}, - DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}, + DAH: &header.DataAvailabilityHeader{RowRoots: make([][]byte, 0)}, }) } diff --git a/das/daser_test.go b/das/daser_test.go index 2f1c494309..7398310a6b 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -345,7 +345,7 @@ type benchGetterStub struct { func newBenchGetter() benchGetterStub { return benchGetterStub{header: &header.ExtendedHeader{ - DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}} + DAH: &header.DataAvailabilityHeader{RowRoots: make([][]byte, 0)}}} } func (m benchGetterStub) GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) { @@ -362,7 +362,7 @@ func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.Exten return &header.ExtendedHeader{ Commit: &types.Commit{}, RawHeader: header.RawHeader{Height: int64(height)}, - DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}, nil + DAH: &header.DataAvailabilityHeader{RowRoots: make([][]byte, 0)}}, nil } func (m getterStub) GetRangeByHeight(context.Context, uint64, uint64) ([]*header.ExtendedHeader, error) { diff --git a/das/metrics.go b/das/metrics.go index fd72fe5e4e..1dcf5c8165 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -154,13 +154,13 @@ func (m *metrics) observeSample( } m.sampleTime.Record(ctx, sampleTime.Seconds(), attribute.Bool(failedLabel, err != nil), - attribute.Int(headerWidthLabel, len(h.DAH.RowsRoots)), + attribute.Int(headerWidthLabel, len(h.DAH.RowRoots)), attribute.String(jobTypeLabel, string(jobType)), ) m.sampled.Add(ctx, 1, attribute.Bool(failedLabel, err != nil), - attribute.Int(headerWidthLabel, len(h.DAH.RowsRoots)), + attribute.Int(headerWidthLabel, len(h.DAH.RowRoots)), attribute.String(jobTypeLabel, string(jobType)), ) diff --git a/das/worker.go b/das/worker.go index 06585e49b0..746324ec48 100644 --- a/das/worker.go +++ b/das/worker.go @@ -120,7 +120,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 "type", w.state.jobType, "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), + "square width", len(h.DAH.RowRoots), "data root", h.DAH.String(), "err", err, "finished (s)", time.Since(start), @@ -150,7 +150,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 "type", w.state.jobType, "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), + "square width", len(h.DAH.RowRoots), "data root", h.DAH.String(), "finished (s)", time.Since(start), ) @@ -179,7 +179,7 @@ func (w *worker) getHeader(ctx context.Context, height uint64) (*header.Extended "got header from header store", "height", h.Height(), "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), + "square width", len(h.DAH.RowRoots), "data root", h.DAH.String(), "finished (s)", time.Since(start), ) diff --git a/go.mod b/go.mod index 431a78f519..abdc7b6afc 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 - github.com/celestiaorg/celestia-app v0.13.3 + github.com/celestiaorg/celestia-app v1.0.0-rc0 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/nmt v0.15.0 - github.com/celestiaorg/rsmt2d v0.8.0 - github.com/cosmos/cosmos-sdk v0.46.7 + github.com/celestiaorg/nmt v0.16.0 + github.com/celestiaorg/rsmt2d v0.9.0 + github.com/cosmos/cosmos-sdk v0.46.11 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 github.com/dgraph-io/badger/v2 v2.2007.4 @@ -60,8 +60,8 @@ require ( github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.1 - github.com/tendermint/tendermint v0.35.4 + github.com/stretchr/testify v1.8.3 + github.com/tendermint/tendermint v0.34.24 go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 @@ -99,7 +99,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/btcsuite/btcd v0.22.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect github.com/celestiaorg/quantum-gravity-bridge v1.3.0 // indirect @@ -109,6 +108,7 @@ require ( github.com/chzyer/readline v1.5.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect + github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/confio/ics23/go v0.9.0 // indirect github.com/containerd/cgroups v1.0.4 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -117,16 +117,17 @@ require ( github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogoproto v1.4.2 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.19.4 // indirect + github.com/cosmos/iavl v0.19.5 // indirect github.com/cosmos/ibc-go/v6 v6.1.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/deckarep/golang-set v1.8.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect @@ -135,7 +136,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect - github.com/ethereum/go-ethereum v1.10.26 // indirect + github.com/ethereum/go-ethereum v1.11.6 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -151,7 +152,7 @@ require ( github.com/go-openapi/jsonreference v0.19.4 // indirect github.com/go-openapi/spec v0.19.11 // indirect github.com/go-openapi/swag v0.19.11 // indirect - github.com/go-stack/stack v1.8.0 // indirect + github.com/go-stack/stack v1.8.1 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect @@ -159,8 +160,8 @@ require ( github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.0.1 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect @@ -186,10 +187,13 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/influxdata/influxdb-client-go/v2 v2.12.2 // indirect + github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-bitswap v0.12.0 // indirect github.com/ipfs/go-block-format v0.1.1 // indirect @@ -212,7 +216,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.15.12 // indirect + github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect github.com/koron/go-ssdp v0.0.3 // indirect @@ -261,8 +265,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.1 // indirect @@ -274,7 +278,6 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rivo/uniseg v0.4.2 // indirect - github.com/rjeczalik/notify v0.9.1 // indirect github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.27.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect @@ -286,19 +289,18 @@ require ( github.com/spf13/viper v1.14.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/tendermint/btcd v0.1.1 // indirect - github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect + github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tendermint/tm-db v0.6.7 // indirect + github.com/tidwall/btree v1.5.0 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect - github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/zondax/hid v0.9.1 // indirect - github.com/zondax/ledger-go v0.14.0 // indirect + github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect @@ -307,13 +309,13 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect - golang.org/x/mod v0.8.0 // indirect + golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect + golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.6.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -328,8 +330,8 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.21.0-tm-v0.34.27 ) diff --git a/go.sum b/go.sum index a7feaf83ab..9eca643fef 100644 --- a/go.sum +++ b/go.sum @@ -229,6 +229,7 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= @@ -320,6 +321,7 @@ github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/i github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= @@ -327,7 +329,6 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= @@ -340,12 +341,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.13.3 h1:lJQLBzKALrKe0lLe3Gf8Si7+CF0eqRuNhjIjj7pM2fk= -github.com/celestiaorg/celestia-app v0.13.3/go.mod h1:OcPBfWDyowJgoEQ89NB2LgLOm9LSwloCgCzdZKjmi78= -github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23 h1:BHvn41IHOtvHeX1VZqO/xBFIHj93llcw9ZQfNxyVRlI= -github.com/celestiaorg/celestia-core v1.15.0-tm-v0.34.23/go.mod h1:nL+vkAMKy/A8wWemWqMwBy4pOGWYYbboAVTEe3N5gIU= -github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7 h1:EADZy33ufskVIy6Rj6jbi3SOVCeYYo26zUi7iYx+QR0= -github.com/celestiaorg/cosmos-sdk v1.8.0-sdk-v0.46.7/go.mod h1:vg3Eza9adJJ5Mdx6boz5MpZsZcTZyrfTVYZHyi2zLm4= +github.com/celestiaorg/celestia-app v1.0.0-rc0 h1:wpuP5fTIEbLCP+U5pGwKfSzXUTE/bE8oqKECFN5yoO0= +github.com/celestiaorg/celestia-app v1.0.0-rc0/go.mod h1:C8pNwFQWBLYIGpdrFesO1uezthrKjv0H5meecYQc1ek= +github.com/celestiaorg/celestia-core v1.21.0-tm-v0.34.27 h1:EdkqFRBypVEq/nX2ZE7KQ6dTlN8j3rEYe+WGahWuSUk= +github.com/celestiaorg/celestia-core v1.21.0-tm-v0.34.27/go.mod h1:GVo91Wifg9KL/nFx9nPkpl0UIFdvvs4fhnly9GhGxZU= +github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11 h1:Rd5EvJx1nG3KurBspVN51RVmvif0Lp2UVURbG2ad3Cs= +github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11/go.mod h1:xCG6OUkJy5KUMEg20Zk010lra9XjkmKS3+bk0wp7bd8= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= @@ -358,12 +359,12 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.15.0 h1:ID9QlMIeP6WK/iiGcfnYLu2qqVIq0UYe/dc3TVPt6EA= -github.com/celestiaorg/nmt v0.15.0/go.mod h1:GfwIvQPhUakn1modWxJ+rv8dUjJzuXg5H+MLFM1o7nY= +github.com/celestiaorg/nmt v0.16.0 h1:4CX6d1Uwf1C+tGcAWskPve0HCDTnI4Ey8ffjiDwcGH0= +github.com/celestiaorg/nmt v0.16.0/go.mod h1:GfwIvQPhUakn1modWxJ+rv8dUjJzuXg5H+MLFM1o7nY= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= -github.com/celestiaorg/rsmt2d v0.8.0 h1:ZUxTCELZCM9zMGKNF3cT+rUqMddXMeiuyleSJPZ3Wn4= -github.com/celestiaorg/rsmt2d v0.8.0/go.mod h1:hhlsTi6G3+X5jOP/8Lb/d7i5y2XNFmnyMddYbFSmrgo= +github.com/celestiaorg/rsmt2d v0.9.0 h1:kon78I748ZqjNzI8OAqPN+2EImuZuanj/6gTh8brX3o= +github.com/celestiaorg/rsmt2d v0.9.0/go.mod h1:E06nDxfoeBDltWRvTR9dLviiUZI5/6mLXAuhSJzz3Iw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -408,11 +409,17 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= +github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= +github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= +github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= @@ -451,12 +458,12 @@ github.com/cosmos/gogoproto v1.4.2 h1:UeGRcmFW41l0G0MiefWhkPEVEwvu78SZsHBvI78dAY github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= -github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= +github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= +github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-go/v6 v6.1.0 h1:o7oXws2vKkKfOFzJI+oNylRn44PCNt5wzHd/zKQKbvQ= github.com/cosmos/ibc-go/v6 v6.1.0/go.mod h1:CY3zh2HLfetRiW8LY6kVHMATe90Wj/UOoY8T6cuB0is= -github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= -github.com/cosmos/ledger-cosmos-go v0.12.1/go.mod h1:dhO6kj+Y+AHIOgAe4L9HL/6NDdyyth4q238I9yFpD2g= +github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= +github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -482,8 +489,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= -github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= @@ -491,6 +499,7 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= @@ -555,8 +564,8 @@ github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWS github.com/etclabscore/go-openrpc-reflect v0.0.37 h1:IH0e7JqIvR9OhbbFWi/BHIkXrqbR3Zyia3RJ733eT6c= github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/ethereum/go-ethereum v1.11.6 h1:2VF8Mf7XiSUfmoNOy3D+ocfl9Qu8baQBrCNbo2CXQ8E= +github.com/ethereum/go-ethereum v1.11.6/go.mod h1:+a8pUj1tOyJ2RinsNQD4326YS+leSoKGiG/uVVb0x6Y= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= @@ -594,6 +603,7 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqG github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -658,8 +668,9 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= @@ -675,6 +686,7 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= @@ -726,13 +738,14 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -902,8 +915,9 @@ github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSV github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= +github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= @@ -927,10 +941,13 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb-client-go/v2 v2.12.2 h1:uYABKdrEKlYm+++qfKdbgaHKBPmoWR5wpbmj6MBB/2g= +github.com/influxdata/influxdb-client-go/v2 v2.12.2/go.mod h1:YteV91FiQxRdccyJ2cHvj2f/5sq4y4Njqu1fQzsQCOU= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= @@ -1126,8 +1143,8 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= -github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -1157,6 +1174,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -1742,8 +1760,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1754,9 +1772,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.0 h1:LWuuqPQ1oa6x7BnmUOuo/aGwdX85QGhWZUBYWWW3zdk= github.com/pyroscope-io/client v0.7.0/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= @@ -1790,7 +1807,6 @@ github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -1889,8 +1905,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1909,22 +1925,23 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= -github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= -github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= -github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= +github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= +github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= @@ -1948,8 +1965,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -1963,14 +1980,12 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= +github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= -github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k= -github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= @@ -2008,8 +2023,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/ledger-go v0.14.0 h1:dlMC7aO8Wss1CxBq2I96kZ69Nh1ligzbs8UWOtq/AsA= -github.com/zondax/ledger-go v0.14.0/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= +github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= +github.com/zondax/ledger-go v0.14.1/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc= gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 h1:dizWJqTWjwyD8KGcMOwgrkqu1JIkofYgKkmDeNE7oAs= @@ -2156,8 +2171,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 h1:5sPMf9HJXrvBWIamTw+rTST0bZ3Mho2n1p58M0+W99c= -golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2189,8 +2204,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2473,7 +2488,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2546,8 +2561,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2823,6 +2838,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index f14243d342..630920609f 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -15,8 +15,8 @@ type Config struct { // 100ms func DefaultConfig() *Config { cfg := core.DefaultTestConfig() - // timeout commits faster than this tend to be flakier - cfg.Tendermint.Consensus.TimeoutCommit = 200 * time.Millisecond + // target height duration lower than this tend to be flakier + cfg.Tendermint.Consensus.TargetHeightDuration = 200 * time.Millisecond return &Config{ cfg, } @@ -31,7 +31,7 @@ func WithBlockTime(t time.Duration) Option { // for empty block c.Tendermint.Consensus.CreateEmptyBlocksInterval = t // for filled block - c.Tendermint.Consensus.TimeoutCommit = t + c.Tendermint.Consensus.TargetHeightDuration = t c.Tendermint.Consensus.SkipTimeoutCommit = false } } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 58ff807a4d..5b99b577b1 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -17,7 +17,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/fx" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-app/test/util/testnode" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" libhead "github.com/celestiaorg/go-header" diff --git a/nodebuilder/tests/swamp/swamp_tx.go b/nodebuilder/tests/swamp/swamp_tx.go index 956c8cf7c4..656b2f341d 100644 --- a/nodebuilder/tests/swamp/swamp_tx.go +++ b/nodebuilder/tests/swamp/swamp_tx.go @@ -6,7 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-app/test/util/testnode" ) // FillBlocks produces the given amount of contiguous blocks with customizable size. diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index 47df434c06..03df47f848 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -60,7 +60,7 @@ func TestCacheAvailability(t *testing.T) { } var invalidHeader = da.DataAvailabilityHeader{ - RowsRoots: [][]byte{{1, 2}}, + RowRoots: [][]byte{{1, 2}}, } // TestCacheAvailability_Failed tests to make sure a failed diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 07a3e801d9..761671b955 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -48,7 +48,7 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo "err", err) panic(err) } - samples, err := SampleSquare(len(dah.RowsRoots), int(la.params.SampleAmount)) + samples, err := SampleSquare(len(dah.RowRoots), int(la.params.SampleAmount)) if err != nil { return err } diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 146cb8554a..4980af031e 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -1,23 +1,16 @@ package light import ( - "bytes" "context" - "crypto/rand" _ "embed" - "encoding/hex" - "encoding/json" - mrand "math/rand" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" - appshares "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" @@ -87,16 +80,16 @@ func TestService_GetSharesByNamespace(t *testing.T) { for _, tt := range tests { t.Run("size: "+strconv.Itoa(tt.squareSize), func(t *testing.T) { getter, bServ := EmptyGetter() - n := tt.squareSize * tt.squareSize - randShares := share.RandShares(t, n) - idx1 := (n - 1) / 2 - idx2 := n / 2 + totalShares := tt.squareSize * tt.squareSize + randShares := share.RandShares(t, totalShares) + idx1 := (totalShares - 1) / 2 + idx2 := totalShares / 2 if tt.expectedShareCount > 1 { // make it so that two rows have the same namespace ID - copy(randShares[idx2][:8], randShares[idx1][:8]) + copy(randShares[idx2][:namespace.NamespaceSize], randShares[idx1][:namespace.NamespaceSize]) } root := availability_test.FillBS(t, bServ, randShares) - randNID := randShares[idx1][:8] + randNID := randShares[idx1][:namespace.NamespaceSize] shares, err := getter.GetSharesByNamespace(context.Background(), root, randNID) require.NoError(t, err) @@ -112,6 +105,21 @@ func TestService_GetSharesByNamespace(t *testing.T) { assert.Equal(t, randShares[idx2], flattened[1]) } }) + t.Run("last two rows of a 4x4 square that have the same namespace ID have valid NMT proofs", func(t *testing.T) { + squareSize := 4 + totalShares := squareSize * squareSize + getter, bServ := EmptyGetter() + randShares := share.RandShares(t, totalShares) + lastNID := randShares[totalShares-1][:namespace.NamespaceSize] + for i := totalShares / 2; i < totalShares; i++ { + copy(randShares[i][:namespace.NamespaceSize], lastNID) + } + root := availability_test.FillBS(t, bServ, randShares) + + shares, err := getter.GetSharesByNamespace(context.Background(), root, lastNID) + require.NoError(t, err) + require.NoError(t, shares.Verify(root, lastNID)) + }) } } @@ -131,9 +139,9 @@ func TestGetShares(t *testing.T) { func TestService_GetSharesByNamespaceNotFound(t *testing.T) { getter, root := GetterWithRandSquare(t, 1) - root.RowsRoots = nil + root.RowRoots = nil - _, err := getter.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) + _, err := getter.GetSharesByNamespace(context.Background(), root, namespace.RandomNamespace().Bytes()) assert.ErrorIs(t, err, share.ErrNamespaceNotFound) } @@ -150,8 +158,8 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { t := &testing.T{} getter, root := GetterWithRandSquare(t, tt.amountShares) - randNID := root.RowsRoots[(len(root.RowsRoots)-1)/2][:8] - root.RowsRoots[(len(root.RowsRoots) / 2)] = root.RowsRoots[(len(root.RowsRoots)-1)/2] + randNID := root.RowRoots[(len(root.RowRoots)-1)/2][:8] + root.RowRoots[(len(root.RowRoots) / 2)] = root.RowRoots[(len(root.RowRoots)-1)/2] b.ResetTimer() for i := 0; i < b.N; i++ { _, err := getter.GetSharesByNamespace(context.Background(), root, randNID) @@ -160,168 +168,3 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { }) } } - -func TestSharesRoundTrip(t *testing.T) { - getter, store := EmptyGetter() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - var pb tmproto.Block - err := json.Unmarshal([]byte(sampleBlock), &pb) - require.NoError(t, err) - - b, err := core.BlockFromProto(&pb) - require.NoError(t, err) - - namespace, err := hex.DecodeString("00001337BEEF0000") - require.NoError(t, err) - namespaceBefore, err := hex.DecodeString("0000000000000123") - require.NoError(t, err) - namespaceAfter, err := hex.DecodeString("1234000000000123") - require.NoError(t, err) - - type testCase struct { - name string - messages [][]byte - namespaces [][]byte - } - - cases := []testCase{ - { - "original test case", - [][]byte{b.Data.Blobs[0].Data}, - [][]byte{namespace}}, - { - "one short message", - [][]byte{{1, 2, 3, 4}}, - [][]byte{namespace}}, - { - "one short before other namespace", - [][]byte{{1, 2, 3, 4}, {4, 5, 6, 7}}, - [][]byte{namespace, namespaceAfter}, - }, - { - "one short after other namespace", - [][]byte{{1, 2, 3, 4}, {4, 5, 6, 7}}, - [][]byte{namespaceBefore, namespace}, - }, - { - "two short messages", - [][]byte{{1, 2, 3, 4}, {4, 5, 6, 7}}, - [][]byte{namespace, namespace}, - }, - { - "two short messages before other namespace", - [][]byte{{1, 2, 3, 4}, {4, 5, 6, 7}, {7, 8, 9}}, - [][]byte{namespace, namespace, namespaceAfter}, - }, - { - "two short messages after other namespace", - [][]byte{{1, 2, 3, 4}, {4, 5, 6, 7}, {7, 8, 9}}, - [][]byte{namespaceBefore, namespace, namespace}, - }, - } - randBytes := func(n int) []byte { - bs := make([]byte, n) - _, _ = rand.Read(bs) - return bs - } - for i := 128; i < 4192; i += mrand.Intn(256) { - l := strconv.Itoa(i) - cases = append(cases, testCase{ - "one " + l + " bytes message", - [][]byte{randBytes(i)}, - [][]byte{namespace}, - }) - cases = append(cases, testCase{ - "one " + l + " bytes before other namespace", - [][]byte{randBytes(i), randBytes(1 + mrand.Intn(i))}, - [][]byte{namespace, namespaceAfter}, - }) - cases = append(cases, testCase{ - "one " + l + " bytes after other namespace", - [][]byte{randBytes(1 + mrand.Intn(i)), randBytes(i)}, - [][]byte{namespaceBefore, namespace}, - }) - cases = append(cases, testCase{ - "two " + l + " bytes messages", - [][]byte{randBytes(i), randBytes(i)}, - [][]byte{namespace, namespace}, - }) - cases = append(cases, testCase{ - "two " + l + " bytes messages before other namespace", - [][]byte{randBytes(i), randBytes(i), randBytes(1 + mrand.Intn(i))}, - [][]byte{namespace, namespace, namespaceAfter}, - }) - cases = append(cases, testCase{ - "two " + l + " bytes messages after other namespace", - [][]byte{randBytes(1 + mrand.Intn(i)), randBytes(i), randBytes(i)}, - [][]byte{namespaceBefore, namespace, namespace}, - }) - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - // prepare data - b.Data.Blobs = make([]core.Blob, len(tc.messages)) - b.SquareSize = 16 - var msgsInNamespace [][]byte - require.Equal(t, len(tc.namespaces), len(tc.messages)) - for i := range tc.messages { - b.Data.Blobs[i] = core.Blob{NamespaceID: tc.namespaces[i], Data: tc.messages[i]} - if bytes.Equal(tc.namespaces[i], namespace) { - msgsInNamespace = append(msgsInNamespace, tc.messages[i]) - } - } - - // TODO: set useShareIndexes to true. This requires updating the - // transaction data in this test to include share indexes. - shares, err := appshares.Split(b.Data, false) - if err != nil { - t.Fatal(err) - } - - // test round trip using only encoding, without IPLD - { - myShares := make([][]byte, 0) - for _, sh := range shares { - if bytes.Equal(namespace, sh[:8]) { - myShares = append(myShares, sh) - } - } - blobs, err := appshares.ParseBlobs(myShares) - require.NoError(t, err) - assert.Len(t, blobs, len(msgsInNamespace)) - for i := range blobs { - assert.Equal(t, msgsInNamespace[i], blobs[i].Data) - } - } - - // test full round trip - with IPLD + decoding shares - { - extSquare, err := share.AddShares(ctx, appshares.ToBytes(shares), store) - require.NoError(t, err) - - dah := da.NewDataAvailabilityHeader(extSquare) - shares, err := getter.GetSharesByNamespace(ctx, &dah, namespace) - require.NoError(t, err) - require.NoError(t, shares.Verify(&dah, namespace)) - require.NotEmpty(t, shares) - - blobs, err := appshares.ParseBlobs(shares.Flatten()) - require.NoError(t, err) - assert.Len(t, blobs, len(msgsInNamespace)) - for i := range blobs { - assert.Equal(t, namespace, []byte(blobs[i].NamespaceID)) - assert.Equal(t, msgsInNamespace[i], blobs[i].Data) - } - } - }) - } -} - -// this is a sample block -// -//go:embed "testdata/sample-block.json" -var sampleBlock string diff --git a/share/availability/light/testdata/sample-block.json b/share/availability/light/testdata/sample-block.json deleted file mode 100755 index 2bb8d35bfb..0000000000 --- a/share/availability/light/testdata/sample-block.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "header": { - "version": { - "block": 11 - }, - "chain_id": "7sUWGE", - "height": 12, - "time": "2023-01-19T01:41:20.109585974Z", - "last_block_id": { - "hash": "MfZsPUTgVOBRak+3xnKdwWEr2NQ1v095T4BYaWcdYTs=", - "part_set_header": { - "total": 1, - "hash": "/qyxxHNcuOyYBcaGw90PqAa2GdFErMHkkbH66BDI1hI=" - } - }, - "last_commit_hash": "sQXqALyCQ+tHFKOjfQhB3+lQuud0d3C2ohuOgrO6YX4=", - "data_hash": "Pu8CIJqgqCFYkaSvfeS5jvb6+ThXtJkXeRDZPnb1auM=", - "validators_hash": "X7yBLGyFWnZvyakBElaaaqyV0iCSNTINfm+puFxcX/g=", - "next_validators_hash": "X7yBLGyFWnZvyakBElaaaqyV0iCSNTINfm+puFxcX/g=", - "consensus_hash": "BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=", - "app_hash": "pqd8GP5/icLOzlysLtq9IupqfLmoxNVySLWNarJssbE=", - "last_results_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", - "evidence_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", - "proposer_address": "oFwwfI48D9wGpICbXtGZNlx6xOI=" - }, - "data": { - "txs": [ - "CpsCCnsKeQoTL2Jsb2IuTXNnUGF5Rm9yQmxvYhJiCi1jb3Ntb3Mxemdwamp4bWZhNm00ZjU5cTJsNjgzOGp1c2RjcmZ4eHc1ajI4ZG0SCF62zWQ6Pw9TGgLBGyIgo5vwRkbYLo3+Xm1IpV8weKHdFYfqkfDyt8a1vfthQQJCAQASWgpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA64OVjUldXAMb1uSZqrBNuYaatsk+CMGyQKNzY2vM5T+EgQKAggBEggQgIDpg7HeFhpAM88quyAFPvk6/4fuiolAJf3SmOLNv471av8uir6yHaJPdsusvOUQ6gK+/gwoksZ2Lytk48k3Gf4F+tcJO06ZBxIBBBoESU5EWA==" - ], - "blobs": [ - { - "namespace_id": "XrbNZDo/D1M=", - "data": "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" - } - ], - "square_size": 4, - "hash": "Pu8CIJqgqCFYkaSvfeS5jvb6+ThXtJkXeRDZPnb1auM=" - }, - "evidence": { - "evidence": [] - }, - "last_commit": { - "height": 11, - "block_id": { - "hash": "MfZsPUTgVOBRak+3xnKdwWEr2NQ1v095T4BYaWcdYTs=", - "part_set_header": { - "total": 1, - "hash": "/qyxxHNcuOyYBcaGw90PqAa2GdFErMHkkbH66BDI1hI=" - } - }, - "signatures": [ - { - "block_id_flag": 2, - "validator_address": "oFwwfI48D9wGpICbXtGZNlx6xOI=", - "timestamp": "2023-01-19T01:41:20.109585974Z", - "signature": "mPRPdLbp8cQcZ2w6CmWlmE+4lVzX9YgxzxOBp7lYTT8jYmehJC62sC/Nt/X/ezVW7u9OCXZjGG2oIDYmabKsAA==" - } - ] - } -} \ No newline at end of file diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 9ca481d552..4e673ed8b8 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -5,13 +5,13 @@ import ( "errors" "fmt" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/go-fraud" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -118,7 +118,7 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { if header.Height() != int64(p.BlockHeight) { return errors.New("fraud: incorrect block height") } - merkleRowRoots := header.DAH.RowsRoots + merkleRowRoots := header.DAH.RowRoots merkleColRoots := header.DAH.ColumnRoots if len(merkleRowRoots) != len(merkleColRoots) { // NOTE: This should never happen as callers of this method should not feed it with a @@ -157,7 +157,7 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { } odsWidth := uint64(len(merkleRowRoots) / 2) - codec := appconsts.DefaultCodec() + codec := share.DefaultRSMT2DCodec() // rebuild a row or col. rebuiltShares, err := codec.Decode(shares) @@ -172,11 +172,19 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { tree := wrapper.NewErasuredNamespacedMerkleTree(odsWidth, uint(p.Index)) for _, share := range rebuiltShares { - tree.Push(share) + err = tree.Push(share) + if err != nil { + return err + } + } + + expectedRoot, err := tree.Root() + if err != nil { + return err } // comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block. - if bytes.Equal(tree.Root(), root) { + if bytes.Equal(expectedRoot, root) { return errors.New("fraud: invalid proof: recomputed Merkle root matches the DAH's row/column root") } diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 6604ea1e17..6e413b17a2 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -34,7 +34,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { // get an arbitrary row row := uint(squareSize / 2) rowShares := eds.Row(row) - rowRoot := dah.RowsRoots[row] + rowRoot := dah.RowRoots[row] shareProofs, err := GetProofsForShares(ctx, bServ, ipld.MustCidFromNamespacedSha256(rowRoot), rowShares) require.NoError(t, err) diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index 67a6ee54c2..b9c8ef414f 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -36,7 +36,7 @@ func NewErrByzantine( errByz *rsmt2d.ErrByzantineData, ) *ErrByzantine { root := [][][]byte{ - dah.RowsRoots, + dah.RowRoots, dah.ColumnRoots, }[errByz.Axis][errByz.Index] sharesWithProof, err := GetProofsForShares( diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go index 501f4b40d9..9cffe6eb18 100644 --- a/share/eds/byzantine/share_proof_test.go +++ b/share/eds/byzantine/share_proof_test.go @@ -31,7 +31,7 @@ func TestGetProof(t *testing.T) { var tests = []struct { roots [][]byte }{ - {dah.RowsRoots}, + {dah.RowRoots}, {dah.ColumnRoots}, } diff --git a/share/eds/eds.go b/share/eds/eds.go index b183731a18..4e96fd684e 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -20,6 +20,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -219,11 +220,12 @@ func getQuadrantCells(eds *rsmt2d.ExtendedDataSquare, i, j uint) [][]byte { // prependNamespace adds the namespace to the passed share if in the first quadrant, // otherwise it adds the ParitySharesNamespace to the beginning. func prependNamespace(quadrant int, share []byte) []byte { + namespacedShare := make([]byte, 0, appconsts.NamespaceSize+appconsts.ShareSize) switch quadrant { case 0: - return append(share[:ipld.NamespaceSize], share...) + return append(append(namespacedShare, share[:ipld.NamespaceSize]...), share...) case 1, 2, 3: - return append(appconsts.ParitySharesNamespaceID, share...) + return append(append(namespacedShare, namespace.ParitySharesNamespace.Bytes()...), share...) default: panic("invalid quadrant") } diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 5eac847ac2..8df05d7d53 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -14,9 +14,11 @@ import ( carv1 "github.com/ipld/go-car" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/rand" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -30,26 +32,47 @@ var exampleRoot string var f embed.FS func TestQuadrantOrder(t *testing.T) { - // TODO: add more test cases - nID := []byte{0, 0, 0, 0, 0, 0, 0, 0} - parity := append(appconsts.ParitySharesNamespaceID, nID...) //nolint - doubleNID := append(nID, nID...) //nolint - result, _ := rsmt2d.ComputeExtendedDataSquare([][]byte{ - append(nID, 1), append(nID, 2), - append(nID, 3), append(nID, 4), - }, rsmt2d.NewRSGF8Codec(), rsmt2d.NewDefaultTree) - // {{1}, {2}, {7}, {13}}, - // {{3}, {4}, {13}, {31}}, - // {{5}, {14}, {19}, {41}}, - // {{9}, {26}, {47}, {69}}, - require.Equal(t, - [][]byte{ - append(doubleNID, 1), append(doubleNID, 2), append(doubleNID, 3), append(doubleNID, 4), - append(parity, 7), append(parity, 13), append(parity, 13), append(parity, 31), - append(parity, 5), append(parity, 14), append(parity, 9), append(parity, 26), - append(parity, 19), append(parity, 41), append(parity, 47), append(parity, 69), - }, quadrantOrder(result), - ) + testCases := []struct { + name string + squareSize int + }{ + {"smol", 2}, + {"still smol", 8}, + {"default mainnet", appconsts.DefaultGovMaxSquareSize}, + {"max", appconsts.MaxSquareSize}, + } + + testShareSize := 64 + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + shares := make([][]byte, tc.squareSize*tc.squareSize) + + for i := 0; i < tc.squareSize*tc.squareSize; i++ { + shares[i] = rand.Bytes(testShareSize) + } + + eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), rsmt2d.NewDefaultTree) + require.NoError(t, err) + + res := quadrantOrder(eds) + for _, s := range res { + require.Len(t, s, testShareSize+namespace.NamespaceSize) + } + + for q := 0; q < 4; q++ { + for i := 0; i < tc.squareSize; i++ { + for j := 0; j < tc.squareSize; j++ { + resIndex := q*tc.squareSize*tc.squareSize + i*tc.squareSize + j + edsRow := q/2*tc.squareSize + i + edsCol := (q%2)*tc.squareSize + j + + assert.Equal(t, res[resIndex], prependNamespace(q, eds.Row(uint(edsRow))[edsCol])) + } + } + } + }) + } } func TestWriteEDS(t *testing.T) { @@ -164,7 +187,7 @@ func TestReadEDS(t *testing.T) { loaded, err := ReadEDS(context.Background(), f, dah.Hash()) require.NoError(t, err, "error reading EDS from file") - require.Equal(t, dah.RowsRoots, loaded.RowRoots()) + require.Equal(t, dah.RowRoots, loaded.RowRoots()) require.Equal(t, dah.ColumnRoots, loaded.ColRoots()) } diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 5d3c61cabb..b2dcc4ff7a 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -63,11 +63,11 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader ctx, span := tracer.Start(ctx, "retrieve-square") defer span.End() span.SetAttributes( - attribute.Int("size", len(dah.RowsRoots)), + attribute.Int("size", len(dah.RowRoots)), attribute.String("data_hash", dah.String()), ) - log.Debugw("retrieving data square", "data_hash", dah.String(), "size", len(dah.RowsRoots)) + log.Debugw("retrieving data square", "data_hash", dah.String(), "size", len(dah.RowRoots)) ses, err := r.newSession(ctx, dah) if err != nil { return nil, err @@ -121,7 +121,7 @@ type retrievalSession struct { // newSession creates a new retrieval session and kicks off requesting process. func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHeader) (*retrievalSession, error) { - size := len(dah.RowsRoots) + size := len(dah.RowRoots) treeFn := func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index) return &tree @@ -169,12 +169,12 @@ func (rs *retrievalSession) Reconstruct(ctx context.Context) (*rsmt2d.ExtendedDa defer span.End() // and try to repair with what we have - err := rs.square.Repair(rs.dah.RowsRoots, rs.dah.ColumnRoots) + err := rs.square.Repair(rs.dah.RowRoots, rs.dah.ColumnRoots) if err != nil { span.RecordError(err) return nil, err } - log.Infow("data square reconstructed", "data_hash", rs.dah.String(), "size", len(rs.dah.RowsRoots)) + log.Infow("data square reconstructed", "data_hash", rs.dah.String(), "size", len(rs.dah.RowRoots)) close(rs.squareDn) return rs.square, nil } diff --git a/share/eds/retriever_quadrant.go b/share/eds/retriever_quadrant.go index 8b8037ce85..3d616e9cd4 100644 --- a/share/eds/retriever_quadrant.go +++ b/share/eds/retriever_quadrant.go @@ -52,7 +52,7 @@ type quadrant struct { func newQuadrants(dah *da.DataAvailabilityHeader) []*quadrant { // combine all the roots into one slice, so they can be easily accessible by index daRoots := [][][]byte{ - dah.RowsRoots, + dah.RowRoots, dah.ColumnRoots, } // create a quadrant slice for each source(row;col) diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 1c98c133c4..e90216de13 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -75,7 +75,7 @@ func TestRetriever_ByzantineError(t *testing.T) { require.NoError(t, err) // corrupt shares so that eds erasure coding does not match - copy(shares[14][8:], shares[15][8:]) + copy(shares[14][share.NamespaceSize:], shares[15][share.NamespaceSize:]) // import corrupted eds batchAdder := ipld.NewNmtNodeAdder(ctx, bserv, ipld.MaxSizeBatchOption(width*2)) diff --git a/share/eds/store.go b/share/eds/store.go index 8222d04dd6..f01e96a24b 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -280,7 +280,7 @@ func dahFromCARHeader(carHeader *carv1.CarHeader) *header.DataAvailabilityHeader rootBytes = append(rootBytes, ipld.NamespacedSha256FromCID(root)) } return &header.DataAvailabilityHeader{ - RowsRoots: rootBytes[:rootCount/2], + RowRoots: rootBytes[:rootCount/2], ColumnRoots: rootBytes[rootCount/2:], } } diff --git a/share/eds/testdata/example-root.json b/share/eds/testdata/example-root.json index b687e3cbeb..999d6301b6 100644 --- a/share/eds/testdata/example-root.json +++ b/share/eds/testdata/example-root.json @@ -1,22 +1,22 @@ { "row_roots": [ -"A3I8MUKAXaEf7JDjUxllL5CGD0/uS8k2l3KpbN+u5fa3tGL1h9FSqZhCJFcZhzAT", -"IA74YwcC1tFMkQfD9sE2NO7hwDtjo37O1KiiptYPCgAQWQnv/G7bxu6mUJ2s3Ljk", -"ZskuRtu6YfK9pTqLAOsfLXbvjrvDD+80xoj/850Z4YGvmAaRH5vFXraZNWgEQNyr", -"w9Tz/Q7t/JfX7McKTWvNjhqi784y+nGCYXrQtu4jy90kx0irPJcVTXTQ7v6Xnjdf", -"/////////////////////1MCHZjf0ySoNlpt+h8Vb6QFm7BHYJXI14SMiNRgwA0K", -"/////////////////////2dOgrZG2ZT+KIrfjoPmTcGpqXdsdFZE6ZHcF0l+fL6i", -"/////////////////////7OJEeOYA6VsagglTnpIysAtyK3m3u4d8Zd66FuqX0+M", -"/////////////////////+hYZyJ10gx7Brp05g/MLtSV4z7SLLwGcMsXZI/Fgw0J" +"AAAAAAAAAAAAAAAAAAAAAAAAABPYEuDlO9Dz69oAAAAAAAAAAAAAAAAAAAAAAAAAMcklN0h38T4b/UBC/Cmr5YWmjmmxvi1e35vZBW14b8gDHBoTFVvY6H4J", +"AAAAAAAAAAAAAAAAAAAAAAAAADxyZecUZD41W5IAAAAAAAAAAAAAAAAAAAAAAAAAh8vQUZ38PaWyeUs7dQhphIuRIKiGaTr4KFwEhMRhejTd6/4NHdnKTDyY", +"AAAAAAAAAAAAAAAAAAAAAAAAAKDQatbQSwQ9uJsAAAAAAAAAAAAAAAAAAAAAAAAArtdqXCSsM1OlVCRZqqfZDnEO9eC5cwlgy5MQHb2g4NLr7nZYTruiOoz7", +"AAAAAAAAAAAAAAAAAAAAAAAAAMeUhM8LZBo9sWwAAAAAAAAAAAAAAAAAAAAAAAAA8PtvJpbDc4APKOK6MT1k61HuQXwauWw3nFWwr9pSljiYMv6jjjdLDF8o", +"/////////////////////////////////////////////////////////////////////////////xnHmhDh4Y8vfJrgewAcvLWpvI5XOyATj1IQDkCwvIEh", +"/////////////////////////////////////////////////////////////////////////////+qngp0AfoykfXwsMBukRtYxNA/bzW0+F3J7Q/+S1YZJ", +"/////////////////////////////////////////////////////////////////////////////4WNPrME/2MLrIZgAUoKaVx2GzJqDcYGrBg+sudPKUDy", +"/////////////////////////////////////////////////////////////////////////////6HdebpaHl7iTpLvmuPvtQNnkHfNOPyEhahxbVnIB2d1" ], "column_roots": [ -"A3I8MUKAXaHD1PP9Du38l5n9/iXzaDzWlVrLnsRE5zp+rEeyH2A0is+czwU/aHUn", -"D7uVznpBCw/S2nVDrzgGx9Ditcfzy/cL2Zdn+MBV31zeOgzCv+zXA0QP3SQRWXlb", -"FQDYNIesVPTUn9q9Ybq0VqDgprQbzKsMGrX+7kTlnbdOu434iOmx4P+vZVGjXAsm", -"H+yQ41MZZS/X7McKTWvNjoZId6AiHkxnGRsmYKuh4EvQCy+eEj19Q6Z5eQoMGWZL", -"/////////////////////1wS3guBpJRGNMLQMkEwgVOuXrTG3gUwJwI+nybYxTRF", -"/////////////////////1Eg48mqxsQejtwUIJkwrjEYCPvDYCRYm+dPXVitBCjl", -"/////////////////////zDz519rhl2FfQp6S3hR887dRSi6zN+Qs4PwIFdRqbxP", -"/////////////////////zARSBQBFxvGNpLt8I1qtbBOyEfFOXD4V/beDdU1kXPm" +"AAAAAAAAAAAAAAAAAAAAAAAAABPYEuDlO9Dz69oAAAAAAAAAAAAAAAAAAAAAAAAAx5SEzwtkGj2xbESyOeamsjGWUBQdAQoiSl+rMtNMo1wEtfGQnFS/g+K+", +"AAAAAAAAAAAAAAAAAAAAAAAAAC3uK6nhCxHTfBwAAAAAAAAAAAAAAAAAAAAAAAAA1fxnqHyO6qV39pcUQ8MuTfJ7RBhbSVWf0aamUP27KRY0II55oJoY6Ng6", +"AAAAAAAAAAAAAAAAAAAAAAAAAC6DkYeeBY/kKvAAAAAAAAAAAAAAAAAAAAAAAAAA47rxk8hoCnWGM+CX47TlYWBeE2unvRhA/j3EvHdxeL1rFRkaYfAd5eg7", +"AAAAAAAAAAAAAAAAAAAAAAAAADHJJTdId/E+G/0AAAAAAAAAAAAAAAAAAAAAAAAA8PtvJpbDc4APKAk5QPSH59HECE2sf/CDLKAZJjWo9DD4sLXJQ4jTZoH6", +"/////////////////////////////////////////////////////////////////////////////4lKCT3K11RnNIuLNfY+SfDZCYAE2iW0hjQHIVBpoN0q", +"/////////////////////////////////////////////////////////////////////////////1NpYcgayEVenbFeEO5LJ1j1/1sD+PvZWHDv+jqT1dLR", +"/////////////////////////////////////////////////////////////////////////////8FOWVuCU0rTzUW9tP2R47RmTBvwXX8ycKrMhgKEi1xa", +"/////////////////////////////////////////////////////////////////////////////7K5SoZ3HF5QgPvIXpKSr9eT4Xfiokc3PUMmXE4pBDTf" ] } \ No newline at end of file diff --git a/share/eds/testdata/example.car b/share/eds/testdata/example.car index 487bca4653922f3b43ea1e6639470f051c7a2534..4d33c0ef33300d68c8f3a05f7e09c65409c30b9d 100644 GIT binary patch literal 74051 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
    +WMn=G?azFh_o`B4HP{apfoD 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%0ATxWzIMRCk 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 dFr7!jboDOM*g1Mj} diff --git a/share/empty.go b/share/empty.go index 6b5d7bb1e8..b5a8b11041 100644 --- a/share/empty.go +++ b/share/empty.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "math" "github.com/ipfs/go-blockservice" @@ -22,8 +21,7 @@ var ( func init() { // compute empty block EDS and DAH for it shares := emptyDataSquare() - squareSize := uint64(math.Sqrt(float64(appconsts.DefaultMinSquareSize))) - eds, err := da.ExtendShares(squareSize, shares) + eds, err := da.ExtendShares(shares) if err != nil { panic(fmt.Errorf("failed to create empty EDS: %w", err)) } @@ -51,7 +49,8 @@ func EmptyRoot() *Root { // redundant storing of empty block data so that it is only stored once and returned // upon request for a block with an empty data square. Ref: header/constructors.go#L56 func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockService) (*rsmt2d.ExtendedDataSquare, error) { - return AddShares(ctx, emptyDataSquare(), bServ) + shares := emptyDataSquare() + return AddShares(ctx, shares, bServ) } // EmptyExtendedDataSquare returns the EDS of the empty block data square. @@ -61,5 +60,6 @@ func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { // emptyDataSquare returns the minimum size data square filled with tail padding. func emptyDataSquare() [][]byte { - return shares.ToBytes(shares.TailPaddingShares(appconsts.MinShareCount)) + result := shares.TailPaddingShares(appconsts.MinShareCount) + return shares.ToBytes(result) } diff --git a/share/get_test.go b/share/get_test.go index 0ea83fd178..8eafe84cd8 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -75,7 +75,7 @@ func TestBlockRecovery(t *testing.T) { t.Run(tc.name, func(t *testing.T) { squareSize := utils.SquareSize(len(tc.shares)) - eds, err := rsmt2d.ComputeExtendedDataSquare(tc.shares, rsmt2d.NewRSGF8Codec(), wrapper.NewConstructor(squareSize)) + eds, err := rsmt2d.ComputeExtendedDataSquare(tc.shares, DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize)) require.NoError(t, err) // calculate roots using the first complete square @@ -88,7 +88,7 @@ func TestBlockRecovery(t *testing.T) { rdata := removeRandShares(flat, tc.d) eds, err = rsmt2d.ImportExtendedDataSquare( rdata, - rsmt2d.NewRSGF8Codec(), + DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize), ) require.NoError(t, err) @@ -101,7 +101,7 @@ func TestBlockRecovery(t *testing.T) { } assert.NoError(t, err) - reds, err := rsmt2d.ImportExtendedDataSquare(rdata, rsmt2d.NewRSGF8Codec(), wrapper.NewConstructor(squareSize)) + reds, err := rsmt2d.ImportExtendedDataSquare(rdata, DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize)) require.NoError(t, err) // check that the squares are equal assert.Equal(t, ExtractEDS(eds), ExtractEDS(reds)) @@ -116,7 +116,7 @@ func Test_ConvertEDStoShares(t *testing.T) { // compute extended square eds, err := rsmt2d.ComputeExtendedDataSquare( shares, - rsmt2d.NewRSGF8Codec(), + DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(squareWidth)), ) require.NoError(t, err) diff --git a/share/getter.go b/share/getter.go index 163741f925..fa2cf5f055 100644 --- a/share/getter.go +++ b/share/getter.go @@ -57,7 +57,7 @@ type NamespacedRow struct { // Verify validates NamespacedShares by checking every row with nmt inclusion proof. func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { originalRoots := make([][]byte, 0) - for _, row := range root.RowsRoots { + for _, row := range root.RowRoots { if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { originalRoots = append(originalRoots, row) } diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 8751f49da0..c9bf82031a 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" @@ -136,7 +137,7 @@ func TestStoreGetter(t *testing.T) { assert.Len(t, shares.Flatten(), 2) // nid not found - nID = make([]byte, 8) + nID = make([]byte, namespace.NamespaceSize) _, err = sg.GetSharesByNamespace(ctx, &dah, nID) require.ErrorIs(t, err, share.ErrNamespaceNotFound) @@ -213,7 +214,7 @@ func TestIPLDGetter(t *testing.T) { assert.Len(t, shares.Flatten(), 2) // nid not found - nID = make([]byte, 8) + nID = make([]byte, namespace.NamespaceSize) emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, nID) require.ErrorIs(t, err, share.ErrNamespaceNotFound) require.Nil(t, emptyShares) @@ -239,8 +240,16 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData randShares := share.RandShares(t, n) idx1 := (n - 1) / 2 idx2 := n / 2 - // make it so that two rows have the same namespace ID - copy(randShares[idx2][:8], randShares[idx1][:8]) + + // Make it so that the two shares in two different rows have a common + // namespace. For example if size=4, the original data square looks like + // this: + // _ _ _ _ + // _ _ _ D + // D _ _ _ + // _ _ _ _ + // where the D shares have a common namespace. + copy(randShares[idx2][:share.NamespaceSize], randShares[idx1][:share.NamespaceSize]) eds, err := rsmt2d.ComputeExtendedDataSquare( randShares, @@ -250,5 +259,5 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData require.NoError(t, err, "failure to recompute the extended data square") dah := da.NewDataAvailabilityHeader(eds) - return eds, randShares[idx1][:8], dah + return eds, randShares[idx1][:share.NamespaceSize], dah } diff --git a/share/getters/ipld.go b/share/getters/ipld.go index 5ad8c92fb2..c2f8f18f9d 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -54,7 +54,7 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots)) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 9d727635a8..db60e0138a 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -15,8 +15,9 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/namespace" libhead "github.com/celestiaorg/go-header" - "github.com/celestiaorg/nmt/namespace" + nmtnamespace "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" @@ -162,10 +163,10 @@ func newStore(t *testing.T) (*eds.Store, error) { return eds.NewStore(tmpDir, ds) } -func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, namespace.ID) { +func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, nmtnamespace.ID) { eds := share.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) - randNID := dah.RowsRoots[(len(dah.RowsRoots)-1)/2][:8] + randNID := dah.RowRoots[(len(dah.RowRoots)-1)/2][:namespace.NamespaceSize] return eds, dah, randNID } diff --git a/share/getters/store.go b/share/getters/store.go index 156c9cf2ee..48b08d9759 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -57,7 +57,7 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) - s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowsRoots)) + s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots)) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound diff --git a/share/getters/utils.go b/share/getters/utils.go index b80478af15..a2b172e51e 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -32,8 +32,8 @@ var ( // filterRootsByNamespace returns the row roots from the given share.Root that contain the passed // namespace ID. func filterRootsByNamespace(root *share.Root, nID namespace.ID) []cid.Cid { - rowRootCIDs := make([]cid.Cid, 0, len(root.RowsRoots)) - for _, row := range root.RowsRoots { + rowRootCIDs := make([]cid.Cid, 0, len(root.RowRoots)) + for _, row := range root.RowRoots { if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) } @@ -68,7 +68,7 @@ func collectSharesByNamespace( // shadow loop variables, to ensure correct values are captured i, rootCID := i, rootCID errGroup.Go(func() error { - row, proof, err := share.GetSharesByNamespace(ctx, bg, rootCID, nID, len(root.RowsRoots)) + row, proof, err := share.GetSharesByNamespace(ctx, bg, rootCID, nID, len(root.RowRoots)) shares[i] = share.NamespacedRow{ Shares: row, Proof: proof, diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 91c413e05b..e5e7d41cd1 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -34,13 +34,13 @@ const ( // nmtCodec is the codec used for leaf and inner nodes of a Namespaced Merkle Tree. nmtCodec = 0x7700 - // sha256Namespace8Flagged is the multihash code used to hash blocks + // sha256NamespaceFlagged is the multihash code used to hash blocks // that contain an NMT node (inner and leaf nodes). - sha256Namespace8Flagged = 0x7701 + sha256NamespaceFlagged = 0x7701 // MaxSquareSize is currently the maximum size supported for unerasured data in // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.DefaultMaxSquareSize + MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize @@ -67,7 +67,7 @@ const ( func init() { // required for Bitswap to hash and verify inbound data correctly - mhcore.Register(sha256Namespace8Flagged, func() hash.Hash { + mhcore.Register(sha256NamespaceFlagged, func() hash.Hash { nh := nmt.NewNmtHasher(sha256.New(), NamespaceSize, true) nh.Reset() return nh @@ -144,7 +144,7 @@ func CidFromNamespacedSha256(namespacedHash []byte) (cid.Cid, error) { if got, want := len(namespacedHash), NmtHashSize; got != want { return cid.Cid{}, fmt.Errorf("invalid namespaced hash length, got: %v, want: %v", got, want) } - buf, err := mh.Encode(namespacedHash, sha256Namespace8Flagged) + buf, err := mh.Encode(namespacedHash, sha256NamespaceFlagged) if err != nil { return cid.Undef, err } @@ -159,7 +159,7 @@ func MustCidFromNamespacedSha256(hash []byte) cid.Cid { panic( fmt.Sprintf("malformed hash: %s, codec: %v", err, - mh.Codes[sha256Namespace8Flagged]), + mh.Codes[sha256NamespaceFlagged]), ) } return cidFromHash @@ -172,7 +172,7 @@ func Translate(dah *da.DataAvailabilityHeader, row, col int) (cid.Cid, int) { return MustCidFromNamespacedSha256(dah.ColumnRoots[col]), row } - return MustCidFromNamespacedSha256(dah.RowsRoots[row]), col + return MustCidFromNamespacedSha256(dah.RowRoots[row]), col } // NamespacedSha256FromCID derives the Namespaced hash from the given CID. diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index cd2f22cb5b..b52a75c150 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -12,8 +12,6 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" - - "github.com/celestiaorg/celestia-node/libs/utils" ) // TestNamespaceFromCID checks that deriving the Namespaced hash from @@ -22,6 +20,7 @@ func TestNamespaceFromCID(t *testing.T) { var tests = []struct { randData [][]byte }{ + // note that the number of shares must be a power of two {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize-appconsts.NamespaceSize)}, {randData: generateRandNamespacedRawData(16, appconsts.NamespaceSize, appconsts.ShareSize-appconsts.NamespaceSize)}, } @@ -29,12 +28,11 @@ func TestNamespaceFromCID(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { // create DAH from rand data - squareSize := utils.SquareSize(len(tt.randData)) - eds, err := da.ExtendShares(squareSize, tt.randData) + eds, err := da.ExtendShares(tt.randData) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(eds) // check to make sure NamespacedHash is correctly derived from CID - for _, row := range dah.RowsRoots { + for _, row := range dah.RowRoots { c, err := CidFromNamespacedSha256(row) require.NoError(t, err) @@ -45,7 +43,8 @@ func TestNamespaceFromCID(t *testing.T) { } } -// generateRandNamespacedRawData returns random namespaced raw data for testing purposes. +// generateRandNamespacedRawData returns random namespaced raw data for testing +// purposes. Note that this does not check that total is a power of two. func generateRandNamespacedRawData(total, nidSize, leafSize uint32) [][]byte { data := make([][]byte, total) for i := uint32(0); i < total; i++ { diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 944b763229..8c5a132fdc 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -14,7 +14,8 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-app/pkg/namespace" + nmtnamespace "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -34,7 +35,7 @@ func TestExchange_RequestND_NotFound(t *testing.T) { t.Cleanup(cancel) root := share.Root{} - nID := make([]byte, 8) + nID := make([]byte, namespace.NamespaceSize) _, err := client.RequestND(ctx, &root, nID, server.host.ID()) require.ErrorIs(t, err, p2p.ErrNotFound) }) @@ -47,7 +48,7 @@ func TestExchange_RequestND_NotFound(t *testing.T) { dah := da.NewDataAvailabilityHeader(eds) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) - randNID := dah.RowsRoots[(len(dah.RowsRoots)-1)/2][:8] + randNID := dah.RowRoots[(len(dah.RowRoots)-1)/2][:namespace.NamespaceSize] _, err := client.RequestND(ctx, &dah, randNID, server.host.ID()) require.ErrorIs(t, err, share.ErrNamespaceNotFound) }) @@ -116,7 +117,7 @@ func (m notFoundGetter) GetEDS( } func (m notFoundGetter) GetSharesByNamespace( - _ context.Context, _ *share.Root, _ namespace.ID, + _ context.Context, _ *share.Root, _ nmtnamespace.ID, ) (share.NamespacedShares, error) { return nil, share.ErrNamespaceNotFound } diff --git a/share/share.go b/share/share.go index 643f0c477a..06f911636d 100644 --- a/share/share.go +++ b/share/share.go @@ -20,7 +20,7 @@ var ( const ( // MaxSquareSize is currently the maximum size supported for unerasured data in // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.DefaultMaxSquareSize + MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize // Size is a system-wide size of a share, including both data and namespace ID diff --git a/share/test_helpers.go b/share/test_helpers.go index f3cb6776f2..c02bfc55ac 100644 --- a/share/test_helpers.go +++ b/share/test_helpers.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" ) @@ -46,23 +47,19 @@ func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { // to be able to take both a *testing.T and a *testing.B. func RandShares(t require.TestingT, total int) []Share { if total&(total-1) != 0 { - t.Errorf("Namespace total must be power of 2: %d", total) + t.Errorf("total must be power of 2: %d", total) t.FailNow() } shares := make([]Share, total) for i := range shares { - nid := make([]byte, Size) - _, err := rand.Read(nid[:NamespaceSize]) + share := make([]byte, Size) + copy(share[:NamespaceSize], namespace.RandomNamespace().Bytes()) + _, err := rand.Read(share[NamespaceSize:]) require.NoError(t, err) - shares[i] = nid + shares[i] = share } sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) - for i := range shares { - _, err := rand.Read(shares[i][NamespaceSize:]) - require.NoError(t, err) - } - return shares } diff --git a/state/integration_test.go b/state/integration_test.go index 25b8e00a2d..e7d2496397 100644 --- a/state/integration_test.go +++ b/state/integration_test.go @@ -17,8 +17,8 @@ import ( "google.golang.org/grpc" "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/testutil/testfactory" - "github.com/celestiaorg/celestia-app/testutil/testnode" + "github.com/celestiaorg/celestia-app/test/util/testfactory" + "github.com/celestiaorg/celestia-app/test/util/testnode" blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/core" From 6e0f3b9467d77493c959f48e5c0f293d877a9400 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 29 May 2023 20:43:00 +0800 Subject: [PATCH 0615/1008] refactor(misc): pretty print namespace.iD and peer.ID (#2255) ## Overview Log `namespace.ID` and `peer.ID` in pretty format. --- das/daser.go | 2 +- share/empty.go | 2 +- share/getter.go | 2 +- share/getters/cascade.go | 3 ++- share/getters/ipld.go | 3 ++- share/getters/shrex.go | 3 +++ share/getters/store.go | 3 ++- share/getters/tee.go | 3 ++- share/getters/utils.go | 3 ++- share/p2p/discovery/discovery.go | 6 +++--- share/p2p/peers/manager.go | 23 ++++++++++++----------- share/p2p/shrexeds/client.go | 6 +++--- share/p2p/shrexeds/server.go | 4 ++-- share/p2p/shrexnd/server.go | 5 +++-- 14 files changed, 39 insertions(+), 29 deletions(-) diff --git a/das/daser.go b/das/daser.go index 8b7e2a145c..d4ad0ee641 100644 --- a/das/daser.go +++ b/das/daser.go @@ -137,7 +137,7 @@ func (d *DASer) Stop(ctx context.Context) error { // save updated checkpoint after sampler and all workers are shut down if err = d.store.store(ctx, newCheckpoint(d.sampler.state.unsafeStats())); err != nil { - log.Errorw("storing checkpoint to disk", "Err", err) + log.Errorw("storing checkpoint to disk", "err", err) } if err = d.store.wait(ctx); err != nil { diff --git a/share/empty.go b/share/empty.go index b5a8b11041..0b7ea2e775 100644 --- a/share/empty.go +++ b/share/empty.go @@ -31,7 +31,7 @@ func init() { minDAH := da.MinDataAvailabilityHeader() if !bytes.Equal(minDAH.Hash(), dah.Hash()) { panic(fmt.Sprintf("mismatch in calculated minimum DAH and minimum DAH from celestia-app, "+ - "expected %X, got %X", minDAH.Hash(), dah.Hash())) + "expected %s, got %s", minDAH.String(), dah.String())) } emptyRoot = &dah diff --git a/share/getter.go b/share/getter.go index fa2cf5f055..f7a7b9c129 100644 --- a/share/getter.go +++ b/share/getter.go @@ -71,7 +71,7 @@ func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { for i, row := range ns { // verify row data against row hash from original root if !row.verify(originalRoots[i], nID) { - return fmt.Errorf("row verification failed: row %d doesn't match original root: %s", i, root.Hash()) + return fmt.Errorf("row verification failed: row %d doesn't match original root: %s", i, root.String()) } } return nil diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 3dcb2a472b..d65b902e81 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -2,6 +2,7 @@ package getters import ( "context" + "encoding/hex" "errors" "go.opentelemetry.io/otel/attribute" @@ -70,7 +71,7 @@ func (cg *CascadeGetter) GetSharesByNamespace( ) (share.NamespacedShares, error) { ctx, span := tracer.Start(ctx, "cascade/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", id.String()), + attribute.String("nid", hex.EncodeToString(id)), )) defer span.End() diff --git a/share/getters/ipld.go b/share/getters/ipld.go index c2f8f18f9d..04a1f4f728 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -2,6 +2,7 @@ package getters import ( "context" + "encoding/hex" "errors" "fmt" "sync" @@ -93,7 +94,7 @@ func (ig *IPLDGetter) GetSharesByNamespace( ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "ipld/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nID", nID.String()), + attribute.String("nid", hex.EncodeToString(nID)), )) defer func() { utils.SetStatusAndEnd(span, err) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index a8dda90045..58a85abc0c 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -2,6 +2,7 @@ package getters import ( "context" + "encoding/hex" "errors" "fmt" "time" @@ -204,6 +205,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( if getErr != nil { log.Debugw("nd: couldn't find peer", "hash", root.String(), + "nid", hex.EncodeToString(id), "err", getErr, "finished (s)", time.Since(start)) sg.metrics.recordNDAttempt(ctx, attempt, false) @@ -245,6 +247,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( } log.Debugw("nd: request failed", "hash", root.String(), + "nid", hex.EncodeToString(id), "peer", peer.String(), "attempt", attempt, "err", getErr, diff --git a/share/getters/store.go b/share/getters/store.go index 48b08d9759..91200b78f3 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -2,6 +2,7 @@ package getters import ( "context" + "encoding/hex" "errors" "fmt" @@ -98,7 +99,7 @@ func (sg *StoreGetter) GetSharesByNamespace( ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "store/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nID", nID.String()), + attribute.String("nid", hex.EncodeToString(nID)), )) defer func() { utils.SetStatusAndEnd(span, err) diff --git a/share/getters/tee.go b/share/getters/tee.go index 41e9073fd4..50e2e1b55d 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -2,6 +2,7 @@ package getters import ( "context" + "encoding/hex" "errors" "fmt" @@ -75,7 +76,7 @@ func (tg *TeeGetter) GetSharesByNamespace( ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "tee/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nID", id.String()), + attribute.String("nid", hex.EncodeToString(id)), )) defer func() { utils.SetStatusAndEnd(span, err) diff --git a/share/getters/utils.go b/share/getters/utils.go index a2b172e51e..c99a7689b8 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -2,6 +2,7 @@ package getters import ( "context" + "encoding/hex" "errors" "fmt" "time" @@ -51,7 +52,7 @@ func collectSharesByNamespace( ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "collect-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", nID.String()), + attribute.String("nid", hex.EncodeToString(nID)), )) defer func() { utils.SetStatusAndEnd(span, err) diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index 23489c3147..01001376d5 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -134,7 +134,7 @@ func (d *Discovery) Discard(id peer.ID) bool { d.connector.Backoff(id) d.set.Remove(id) d.onUpdatedPeers(id, false) - log.Debugw("removed peer from the peer set", "peer", id) + log.Debugw("removed peer from the peer set", "peer", id.String()) if d.set.Size() < d.set.Limit() { // trigger discovery @@ -307,7 +307,7 @@ func (d *Discovery) discover(ctx context.Context) bool { } size := d.set.Size() - log.Debugw("found peer", "peer", peer.ID, "found_amount", size) + log.Debugw("found peer", "peer", peer.ID.String(), "found_amount", size) if size < d.set.Limit() { return nil } @@ -323,7 +323,7 @@ func (d *Discovery) discover(ctx context.Context) bool { // handleDiscoveredPeer adds peer to the internal if can connect or is connected. // Report whether it succeeded. func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo) bool { - logger := log.With("peer", peer.ID) + logger := log.With("peer", peer.ID.String()) switch { case peer.ID == d.host.ID(): d.metrics.observeHandlePeer(ctx, handlePeerSkipSelf) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 313cca8db0..2a7c1fee18 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -128,7 +128,7 @@ func NewManager( func(peerID peer.ID, isAdded bool) { if isAdded { if s.isBlacklistedPeer(peerID) { - log.Debugw("got blacklisted peer from discovery", "peer", peerID) + log.Debugw("got blacklisted peer from discovery", "peer", peerID.String()) return } s.fullNodes.add(peerID) @@ -136,7 +136,7 @@ func NewManager( return } - log.Debugw("removing peer from discovered full nodes", "peer", peerID) + log.Debugw("removing peer from discovered full nodes", "peer", peerID.String()) s.fullNodes.remove(peerID) }) @@ -256,7 +256,7 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, source peerS return func(result result) { log.Debugw("set peer result", "hash", datahash.String(), - "peer", peerID, + "peer", peerID.String(), "source", source, "result", result) m.metrics.observeDoneResult(source, result) @@ -318,7 +318,7 @@ func (m *Manager) subscribeDisconnectedPeers(ctx context.Context, sub event.Subs if connStatus.Connectedness == network.NotConnected { peer := connStatus.Peer if m.fullNodes.has(peer) { - log.Debugw("peer disconnected, removing from full nodes", "peer", peer) + log.Debugw("peer disconnected, removing from full nodes", "peer", peer.String()) m.fullNodes.remove(peer) } } @@ -328,7 +328,7 @@ func (m *Manager) subscribeDisconnectedPeers(ctx context.Context, sub event.Subs // Validate will collect peer.ID into corresponding peer pool func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notification) pubsub.ValidationResult { - logger := log.With("peer", peerID, "hash", msg.DataHash.String()) + logger := log.With("peer", peerID.String(), "hash", msg.DataHash.String()) // messages broadcast from self should bypass the validation with Accept if peerID == m.host.ID() { @@ -390,15 +390,16 @@ func (m *Manager) getOrCreatePool(datahash string) *syncPool { } func (m *Manager) blacklistPeers(reason blacklistPeerReason, peerIDs ...peer.ID) { - log.Debugw("blacklisting peers", - "peers", peerIDs, - "reason", reason) m.metrics.observeBlacklistPeers(reason, len(peerIDs)) - if !m.params.EnableBlackListing { - return - } for _, peerID := range peerIDs { + // blacklisted peers will be logged regardless of EnableBlackListing whether option being is + // enabled, until blacklisting is not properly tested and enabled by default. + log.Debugw("blacklisting peer", "peer", peerID.String(), "reason", reason) + if !m.params.EnableBlackListing { + continue + } + m.fullNodes.remove(peerID) // add peer to the blacklist, so we can't connect to it in the future. err := m.connGater.BlockPeer(peerID) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 7c2591bcbd..387594186e 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -54,7 +54,7 @@ func (c *Client) RequestEDS( if err == nil { return eds, nil } - log.Debugw("client: eds request to peer failed", "peer", peer, "hash", dataHash.String(), "error", err) + log.Debugw("client: eds request to peer failed", "peer", peer.String(), "hash", dataHash.String(), "error", err) if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) return nil, err @@ -70,7 +70,7 @@ func (c *Client) RequestEDS( } if err != p2p.ErrNotFound { log.Warnw("client: eds request to peer failed", - "peer", peer, + "peer", peer.String(), "hash", dataHash.String(), "err", err) } @@ -95,7 +95,7 @@ func (c *Client) doRequest( req := &pb.EDSRequest{Hash: dataHash} // request ODS - log.Debugf("client: requesting ods %s from peer %s", dataHash.String(), to) + log.Debugw("client: requesting ods", "hash", dataHash.String(), "peer", to.String()) _, err = serde.Write(stream, req) if err != nil { stream.Reset() //nolint:errcheck diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index 60e4198842..fffa0e8152 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -70,7 +70,7 @@ func (s *Server) observeRateLimitedRequests() { } func (s *Server) handleStream(stream network.Stream) { - logger := log.With("peer", stream.Conn().RemotePeer()) + logger := log.With("peer", stream.Conn().RemotePeer().String()) logger.Debug("server: handling eds request") s.observeRateLimitedRequests() @@ -91,7 +91,7 @@ func (s *Server) handleStream(stream network.Stream) { stream.Reset() //nolint:errcheck return } - logger = logger.With("hash", hash) + logger = logger.With("hash", hash.String()) ctx, cancel := context.WithTimeout(s.ctx, s.params.HandleRequestTimeout) defer cancel() diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 748d9fbd10..67f64b8393 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -2,6 +2,7 @@ package shrexnd import ( "context" + "encoding/hex" "errors" "fmt" "time" @@ -82,7 +83,7 @@ func (srv *Server) observeRateLimitedRequests() { } func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { - logger := log.With("peer", stream.Conn().RemotePeer()) + logger := log.With("peer", stream.Conn().RemotePeer().String()) logger.Debug("server: handling nd request") srv.observeRateLimitedRequests() @@ -99,7 +100,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre stream.Reset() //nolint:errcheck return } - logger = logger.With("namespaceId", string(req.NamespaceId), "hash", string(req.RootHash)) + logger = logger.With("namespaceId", hex.EncodeToString(req.NamespaceId), "hash", share.DataHash(req.RootHash).String()) logger.Debugw("server: new request") err = stream.CloseRead() From 009d5a95ae89880df6c309e277ea7a700f13d78a Mon Sep 17 00:00:00 2001 From: Rootul P Date: Mon, 29 May 2023 11:43:49 -0400 Subject: [PATCH 0616/1008] test: delete `Test_dataFromSharesBSR` (#2271) Closes #2267 --- api/gateway/share_test.go | 37 -------------------------- api/gateway/testdata/sharesBase64.json | 8 ------ 2 files changed, 45 deletions(-) delete mode 100644 api/gateway/testdata/sharesBase64.json diff --git a/api/gateway/share_test.go b/api/gateway/share_test.go index 423d08682b..16cf606680 100644 --- a/api/gateway/share_test.go +++ b/api/gateway/share_test.go @@ -2,11 +2,8 @@ package gateway import ( _ "embed" - "encoding/base64" - "encoding/json" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" coretypes "github.com/tendermint/tendermint/types" @@ -48,37 +45,3 @@ func Test_dataFromShares(t *testing.T) { require.Equal(t, testData, parsedSSSShares) } - -// sharesBase64JSON is the base64 encoded share data from Blockspace Race -// block height 559108 and namespace e8e5f679bf7116cb. -// -//go:embed "testdata/sharesBase64.json" -var sharesBase64JSON string - -// Test_dataFromSharesBSR reproduces an error that occurred when parsing shares -// on Blockspace Race block height 559108 namespace e8e5f679bf7116cb. -// -// https://github.com/celestiaorg/celestia-app/issues/1816 -func Test_dataFromSharesBSR(t *testing.T) { - t.Skip("skip until sharesBase64JSON is regenerated with v1 compatibility") - - var sharesBase64 []string - err := json.Unmarshal([]byte(sharesBase64JSON), &sharesBase64) - assert.NoError(t, err) - input := decode(sharesBase64) - - _, err = dataFromShares(input) - assert.NoError(t, err) -} - -// decode returns the raw shares from base64Encoded. -func decode(base64Encoded []string) (rawShares [][]byte) { - for _, share := range base64Encoded { - rawShare, err := base64.StdEncoding.DecodeString(share) - if err != nil { - panic(err) - } - rawShares = append(rawShares, rawShare) - } - return rawShares -} diff --git a/api/gateway/testdata/sharesBase64.json b/api/gateway/testdata/sharesBase64.json deleted file mode 100644 index e73d69f420..0000000000 --- a/api/gateway/testdata/sharesBase64.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - "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" -] From 77d511d66e1da7b758003e90af0de739fb07c891 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 30 May 2023 10:35:32 +0200 Subject: [PATCH 0617/1008] fix(share/discovery): memoize discovery trigger and limit FindPeers operation (#2263) Closes https://github.com/celestiaorg/celestia-node/issues/2258 Two cases were possible: * Sometimes, the discovery is not triggered, and memorizing triggers might help * It's an unconfirmed theory, and we are still determining if it fixes anything yet. * We considered a case with @walldiss, but it should not happen. * FindPeers can get stuck sometimes for an indefinite time and so forth stopping the whole discovery. We should stop and restart it. --- share/p2p/discovery/discovery.go | 47 ++++++++++++++------------------ share/p2p/discovery/metrics.go | 27 ++---------------- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index 01001376d5..96eda8bd78 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -28,16 +28,15 @@ const ( // (by default it is 16) eventbusBufSize = 64 - // findPeersStuckWarnDelay is the duration after which discover will log an error message to - // notify that it is stuck. - findPeersStuckWarnDelay = time.Minute + // findPeersTimeout limits the FindPeers operation in time + findPeersTimeout = time.Minute - // defaultRetryTimeout defines time interval between discovery attempts. - defaultRetryTimeout = time.Second + // retryTimeout defines time interval between discovery and advertise attempts. + retryTimeout = time.Second ) -// defaultRetryTimeout defines time interval between discovery attempts. -var discoveryRetryTimeout = defaultRetryTimeout +// discoveryRetryTimeout defines time interval between discovery attempts, needed for tests +var discoveryRetryTimeout = retryTimeout // Discovery combines advertise and discover services and allows to store discovered nodes. // TODO: The code here gets horribly hairy, so we should refactor this at some point @@ -166,7 +165,9 @@ func (d *Discovery) Advertise(ctx context.Context) { } log.Warnw("error advertising", "rendezvous", rendezvousPoint, "err", err) - errTimer := time.NewTimer(time.Minute) + // we don't want retry indefinitely in busy loop + // internal discovery mechanism may need some time before attempts + errTimer := time.NewTimer(retryTimeout) select { case <-errTimer.C: errTimer.Stop() @@ -257,8 +258,7 @@ func (d *Discovery) discover(ctx context.Context) bool { // limit to minimize chances of overreaching the limit wg.SetLimit(int(d.set.Limit())) - // stop discovery when we are done - findCtx, findCancel := context.WithCancel(ctx) + findCtx, findCancel := context.WithTimeout(ctx, findPeersTimeout) defer func() { // some workers could still be running, wait them to finish before canceling findCtx wg.Wait() //nolint:errcheck @@ -271,26 +271,11 @@ func (d *Discovery) discover(ctx context.Context) bool { return false } - ticker := time.NewTicker(findPeersStuckWarnDelay) - defer ticker.Stop() for { - ticker.Reset(findPeersStuckWarnDelay) - // drain all previous ticks from channel - drainChannel(ticker.C) select { - case <-findCtx.Done(): - d.metrics.observeFindPeers(ctx, true, true) - return true - case <-ticker.C: - d.metrics.observeDiscoveryStuck(ctx) - log.Warn("wasn't able to find new peers for long time") - continue case p, ok := <-peers: if !ok { - isEnoughPeers := d.set.Size() >= d.set.Limit() - d.metrics.observeFindPeers(ctx, ctx.Err() != nil, isEnoughPeers) - log.Debugw("discovery channel closed", "find_is_canceled", findCtx.Err() != nil) - return isEnoughPeers + break } peer := p @@ -313,10 +298,18 @@ func (d *Discovery) discover(ctx context.Context) bool { } log.Infow("discovered wanted peers", "amount", size) - findCancel() + findCancel() // stop discovery when we are done return nil }) + + continue + case <-findCtx.Done(): } + + isEnoughPeers := d.set.Size() >= d.set.Limit() + d.metrics.observeFindPeers(ctx, isEnoughPeers) + log.Debugw("discovery finished", "discovered_wanted", isEnoughPeers) + return isEnoughPeers } } diff --git a/share/p2p/discovery/metrics.go b/share/p2p/discovery/metrics.go index c147a2eeeb..b6adbb1984 100644 --- a/share/p2p/discovery/metrics.go +++ b/share/p2p/discovery/metrics.go @@ -13,8 +13,7 @@ import ( ) const ( - discoveryEnougPeersKey = "enough_peers" - discoveryFindCancledKey = "is_canceled" + discoveryEnoughPeersKey = "enough_peers" handlePeerResultKey = "result" handlePeerSkipSelf handlePeerResult = "skip_self" @@ -37,7 +36,6 @@ type handlePeerResult string type metrics struct { peersAmount asyncint64.Gauge discoveryResult syncint64.Counter // attributes: enough_peers[bool],is_canceled[bool] - discoveryStuck syncint64.Counter handlePeerResult syncint64.Counter // attributes: result[string] advertise syncint64.Counter // attributes: failed[bool] peerAdded syncint64.Counter @@ -68,12 +66,6 @@ func initMetrics(d *Discovery) (*metrics, error) { return nil, err } - discoveryStuck, err := meter.SyncInt64().Counter("discovery_lookup_is_stuck", - instrument.WithDescription("indicates discovery wasn't able to find peers for more than 1 min")) - if err != nil { - return nil, err - } - handlePeerResultCounter, err := meter.SyncInt64().Counter("discovery_handler_peer_result", instrument.WithDescription("result handling found peer")) if err != nil { @@ -107,7 +99,6 @@ func initMetrics(d *Discovery) (*metrics, error) { metrics := &metrics{ peersAmount: peersAmount, discoveryResult: discoveryResult, - discoveryStuck: discoveryStuck, handlePeerResult: handlePeerResultCounter, advertise: advertise, peerAdded: peerAdded, @@ -130,7 +121,7 @@ func initMetrics(d *Discovery) (*metrics, error) { return metrics, nil } -func (m *metrics) observeFindPeers(ctx context.Context, canceled, isEnoughPeers bool) { +func (m *metrics) observeFindPeers(ctx context.Context, isEnoughPeers bool) { if m == nil { return } @@ -139,8 +130,7 @@ func (m *metrics) observeFindPeers(ctx context.Context, canceled, isEnoughPeers } m.discoveryResult.Add(ctx, 1, - attribute.Bool(discoveryFindCancledKey, canceled), - attribute.Bool(discoveryEnougPeersKey, isEnoughPeers)) + attribute.Bool(discoveryEnoughPeersKey, isEnoughPeers)) } func (m *metrics) observeHandlePeer(ctx context.Context, result handlePeerResult) { @@ -179,14 +169,3 @@ func (m *metrics) observeOnPeersUpdate(_ peer.ID, isAdded bool) { } m.peerRemoved.Add(ctx, 1) } - -func (m *metrics) observeDiscoveryStuck(ctx context.Context) { - if m == nil { - return - } - if ctx.Err() != nil { - ctx = context.Background() - } - - m.discoveryStuck.Add(ctx, 1) -} From 2496013598373aef411bc0bc37e16a1d736fd2ac Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 30 May 2023 11:03:23 +0200 Subject: [PATCH 0618/1008] feat!(blob): blob module (#2273) Co-authored-by: Hlib Kanunnikov --- Makefile | 2 +- api/gateway/state.go | 12 +- api/rpc/client/client.go | 3 + api/rpc_test.go | 8 + blob/blob.go | 93 +++++++ blob/blob_test.go | 82 +++++++ blob/blobtest/testing.go | 36 +++ blob/helper.go | 109 +++++++++ blob/service.go | 292 ++++++++++++++++++++++ blob/service_test.go | 412 ++++++++++++++++++++++++++++++++ header/headertest/testing.go | 25 ++ nodebuilder/blob/blob.go | 76 ++++++ nodebuilder/blob/mocks/api.go | 122 ++++++++++ nodebuilder/blob/module.go | 28 +++ nodebuilder/default_services.go | 2 + nodebuilder/module.go | 2 + nodebuilder/node.go | 2 + nodebuilder/node/admin.go | 2 +- nodebuilder/rpc/constructors.go | 3 + nodebuilder/state/mocks/api.go | 24 +- nodebuilder/state/state.go | 13 +- nodebuilder/tests/blob_test.go | 131 ++++++++++ share/nid.go | 26 ++ state/core_access.go | 12 +- 24 files changed, 1490 insertions(+), 27 deletions(-) create mode 100644 blob/blob.go create mode 100644 blob/blob_test.go create mode 100644 blob/blobtest/testing.go create mode 100644 blob/helper.go create mode 100644 blob/service.go create mode 100644 blob/service_test.go create mode 100644 nodebuilder/blob/blob.go create mode 100644 nodebuilder/blob/mocks/api.go create mode 100644 nodebuilder/blob/module.go create mode 100644 nodebuilder/tests/blob_test.go create mode 100644 share/nid.go diff --git a/Makefile b/Makefile index 79c45d6597..8f4d1b42a9 100644 --- a/Makefile +++ b/Makefile @@ -152,7 +152,7 @@ pb-gen: ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api openrpc-gen: @echo "--> Generating OpenRPC spec" - @go run ./cmd/docgen fraud header state share das p2p node + @go run ./cmd/docgen fraud header state share das p2p node blob .PHONY: openrpc-gen ## lint-imports: Lint only Go imports. diff --git a/api/gateway/state.go b/api/gateway/state.go index 00a318c5ae..8997f4ae21 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -9,6 +9,9 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/celestia-node/state" ) @@ -137,8 +140,15 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { return } fee := types.NewInt(req.Fee) + + blob := &apptypes.Blob{ + NamespaceId: nID, + Data: data, + ShareVersion: uint32(appconsts.DefaultShareVersion), + } + // perform request - txResp, err := h.state.SubmitPayForBlob(r.Context(), nID, data, fee, req.GasLimit) + txResp, err := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, blob) if err != nil { writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) return diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 783d3f53cd..9e7e577074 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-jsonrpc" "github.com/celestiaorg/celestia-node/api/rpc/perms" + "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -27,6 +28,7 @@ var Modules = map[string]interface{}{ "das": &client.DAS.Internal, "p2p": &client.P2P.Internal, "node": &client.Node.Internal, + "blob": &client.Blob.Internal, } type Client struct { @@ -37,6 +39,7 @@ type Client struct { DAS das.API P2P p2p.API Node node.API + Blob blob.API closer multiClientCloser } diff --git a/api/rpc_test.go b/api/rpc_test.go index f2e088ff1f..898a307389 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -21,6 +21,8 @@ import ( daspkg "github.com/celestiaorg/celestia-node/das" headerpkg "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/blob" + blobMock "github.com/celestiaorg/celestia-node/nodebuilder/blob/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/das" dasMock "github.com/celestiaorg/celestia-node/nodebuilder/das/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -81,6 +83,7 @@ type api struct { DAS das.Module Node node.Module P2P p2p.Module + Blob blob.Module } func TestModulesImplementFullAPI(t *testing.T) { @@ -336,6 +339,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { dasMock.NewMockModule(ctrl), p2pMock.NewMockModule(ctrl), nodeMock.NewMockModule(ctrl), + blobMock.NewMockModule(ctrl), } // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root @@ -348,6 +352,7 @@ func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { srv.RegisterService("das", mockAPI.Das) srv.RegisterService("p2p", mockAPI.P2P) srv.RegisterService("node", mockAPI.Node) + srv.RegisterService("blob", mockAPI.Blob) }) nd := nodebuilder.TestNode(t, node.Full, invokeRPC) // start node @@ -376,6 +381,7 @@ func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, * dasMock.NewMockModule(ctrl), p2pMock.NewMockModule(ctrl), nodeMock.NewMockModule(ctrl), + blobMock.NewMockModule(ctrl), } // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root @@ -388,6 +394,7 @@ func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, * srv.RegisterAuthedService("das", mockAPI.Das, &das.API{}) srv.RegisterAuthedService("p2p", mockAPI.P2P, &p2p.API{}) srv.RegisterAuthedService("node", mockAPI.Node, &node.API{}) + srv.RegisterAuthedService("blob", mockAPI.Blob, &blob.API{}) }) // fx.Replace does not work here, but fx.Decorate does nd := nodebuilder.TestNode(t, node.Full, invokeRPC, fx.Decorate(func() (jwt.Signer, error) { @@ -411,4 +418,5 @@ type mockAPI struct { Das *dasMock.MockModule P2P *p2pMock.MockModule Node *nodeMock.MockModule + Blob *blobMock.MockModule } diff --git a/blob/blob.go b/blob/blob.go new file mode 100644 index 0000000000..434ab220c7 --- /dev/null +++ b/blob/blob.go @@ -0,0 +1,93 @@ +package blob + +import ( + "bytes" + "fmt" + + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" +) + +// Commitment is a Merkle Root of the subtree built from shares of the Blob. +// It is computed by splitting the blob into shares and building the Merkle subtree to be included +// after Submit. +type Commitment []byte + +func (com Commitment) String() string { + return string(com) +} + +// Equal ensures that commitments are the same +func (com Commitment) Equal(c Commitment) bool { + return bytes.Equal(com, c) +} + +// Proof is a collection of nmt.Proofs that verifies the inclusion of the data. +type Proof []*nmt.Proof + +func (p Proof) Len() int { return len(p) } + +// equal is a temporary method that compares two proofs. +// should be removed in BlobService V1. +func (p Proof) equal(input Proof) error { + if p.Len() != input.Len() { + return ErrInvalidProof + } + + for i, proof := range p { + pNodes := proof.Nodes() + inputNodes := input[i].Nodes() + for i, node := range pNodes { + if !bytes.Equal(node, inputNodes[i]) { + return ErrInvalidProof + } + } + + if proof.Start() != input[i].Start() || proof.End() != input[i].End() { + return ErrInvalidProof + } + + if !bytes.Equal(proof.LeafHash(), input[i].LeafHash()) { + return ErrInvalidProof + } + + } + return nil +} + +// Blob represents any application-specific binary data that anyone can submit to Celestia. +type Blob struct { + types.Blob + + Commitment Commitment +} + +// NewBlob constructs a new blob from the provided namespace.ID and data. +func NewBlob(shareVersion uint8, namespace namespace.ID, data []byte) (*Blob, error) { + if len(namespace) != appns.NamespaceSize { + return nil, fmt.Errorf("invalid size of the namespace id. got:%d, want:%d", len(namespace), appns.NamespaceSize) + } + + ns, err := appns.New(namespace[appns.NamespaceVersionSize-1], namespace[appns.NamespaceVersionSize:]) + if err != nil { + return nil, err + } + + blob, err := types.NewBlob(ns, data, shareVersion) + if err != nil { + return nil, err + } + + com, err := types.CreateCommitment(blob) + if err != nil { + return nil, err + } + return &Blob{Blob: *blob, Commitment: com}, nil +} + +// Namespace returns blob's namespace. +func (b *Blob) Namespace() namespace.ID { + return append([]byte{uint8(b.NamespaceVersion)}, b.NamespaceId...) +} diff --git a/blob/blob_test.go b/blob/blob_test.go new file mode 100644 index 0000000000..5b3920ae9e --- /dev/null +++ b/blob/blob_test.go @@ -0,0 +1,82 @@ +package blob + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" + + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + + "github.com/celestiaorg/celestia-node/blob/blobtest" +) + +func TestBlob(t *testing.T) { + appBlobs, err := blobtest.GenerateBlobs([]int{1}, false) + require.NoError(t, err) + blob, err := convertBlobs(appBlobs...) + require.NoError(t, err) + + var test = []struct { + name string + expectedRes func(t *testing.T) + }{ + { + name: "new blob", + expectedRes: func(t *testing.T) { + require.NotEmpty(t, blob) + require.NotEmpty(t, blob[0].Namespace()) + require.NotEmpty(t, blob[0].Data) + require.NotEmpty(t, blob[0].Commitment) + }, + }, + { + name: "compare commitments", + expectedRes: func(t *testing.T) { + comm, err := apptypes.CreateCommitment(&blob[0].Blob) + require.NoError(t, err) + assert.Equal(t, blob[0].Commitment, Commitment(comm)) + }, + }, + { + name: "verify nID", + expectedRes: func(t *testing.T) { + ns, err := appns.New( + blob[0].Namespace()[appns.NamespaceVersionSize-1], + blob[0].Namespace()[appns.NamespaceVersionSize:], + ) + require.NoError(t, err) + require.NoError(t, apptypes.ValidateBlobNamespaceID(ns)) + }, + }, + { + name: "shares to blobs", + expectedRes: func(t *testing.T) { + sh, err := BlobsToShares(blob...) + require.NoError(t, err) + b, err := SharesToBlobs(sh) + require.NoError(t, err) + assert.Equal(t, len(b), 1) + assert.Equal(t, blob[0].Commitment, b[0].Commitment) + }, + }, + } + + for _, tt := range test { + t.Run(tt.name, tt.expectedRes) + } +} + +func convertBlobs(appBlobs ...types.Blob) ([]*Blob, error) { + blobs := make([]*Blob, 0, len(appBlobs)) + for _, b := range appBlobs { + blob, err := NewBlob(b.ShareVersion, append([]byte{b.NamespaceVersion}, b.NamespaceID...), b.Data) + if err != nil { + return nil, err + } + blobs = append(blobs, blob) + } + return blobs, nil +} diff --git a/blob/blobtest/testing.go b/blob/blobtest/testing.go new file mode 100644 index 0000000000..395ef4167a --- /dev/null +++ b/blob/blobtest/testing.go @@ -0,0 +1,36 @@ +package blobtest + +import ( + tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/celestia-app/test/util/testfactory" + + "github.com/celestiaorg/celestia-node/share" +) + +func GenerateBlobs(sizes []int, sameNID bool) ([]types.Blob, error) { + blobs := make([]types.Blob, 0, len(sizes)) + + for _, size := range sizes { + size := rawBlobSize(appconsts.FirstSparseShareContentSize * size) + appBlob := testfactory.GenerateRandomBlob(size) + if !sameNID { + nid, err := share.NewNamespaceV0(tmrand.Bytes(7)) + if err != nil { + return nil, err + } + appBlob.NamespaceVersion = nid[0] + appBlob.NamespaceID = nid[1:] + } + + blobs = append(blobs, appBlob) + } + return blobs, nil +} + +func rawBlobSize(totalSize int) int { + return totalSize - shares.DelimLen(uint64(totalSize)) +} diff --git a/blob/helper.go b/blob/helper.go new file mode 100644 index 0000000000..1fef41dc22 --- /dev/null +++ b/blob/helper.go @@ -0,0 +1,109 @@ +package blob + +import ( + "bytes" + "sort" + + "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/shares" + + "github.com/celestiaorg/celestia-node/share" +) + +// SharesToBlobs takes raw shares and converts them to the blobs. +func SharesToBlobs(rawShares []share.Share) ([]*Blob, error) { + if len(rawShares) == 0 { + return nil, ErrBlobNotFound + } + + appShares := make([]shares.Share, 0, len(rawShares)) + for _, sh := range rawShares { + bShare, err := shares.NewShare(sh) + if err != nil { + return nil, err + } + appShares = append(appShares, *bShare) + } + + shareSequences, err := shares.ParseShares(appShares, true) + if err != nil { + return nil, err + } + + blobs := make([]*Blob, len(shareSequences)) + for i, sequence := range shareSequences { + data, err := sequence.RawData() + if err != nil { + return nil, err + } + if len(data) == 0 { + continue + } + + shareVersion, err := sequence.Shares[0].Version() + if err != nil { + return nil, err + } + + blob, err := NewBlob(shareVersion, sequence.Namespace.Bytes(), data) + if err != nil { + return nil, err + } + blobs[i] = blob + } + return blobs, nil +} + +// BlobsToShares accepts blobs and convert them to the Shares. +func BlobsToShares(blobs ...*Blob) ([]share.Share, error) { + b := make([]types.Blob, len(blobs)) + for i, blob := range blobs { + namespace := blob.Namespace() + b[i] = types.Blob{ + NamespaceVersion: namespace[0], + NamespaceID: namespace[1:], + Data: blob.Data, + ShareVersion: uint8(blob.ShareVersion), + } + } + + sort.Slice(b, func(i, j int) bool { + val := bytes.Compare(b[i].NamespaceID, b[j].NamespaceID) + return val <= 0 + }) + + rawShares, err := shares.SplitBlobs(0, nil, b, false) + if err != nil { + return nil, err + } + return shares.ToBytes(rawShares), nil +} + +const ( + perByteGasTolerance = 2 + pfbGasFixedCost = 80000 +) + +// estimateGas estimates the gas required to pay for a set of blobs in a PFB. +func estimateGas(blobs ...*Blob) uint64 { + totalByteCount := 0 + for _, blob := range blobs { + totalByteCount += len(blob.Data) + appconsts.NamespaceSize + } + variableGasAmount := (appconsts.DefaultGasPerBlobByte + perByteGasTolerance) * totalByteCount + + return uint64(variableGasAmount + pfbGasFixedCost) +} + +// constructAndVerifyBlob reconstruct a Blob from the passed shares and compares commitments. +func constructAndVerifyBlob(sh []share.Share, commitment Commitment) (*Blob, bool, error) { + blob, err := SharesToBlobs(sh) + if err != nil { + return nil, false, err + } + + equal := blob[0].Commitment.Equal(commitment) + return blob[0], equal, nil +} diff --git a/blob/service.go b/blob/service.go new file mode 100644 index 0000000000..0fcf47bffd --- /dev/null +++ b/blob/service.go @@ -0,0 +1,292 @@ +package blob + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/cosmos/cosmos-sdk/types" + logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-app/pkg/shares" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/state" +) + +var ( + ErrBlobNotFound = errors.New("blob: not found") + ErrInvalidProof = errors.New("blob: invalid proof") + + log = logging.Logger("blob") +) + +// TODO(@vgonkivs): remove after bumping celestia-app +// defaultMinGasPrice is the default min gas price. +const defaultMinGasPrice = 0.001 + +type Service struct { + // accessor dials the given celestia-core endpoint to submit blobs. + accessor *state.CoreAccessor + // shareGetter retrieves the EDS to fetch all shares from the requested header. + shareGetter share.Getter + // headerGetter fetches header by the provided height + headerGetter func(context.Context, uint64) (*header.ExtendedHeader, error) +} + +func NewService( + state *state.CoreAccessor, + getter share.Getter, + headerGetter func(context.Context, uint64) (*header.ExtendedHeader, error), +) *Service { + return &Service{ + accessor: state, + shareGetter: getter, + headerGetter: headerGetter, + } +} + +// Submit sends PFB transaction and reports the height in which it was included. +// Allows sending multiple Blobs atomically synchronously. +// Uses default wallet registered on the Node. +func (s *Service) Submit(ctx context.Context, blobs ...*Blob) (uint64, error) { + log.Debugw("submitting blobs", "amount", len(blobs)) + + var ( + gasLimit = estimateGas(blobs...) + fee = int64(defaultMinGasPrice * float64(gasLimit)) + b = make([]*apptypes.Blob, len(blobs)) + ) + + for i, blob := range blobs { + b[i] = &blob.Blob + } + + resp, err := s.accessor.SubmitPayForBlob(ctx, types.NewInt(fee), gasLimit, b...) + if err != nil { + return 0, err + } + return uint64(resp.Height), nil +} + +// Get retrieves all the blobs for given namespaces at the given height by commitment. +func (s *Service) Get(ctx context.Context, height uint64, nID namespace.ID, commitment Commitment) (*Blob, error) { + blob, _, err := s.getByCommitment(ctx, height, nID, commitment) + if err != nil { + return nil, err + } + return blob, nil +} + +// GetProof retrieves all blobs in the given namespaces at the given height by commitment +// and returns their Proof. +func (s *Service) GetProof( + ctx context.Context, + height uint64, + nID namespace.ID, + commitment Commitment, +) (*Proof, error) { + _, proof, err := s.getByCommitment(ctx, height, nID, commitment) + if err != nil { + return nil, err + } + return proof, nil +} + +// GetAll returns all blobs under the given namespaces at the given height. +// GetAll can return blobs and an error in case if some requests failed. +func (s *Service) GetAll(ctx context.Context, height uint64, nIDs ...namespace.ID) ([]*Blob, error) { + header, err := s.headerGetter(ctx, height) + if err != nil { + return nil, err + } + + var ( + resultBlobs = make([][]*Blob, len(nIDs)) + resultErr = make([]error, len(nIDs)) + ) + + wg := sync.WaitGroup{} + for i, nID := range nIDs { + wg.Add(1) + go func(i int, nID namespace.ID) { + defer wg.Done() + blobs, err := s.getBlobs(ctx, nID, header.DAH) + if err != nil { + resultErr[i] = fmt.Errorf("getting blobs for nID(%s): %s", nID.String(), err) + return + } + resultBlobs[i] = blobs + }(i, nID) + } + wg.Wait() + + blobs := make([]*Blob, 0) + for _, resBlobs := range resultBlobs { + if len(resBlobs) > 0 { + blobs = append(blobs, resBlobs...) + } + } + + if len(blobs) == 0 { + resultErr = append(resultErr, ErrBlobNotFound) + } + return blobs, errors.Join(resultErr...) +} + +// Included verifies that the blob was included in a specific height. +// To ensure that blob was included in a specific height, we need: +// 1. verify the provided commitment by recomputing it; +// 2. verify the provided Proof against subtree roots that were used in 1.; +func (s *Service) Included( + ctx context.Context, + height uint64, + nID namespace.ID, + proof *Proof, + com Commitment, +) (bool, error) { + // In the current implementation, LNs will have to download all shares to recompute the commitment. + // To achieve 1. we need to modify Proof structure and to store all subtree roots, that were + // involved in commitment creation and then call `merkle.HashFromByteSlices`(tendermint package). + // nmt.Proof is verifying share inclusion by recomputing row roots, so, theoretically, we can do + // the same but using subtree roots. For this case, we need an extra method in nmt.Proof + // that will perform all reconstructions, + // but we have to guarantee that all our stored subtree roots will be on the same height(e.g. one + // level above shares). + // TODO(@vgonkivs): rework the implementation to perform all verification without network requests. + _, resProof, err := s.getByCommitment(ctx, height, nID, com) + switch err { + case nil: + case ErrBlobNotFound: + return false, nil + default: + return false, err + } + return true, resProof.equal(*proof) +} + +// getByCommitment retrieves the DAH row by row, fetching shares and constructing blobs in order to +// compare Commitments. Retrieving is stopped once the requested blob/proof is found. +func (s *Service) getByCommitment( + ctx context.Context, + height uint64, + nID namespace.ID, + commitment Commitment, +) (*Blob, *Proof, error) { + log.Infow("requesting blob", + "height", height, + "nID", nID.String()) + + header, err := s.headerGetter(ctx, height) + if err != nil { + return nil, nil, err + } + + var ( + rawShares = make([]share.Share, 0) + proofs = make(Proof, 0) + amount int + blobShare *shares.Share + ) + + namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header.DAH, nID) + if err != nil { + if errors.Is(err, share.ErrNamespaceNotFound) || + errors.Is(err, share.ErrNotFound) { + err = ErrBlobNotFound + } + return nil, nil, err + } + for _, row := range namespacedShares { + if len(row.Shares) == 0 { + break + } + + rawShares = append(rawShares, row.Shares...) + proofs = append(proofs, row.Proof) + + // reconstruct the `blobShare` from the first rawShare in range + // in order to get blob's length(first share will contain this info) + if blobShare == nil { + for i, sh := range rawShares { + bShare, err := shares.NewShare(sh) + if err != nil { + return nil, nil, err + } + + // ensure that the first share is not a NamespacePaddingShare + // these shares are used to satisfy the non-interactive default rules + // and are not the part of the blob, so should be removed. + isPadding, err := bShare.IsPadding() + if err != nil { + return nil, nil, err + } + if isPadding { + continue + } + + blobShare = bShare + // save the length. + length, err := blobShare.SequenceLen() + if err != nil { + return nil, nil, err + } + amount = shares.SparseSharesNeeded(length) + rawShares = rawShares[i:] + break + } + } + + // move to the next row if the blob is incomplete. + if amount > len(rawShares) { + continue + } + + blob, same, err := constructAndVerifyBlob(rawShares[:amount], commitment) + if err != nil { + return nil, nil, err + } + if same { + return blob, &proofs, nil + } + + // drop info of the checked blob + rawShares = rawShares[amount:] + if len(rawShares) > 0 { + // save proof for the last row in case we have rawShares + proofs = proofs[len(proofs)-1:] + } else { + // otherwise clear proofs + proofs = nil + } + blobShare = nil + } + + if len(rawShares) == 0 { + return nil, nil, ErrBlobNotFound + } + + blob, same, err := constructAndVerifyBlob(rawShares, commitment) + if err != nil { + return nil, nil, err + } + if same { + return blob, &proofs, nil + } + + return nil, nil, ErrBlobNotFound +} + +// getBlobs retrieves the DAH and fetches all shares from the requested namespace.ID and converts +// them to Blobs. +func (s *Service) getBlobs(ctx context.Context, nID namespace.ID, root *share.Root) ([]*Blob, error) { + namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, root, nID) + if err != nil { + return nil, err + } + return SharesToBlobs(namespacedShares.Flatten()) +} diff --git a/blob/service_test.go b/blob/service_test.go new file mode 100644 index 0000000000..87554eb3d0 --- /dev/null +++ b/blob/service_test.go @@ -0,0 +1,412 @@ +package blob + +import ( + "bytes" + "context" + "crypto/sha256" + "testing" + "time" + + ds "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + mdutils "github.com/ipfs/go-merkledag/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + tmrand "github.com/tendermint/tendermint/libs/rand" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/go-header/store" + "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/blob/blobtest" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/getters" +) + +func TestBlobService_Get(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + var ( + blobSize0 = 18 + blobSize1 = 14 + blobSize2 = 20 + blobSize3 = 12 + ) + + appBlobs, err := blobtest.GenerateBlobs([]int{blobSize0, blobSize1}, false) + require.NoError(t, err) + blobs0, err := convertBlobs(appBlobs...) + require.NoError(t, err) + + appBlobs, err = blobtest.GenerateBlobs([]int{blobSize2, blobSize3}, true) + require.NoError(t, err) + blobs1, err := convertBlobs(appBlobs...) + require.NoError(t, err) + + service := createService(ctx, t, append(blobs0, blobs1...)) + var test = []struct { + name string + doFn func() (interface{}, error) + expectedResult func(interface{}, error) + }{ + { + name: "get single blob", + doFn: func() (interface{}, error) { + b, err := service.Get(ctx, 1, blobs0[0].Namespace(), blobs0[0].Commitment) + return []*Blob{b}, err + }, + expectedResult: func(res interface{}, err error) { + require.NoError(t, err) + assert.NotEmpty(t, res) + + blobs, ok := res.([]*Blob) + assert.True(t, ok) + assert.Len(t, blobs, 1) + + assert.Equal(t, blobs0[0].Commitment, blobs[0].Commitment) + }, + }, + { + name: "get all with the same nID", + doFn: func() (interface{}, error) { + b, err := service.GetAll(ctx, 1, blobs1[0].Namespace()) + return b, err + }, + expectedResult: func(res interface{}, err error) { + require.NoError(t, err) + + blobs, ok := res.([]*Blob) + assert.True(t, ok) + assert.NotEmpty(t, blobs) + + assert.Len(t, blobs, 2) + + for i := range blobs1 { + bytes.Equal(blobs1[i].Commitment, blobs[i].Commitment) + } + }, + }, + { + name: "get all with different nIDs", + doFn: func() (interface{}, error) { + b, err := service.GetAll(ctx, 1, blobs0[0].Namespace(), blobs0[1].Namespace()) + return b, err + }, + expectedResult: func(res interface{}, err error) { + require.NoError(t, err) + + blobs, ok := res.([]*Blob) + assert.True(t, ok) + assert.NotEmpty(t, blobs) + + assert.Len(t, blobs, 2) + // check the order + require.True(t, bytes.Equal(blobs[0].Namespace(), blobs0[0].Namespace())) + require.True(t, bytes.Equal(blobs[1].Namespace(), blobs0[1].Namespace())) + }, + }, + { + name: "get blob with incorrect commitment", + doFn: func() (interface{}, error) { + b, err := service.Get(ctx, 1, blobs0[0].Namespace(), blobs0[1].Commitment) + return []*Blob{b}, err + }, + expectedResult: func(res interface{}, err error) { + require.Error(t, err) + + blobs, ok := res.([]*Blob) + assert.True(t, ok) + assert.Empty(t, blobs[0]) + }, + }, + { + name: "get invalid blob", + doFn: func() (interface{}, error) { + appBlob, err := blobtest.GenerateBlobs([]int{10}, false) + require.NoError(t, err) + blob, err := convertBlobs(appBlob...) + require.NoError(t, err) + + b, err := service.Get(ctx, 1, blob[0].Namespace(), blob[0].Commitment) + return []*Blob{b}, err + }, + expectedResult: func(res interface{}, err error) { + require.Error(t, err) + + blobs, ok := res.([]*Blob) + assert.True(t, ok) + assert.Empty(t, blobs[0]) + }, + }, + { + name: "get proof", + doFn: func() (interface{}, error) { + proof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) + return proof, err + }, + expectedResult: func(res interface{}, err error) { + require.NoError(t, err) + + header, err := service.headerGetter(ctx, 1) + require.NoError(t, err) + + proof, ok := res.(*Proof) + assert.True(t, ok) + + verifyFn := func(t *testing.T, rawShares [][]byte, proof *Proof, nID namespace.ID) { + for _, row := range header.DAH.RowRoots { + to := 0 + for _, p := range *proof { + from := to + to = p.End() - p.Start() + from + eq := p.VerifyInclusion(sha256.New(), nID, rawShares[from:to], row) + if eq == true { + return + } + } + } + t.Fatal("could not prove the shares") + } + + rawShares, err := BlobsToShares(blobs0[1]) + require.NoError(t, err) + verifyFn(t, rawShares, proof, blobs0[1].Namespace()) + }, + }, + { + name: "verify inclusion", + doFn: func() (interface{}, error) { + proof, err := service.GetProof(ctx, 1, blobs0[0].Namespace(), blobs0[0].Commitment) + require.NoError(t, err) + return service.Included(ctx, 1, blobs0[0].Namespace(), proof, blobs0[0].Commitment) + }, + expectedResult: func(res interface{}, err error) { + require.NoError(t, err) + included, ok := res.(bool) + require.True(t, ok) + require.True(t, included) + }, + }, + { + name: "verify inclusion fails with different proof", + doFn: func() (interface{}, error) { + proof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) + require.NoError(t, err) + return service.Included(ctx, 1, blobs0[0].Namespace(), proof, blobs0[0].Commitment) + }, + expectedResult: func(res interface{}, err error) { + require.Error(t, err) + require.ErrorIs(t, err, ErrInvalidProof) + included, ok := res.(bool) + require.True(t, ok) + require.True(t, included) + }, + }, + { + name: "not included", + doFn: func() (interface{}, error) { + appBlob, err := blobtest.GenerateBlobs([]int{10}, false) + require.NoError(t, err) + blob, err := convertBlobs(appBlob...) + require.NoError(t, err) + + proof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) + require.NoError(t, err) + return service.Included(ctx, 1, blob[0].Namespace(), proof, blob[0].Commitment) + }, + expectedResult: func(res interface{}, err error) { + require.NoError(t, err) + included, ok := res.(bool) + require.True(t, ok) + require.False(t, included) + }, + }, + { + name: "count proofs for the blob", + doFn: func() (interface{}, error) { + proof0, err := service.GetProof(ctx, 1, blobs0[0].Namespace(), blobs0[0].Commitment) + if err != nil { + return nil, err + } + proof1, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) + if err != nil { + return nil, err + } + return []*Proof{proof0, proof1}, nil + }, + expectedResult: func(i interface{}, err error) { + require.NoError(t, err) + proofs, ok := i.([]*Proof) + require.True(t, ok) + + h, err := service.headerGetter(ctx, 1) + require.NoError(t, err) + + originalDataWidth := len(h.DAH.RowRoots) / 2 + sizes := []int{blobSize0, blobSize1} + for i, proof := range proofs { + require.True(t, sizes[i]/originalDataWidth+1 == proof.Len()) + } + }, + }, + { + name: "get all not found", + doFn: func() (interface{}, error) { + nID := tmrand.Bytes(appconsts.NamespaceSize) + return service.GetAll(ctx, 1, nID) + }, + expectedResult: func(i interface{}, err error) { + blobs, ok := i.([]*Blob) + require.True(t, ok) + assert.Empty(t, blobs) + require.Error(t, err) + require.ErrorIs(t, err, ErrBlobNotFound) + + }, + }, + } + + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + blobs, err := tt.doFn() + tt.expectedResult(blobs, err) + }) + } +} + +// TestService_GetSingleBlobWithoutPadding creates two blobs with the same nID +// But to satisfy the rule of eds creating, padding namespace share is placed between +// blobs. Test ensures that blob service will skip padding share and return the correct blob. +func TestService_GetSingleBlobWithoutPadding(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + appBlob, err := blobtest.GenerateBlobs([]int{9, 5}, true) + require.NoError(t, err) + blobs, err := convertBlobs(appBlob...) + require.NoError(t, err) + + ns1, err := appns.New(blobs[0].Namespace()[0], blobs[0].Namespace()[appns.NamespaceVersionSize:]) + require.NoError(t, err) + + ns2, err := appns.New(blobs[1].Namespace()[0], blobs[1].Namespace()[appns.NamespaceVersionSize:]) + require.NoError(t, err) + + padding0, err := shares.NamespacePaddingShare(ns1) + require.NoError(t, err) + padding1, err := shares.NamespacePaddingShare(ns2) + require.NoError(t, err) + rawShares0, err := BlobsToShares(blobs[0]) + require.NoError(t, err) + rawShares1, err := BlobsToShares(blobs[1]) + require.NoError(t, err) + + rawShares := make([][]byte, 0) + rawShares = append(rawShares, append(rawShares0, padding0.ToBytes())...) + rawShares = append(rawShares, append(rawShares1, padding1.ToBytes())...) + + bs := mdutils.Bserv() + batching := ds_sync.MutexWrap(ds.NewMapDatastore()) + headerStore, err := store.NewStore[*header.ExtendedHeader](batching) + require.NoError(t, err) + eds, err := share.AddShares(ctx, rawShares, bs) + require.NoError(t, err) + + h := headertest.ExtendedHeaderFromEDS(t, 1, eds) + err = headerStore.Init(ctx, h) + require.NoError(t, err) + + fn := func(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + return headerStore.GetByHeight(ctx, height) + } + service := NewService(nil, getters.NewIPLDGetter(bs), fn) + + newBlob, err := service.Get(ctx, 1, blobs[1].Namespace(), blobs[1].Commitment) + require.NoError(t, err) + assert.Equal(t, newBlob.Commitment, blobs[1].Commitment) +} + +func TestService_GetAllWithoutPadding(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + appBlob, err := blobtest.GenerateBlobs([]int{9, 5}, true) + require.NoError(t, err) + blobs, err := convertBlobs(appBlob...) + require.NoError(t, err) + + ns1, err := appns.New( + blobs[0].Namespace()[appns.NamespaceVersionSize-1], + blobs[0].Namespace()[appns.NamespaceVersionSize:], + ) + require.NoError(t, err) + + ns2, err := appns.New( + blobs[1].Namespace()[appns.NamespaceVersionSize-1], + blobs[1].Namespace()[appns.NamespaceVersionSize:], + ) + require.NoError(t, err) + + padding0, err := shares.NamespacePaddingShare(ns1) + require.NoError(t, err) + padding1, err := shares.NamespacePaddingShare(ns2) + require.NoError(t, err) + rawShares0, err := BlobsToShares(blobs[0]) + require.NoError(t, err) + rawShares1, err := BlobsToShares(blobs[1]) + require.NoError(t, err) + rawShares := make([][]byte, 0) + + // create shares in correct order with padding shares + if bytes.Compare(blobs[0].Namespace(), blobs[1].Namespace()) <= 0 { + rawShares = append(rawShares, append(rawShares0, padding0.ToBytes())...) + rawShares = append(rawShares, append(rawShares1, padding1.ToBytes())...) + } else { + rawShares = append(rawShares, append(rawShares1, padding1.ToBytes())...) + rawShares = append(rawShares, append(rawShares0, padding0.ToBytes())...) + } + + bs := mdutils.Bserv() + batching := ds_sync.MutexWrap(ds.NewMapDatastore()) + headerStore, err := store.NewStore[*header.ExtendedHeader](batching) + require.NoError(t, err) + eds, err := share.AddShares(ctx, rawShares, bs) + require.NoError(t, err) + + h := headertest.ExtendedHeaderFromEDS(t, 1, eds) + err = headerStore.Init(ctx, h) + require.NoError(t, err) + + fn := func(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + return headerStore.GetByHeight(ctx, height) + } + + service := NewService(nil, getters.NewIPLDGetter(bs), fn) + + _, err = service.GetAll(ctx, 1, blobs[0].Namespace(), blobs[1].Namespace()) + require.NoError(t, err) +} + +func createService(ctx context.Context, t *testing.T, blobs []*Blob) *Service { + bs := mdutils.Bserv() + batching := ds_sync.MutexWrap(ds.NewMapDatastore()) + headerStore, err := store.NewStore[*header.ExtendedHeader](batching) + require.NoError(t, err) + rawShares, err := BlobsToShares(blobs...) + require.NoError(t, err) + eds, err := share.AddShares(ctx, rawShares, bs) + require.NoError(t, err) + + h := headertest.ExtendedHeaderFromEDS(t, 1, eds) + err = headerStore.Init(ctx, h) + require.NoError(t, err) + + fn := func(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + return headerStore.GetByHeight(ctx, height) + } + return NewService(nil, getters.NewIPLDGetter(bs), fn) +} diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 76bda50db5..3e0da71d69 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -321,6 +321,31 @@ func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService } } +func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader { + valSet, vals := RandValidatorSet(10, 10) + gen := RandRawHeader(t) + dah := da.NewDataAvailabilityHeader(eds) + + gen.DataHash = dah.Hash() + gen.ValidatorsHash = valSet.Hash() + gen.NextValidatorsHash = valSet.Hash() + gen.Height = int64(height) + blockID := RandBlockID(t) + blockID.Hash = gen.Hash() + voteSet := types.NewVoteSet(gen.ChainID, gen.Height, 0, tmproto.PrecommitType, valSet) + commit, err := MakeCommit(blockID, gen.Height, 0, voteSet, vals, time.Now()) + require.NoError(t, err) + + eh := &header.ExtendedHeader{ + RawHeader: *gen, + Commit: commit, + ValidatorSet: valSet, + DAH: &dah, + } + require.NoError(t, eh.Validate()) + return eh +} + func CreateFraudExtHeader( t *testing.T, eh *header.ExtendedHeader, diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go new file mode 100644 index 0000000000..8c309ba5b2 --- /dev/null +++ b/nodebuilder/blob/blob.go @@ -0,0 +1,76 @@ +package blob + +import ( + "context" + + "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/blob" +) + +var _ Module = (*API)(nil) + +// Module defines the API related to interacting with the blobs +// +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + // Submit sends Blobs and reports the height in which they were included. + // Allows sending multiple Blobs atomically synchronously. + // Uses default wallet registered on the Node. + Submit(_ context.Context, _ ...*blob.Blob) (height uint64, _ error) + // Get retrieves the blob by commitment under the given namespace and height. + Get(_ context.Context, height uint64, _ namespace.ID, _ blob.Commitment) (*blob.Blob, error) + // GetAll returns all blobs under the given namespaces and height. + GetAll(_ context.Context, height uint64, _ ...namespace.ID) ([]*blob.Blob, error) + // GetProof retrieves proofs in the given namespaces at the given height by commitment. + GetProof(_ context.Context, height uint64, _ namespace.ID, _ blob.Commitment) (*blob.Proof, error) + // Included checks whether a blob's given commitment(Merkle subtree root) is included at + // given height and under the namespace. + Included(_ context.Context, height uint64, _ namespace.ID, _ *blob.Proof, _ blob.Commitment) (bool, error) +} + +type API struct { + Internal struct { + Submit func(context.Context, ...*blob.Blob) (uint64, error) `perm:"write"` + Get func(context.Context, uint64, namespace.ID, blob.Commitment) (*blob.Blob, error) `perm:"read"` + GetAll func(context.Context, uint64, ...namespace.ID) ([]*blob.Blob, error) `perm:"read"` + GetProof func(context.Context, uint64, namespace.ID, blob.Commitment) (*blob.Proof, error) `perm:"read"` + Included func(context.Context, uint64, namespace.ID, *blob.Proof, blob.Commitment) (bool, error) `perm:"read"` + } +} + +func (api *API) Submit(ctx context.Context, blobs ...*blob.Blob) (uint64, error) { + return api.Internal.Submit(ctx, blobs...) +} + +func (api *API) Get( + ctx context.Context, + height uint64, + nID namespace.ID, + commitment blob.Commitment, +) (*blob.Blob, error) { + return api.Internal.Get(ctx, height, nID, commitment) +} + +func (api *API) GetAll(ctx context.Context, height uint64, nIDs ...namespace.ID) ([]*blob.Blob, error) { + return api.Internal.GetAll(ctx, height, nIDs...) +} + +func (api *API) GetProof( + ctx context.Context, + height uint64, + nID namespace.ID, + commitment blob.Commitment, +) (*blob.Proof, error) { + return api.Internal.GetProof(ctx, height, nID, commitment) +} + +func (api *API) Included( + ctx context.Context, + height uint64, + nID namespace.ID, + proof *blob.Proof, + commitment blob.Commitment, +) (bool, error) { + return api.Internal.Included(ctx, height, nID, proof, commitment) +} diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go new file mode 100644 index 0000000000..93824bc625 --- /dev/null +++ b/nodebuilder/blob/mocks/api.go @@ -0,0 +1,122 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/blob (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + blob "github.com/celestiaorg/celestia-node/blob" + namespace "github.com/celestiaorg/nmt/namespace" + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockModule) Get(arg0 context.Context, arg1 uint64, arg2 namespace.ID, arg3 blob.Commitment) (*blob.Blob, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*blob.Blob) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockModuleMockRecorder) Get(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockModule)(nil).Get), arg0, arg1, arg2, arg3) +} + +// GetAll mocks base method. +func (m *MockModule) GetAll(arg0 context.Context, arg1 uint64, arg2 ...namespace.ID) ([]*blob.Blob, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetAll", varargs...) + ret0, _ := ret[0].([]*blob.Blob) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAll indicates an expected call of GetAll. +func (mr *MockModuleMockRecorder) GetAll(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockModule)(nil).GetAll), varargs...) +} + +// GetProof mocks base method. +func (m *MockModule) GetProof(arg0 context.Context, arg1 uint64, arg2 namespace.ID, arg3 blob.Commitment) (*blob.Proof, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProof", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*blob.Proof) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProof indicates an expected call of GetProof. +func (mr *MockModuleMockRecorder) GetProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockModule)(nil).GetProof), arg0, arg1, arg2, arg3) +} + +// Included mocks base method. +func (m *MockModule) Included(arg0 context.Context, arg1 uint64, arg2 namespace.ID, arg3 *blob.Proof, arg4 blob.Commitment) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Included", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Included indicates an expected call of Included. +func (mr *MockModuleMockRecorder) Included(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Included", reflect.TypeOf((*MockModule)(nil).Included), arg0, arg1, arg2, arg3, arg4) +} + +// Submit mocks base method. +func (m *MockModule) Submit(arg0 context.Context, arg1 ...*blob.Blob) (uint64, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Submit", varargs...) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Submit indicates an expected call of Submit. +func (mr *MockModuleMockRecorder) Submit(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockModule)(nil).Submit), varargs...) +} diff --git a/nodebuilder/blob/module.go b/nodebuilder/blob/module.go new file mode 100644 index 0000000000..76e7677725 --- /dev/null +++ b/nodebuilder/blob/module.go @@ -0,0 +1,28 @@ +package blob + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/header" + headerService "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/state" +) + +func ConstructModule() fx.Option { + return fx.Module("blob", + fx.Provide( + func(service headerService.Module) func(context.Context, uint64) (*header.ExtendedHeader, error) { + return service.GetByHeight + }), + fx.Provide(func( + state *state.CoreAccessor, + sGetter share.Getter, + getByHeightFn func(context.Context, uint64) (*header.ExtendedHeader, error), + ) Module { + return blob.NewService(state, sGetter, getByHeightFn) + })) +} diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go index 03954c726a..0373e44244 100644 --- a/nodebuilder/default_services.go +++ b/nodebuilder/default_services.go @@ -1,6 +1,7 @@ package nodebuilder import ( + "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -19,5 +20,6 @@ var PackageToAPI = map[string]interface{}{ "header": &header.API{}, "daser": &das.API{}, "p2p": &p2p.API{}, + "blob": &blob.API{}, "node": &node.API{}, } diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 3f3b08e68b..51edc26c72 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -6,6 +6,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -52,6 +53,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store core.ConstructModule(tp, &cfg.Core), das.ConstructModule(tp, &cfg.DASer), fraud.ConstructModule(tp), + blob.ConstructModule(), node.ConstructModule(tp), ) diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 90caac5dc9..243ac47163 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -21,6 +21,7 @@ import ( "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -68,6 +69,7 @@ type Node struct { HeaderServ header.Module // not optional StateServ state.Module // not optional FraudServ fraud.Module // not optional + BlobServ blob.Module // not optional DASer das.Module // not optional // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index 4607c4d33f..41f8400101 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -8,7 +8,7 @@ import ( logging "github.com/ipfs/go-log/v2" ) -const APIVersion = "v0.1.0" +const APIVersion = "v0.1.1" type module struct { tp Type diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index 3d5c368042..ca30af6305 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -4,6 +4,7 @@ import ( "github.com/cristalhq/jwt" "github.com/celestiaorg/celestia-node/api/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -22,6 +23,7 @@ func registerEndpoints( daserMod das.Module, p2pMod p2p.Module, nodeMod node.Module, + blobMod blob.Module, serv *rpc.Server, ) { serv.RegisterAuthedService("fraud", fraudMod, &fraud.API{}) @@ -31,6 +33,7 @@ func registerEndpoints( serv.RegisterAuthedService("share", shareMod, &share.API{}) serv.RegisterAuthedService("p2p", p2pMod, &p2p.API{}) serv.RegisterAuthedService("node", nodeMod, &node.API{}) + serv.RegisterAuthedService("blob", blobMod, &blob.API{}) } func server(cfg *Config, auth jwt.Signer) *rpc.Server { diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 3658c7ebf8..33663d7ce8 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -12,9 +12,8 @@ import ( types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" - types1 "github.com/tendermint/tendermint/types" - - namespace "github.com/celestiaorg/nmt/namespace" + types1 "github.com/tendermint/tendermint/proto/tendermint/types" + types2 "github.com/tendermint/tendermint/types" ) // MockModule is a mock of Module interface. @@ -189,23 +188,28 @@ func (mr *MockModuleMockRecorder) QueryUnbonding(arg0, arg1 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryUnbonding", reflect.TypeOf((*MockModule)(nil).QueryUnbonding), arg0, arg1) } -// SubmitPayForData mocks base method. -func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 namespace.ID, arg2 []byte, arg3 math.Int, arg4 uint64) (*types.TxResponse, error) { +// SubmitPayForBlob mocks base method. +func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 math.Int, arg2 uint64, arg3 ...*types1.Blob) (*types.TxResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubmitPayForBlob", arg0, arg1, arg2, arg3, arg4) + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SubmitPayForBlob", varargs...) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } -// SubmitPayForData indicates an expected call of SubmitPayForData. -func (mr *MockModuleMockRecorder) SubmitPayForData(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +// SubmitPayForBlob indicates an expected call of SubmitPayForBlob. +func (mr *MockModuleMockRecorder) SubmitPayForBlob(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForBlob", reflect.TypeOf((*MockModule)(nil).SubmitPayForBlob), arg0, arg1, arg2, arg3, arg4) + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForBlob", reflect.TypeOf((*MockModule)(nil).SubmitPayForBlob), varargs...) } // SubmitTx mocks base method. -func (m *MockModule) SubmitTx(arg0 context.Context, arg1 types1.Tx) (*types.TxResponse, error) { +func (m *MockModule) SubmitTx(arg0 context.Context, arg1 types2.Tx) (*types.TxResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubmitTx", arg0, arg1) ret0, _ := ret[0].(*types.TxResponse) diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 07f61e5952..fc1932cfae 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -5,7 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/celestiaorg/nmt/namespace" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/state" ) @@ -44,10 +44,9 @@ type Module interface { // SubmitPayForBlob builds, signs and submits a PayForBlob transaction. SubmitPayForBlob( ctx context.Context, - nID namespace.ID, - data []byte, fee state.Int, gasLim uint64, + blobs ...*apptypes.Blob, ) (*state.TxResponse, error) // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. @@ -113,10 +112,9 @@ type API struct { SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"write"` SubmitPayForBlob func( ctx context.Context, - nID namespace.ID, - data []byte, fee state.Int, gasLim uint64, + blobs ...*apptypes.Blob, ) (*state.TxResponse, error) `perm:"write"` CancelUnbondingDelegation func( ctx context.Context, @@ -192,12 +190,11 @@ func (api *API) SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, e func (api *API) SubmitPayForBlob( ctx context.Context, - nID namespace.ID, - data []byte, fee state.Int, gasLim uint64, + blobs ...*apptypes.Blob, ) (*state.TxResponse, error) { - return api.Internal.SubmitPayForBlob(ctx, nID, data, fee, gasLim) + return api.Internal.SubmitPayForBlob(ctx, fee, gasLim, blobs...) } func (api *API) CancelUnbondingDelegation( diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go new file mode 100644 index 0000000000..db56bad8c4 --- /dev/null +++ b/nodebuilder/tests/blob_test.go @@ -0,0 +1,131 @@ +package tests + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/blob/blobtest" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" +) + +func TestBlobModule(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + sw := swamp.NewSwamp(t) + + appBlobs0, err := blobtest.GenerateBlobs([]int{8, 4}, true) + require.NoError(t, err) + appBlobs1, err := blobtest.GenerateBlobs([]int{4}, false) + require.NoError(t, err) + blobs := make([]*blob.Blob, 0, len(appBlobs0)+len(appBlobs1)) + + for _, b := range append(appBlobs0, appBlobs1...) { + blob, err := blob.NewBlob(b.ShareVersion, append([]byte{b.NamespaceVersion}, b.NamespaceID...), b.Data) + require.NoError(t, err) + blobs = append(blobs, blob) + } + + require.NoError(t, err) + bridge := sw.NewBridgeNode() + require.NoError(t, bridge.Start(ctx)) + + addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + require.NoError(t, err) + + fullCfg := sw.DefaultTestConfig(node.Full) + fullCfg.Header.TrustedPeers = append(fullCfg.Header.TrustedPeers, addrs[0].String()) + fullNode := sw.NewNodeWithConfig(node.Full, fullCfg) + require.NoError(t, fullNode.Start(ctx)) + + addrsFull, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(fullNode.Host)) + require.NoError(t, err) + + lightCfg := sw.DefaultTestConfig(node.Light) + lightCfg.Header.TrustedPeers = append(lightCfg.Header.TrustedPeers, addrsFull[0].String()) + lightNode := sw.NewNodeWithConfig(node.Light, lightCfg) + require.NoError(t, lightNode.Start(ctx)) + + height, err := fullNode.BlobServ.Submit(ctx, blobs...) + require.NoError(t, err) + + _, err = fullNode.HeaderServ.WaitForHeight(ctx, height) + require.NoError(t, err) + _, err = lightNode.HeaderServ.WaitForHeight(ctx, height) + require.NoError(t, err) + + var test = []struct { + name string + doFn func(t *testing.T) + }{ + { + name: "Get", + doFn: func(t *testing.T) { + blob1, err := fullNode.BlobServ.Get(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) + require.NoError(t, err) + require.Equal(t, blobs[0], blob1) + }, + }, + { + name: "GetAll", + doFn: func(t *testing.T) { + newBlobs, err := fullNode.BlobServ.GetAll(ctx, height, blobs[0].Namespace()) + require.NoError(t, err) + require.Len(t, newBlobs, len(appBlobs0)) + require.True(t, bytes.Equal(blobs[0].Commitment, newBlobs[0].Commitment)) + require.True(t, bytes.Equal(blobs[1].Commitment, newBlobs[1].Commitment)) + }, + }, + { + name: "Included", + doFn: func(t *testing.T) { + proof, err := fullNode.BlobServ.GetProof(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) + require.NoError(t, err) + + included, err := lightNode.BlobServ.Included( + ctx, + height, + blobs[0].Namespace(), + proof, + blobs[0].Commitment, + ) + require.NoError(t, err) + require.True(t, included) + }, + }, + { + name: "Not Found", + doFn: func(t *testing.T) { + appBlob, err := blobtest.GenerateBlobs([]int{4}, false) + require.NoError(t, err) + newBlob, err := blob.NewBlob( + appBlob[0].ShareVersion, + append([]byte{appBlob[0].NamespaceVersion}, appBlob[0].NamespaceID...), + appBlob[0].Data, + ) + require.NoError(t, err) + + b, err := fullNode.BlobServ.Get(ctx, height, newBlob.Namespace(), newBlob.Commitment) + assert.Nil(t, b) + require.Error(t, err) + require.ErrorIs(t, err, blob.ErrBlobNotFound) + }, + }, + } + + for _, tt := range test { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tt.doFn(t) + }) + } +} diff --git a/share/nid.go b/share/nid.go new file mode 100644 index 0000000000..dfd6a5de7e --- /dev/null +++ b/share/nid.go @@ -0,0 +1,26 @@ +package share + +import ( + "fmt" + + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/celestiaorg/nmt/namespace" +) + +// NewNamespaceV0 takes variable size byte slice anc creates version 0 Namespace ID. +// The bytes slice must be <= 10 bytes. +func NewNamespaceV0(subNId []byte) (namespace.ID, error) { + if lnid := len(subNId); lnid > appns.NamespaceVersionZeroIDSize { + return nil, fmt.Errorf("namespace id must be <= %v, but it was %v bytes", appns.NamespaceVersionZeroIDSize, lnid) + } + + id := make([]byte, appns.NamespaceIDSize) + copy(id[appns.NamespaceVersionZeroPrefixSize:], subNId) + + appID, err := appns.New(appns.NamespaceVersionZero, id) + if err != nil { + return nil, err + } + + return appID.Bytes(), nil +} diff --git a/state/core_access.go b/state/core_access.go index aed7db3df3..1a0a5650ab 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -20,11 +20,9 @@ import ( "google.golang.org/grpc/credentials/insecure" "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/x/blob" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" libhead "github.com/celestiaorg/go-header" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/header" ) @@ -158,17 +156,19 @@ func (ca *CoreAccessor) constructSignedTx( func (ca *CoreAccessor) SubmitPayForBlob( ctx context.Context, - nID namespace.ID, - data []byte, fee Int, gasLim uint64, + blobs ...*apptypes.Blob, ) (*TxResponse, error) { - b := &apptypes.Blob{NamespaceId: nID, Data: data, ShareVersion: uint32(appconsts.DefaultShareVersion)} + if len(blobs) == 0 { + return nil, errors.New("state: no blobs provided") + } + response, err := blob.SubmitPayForBlob( ctx, ca.signer, ca.coreConn, - []*apptypes.Blob{b}, + blobs, apptypes.SetGasLimit(gasLim), withFee(fee), ) From 64c18a5554cbd39772921c5dcb46b8de06b9e9f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 11:22:23 +0200 Subject: [PATCH 0619/1008] chore(deps): bump go.uber.org/fx from 1.19.2 to 1.19.3 (#2199) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index abdc7b6afc..2773458ec2 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.34.0 go.opentelemetry.io/otel/trace v1.13.0 go.opentelemetry.io/proto/otlp v0.19.0 - go.uber.org/fx v1.19.2 + go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.7.0 golang.org/x/sync v0.1.0 diff --git a/go.sum b/go.sum index 9eca643fef..3162a6abab 100644 --- a/go.sum +++ b/go.sum @@ -2086,8 +2086,8 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= -go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= -go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= From a438788f474f65aae3e33b3b8ee6db055f8d3607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 09:31:41 +0000 Subject: [PATCH 0620/1008] chore(deps): bump golang.org/x/crypto from 0.7.0 to 0.9.0 (#2276) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 2773458ec2..fd0bf7eb4c 100644 --- a/go.mod +++ b/go.mod @@ -72,9 +72,9 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.7.0 + golang.org/x/crypto v0.9.0 golang.org/x/sync v0.1.0 - golang.org/x/text v0.8.0 + golang.org/x/text v0.9.0 google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 ) @@ -311,10 +311,10 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect diff --git a/go.sum b/go.sum index 3162a6abab..92583018a9 100644 --- a/go.sum +++ b/go.sum @@ -2152,8 +2152,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2286,8 +2286,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2462,14 +2462,14 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2480,8 +2480,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 29203b67df7abcc7ce08dcea592c32b4dba439b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 09:54:55 +0000 Subject: [PATCH 0621/1008] chore(deps): bump codecov/codecov-action from 3.1.3 to 3.1.4 (#2235) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 8f4729c7c8..b519db7763 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -59,7 +59,7 @@ jobs: run: make test-unit - name: upload coverage - uses: codecov/codecov-action@v3.1.3 + uses: codecov/codecov-action@v3.1.4 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.txt From 5d0424aa06895d3782de2f5181679929647e7609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 10:04:08 +0000 Subject: [PATCH 0622/1008] chore(deps): bump alpine from 3.17.3 to 3.18.0 (#2198) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 214003dc08..9346c559d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ COPY . . RUN make build && make cel-key -FROM docker.io/alpine:3.17.3 +FROM docker.io/alpine:3.18.0 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From b62452af1e7dd1cc1b9a5ca1d96d1545154b4d04 Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Wed, 31 May 2023 09:49:03 +0200 Subject: [PATCH 0623/1008] chore!: bump arabica testnet params (#2281) --- nodebuilder/p2p/bootstrap.go | 8 ++++---- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 0ceefa0453..25f0a85120 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -38,10 +38,10 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ Arabica: { - "/dns4/limani.celestia-devops.dev/tcp/2121/p2p/12D3KooWDgG69kXfmSiHjUErN2ahpUC1SXpSfB2urrqMZ6aWC8NS", - "/dns4/marsellesa.celestia-devops.dev/tcp/2121/p2p/12D3KooWHr2wqFAsMXnPzpFsgxmePgXb8BqpkePebwUgLyZc95bd", - "/dns4/parainem.celestia-devops.dev/tcp/2121/p2p/12D3KooWHX8xpwg8qkP7kLKmKGtgZvmsopvgxc6Fwtu665QC7G8q", - "/dns4/kaarina.celestia-devops.dev/tcp/2121/p2p/12D3KooWN6fzdt4sG5QfWRPn4kwCQBdkt7TDNQkWsUymAwKrmvUs", + "/dns4/da-bridge-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWDXkXARv79Dtn5xrGBgJePtCzCsEwWR7eGWnx9ZCyUyD6", + "/dns4/da-bridge-arabica-8-2.celestia-arabica.com/tcp/2121/p2p/12D3KooWPu8qKmmNgYFMBsTkLBa1m3D9Cy9ReCAoQLqxEn9MHD1i", + "/dns4/da-full-1-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWEmeFodzypdTBTcw8Yub6WZRT4h1UgFtwCwwq6wS5Dtqm", + "/dns4/da-full-2-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWCs3wFmqwPn1u8pNU4BGsvLsob1ShTzvps8qEtTRuuuK5", }, Mocha: { "/dns4/andromeda.celestia-devops.dev/tcp/2121/p2p/12D3KooWKvPXtV1yaQ6e3BRNUHa5Phh8daBwBi3KkGaSSkUPys6D", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 670699b292..bc0fc3810d 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "EE310062CBB13CE98CBC7EAD3F6A827F0E4A86043FDEB2DA42048821877FE45C", + Arabica: "E5D620B5BE7873222DCD83464C285FD0F215C209393E7481F9A5979280AD6CA2", Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 025ba3bddf..ded3f0f1b1 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-6" + Arabica Network = "arabica-8" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha" // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. From 011765ac0fbf33eb7666467e145663dae2e60589 Mon Sep 17 00:00:00 2001 From: Alex Kiss Date: Wed, 31 May 2023 10:42:58 +0200 Subject: [PATCH 0624/1008] feat(nodebuilder): add ip resolving capabilities to core.ip (#2277) Co-authored-by: Hlib Kanunnikov --- nodebuilder/core/flags.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/nodebuilder/core/flags.go b/nodebuilder/core/flags.go index b98c7acd07..901d910ea6 100644 --- a/nodebuilder/core/flags.go +++ b/nodebuilder/core/flags.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "net" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -21,7 +22,8 @@ func Flags() *flag.FlagSet { coreFlag, "", "Indicates node to connect to the given core node. "+ - "Example: , 127.0.0.1. Assumes RPC port 26657 and gRPC port 9090 as default unless otherwise specified.", + "Example: , 127.0.0.1. , subdomain.domain.tld "+ + "Assumes RPC port 26657 and gRPC port 9090 as default unless otherwise specified.", ) flags.String( coreRPCFlag, @@ -37,10 +39,7 @@ func Flags() *flag.FlagSet { } // ParseFlags parses Core flags from the given cmd and saves them to the passed config. -func ParseFlags( - cmd *cobra.Command, - cfg *Config, -) error { +func ParseFlags(cmd *cobra.Command, cfg *Config) error { coreIP := cmd.Flag(coreFlag).Value.String() if coreIP == "" { if cmd.Flag(coreGRPCFlag).Changed || cmd.Flag(coreRPCFlag).Changed { @@ -49,10 +48,22 @@ func ParseFlags( return nil } + ip := net.ParseIP(coreIP) + if ip == nil { + ips, err := net.LookupIP(coreIP) + if err != nil { + return fmt.Errorf("failed to resolve DNS record: %v", err) + } + if len(ips) == 0 { + return fmt.Errorf("no IP addresses found for DNS record") + } + ip = ips[0] + } + rpc := cmd.Flag(coreRPCFlag).Value.String() grpc := cmd.Flag(coreGRPCFlag).Value.String() - cfg.IP = coreIP + cfg.IP = ip.String() cfg.RPCPort = rpc cfg.GRPCPort = grpc return nil From d53efbd03851c487bd96ff0c88fb92c4d1ed89d1 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 31 May 2023 14:30:10 +0200 Subject: [PATCH 0625/1008] chore(share/ipld): pretify error (#2284) --- share/ipld/namespace_data.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index 9698ef990d..13be26c06a 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -2,6 +2,7 @@ package ipld import ( "context" + "encoding/hex" "errors" "fmt" "sync" @@ -166,7 +167,7 @@ func (n *NamespaceData) CollectLeavesByNamespace( defer span.End() span.SetAttributes( - attribute.String("namespace", n.nID.String()), + attribute.String("namespace", hex.EncodeToString(n.nID)), attribute.String("root", root.String()), ) @@ -213,8 +214,8 @@ func (n *NamespaceData) CollectLeavesByNamespace( singleErr.Do(func() { retrievalErr = err }) - log.Errorw("getLeavesWithProofsByNamespace:could not retrieve node", - "nID", n.nID, + log.Errorw("could not retrieve IPLD node", + "nID", hex.EncodeToString(n.nID), "pos", j.sharePos, "err", err, ) From 135622abe39e1e1d767861b6c3419a8393c6cbba Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 1 Jun 2023 11:41:35 +0200 Subject: [PATCH 0626/1008] feat(blob): add marshaling for blob and proof (#2286) --- api/docgen/examples.go | 6 ++++ blob/blob.go | 75 ++++++++++++++++++++++++++++++++++++++++-- blob/blob_test.go | 12 +++++++ blob/service_test.go | 19 +++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/api/docgen/examples.go b/api/docgen/examples.go index ae65501ba6..8293396c7b 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -18,8 +18,10 @@ import ( "golang.org/x/text/language" "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -128,6 +130,10 @@ func init() { Addrs: []multiaddr.Multiaddr{ma}, } addToExampleValues(addrInfo) + + proof := nmt.NewInclusionProof(0, 4, [][]byte{[]byte("test")}, true) + blobProof := &blob.Proof{&proof} + addToExampleValues(blobProof) } func addToExampleValues(v interface{}) { diff --git a/blob/blob.go b/blob/blob.go index 434ab220c7..9771714cb9 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -2,12 +2,15 @@ package blob import ( "bytes" + "encoding/json" "fmt" appns "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/share/ipld" ) // Commitment is a Merkle Root of the subtree built from shares of the Blob. @@ -57,11 +60,47 @@ func (p Proof) equal(input Proof) error { return nil } +type jsonProof struct { + Start int `json:"start"` + End int `json:"end"` + Nodes [][]byte `json:"nodes"` +} + +func (p *Proof) MarshalJSON() ([]byte, error) { + proofs := make([]jsonProof, 0, p.Len()) + for _, pp := range *p { + proofs = append(proofs, jsonProof{ + Start: pp.Start(), + End: pp.End(), + Nodes: pp.Nodes(), + }) + } + + return json.Marshal(proofs) +} + +func (p *Proof) UnmarshalJSON(data []byte) error { + var proofs []jsonProof + err := json.Unmarshal(data, &proofs) + if err != nil { + return err + } + + nmtProofs := make([]*nmt.Proof, len(proofs)) + for i, jProof := range proofs { + nmtProof := nmt.NewInclusionProof(jProof.Start, jProof.End, jProof.Nodes, ipld.NMTIgnoreMaxNamespace) + nmtProofs[i] = &nmtProof + } + + *p = nmtProofs + return nil +} + // Blob represents any application-specific binary data that anyone can submit to Celestia. type Blob struct { - types.Blob + types.Blob `json:"blob"` - Commitment Commitment + Commitment Commitment `json:"commitment"` } // NewBlob constructs a new blob from the provided namespace.ID and data. @@ -91,3 +130,35 @@ func NewBlob(shareVersion uint8, namespace namespace.ID, data []byte) (*Blob, er func (b *Blob) Namespace() namespace.ID { return append([]byte{uint8(b.NamespaceVersion)}, b.NamespaceId...) } + +type jsonBlob struct { + Namespace namespace.ID `json:"namespace"` + Data []byte `json:"data"` + ShareVersion uint32 `json:"share_version"` + Commitment Commitment `json:"commitment"` +} + +func (b *Blob) MarshalJSON() ([]byte, error) { + blob := &jsonBlob{ + Namespace: b.Namespace(), + Data: b.Data, + ShareVersion: b.ShareVersion, + Commitment: b.Commitment, + } + return json.Marshal(blob) +} + +func (b *Blob) UnmarshalJSON(data []byte) error { + var blob jsonBlob + err := json.Unmarshal(data, &blob) + if err != nil { + return err + } + + b.Blob.NamespaceVersion = uint32(blob.Namespace[0]) + b.Blob.NamespaceId = blob.Namespace[1:] + b.Blob.Data = blob.Data + b.Blob.ShareVersion = blob.ShareVersion + b.Commitment = blob.Commitment + return nil +} diff --git a/blob/blob_test.go b/blob/blob_test.go index 5b3920ae9e..7f34fbfc84 100644 --- a/blob/blob_test.go +++ b/blob/blob_test.go @@ -1,6 +1,7 @@ package blob import ( + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -62,6 +63,17 @@ func TestBlob(t *testing.T) { assert.Equal(t, blob[0].Commitment, b[0].Commitment) }, }, + { + name: "blob marshaling", + expectedRes: func(t *testing.T) { + data, err := blob[0].MarshalJSON() + require.NoError(t, err) + + newBlob := &Blob{} + require.NoError(t, newBlob.UnmarshalJSON(data)) + require.True(t, reflect.DeepEqual(blob[0], newBlob)) + }, + }, } for _, tt := range test { diff --git a/blob/service_test.go b/blob/service_test.go index 87554eb3d0..4160f36205 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -268,6 +268,25 @@ func TestBlobService_Get(t *testing.T) { }, }, + { + name: "marshal proof", + doFn: func() (interface{}, error) { + proof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) + require.NoError(t, err) + return proof.MarshalJSON() + }, + expectedResult: func(i interface{}, err error) { + require.NoError(t, err) + jsonData, ok := i.([]byte) + require.True(t, ok) + var proof Proof + require.NoError(t, proof.UnmarshalJSON(jsonData)) + + newProof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) + require.NoError(t, err) + require.NoError(t, proof.equal(*newProof)) + }, + }, } for _, tt := range test { From 1c1327b5a47db4377eed1213e06bfa72c117154e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 1 Jun 2023 13:28:46 +0200 Subject: [PATCH 0627/1008] feat(nodebuilder): Add jwt.Signer to Node struct for auth token access (#2295) This PR adds the jwt.Signer to the node struct to be able to generate auth tokens for integration tests that use celestia-node's RPC. --- cmd/auth.go | 7 +++---- libs/authtoken/authtoken.go | 18 ++++++++++++++++++ nodebuilder/node.go | 2 ++ nodebuilder/node_test.go | 1 + 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 libs/authtoken/authtoken.go diff --git a/cmd/auth.go b/cmd/auth.go index c4eb057a29..eb2000675e 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -14,6 +14,7 @@ import ( flag "github.com/spf13/pflag" "github.com/celestiaorg/celestia-node/api/rpc/perms" + "github.com/celestiaorg/celestia-node/libs/authtoken" "github.com/celestiaorg/celestia-node/libs/keystore" nodemod "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -70,14 +71,12 @@ func newToken(cmd *cobra.Command, args []string) error { return err } - token, err := jwt.NewTokenBuilder(signer).Build(&perms.JWTPayload{ - Allow: permissions, - }) + token, err := authtoken.NewSignedJWT(signer, permissions) if err != nil { return err } - fmt.Printf("%s", token.InsecureString()) + fmt.Printf("%s", token) return nil } diff --git a/libs/authtoken/authtoken.go b/libs/authtoken/authtoken.go new file mode 100644 index 0000000000..c86272790d --- /dev/null +++ b/libs/authtoken/authtoken.go @@ -0,0 +1,18 @@ +package authtoken + +import ( + "github.com/cristalhq/jwt" + "github.com/filecoin-project/go-jsonrpc/auth" + + "github.com/celestiaorg/celestia-node/api/rpc/perms" +) + +func NewSignedJWT(signer jwt.Signer, permissions []auth.Permission) (string, error) { + token, err := jwt.NewTokenBuilder(signer).Build(&perms.JWTPayload{ + Allow: permissions, + }) + if err != nil { + return "", err + } + return token.InsecureString(), nil +} diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 243ac47163..fd32a966f4 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/cristalhq/jwt" "github.com/ipfs/go-blockservice" exchange "github.com/ipfs/go-ipfs-exchange-interface" logging "github.com/ipfs/go-log/v2" @@ -51,6 +52,7 @@ type Node struct { Network p2p.Network Bootstrappers p2p.Bootstrappers Config *Config + AdminSigner jwt.Signer // rpc components RPCServer *rpc.Server // not optional diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 11b27b076a..6aa9782a85 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -34,6 +34,7 @@ func TestLifecycle(t *testing.T) { require.NotNil(t, node.Host) require.NotNil(t, node.HeaderServ) require.NotNil(t, node.StateServ) + require.NotNil(t, node.AdminSigner) require.Equal(t, tt.tp, node.Type) ctx, cancel := context.WithCancel(context.Background()) From 218ab99c5455557cac41444160557cbef70a75ec Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 1 Jun 2023 14:32:29 +0200 Subject: [PATCH 0628/1008] chore(nodebuilder): decrease lifecycle timeout for LN (#2290) LNs don't need that much time to run Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- nodebuilder/node.go | 23 +++++++++++++++++------ nodebuilder/node_test.go | 7 +++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/nodebuilder/node.go b/nodebuilder/node.go index fd32a966f4..a6e3a3c3cc 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -32,8 +32,6 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -var Timeout = time.Minute * 2 - var ( log = logging.Logger("node") fxLog = logging.Logger("fx") @@ -97,14 +95,15 @@ func NewWithConfig(tp node.Type, network p2p.Network, store Store, cfg *Config, // Start launches the Node and all its components and services. func (n *Node) Start(ctx context.Context) error { - ctx, cancel := context.WithTimeout(ctx, Timeout) + to := timeout(n.Type) + ctx, cancel := context.WithTimeout(ctx, to) defer cancel() err := n.start(ctx) if err != nil { log.Debugf("error starting %s Node: %s", n.Type, err) if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("node: failed to start within timeout(%s): %w", Timeout, err) + return fmt.Errorf("node: failed to start within timeout(%s): %w", to, err) } return fmt.Errorf("node: failed to start: %w", err) } @@ -142,14 +141,15 @@ func (n *Node) Run(ctx context.Context) error { // Canceling the given context earlier 'ctx' unblocks the Stop and aborts graceful shutdown forcing // remaining Modules/Services to close immediately. func (n *Node) Stop(ctx context.Context) error { - ctx, cancel := context.WithTimeout(ctx, Timeout) + to := timeout(n.Type) + ctx, cancel := context.WithTimeout(ctx, to) defer cancel() err := n.stop(ctx) if err != nil { log.Debugf("error stopping %s Node: %s", n.Type, err) if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("node: failed to stop within timeout(%s): %w", Timeout, err) + return fmt.Errorf("node: failed to stop within timeout(%s): %w", to, err) } return fmt.Errorf("node: failed to stop: %w", err) } @@ -183,3 +183,14 @@ func newNode(opts ...fx.Option) (*Node, error) { // lifecycleFunc defines a type for common lifecycle funcs. type lifecycleFunc func(context.Context) error + +var DefaultLifecycleTimeout = time.Minute * 2 + +func timeout(tp node.Type) time.Duration { + switch tp { + case node.Light: + return time.Second * 20 + default: + return DefaultLifecycleTimeout + } +} diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 6aa9782a85..bd5d1da811 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -137,6 +137,9 @@ func StartMockOtelCollectorHTTPServer(t *testing.T) (string, func()) { } func TestEmptyBlockExists(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + var test = []struct { tp node.Type }{ @@ -149,10 +152,6 @@ func TestEmptyBlockExists(t *testing.T) { for i, tt := range test { t.Run(strconv.Itoa(i), func(t *testing.T) { node := TestNode(t, tt.tp) - - ctx, cancel := context.WithTimeout(context.Background(), Timeout) - defer cancel() - err := node.Start(ctx) require.NoError(t, err) From c77f6db35fe57ccef4110d0c7815e53a24921948 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 1 Jun 2023 17:55:09 +0200 Subject: [PATCH 0629/1008] fix!(blob/module): fix api by changing variadic params to a slice (#2298) --- api/gateway/state.go | 2 +- blob/service.go | 6 ++--- blob/service_test.go | 8 +++---- nodebuilder/blob/blob.go | 16 ++++++------- nodebuilder/blob/mocks/api.go | 26 +++++++------------- nodebuilder/state/mocks/api.go | 13 ++++------ nodebuilder/state/state.go | 8 +++---- nodebuilder/tests/api_test.go | 43 ++++++++++++++++++++++++++++++++++ nodebuilder/tests/blob_test.go | 5 ++-- state/core_access.go | 2 +- 10 files changed, 79 insertions(+), 50 deletions(-) diff --git a/api/gateway/state.go b/api/gateway/state.go index 8997f4ae21..2576ad4848 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -148,7 +148,7 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } // perform request - txResp, err := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, blob) + txResp, err := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*apptypes.Blob{blob}) if err != nil { writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) return diff --git a/blob/service.go b/blob/service.go index 0fcf47bffd..53ada45450 100644 --- a/blob/service.go +++ b/blob/service.go @@ -53,7 +53,7 @@ func NewService( // Submit sends PFB transaction and reports the height in which it was included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. -func (s *Service) Submit(ctx context.Context, blobs ...*Blob) (uint64, error) { +func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { log.Debugw("submitting blobs", "amount", len(blobs)) var ( @@ -66,7 +66,7 @@ func (s *Service) Submit(ctx context.Context, blobs ...*Blob) (uint64, error) { b[i] = &blob.Blob } - resp, err := s.accessor.SubmitPayForBlob(ctx, types.NewInt(fee), gasLimit, b...) + resp, err := s.accessor.SubmitPayForBlob(ctx, types.NewInt(fee), gasLimit, b) if err != nil { return 0, err } @@ -99,7 +99,7 @@ func (s *Service) GetProof( // GetAll returns all blobs under the given namespaces at the given height. // GetAll can return blobs and an error in case if some requests failed. -func (s *Service) GetAll(ctx context.Context, height uint64, nIDs ...namespace.ID) ([]*Blob, error) { +func (s *Service) GetAll(ctx context.Context, height uint64, nIDs []namespace.ID) ([]*Blob, error) { header, err := s.headerGetter(ctx, height) if err != nil { return nil, err diff --git a/blob/service_test.go b/blob/service_test.go index 4160f36205..ee6e982fe8 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -73,7 +73,7 @@ func TestBlobService_Get(t *testing.T) { { name: "get all with the same nID", doFn: func() (interface{}, error) { - b, err := service.GetAll(ctx, 1, blobs1[0].Namespace()) + b, err := service.GetAll(ctx, 1, []namespace.ID{blobs1[0].Namespace()}) return b, err }, expectedResult: func(res interface{}, err error) { @@ -93,7 +93,7 @@ func TestBlobService_Get(t *testing.T) { { name: "get all with different nIDs", doFn: func() (interface{}, error) { - b, err := service.GetAll(ctx, 1, blobs0[0].Namespace(), blobs0[1].Namespace()) + b, err := service.GetAll(ctx, 1, []namespace.ID{blobs0[0].Namespace(), blobs0[1].Namespace()}) return b, err }, expectedResult: func(res interface{}, err error) { @@ -257,7 +257,7 @@ func TestBlobService_Get(t *testing.T) { name: "get all not found", doFn: func() (interface{}, error) { nID := tmrand.Bytes(appconsts.NamespaceSize) - return service.GetAll(ctx, 1, nID) + return service.GetAll(ctx, 1, []namespace.ID{nID}) }, expectedResult: func(i interface{}, err error) { blobs, ok := i.([]*Blob) @@ -406,7 +406,7 @@ func TestService_GetAllWithoutPadding(t *testing.T) { service := NewService(nil, getters.NewIPLDGetter(bs), fn) - _, err = service.GetAll(ctx, 1, blobs[0].Namespace(), blobs[1].Namespace()) + _, err = service.GetAll(ctx, 1, []namespace.ID{blobs[0].Namespace(), blobs[1].Namespace()}) require.NoError(t, err) } diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go index 8c309ba5b2..7cbe312856 100644 --- a/nodebuilder/blob/blob.go +++ b/nodebuilder/blob/blob.go @@ -17,11 +17,11 @@ type Module interface { // Submit sends Blobs and reports the height in which they were included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. - Submit(_ context.Context, _ ...*blob.Blob) (height uint64, _ error) + Submit(_ context.Context, _ []*blob.Blob) (height uint64, _ error) // Get retrieves the blob by commitment under the given namespace and height. Get(_ context.Context, height uint64, _ namespace.ID, _ blob.Commitment) (*blob.Blob, error) // GetAll returns all blobs under the given namespaces and height. - GetAll(_ context.Context, height uint64, _ ...namespace.ID) ([]*blob.Blob, error) + GetAll(_ context.Context, height uint64, _ []namespace.ID) ([]*blob.Blob, error) // GetProof retrieves proofs in the given namespaces at the given height by commitment. GetProof(_ context.Context, height uint64, _ namespace.ID, _ blob.Commitment) (*blob.Proof, error) // Included checks whether a blob's given commitment(Merkle subtree root) is included at @@ -31,16 +31,16 @@ type Module interface { type API struct { Internal struct { - Submit func(context.Context, ...*blob.Blob) (uint64, error) `perm:"write"` + Submit func(context.Context, []*blob.Blob) (uint64, error) `perm:"write"` Get func(context.Context, uint64, namespace.ID, blob.Commitment) (*blob.Blob, error) `perm:"read"` - GetAll func(context.Context, uint64, ...namespace.ID) ([]*blob.Blob, error) `perm:"read"` + GetAll func(context.Context, uint64, []namespace.ID) ([]*blob.Blob, error) `perm:"read"` GetProof func(context.Context, uint64, namespace.ID, blob.Commitment) (*blob.Proof, error) `perm:"read"` Included func(context.Context, uint64, namespace.ID, *blob.Proof, blob.Commitment) (bool, error) `perm:"read"` } } -func (api *API) Submit(ctx context.Context, blobs ...*blob.Blob) (uint64, error) { - return api.Internal.Submit(ctx, blobs...) +func (api *API) Submit(ctx context.Context, blobs []*blob.Blob) (uint64, error) { + return api.Internal.Submit(ctx, blobs) } func (api *API) Get( @@ -52,8 +52,8 @@ func (api *API) Get( return api.Internal.Get(ctx, height, nID, commitment) } -func (api *API) GetAll(ctx context.Context, height uint64, nIDs ...namespace.ID) ([]*blob.Blob, error) { - return api.Internal.GetAll(ctx, height, nIDs...) +func (api *API) GetAll(ctx context.Context, height uint64, nIDs []namespace.ID) ([]*blob.Blob, error) { + return api.Internal.GetAll(ctx, height, nIDs) } func (api *API) GetProof( diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index 93824bc625..f99d1d8168 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -52,23 +52,18 @@ func (mr *MockModuleMockRecorder) Get(arg0, arg1, arg2, arg3 interface{}) *gomoc } // GetAll mocks base method. -func (m *MockModule) GetAll(arg0 context.Context, arg1 uint64, arg2 ...namespace.ID) ([]*blob.Blob, error) { +func (m *MockModule) GetAll(arg0 context.Context, arg1 uint64, arg2 []namespace.ID) ([]*blob.Blob, error) { m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetAll", varargs...) + ret := m.ctrl.Call(m, "GetAll", arg0, arg1, arg2) ret0, _ := ret[0].([]*blob.Blob) ret1, _ := ret[1].(error) return ret0, ret1 } // GetAll indicates an expected call of GetAll. -func (mr *MockModuleMockRecorder) GetAll(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) GetAll(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockModule)(nil).GetAll), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockModule)(nil).GetAll), arg0, arg1, arg2) } // GetProof mocks base method. @@ -102,21 +97,16 @@ func (mr *MockModuleMockRecorder) Included(arg0, arg1, arg2, arg3, arg4 interfac } // Submit mocks base method. -func (m *MockModule) Submit(arg0 context.Context, arg1 ...*blob.Blob) (uint64, error) { +func (m *MockModule) Submit(arg0 context.Context, arg1 []*blob.Blob) (uint64, error) { m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Submit", varargs...) + ret := m.ctrl.Call(m, "Submit", arg0, arg1) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } // Submit indicates an expected call of Submit. -func (mr *MockModuleMockRecorder) Submit(arg0 interface{}, arg1 ...interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Submit(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockModule)(nil).Submit), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockModule)(nil).Submit), arg0, arg1) } diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 33663d7ce8..8398e0fd7b 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -189,23 +189,18 @@ func (mr *MockModuleMockRecorder) QueryUnbonding(arg0, arg1 interface{}) *gomock } // SubmitPayForBlob mocks base method. -func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 math.Int, arg2 uint64, arg3 ...*types1.Blob) (*types.TxResponse, error) { +func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 math.Int, arg2 uint64, arg3 []*types1.Blob) (*types.TxResponse, error) { m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1, arg2} - for _, a := range arg3 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "SubmitPayForBlob", varargs...) + ret := m.ctrl.Call(m, "SubmitPayForBlob", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*types.TxResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // SubmitPayForBlob indicates an expected call of SubmitPayForBlob. -func (mr *MockModuleMockRecorder) SubmitPayForBlob(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) SubmitPayForBlob(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForBlob", reflect.TypeOf((*MockModule)(nil).SubmitPayForBlob), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubmitPayForBlob", reflect.TypeOf((*MockModule)(nil).SubmitPayForBlob), arg0, arg1, arg2, arg3) } // SubmitTx mocks base method. diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index fc1932cfae..57f46a8fd6 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -46,7 +46,7 @@ type Module interface { ctx context.Context, fee state.Int, gasLim uint64, - blobs ...*apptypes.Blob, + blobs []*apptypes.Blob, ) (*state.TxResponse, error) // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. @@ -114,7 +114,7 @@ type API struct { ctx context.Context, fee state.Int, gasLim uint64, - blobs ...*apptypes.Blob, + blobs []*apptypes.Blob, ) (*state.TxResponse, error) `perm:"write"` CancelUnbondingDelegation func( ctx context.Context, @@ -192,9 +192,9 @@ func (api *API) SubmitPayForBlob( ctx context.Context, fee state.Int, gasLim uint64, - blobs ...*apptypes.Blob, + blobs []*apptypes.Blob, ) (*state.TxResponse, error) { - return api.Internal.SubmitPayForBlob(ctx, fee, gasLim, blobs...) + return api.Internal.SubmitPayForBlob(ctx, fee, gasLim, blobs) } func (api *API) CancelUnbondingDelegation( diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 27a10a5592..f740dc12fa 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -2,17 +2,60 @@ package tests import ( "context" + "fmt" "testing" + "github.com/filecoin-project/go-jsonrpc/auth" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/blob/blobtest" + "github.com/celestiaorg/celestia-node/libs/authtoken" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) +// TestBlobRPC ensures that blobs can be submited via rpc +func TestBlobRPC(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + + // start a bridge node + bridge := sw.NewBridgeNode() + err := bridge.Start(ctx) + require.NoError(t, err) + + signer := bridge.AdminSigner + bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() + fmt.Println("bridgeAddr: ", bridgeAddr) + + jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) + require.NoError(t, err) + + client, err := client.NewClient(ctx, bridgeAddr, jwt) + require.NoError(t, err) + + appBlobs, err := blobtest.GenerateBlobs([]int{8}, false) + require.NoError(t, err) + + newBlob, err := blob.NewBlob( + appBlobs[0].ShareVersion, + append([]byte{appBlobs[0].NamespaceVersion}, appBlobs[0].NamespaceID...), + appBlobs[0].Data, + ) + require.NoError(t, err) + + height, err := client.Blob.Submit(ctx, []*blob.Blob{newBlob}) + require.NoError(t, err) + require.True(t, height != 0) +} + // TestHeaderSubscription ensures that the header subscription over RPC works // as intended and gets canceled successfully after rpc context cancellation. func TestHeaderSubscription(t *testing.T) { diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index db56bad8c4..51c2ee9d62 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/celestiaorg/nmt/namespace" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" @@ -54,7 +55,7 @@ func TestBlobModule(t *testing.T) { lightNode := sw.NewNodeWithConfig(node.Light, lightCfg) require.NoError(t, lightNode.Start(ctx)) - height, err := fullNode.BlobServ.Submit(ctx, blobs...) + height, err := fullNode.BlobServ.Submit(ctx, blobs) require.NoError(t, err) _, err = fullNode.HeaderServ.WaitForHeight(ctx, height) @@ -77,7 +78,7 @@ func TestBlobModule(t *testing.T) { { name: "GetAll", doFn: func(t *testing.T) { - newBlobs, err := fullNode.BlobServ.GetAll(ctx, height, blobs[0].Namespace()) + newBlobs, err := fullNode.BlobServ.GetAll(ctx, height, []namespace.ID{blobs[0].Namespace()}) require.NoError(t, err) require.Len(t, newBlobs, len(appBlobs0)) require.True(t, bytes.Equal(blobs[0].Commitment, newBlobs[0].Commitment)) diff --git a/state/core_access.go b/state/core_access.go index 1a0a5650ab..bd27f479e9 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -158,7 +158,7 @@ func (ca *CoreAccessor) SubmitPayForBlob( ctx context.Context, fee Int, gasLim uint64, - blobs ...*apptypes.Blob, + blobs []*apptypes.Blob, ) (*TxResponse, error) { if len(blobs) == 0 { return nil, errors.New("state: no blobs provided") From af8460aaee220162bbd7292a8df04c898fb34618 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 2 Jun 2023 12:22:41 +0200 Subject: [PATCH 0630/1008] fix(gateway): remove the GetByHeight hussle and rely on Module's logic (#2302) --- api/gateway/availability.go | 15 -------------- api/gateway/header.go | 17 ++------------- api/gateway/share.go | 39 ++++++++++------------------------- nodebuilder/blob/mocks/api.go | 3 ++- 4 files changed, 15 insertions(+), 59 deletions(-) diff --git a/api/gateway/availability.go b/api/gateway/availability.go index b35593e24f..e5e53e0dba 100644 --- a/api/gateway/availability.go +++ b/api/gateway/availability.go @@ -2,7 +2,6 @@ package gateway import ( "encoding/json" - "fmt" "net/http" "strconv" @@ -28,20 +27,6 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http return } - //TODO: change this to NetworkHead once the adjacency in the store is fixed. - head, err := h.header.LocalHead(r.Context()) - if err != nil { - writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) - return - } - if headHeight := int(head.Height()); headHeight < height { - err = fmt.Errorf( - "current head local chain head: %d is lower than requested height: %d"+ - " give header sync some time and retry later", headHeight, height) - writeError(w, http.StatusServiceUnavailable, heightAvailabilityEndpoint, err) - return - } - header, err := h.header.GetByHeight(r.Context(), uint64(height)) if err != nil { writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) diff --git a/api/gateway/header.go b/api/gateway/header.go index 88ac3b0923..5b8a82351c 100644 --- a/api/gateway/header.go +++ b/api/gateway/header.go @@ -2,7 +2,6 @@ package gateway import ( "encoding/json" - "fmt" "net/http" "strconv" @@ -70,24 +69,12 @@ func (h *Handler) performGetHeaderRequest( writeError(w, http.StatusBadRequest, endpoint, err) return nil, err } - //TODO: change this to NetworkHead once the adjacency in the store is fixed. - head, err := h.header.LocalHead(r.Context()) - if err != nil { - writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) - return nil, err - } - if headHeight := int(head.Height()); headHeight < height { - err = fmt.Errorf( - "current head local chain head: %d is lower than requested height: %d"+ - " give header sync some time and retry later", headHeight, height) - writeError(w, http.StatusServiceUnavailable, endpoint, err) - return nil, err - } - // perform request + header, err := h.header.GetByHeight(r.Context(), uint64(height)) if err != nil { writeError(w, http.StatusInternalServerError, endpoint, err) return nil, err } + return header, nil } diff --git a/api/gateway/share.go b/api/gateway/share.go index 36a9778f1a..db5ed37286 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "encoding/json" - "fmt" "net/http" "strconv" @@ -13,7 +12,6 @@ import ( "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" ) @@ -44,14 +42,14 @@ func (h *Handler) handleSharesByNamespaceRequest(w http.ResponseWriter, r *http. writeError(w, http.StatusBadRequest, namespacedSharesEndpoint, err) return } - shares, headerHeight, err := h.getShares(r.Context(), height, nID) + shares, err := h.getShares(r.Context(), height, nID) if err != nil { writeError(w, http.StatusInternalServerError, namespacedSharesEndpoint, err) return } resp, err := json.Marshal(&NamespacedSharesResponse{ Shares: shares, - Height: uint64(headerHeight), + Height: height, }) if err != nil { writeError(w, http.StatusInternalServerError, namespacedSharesEndpoint, err) @@ -69,7 +67,7 @@ func (h *Handler) handleDataByNamespaceRequest(w http.ResponseWriter, r *http.Re writeError(w, http.StatusBadRequest, namespacedDataEndpoint, err) return } - shares, headerHeight, err := h.getShares(r.Context(), height, nID) + shares, err := h.getShares(r.Context(), height, nID) if err != nil { writeError(w, http.StatusInternalServerError, namespacedDataEndpoint, err) return @@ -81,7 +79,7 @@ func (h *Handler) handleDataByNamespaceRequest(w http.ResponseWriter, r *http.Re } resp, err := json.Marshal(&NamespacedDataResponse{ Data: data, - Height: uint64(headerHeight), + Height: height, }) if err != nil { writeError(w, http.StatusInternalServerError, namespacedDataEndpoint, err) @@ -93,33 +91,18 @@ func (h *Handler) handleDataByNamespaceRequest(w http.ResponseWriter, r *http.Re } } -func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID) ([]share.Share, int64, error) { - // get header - var ( - err error - header *header.ExtendedHeader - ) - - //TODO: change this to NetworkHead once the adjacency in the store is fixed. - header, err = h.header.LocalHead(ctx) +func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID) ([]share.Share, error) { + header, err := h.header.GetByHeight(ctx, height) if err != nil { - return nil, 0, err + return nil, err } - if height > 0 { - if storeHeight := uint64(header.Height()); storeHeight < height { - return nil, 0, fmt.Errorf( - "current head local chain head: %d is lower than requested height: %d"+ - " give header sync some time and retry later", storeHeight, height) - } - header, err = h.header.GetByHeight(ctx, height) - } + shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, nID) if err != nil { - return nil, 0, err + return nil, err } - // perform request - shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, nID) - return shares.Flatten(), header.Height(), err + + return shares.Flatten(), nil } func dataFromShares(input []share.Share) (data [][]byte, err error) { diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index f99d1d8168..b7c61aa450 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + blob "github.com/celestiaorg/celestia-node/blob" namespace "github.com/celestiaorg/nmt/namespace" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. From c6f4d6896e0cfe847aa9ecff4add0be5631ba6f9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 2 Jun 2023 17:12:34 +0200 Subject: [PATCH 0631/1008] fix(blob): use default min gas price from core (#2306) Gas estimation broke from an incorrect constant, that can now be replaced with an app import Co-authored-by: Hlib Kanunnikov --- blob/service.go | 7 ++----- nodebuilder/tests/blob_test.go | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/blob/service.go b/blob/service.go index 53ada45450..0d1ccf0bf1 100644 --- a/blob/service.go +++ b/blob/service.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/types" logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt/namespace" @@ -25,10 +26,6 @@ var ( log = logging.Logger("blob") ) -// TODO(@vgonkivs): remove after bumping celestia-app -// defaultMinGasPrice is the default min gas price. -const defaultMinGasPrice = 0.001 - type Service struct { // accessor dials the given celestia-core endpoint to submit blobs. accessor *state.CoreAccessor @@ -58,7 +55,7 @@ func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { var ( gasLimit = estimateGas(blobs...) - fee = int64(defaultMinGasPrice * float64(gasLimit)) + fee = int64(appconsts.DefaultMinGasPrice * float64(gasLimit)) b = make([]*apptypes.Blob, len(blobs)) ) diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index 51c2ee9d62..537e320c5a 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -6,12 +6,13 @@ import ( "testing" "time" - "github.com/celestiaorg/nmt/namespace" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/blob/blobtest" "github.com/celestiaorg/celestia-node/nodebuilder/node" From 124722530a5175995944226c4a0048edbfbece59 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 2 Jun 2023 20:32:59 +0200 Subject: [PATCH 0632/1008] feat(cmd/rpc): adding blob module to rpc cli and fixing namespace parsing (#2308) This PR does the following: ### Adds blob module support `blob.Submit` has been added with custom parsing for posting a single blob. `blob.GetAll` also only takes a single namespace id for now. This will make devx easier ### Adds --print-request flag Now the request can also be displayed along with the json response. This will both help for debugging and building things that need to build the requests themselves ### Fixes namespace parsing Namespaces are now encoded correctly again. In addition, if you pass a namespace of 8 bytes, it will zero-pad and use share/nID version 0. ### Fixes `state.SubmitPayForBlob` Another issue was the change to an array. We now submit an array of a single blob, and changed the order of the arguments. The order is now: fee, gasLimit, namespaceID, blob data ### Adds formatted output JQ is no longer needed to have an indented output. But colors are missing --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- cmd/celestia/rpc.go | 188 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 158 insertions(+), 30 deletions(-) diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index afbe13c3f8..7992e8f2a8 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -18,7 +18,12 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" + apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/state" ) @@ -28,6 +33,7 @@ const ( var requestURL string var authTokenFlag string +var printRequest bool type jsonRPCRequest struct { ID int64 `json:"id"` @@ -36,6 +42,11 @@ type jsonRPCRequest struct { Params []interface{} `json:"params"` } +type outputWithRequest struct { + Request jsonRPCRequest + Response json.RawMessage +} + func init() { rpcCmd.PersistentFlags().StringVar( &requestURL, @@ -49,6 +60,12 @@ func init() { "", "Authorization token (if not provided, the "+authEnvKey+" environment variable will be used)", ) + rpcCmd.PersistentFlags().BoolVar( + &printRequest, + "print-request", + false, + "Print JSON-RPC request along with the response", + ) rootCmd.AddCommand(rpcCmd) } @@ -98,52 +115,117 @@ func parseParams(method string, params []string) []interface{} { } parsedParams[0] = root // 2. NamespaceID - if strings.HasPrefix(params[1], "0x") { - decoded, err := hex.DecodeString(params[1][2:]) - if err != nil { - panic("Error decoding namespace ID: hex string could not be decoded.") - } - parsedParams[1] = decoded - } else { - // otherwise, it's just a base64 string - parsedParams[1] = params[1] + nID, err := parseNamespace(params[1]) + if err != nil { + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } - return parsedParams - case "SubmitPayForBlob": + parsedParams[1] = nID + case "Submit": // 1. NamespaceID - if strings.HasPrefix(params[0], "0x") { - decoded, err := hex.DecodeString(params[0][2:]) - if err != nil { - panic("Error decoding namespace ID: hex string could not be decoded.") - } - parsedParams[0] = decoded - } else { - // otherwise, it's just a base64 string - parsedParams[0] = params[0] + var err error + nID, err := parseNamespace(params[0]) + if err != nil { + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } - // 2. Blob + // 2. Blob data + var blobData []byte switch { case strings.HasPrefix(params[1], "0x"): decoded, err := hex.DecodeString(params[1][2:]) if err != nil { panic("Error decoding blob: hex string could not be decoded.") } - parsedParams[0] = decoded + blobData = decoded case strings.HasPrefix(params[1], "\""): // user input an utf string that needs to be encoded to base64 - parsedParams[1] = base64.StdEncoding.EncodeToString([]byte(params[1])) + base64.StdEncoding.Encode(blobData, []byte(params[1])) default: // otherwise, we assume the user has already encoded their input to base64 - parsedParams[1] = params[1] + blobData, err = base64.StdEncoding.DecodeString(params[1]) + if err != nil { + panic("Error decoding blob data: base64 string could not be decoded.") + } } - // 3. Fee (state.Int is a string) - parsedParams[2] = params[2] - // 4. GasLimit (uint64) - num, err := strconv.ParseUint(params[3], 10, 64) + parsedBlob, err := blob.NewBlob(0, nID, blobData) + if err != nil { + panic(fmt.Sprintf("Error creating blob: %v", err)) + } + parsedParams[0] = []*blob.Blob{parsedBlob} + // param count doesn't match input length, so cut off nil values + return parsedParams[:1] + case "SubmitPayForBlob": + // 1. Fee (state.Int is a string) + parsedParams[0] = params[0] + // 2. GasLimit (uint64) + num, err := strconv.ParseUint(params[1], 10, 64) if err != nil { panic("Error parsing gas limit: uint64 could not be parsed.") } - parsedParams[3] = num + parsedParams[1] = num + // 3. NamespaceID + nID, err := parseNamespace(params[2]) + if err != nil { + panic(fmt.Sprintf("Error parsing namespace: %v", err)) + } + // 4. Blob data + var blobData []byte + switch { + case strings.HasPrefix(params[3], "0x"): + decoded, err := hex.DecodeString(params[3][2:]) + if err != nil { + panic("Error decoding blob: hex string could not be decoded.") + } + blobData = decoded + case strings.HasPrefix(params[3], "\""): + // user input an utf string that needs to be encoded to base64 + base64.StdEncoding.Encode(blobData, []byte(params[3])) + default: + // otherwise, we assume the user has already encoded their input to base64 + blobData, err = base64.StdEncoding.DecodeString(params[3]) + if err != nil { + panic("Error decoding blob: base64 string could not be decoded.") + } + } + parsedParams[2] = []*apptypes.Blob{{ + NamespaceId: nID[1:], + Data: blobData, + ShareVersion: 0, + NamespaceVersion: 0, + }} + return parsedParams[:3] + case "Get": + // 1. Height + num, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + panic("Error parsing gas limit: uint64 could not be parsed.") + } + parsedParams[0] = num + // 2. NamespaceID + nID, err := parseNamespace(params[1]) + if err != nil { + panic(fmt.Sprintf("Error parsing namespace: %v", err)) + } + parsedParams[1] = nID + // 3: Commitment + commitment, err := base64.StdEncoding.DecodeString(params[2]) + if err != nil { + panic("Error decoding commitment: base64 string could not be decoded.") + } + parsedParams[2] = commitment + return parsedParams + case "GetAll": // NOTE: Over the cli, you can only pass one namespace + // 1. Height + num, err := strconv.ParseUint(params[0], 10, 64) + if err != nil { + panic("Error parsing gas limit: uint64 could not be parsed.") + } + parsedParams[0] = num + // 2. NamespaceID + nID, err := parseNamespace(params[1]) + if err != nil { + panic(fmt.Sprintf("Error parsing namespace: %v", err)) + } + parsedParams[1] = []namespace.ID{nID} return parsedParams case "QueryDelegation", "QueryUnbonding", "BalanceForAddress": var err error @@ -284,7 +366,27 @@ func sendJSONRPCRequest(namespace, method string, params []interface{}) { log.Fatalf("Error reading response body: %v", err) //nolint:gocritic } - fmt.Println(string(responseBody)) + rawResponseJSON, err := parseJSON(string(responseBody)) + if err != nil { + panic(err) + } + if printRequest { + output, err := json.MarshalIndent(outputWithRequest{ + Request: request, + Response: rawResponseJSON, + }, "", " ") + if err != nil { + panic(fmt.Sprintf("Error marshaling JSON-RPC response: %v", err)) + } + fmt.Println(string(output)) + return + } + + output, err := json.MarshalIndent(rawResponseJSON, "", " ") + if err != nil { + panic(fmt.Sprintf("Error marshaling JSON-RPC response: %v", err)) + } + fmt.Println(string(output)) } func parseAddressFromString(addrStr string) (state.Address, error) { @@ -322,6 +424,32 @@ func parseSignatureForHelpstring(methodSig reflect.StructField) string { return simplifiedSignature } +func parseNamespace(param string) (namespace.ID, error) { + var nID []byte + var err error + if strings.HasPrefix(param, "0x") { + decoded, err := hex.DecodeString(param[2:]) + if err != nil { + return nil, fmt.Errorf("error decoding namespace ID: %w", err) + } + nID = decoded + } else { + // otherwise, it's just a base64 string + nID, err = base64.StdEncoding.DecodeString(param) + if err != nil { + return nil, fmt.Errorf("error decoding namespace ID: %w", err) + } + } + // if the namespace ID is 8 bytes, add v0 share + namespace prefix and zero pad + if len(nID) == 8 { + nID, err = share.NewNamespaceV0(nID) + if err != nil { + return nil, err + } + } + return nID, nil +} + func parseJSON(param string) (json.RawMessage, error) { var raw json.RawMessage err := json.Unmarshal([]byte(param), &raw) From 504ff93444eb46d9c3adeedb2f58d289ec1ef746 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Sat, 3 Jun 2023 03:12:18 +0800 Subject: [PATCH 0633/1008] fix(getter/cascade): cascade getter should return fast on context cancel (#2310) ## Overview Also fixes incorrect errors.Is for `share.ErrNamespaceNotFound` Resolves https://github.com/celestiaorg/celestia-node/issues/2174 --- share/getters/cascade.go | 16 +++++++++------- share/getters/cascade_test.go | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/share/getters/cascade.go b/share/getters/cascade.go index d65b902e81..1a0d8fb274 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -122,16 +122,18 @@ func cascadeGetters[V any]( getCtx, cancel := ctxWithSplitTimeout(ctx, len(getters)-i, 0) val, getErr := get(getCtx, getter) cancel() - if getErr == nil { - return val, nil + if getErr == nil || errors.Is(getErr, share.ErrNamespaceNotFound) { + return val, getErr } - if errors.Is(share.ErrNamespaceNotFound, getErr) { - return zero, getErr + + if errors.Is(getErr, errOperationNotSupported) { + continue } - if !errors.Is(getErr, errOperationNotSupported) { - err = errors.Join(err, getErr) - span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) + err = errors.Join(err, getErr) + span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) + if ctx.Err() != nil { + return zero, err } } return zero, err diff --git a/share/getters/cascade_test.go b/share/getters/cascade_test.go index e3a324c5e9..d955c50682 100644 --- a/share/getters/cascade_test.go +++ b/share/getters/cascade_test.go @@ -52,6 +52,7 @@ func TestCascade(t *testing.T) { timeoutGetter := mocks.NewMockGetter(ctrl) immediateFailGetter := mocks.NewMockGetter(ctrl) successGetter := mocks.NewMockGetter(ctrl) + ctxGetter := mocks.NewMockGetter(ctrl) timeoutGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, _ *share.Root) (*rsmt2d.ExtendedDataSquare, error) { return nil, context.DeadlineExceeded @@ -60,6 +61,10 @@ func TestCascade(t *testing.T) { Return(nil, errors.New("second getter fails immediately")).AnyTimes() successGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). Return(nil, nil).AnyTimes() + ctxGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, _ *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + return nil, ctx.Err() + }).AnyTimes() get := func(ctx context.Context, get share.Getter) (*rsmt2d.ExtendedDataSquare, error) { return get.GetEDS(ctx, nil) @@ -96,6 +101,15 @@ func TestCascade(t *testing.T) { assert.Equal(t, strings.Count(err.Error(), "\n"), 2) }) + t.Run("Context Canceled", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + cancel() + getters := []share.Getter{ctxGetter, ctxGetter, ctxGetter} + _, err := cascadeGetters(ctx, getters, get) + assert.Error(t, err) + assert.Equal(t, strings.Count(err.Error(), "\n"), 0) + }) + t.Run("Single", func(t *testing.T) { getters := []share.Getter{successGetter} _, err := cascadeGetters(ctx, getters, get) From 118c4a90ce90fdd062e2298beec5a4f68db88914 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 5 Jun 2023 18:24:29 +0800 Subject: [PATCH 0634/1008] fix(nodebuilder): fix parsing url for core.ip (#2313) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/2311 --- nodebuilder/core/flags.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nodebuilder/core/flags.go b/nodebuilder/core/flags.go index 901d910ea6..01ff031991 100644 --- a/nodebuilder/core/flags.go +++ b/nodebuilder/core/flags.go @@ -3,6 +3,7 @@ package core import ( "fmt" "net" + "net/url" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -50,7 +51,11 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) error { ip := net.ParseIP(coreIP) if ip == nil { - ips, err := net.LookupIP(coreIP) + u, err := url.Parse(coreIP) + if err != nil { + return fmt.Errorf("failed to parse url: %w", err) + } + ips, err := net.LookupIP(u.Host) if err != nil { return fmt.Errorf("failed to resolve DNS record: %v", err) } From 59b9daf44644f3c60d8e545664d7adb60f8721bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:26:56 +0300 Subject: [PATCH 0635/1008] chore(deps): bump github.com/filecoin-project/go-jsonrpc from 0.1.9 to 0.3.1 (#2318) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index fd0bf7eb4c..474dc02b05 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.4 github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/dagstore v0.5.6 - github.com/filecoin-project/go-jsonrpc v0.1.9 + github.com/filecoin-project/go-jsonrpc v0.3.1 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 diff --git a/go.sum b/go.sum index 92583018a9..285f5c257d 100644 --- a/go.sum +++ b/go.sum @@ -574,8 +574,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/filecoin-project/go-jsonrpc v0.1.9 h1:HRWLxo7HAWzI3xZGeFG4LZJoYpms+Q+8kwmMTLnyS3A= -github.com/filecoin-project/go-jsonrpc v0.1.9/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= +github.com/filecoin-project/go-jsonrpc v0.3.1 h1:qwvAUc5VwAkooquKJmfz9R2+F8znhiqcNHYjEp/NM10= +github.com/filecoin-project/go-jsonrpc v0.3.1/go.mod h1:jBSvPTl8V1N7gSTuCR4bis8wnQnIjHbRPpROol6iQKM= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -830,7 +830,6 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -1052,7 +1051,6 @@ github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JP github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= -github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= From f05b71f39720a202d4043a43eabc2fec401ec735 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 5 Jun 2023 20:30:29 +0800 Subject: [PATCH 0636/1008] fix(das): limit amount of recent jobs (#2314) ## Overview Resolves https://github.com/celestiaorg/celestia-node/issues/2312 --- das/checkpoint.go | 16 ++++++++-------- das/coordinator.go | 9 ++++++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/das/checkpoint.go b/das/checkpoint.go index a38eca828c..bb023a19da 100644 --- a/das/checkpoint.go +++ b/das/checkpoint.go @@ -23,15 +23,15 @@ type workerCheckpoint struct { func newCheckpoint(stats SamplingStats) checkpoint { workers := make([]workerCheckpoint, 0, len(stats.Workers)) for _, w := range stats.Workers { - // no need to store retry jobs, since they will resume from failed heights map - if w.JobType == retryJob { - continue + // no need to resume recent jobs after restart. On the other hand, retry jobs will resume from + // failed heights map. it leaves only catchup jobs to be stored and resumed + if w.JobType == catchupJob { + workers = append(workers, workerCheckpoint{ + From: w.Curr, + To: w.To, + JobType: w.JobType, + }) } - workers = append(workers, workerCheckpoint{ - From: w.Curr, - To: w.To, - JobType: w.JobType, - }) } return checkpoint{ SampleFrom: stats.CatchupHead + 1, diff --git a/das/coordinator.go b/das/coordinator.go index 2184dce2c8..852a40d24d 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -81,7 +81,9 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { select { case head := <-sc.updHeadCh: if sc.state.isNewHead(head.Height()) { - sc.runWorker(ctx, sc.state.recentJob(head)) + if !sc.recentJobsLimitReached() { + sc.runWorker(ctx, sc.state.recentJob(head)) + } sc.state.updateHead(head.Height()) // run worker without concurrency limit restrictions to reduced delay sc.metrics.observeNewHead(ctx) @@ -146,3 +148,8 @@ func (sc *samplingCoordinator) getCheckpoint(ctx context.Context) (checkpoint, e func (sc *samplingCoordinator) concurrencyLimitReached() bool { return len(sc.state.inProgress) >= sc.concurrencyLimit } + +// recentJobsLimitReached indicates whether concurrency limit for recent jobs has been reached +func (sc *samplingCoordinator) recentJobsLimitReached() bool { + return len(sc.state.inProgress) >= 2*sc.concurrencyLimit +} From e5fcbe2a829b30c12df5c5ebfae12cb790a10239 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 5 Jun 2023 14:39:49 +0200 Subject: [PATCH 0637/1008] chore(state): extend error handling in core accessor (#2307) Co-authored-by: Hlib Kanunnikov --- go.mod | 2 +- state/core_access.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 474dc02b05..052eb5bcf5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch require ( + cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.1 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 @@ -85,7 +86,6 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.8.0 // indirect cloud.google.com/go/storage v1.27.0 // indirect - cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect diff --git a/state/core_access.go b/state/core_access.go index bd27f479e9..0a265c2d08 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + sdkErrors "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/api/tendermint/abci" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdktypes "github.com/cosmos/cosmos-sdk/types" @@ -177,6 +178,10 @@ func (ca *CoreAccessor) SubmitPayForBlob( ca.lastPayForBlob = time.Now().UnixMilli() ca.payForBlobCount++ } + + if response.Code != 0 { + err = errors.Join(err, sdkErrors.ABCIError(response.Codespace, response.Code, response.Logs.String())) + } return response, err } From dd1a019349ff14b8b3883d2bb7899636bc318be0 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 6 Jun 2023 04:07:25 -0500 Subject: [PATCH 0638/1008] chore: bump celestia-app to v1.0.0-rc2 (#2305) Co-authored-by: Hlib Kanunnikov --- blob/blob_test.go | 2 +- core/eds.go | 2 +- go.mod | 12 ++++++------ go.sum | 25 ++++++++++++------------- share/eds/eds_test.go | 2 +- share/ipld/nmt.go | 10 ++++++---- share/share.go | 11 ++++++++--- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/blob/blob_test.go b/blob/blob_test.go index 7f34fbfc84..3aabd6559b 100644 --- a/blob/blob_test.go +++ b/blob/blob_test.go @@ -49,7 +49,7 @@ func TestBlob(t *testing.T) { blob[0].Namespace()[appns.NamespaceVersionSize:], ) require.NoError(t, err) - require.NoError(t, apptypes.ValidateBlobNamespaceID(ns)) + require.NoError(t, apptypes.ValidateBlobNamespace(ns)) }, }, { diff --git a/core/eds.go b/core/eds.go index c435f0e649..dc9b2b4a9e 100644 --- a/core/eds.go +++ b/core/eds.go @@ -24,7 +24,7 @@ func extendBlock(data types.Data) (*rsmt2d.ExtendedDataSquare, error) { return nil, nil } - sqr, err := square.Construct(data.Txs.ToSliceOfBytes(), appconsts.MaxSquareSize) + sqr, err := square.Construct(data.Txs.ToSliceOfBytes(), appconsts.LatestVersion, share.MaxSquareSize) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 052eb5bcf5..5656aac5b3 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-beta.3 - github.com/BurntSushi/toml v1.2.1 + github.com/BurntSushi/toml v1.3.0 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.0 - github.com/celestiaorg/celestia-app v1.0.0-rc0 + github.com/celestiaorg/celestia-app v1.0.0-rc2 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 @@ -61,7 +61,7 @@ require ( github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.24 go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 @@ -118,7 +118,7 @@ require ( github.com/cosmos/gogoproto v1.4.2 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.5 // indirect - github.com/cosmos/ibc-go/v6 v6.1.0 // indirect + github.com/cosmos/ibc-go/v6 v6.1.1 // indirect github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect @@ -136,7 +136,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect - github.com/ethereum/go-ethereum v1.11.6 // indirect + github.com/ethereum/go-ethereum v1.12.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -333,5 +333,5 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.21.0-tm-v0.34.27 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27 ) diff --git a/go.sum b/go.sum index 285f5c257d..128a3b4b5e 100644 --- a/go.sum +++ b/go.sum @@ -220,8 +220,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= +github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= @@ -341,10 +341,10 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc0 h1:wpuP5fTIEbLCP+U5pGwKfSzXUTE/bE8oqKECFN5yoO0= -github.com/celestiaorg/celestia-app v1.0.0-rc0/go.mod h1:C8pNwFQWBLYIGpdrFesO1uezthrKjv0H5meecYQc1ek= -github.com/celestiaorg/celestia-core v1.21.0-tm-v0.34.27 h1:EdkqFRBypVEq/nX2ZE7KQ6dTlN8j3rEYe+WGahWuSUk= -github.com/celestiaorg/celestia-core v1.21.0-tm-v0.34.27/go.mod h1:GVo91Wifg9KL/nFx9nPkpl0UIFdvvs4fhnly9GhGxZU= +github.com/celestiaorg/celestia-app v1.0.0-rc2 h1:/u7eespYtBpQtBSz3P8/rKfz9rW7QOxkH8ebh8T4VxI= +github.com/celestiaorg/celestia-app v1.0.0-rc2/go.mod h1:uiTWKTtRpVwvSiFDl2zausrU1ZBHBWgk7z52pfzJqJU= +github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27 h1:nmr9O5BflgNR1aWehs1ZFw4obA//M/+g+SrSMK9sOBA= +github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27/go.mod h1:GVo91Wifg9KL/nFx9nPkpl0UIFdvvs4fhnly9GhGxZU= github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11 h1:Rd5EvJx1nG3KurBspVN51RVmvif0Lp2UVURbG2ad3Cs= github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11/go.mod h1:xCG6OUkJy5KUMEg20Zk010lra9XjkmKS3+bk0wp7bd8= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= @@ -460,8 +460,8 @@ github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4 github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= -github.com/cosmos/ibc-go/v6 v6.1.0 h1:o7oXws2vKkKfOFzJI+oNylRn44PCNt5wzHd/zKQKbvQ= -github.com/cosmos/ibc-go/v6 v6.1.0/go.mod h1:CY3zh2HLfetRiW8LY6kVHMATe90Wj/UOoY8T6cuB0is= +github.com/cosmos/ibc-go/v6 v6.1.1 h1:oqqMNyjj6SLQF8rvgCaDGwfdITEIsbhs8F77/8xvRIo= +github.com/cosmos/ibc-go/v6 v6.1.1/go.mod h1:NL17FpFAaWjRFVb1T7LUKuOoMSsATPpu+Icc4zL5/Ik= github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= @@ -543,7 +543,6 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= @@ -564,8 +563,8 @@ github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWS github.com/etclabscore/go-openrpc-reflect v0.0.37 h1:IH0e7JqIvR9OhbbFWi/BHIkXrqbR3Zyia3RJ733eT6c= github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.11.6 h1:2VF8Mf7XiSUfmoNOy3D+ocfl9Qu8baQBrCNbo2CXQ8E= -github.com/ethereum/go-ethereum v1.11.6/go.mod h1:+a8pUj1tOyJ2RinsNQD4326YS+leSoKGiG/uVVb0x6Y= +github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= +github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= @@ -1924,8 +1923,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 8df05d7d53..ea0f06c138 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -39,7 +39,7 @@ func TestQuadrantOrder(t *testing.T) { {"smol", 2}, {"still smol", 8}, {"default mainnet", appconsts.DefaultGovMaxSquareSize}, - {"max", appconsts.MaxSquareSize}, + {"max", share.MaxSquareSize}, } testShareSize := 64 diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index e5e7d41cd1..3923260555 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -38,10 +38,6 @@ const ( // that contain an NMT node (inner and leaf nodes). sha256NamespaceFlagged = 0x7701 - // MaxSquareSize is currently the maximum size supported for unerasured data in - // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.MaxSquareSize - // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize @@ -65,6 +61,12 @@ const ( NMTIgnoreMaxNamespace = true ) +var ( + // MaxSquareSize is currently the maximum size supported for unerasured data in + // rsmt2d.ExtendedDataSquare. + MaxSquareSize = appconsts.SquareSizeUpperBound(appconsts.LatestVersion) +) + func init() { // required for Bitswap to hash and verify inbound data correctly mhcore.Register(sha256NamespaceFlagged, func() hash.Hash { diff --git a/share/share.go b/share/share.go index 06f911636d..0178054a9f 100644 --- a/share/share.go +++ b/share/share.go @@ -8,6 +8,8 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/share/ipld" ) var ( @@ -18,15 +20,18 @@ var ( ) const ( - // MaxSquareSize is currently the maximum size supported for unerasured data in - // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.MaxSquareSize // NamespaceSize is a system-wide size for NMT namespaces. NamespaceSize = appconsts.NamespaceSize // Size is a system-wide size of a share, including both data and namespace ID Size = appconsts.ShareSize ) +var ( + // MaxSquareSize is currently the maximum size supported for unerasured data in + // rsmt2d.ExtendedDataSquare. + MaxSquareSize = ipld.MaxSquareSize +) + // Share contains the raw share data without the corresponding namespace. // NOTE: Alias for the byte is chosen to keep maximal compatibility, especially with rsmt2d. // Ideally, we should define reusable type elsewhere and make everyone(Core, rsmt2d, ipld) to rely From 72757e32ae3a76a25b6669b9cd54652b9070610d Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Tue, 6 Jun 2023 14:46:42 +0200 Subject: [PATCH 0639/1008] chore: bump common docker ci version pipeline (#2330) --- .github/workflows/docker-build-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index ce629145b9..0ddebbd750 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -17,6 +17,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.1.1 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.0 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From a2685d47a2ca1164cb1cd6d579b849a4f8372a80 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 6 Jun 2023 21:34:13 +0800 Subject: [PATCH 0640/1008] !feat(api/gateway): return txdata from submitPFB if it is present (#2329) ## Overview Closes https://github.com/celestiaorg/celestia-node/issues/2328 --- api/gateway/state.go | 16 +++++++++---- api/gateway/state_test.go | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 api/gateway/state_test.go diff --git a/api/gateway/state.go b/api/gateway/state.go index 2576ad4848..fdd627b232 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -148,17 +148,23 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } // perform request - txResp, err := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*apptypes.Blob{blob}) - if err != nil { + txResp, txerr := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*apptypes.Blob{blob}) + if txerr != nil && txResp == nil { + // no tx data to return writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) - return } - resp, err := json.Marshal(txResp) + + bs, err := json.Marshal(&txResp) if err != nil { writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) return } - _, err = w.Write(resp) + + // if error returned, change status from 200 to 206 + if txerr != nil { + w.WriteHeader(http.StatusPartialContent) + } + _, err = w.Write(bs) if err != nil { log.Errorw("writing response", "endpoint", submitPFBEndpoint, "err", err) } diff --git a/api/gateway/state_test.go b/api/gateway/state_test.go new file mode 100644 index 0000000000..3c01c9f0f8 --- /dev/null +++ b/api/gateway/state_test.go @@ -0,0 +1,48 @@ +package gateway + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks" + "github.com/celestiaorg/celestia-node/state" +) + +func TestHandleSubmitPFB(t *testing.T) { + ctrl := gomock.NewController(t) + mock := stateMock.NewMockModule(ctrl) + handler := NewHandler(mock, nil, nil, nil) + + t.Run("partial response", func(t *testing.T) { + txResponse := state.TxResponse{ + Height: 1, + TxHash: "hash", + Codespace: "codespace", + Code: 1, + } + // simulate core-app err, since it is not exported + timedErr := errors.New("timed out waiting for tx to be included in a block") + mock.EXPECT().SubmitPayForBlob(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(&txResponse, timedErr) + + bs, err := json.Marshal(submitPFBRequest{}) + require.NoError(t, err) + httpreq := httptest.NewRequest("GET", "/", bytes.NewReader(bs)) + respRec := httptest.NewRecorder() + handler.handleSubmitPFB(respRec, httpreq) + + var resp state.TxResponse + err = json.NewDecoder(respRec.Body).Decode(&resp) + require.NoError(t, err) + + require.Equal(t, http.StatusPartialContent, respRec.Code) + require.Equal(t, resp, txResponse) + }) +} From ae8db72b5c956362a07bc5dd0c8d10e553b50aa0 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 8 Jun 2023 08:58:20 +0200 Subject: [PATCH 0641/1008] fix(cmd/rpc): plaintext parsing of data for blob.Submit and state.SubmitPayForBlob (#2340) Closes #2323 --- cmd/celestia/rpc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 7992e8f2a8..4139e0f709 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -138,6 +138,8 @@ func parseParams(method string, params []string) []interface{} { blobData = decoded case strings.HasPrefix(params[1], "\""): // user input an utf string that needs to be encoded to base64 + src := []byte(params[1]) + blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src))) base64.StdEncoding.Encode(blobData, []byte(params[1])) default: // otherwise, we assume the user has already encoded their input to base64 @@ -178,6 +180,8 @@ func parseParams(method string, params []string) []interface{} { blobData = decoded case strings.HasPrefix(params[3], "\""): // user input an utf string that needs to be encoded to base64 + src := []byte(params[1]) + blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src))) base64.StdEncoding.Encode(blobData, []byte(params[3])) default: // otherwise, we assume the user has already encoded their input to base64 From a65280181615110e96908254336bf8d701098b66 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 8 Jun 2023 10:01:45 +0200 Subject: [PATCH 0642/1008] fix(dagstore): changing log level to WARN to shorten startup (#2336) Dagstore's debug logs increase startup time by 3-4x, and dagstore in general produces a lot of debug logs that are not very important for collecting. --- logs/logs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/logs/logs.go b/logs/logs.go index 4ae1cddd3f..15c26888c5 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -21,6 +21,8 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("net/identify", "ERROR") _ = logging.SetLogLevel("shrex/nd", "WARN") _ = logging.SetLogLevel("shrex/eds", "WARN") + _ = logging.SetLogLevel("dagstore", "WARN") + _ = logging.SetLogLevel("dagstore/upgrader", "WARN") _ = logging.SetLogLevel("fx", "FATAL") } From 1cab8ebaf82da2562d3b9e1478dfeb2c6e2ead81 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 8 Jun 2023 12:19:22 +0200 Subject: [PATCH 0643/1008] fix(share): left pad nid in NewNamespaceV0 instead of right padding (#2341) --- share/nid.go | 8 ++++--- share/nid_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 share/nid_test.go diff --git a/share/nid.go b/share/nid.go index dfd6a5de7e..b7fd4e5836 100644 --- a/share/nid.go +++ b/share/nid.go @@ -7,15 +7,17 @@ import ( "github.com/celestiaorg/nmt/namespace" ) -// NewNamespaceV0 takes variable size byte slice anc creates version 0 Namespace ID. -// The bytes slice must be <= 10 bytes. +// NewNamespaceV0 takes a variable size byte slice and creates a version 0 Namespace ID. +// The byte slice must be <= 10 bytes. +// If it is less than 10 bytes, it will be left padded to size 10 with 0s. func NewNamespaceV0(subNId []byte) (namespace.ID, error) { if lnid := len(subNId); lnid > appns.NamespaceVersionZeroIDSize { return nil, fmt.Errorf("namespace id must be <= %v, but it was %v bytes", appns.NamespaceVersionZeroIDSize, lnid) } id := make([]byte, appns.NamespaceIDSize) - copy(id[appns.NamespaceVersionZeroPrefixSize:], subNId) + leftPaddingOffset := appns.NamespaceVersionZeroIDSize - len(subNId) + copy(id[appns.NamespaceVersionZeroPrefixSize+leftPaddingOffset:], subNId) appID, err := appns.New(appns.NamespaceVersionZero, id) if err != nil { diff --git a/share/nid_test.go b/share/nid_test.go new file mode 100644 index 0000000000..8f83d430e3 --- /dev/null +++ b/share/nid_test.go @@ -0,0 +1,56 @@ +package share + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/nmt/namespace" +) + +func TestNewNamespaceV0(t *testing.T) { + type testCase struct { + name string + subNid []byte + expected namespace.ID + wantErr bool + } + testCases := []testCase{ + { + name: "8 byte subNid, gets left padded", + subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + expected: namespace.ID{ + 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // filled zeros + 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, // id with left padding + wantErr: false, + }, + { + name: "10 byte subNid, no padding", + subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x9, 0x10}, + expected: namespace.ID{ + 0x0, // version + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // filled zeros + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10}, // id + wantErr: false, + }, + { + name: "11 byte subNid", + subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x9, 0x10, 0x11}, + expected: []byte{}, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := NewNamespaceV0(tc.subNid) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expected, got) + }) + } +} From b046184fc15745913eb82238cddf0ec7d95f2dc9 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 8 Jun 2023 18:30:15 +0800 Subject: [PATCH 0644/1008] fix(daser) don't count recent jobs in total sampled stats (#2342) ## Overview Small fix for total sampled amount calculations --- das/stats.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/das/stats.go b/das/stats.go index 5799370f91..dda6be6cc0 100644 --- a/das/stats.go +++ b/das/stats.go @@ -34,7 +34,10 @@ type WorkerStats struct { func (s SamplingStats) totalSampled() uint64 { var inProgress uint64 for _, w := range s.Workers { - inProgress += w.To - w.Curr + 1 + // don't count recent jobs, since heights they are working on are after catchup head + if w.JobType != recentJob { + inProgress += w.To - w.Curr + 1 + } } return s.CatchupHead - inProgress - uint64(len(s.Failed)) } From f632908ebff8a349874d0bca3150ec7ca1ed3da1 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 8 Jun 2023 13:38:39 +0200 Subject: [PATCH 0645/1008] fix(state): fixing panic when err != nil but response is nil (#2339) There was a panic when err != nil but response == nil, for example in the case of: `rpc error: code = NotFound desc = account celestia1gchva5av4v798xmqttln9qw9smzrjm2cv2q8e7 not found` ``` 2023-06-07T14:20:38.731+0200 ERROR rpc go-jsonrpc@v0.3.1/handler.go:276 panic in rpc method 'blob.Submit': runtime error: invalid memory address or nil pointer dereference ``` --------- Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> --- state/core_access.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/core_access.go b/state/core_access.go index 0a265c2d08..712100d921 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -179,7 +179,7 @@ func (ca *CoreAccessor) SubmitPayForBlob( ca.payForBlobCount++ } - if response.Code != 0 { + if response != nil && response.Code != 0 { err = errors.Join(err, sdkErrors.ABCIError(response.Codespace, response.Code, response.Logs.String())) } return response, err From 0823f6b644eb6ba176ef5ae3c008c65018ed5f46 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 8 Jun 2023 13:53:31 +0200 Subject: [PATCH 0646/1008] fix!(state): use node blob type instead of app type to fix inconsistent unmarshalling (#2338) Breaks API to v0.2.0 Because state.SubmitPayForBlob was using apptypes.Blob, which cannot be unmarshalled from a node blob.Blob JSON object, it was requiring two separate types downstream. Now both blob Service and state Service use blob.Blob type. This means introducing an interface in blob.Service called blob.Submitter to avoid that state module import. --- api/docgen/examples.go | 12 ++++++++++++ api/gateway/state.go | 12 ++++++------ api/gateway/state_test.go | 11 ++++++++++- blob/service.go | 23 ++++++++++++----------- cmd/celestia/rpc.go | 12 +++++------- nodebuilder/blob/mocks/api.go | 3 +-- nodebuilder/das/mocks/api.go | 3 +-- nodebuilder/fraud/mocks/api.go | 13 ++++++------- nodebuilder/header/mocks/api.go | 18 ++++++++++++++++-- nodebuilder/node/admin.go | 2 +- nodebuilder/node/mocks/api.go | 3 +-- nodebuilder/share/mocks/api.go | 3 +-- nodebuilder/state/mocks/api.go | 8 ++++---- nodebuilder/state/state.go | 13 +++++++------ share/mocks/getter.go | 3 +-- state/core_access.go | 14 ++++++++++---- 16 files changed, 94 insertions(+), 59 deletions(-) diff --git a/api/docgen/examples.go b/api/docgen/examples.go index 8293396c7b..b19b06a7f8 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -131,6 +131,18 @@ func init() { } addToExampleValues(addrInfo) + namespace, err := share.NewNamespaceV0([]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10}) + if err != nil { + panic(err) + } + addToExampleValues(namespace) + + generatedBlob, err := blob.NewBlob(0, namespace, []byte("This is an example of some blob data")) + if err != nil { + panic(err) + } + addToExampleValues(generatedBlob) + proof := nmt.NewInclusionProof(0, 4, [][]byte{[]byte("test")}, true) blobProof := &blob.Proof{&proof} addToExampleValues(blobProof) diff --git a/api/gateway/state.go b/api/gateway/state.go index fdd627b232..63ada8ebb2 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -10,8 +10,8 @@ import ( "github.com/gorilla/mux" "github.com/celestiaorg/celestia-app/pkg/appconsts" - apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/state" ) @@ -141,14 +141,14 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } fee := types.NewInt(req.Fee) - blob := &apptypes.Blob{ - NamespaceId: nID, - Data: data, - ShareVersion: uint32(appconsts.DefaultShareVersion), + constructedBlob, err := blob.NewBlob(appconsts.DefaultShareVersion, nID, data) + if err != nil { + writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) + return } // perform request - txResp, txerr := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*apptypes.Blob{blob}) + txResp, txerr := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*blob.Blob{constructedBlob}) if txerr != nil && txResp == nil { // no tx data to return writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) diff --git a/api/gateway/state_test.go b/api/gateway/state_test.go index 3c01c9f0f8..a613471a04 100644 --- a/api/gateway/state_test.go +++ b/api/gateway/state_test.go @@ -2,6 +2,7 @@ package gateway import ( "bytes" + "encoding/hex" "encoding/json" "errors" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/require" stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/state" ) @@ -32,7 +34,14 @@ func TestHandleSubmitPFB(t *testing.T) { mock.EXPECT().SubmitPayForBlob(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&txResponse, timedErr) - bs, err := json.Marshal(submitPFBRequest{}) + ns, err := share.NewNamespaceV0([]byte("abc")) + require.NoError(t, err) + hexNs := hex.EncodeToString(ns[:]) + + bs, err := json.Marshal(submitPFBRequest{ + NamespaceID: hexNs, + Data: "DEADBEEF", + }) require.NoError(t, err) httpreq := httptest.NewRequest("GET", "/", bytes.NewReader(bs)) respRec := httptest.NewRecorder() diff --git a/blob/service.go b/blob/service.go index 0d1ccf0bf1..0b43493e27 100644 --- a/blob/service.go +++ b/blob/service.go @@ -6,17 +6,16 @@ import ( "fmt" "sync" + "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/types" logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" - apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/state" ) var ( @@ -26,9 +25,16 @@ var ( log = logging.Logger("blob") ) +// Submitter is an interface that allows submitting blobs to the celestia-core. It is used to +// avoid a circular dependency between the blob and the state package, since the state package needs +// the blob.Blob type for this signature. +type Submitter interface { + SubmitPayForBlob(ctx context.Context, fee math.Int, gasLim uint64, blobs []*Blob) (*types.TxResponse, error) +} + type Service struct { // accessor dials the given celestia-core endpoint to submit blobs. - accessor *state.CoreAccessor + blobSumitter Submitter // shareGetter retrieves the EDS to fetch all shares from the requested header. shareGetter share.Getter // headerGetter fetches header by the provided height @@ -36,12 +42,12 @@ type Service struct { } func NewService( - state *state.CoreAccessor, + submitter Submitter, getter share.Getter, headerGetter func(context.Context, uint64) (*header.ExtendedHeader, error), ) *Service { return &Service{ - accessor: state, + blobSumitter: submitter, shareGetter: getter, headerGetter: headerGetter, } @@ -56,14 +62,9 @@ func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { var ( gasLimit = estimateGas(blobs...) fee = int64(appconsts.DefaultMinGasPrice * float64(gasLimit)) - b = make([]*apptypes.Blob, len(blobs)) ) - for i, blob := range blobs { - b[i] = &blob.Blob - } - - resp, err := s.accessor.SubmitPayForBlob(ctx, types.NewInt(fee), gasLimit, b) + resp, err := s.blobSumitter.SubmitPayForBlob(ctx, types.NewInt(fee), gasLimit, blobs) if err != nil { return 0, err } diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 4139e0f709..de8c55e3f4 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" - apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/api/rpc/client" @@ -190,12 +189,11 @@ func parseParams(method string, params []string) []interface{} { panic("Error decoding blob: base64 string could not be decoded.") } } - parsedParams[2] = []*apptypes.Blob{{ - NamespaceId: nID[1:], - Data: blobData, - ShareVersion: 0, - NamespaceVersion: 0, - }} + parsedBlob, err := blob.NewBlob(0, nID, blobData) + if err != nil { + panic(fmt.Sprintf("Error creating blob: %v", err)) + } + parsedParams[2] = []*blob.Blob{parsedBlob} return parsedParams[:3] case "Get": // 1. Height diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index b7c61aa450..f99d1d8168 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - blob "github.com/celestiaorg/celestia-node/blob" namespace "github.com/celestiaorg/nmt/namespace" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go index 68ffaf3c8c..c4046e90e8 100644 --- a/nodebuilder/das/mocks/api.go +++ b/nodebuilder/das/mocks/api.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - das "github.com/celestiaorg/celestia-node/das" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 5ede6f27c5..ba88131695 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + fraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + fraud0 "github.com/celestiaorg/go-fraud" gomock "github.com/golang/mock/gomock" - - fraud0 "github.com/celestiaorg/celestia-node/nodebuilder/fraud" - "github.com/celestiaorg/go-fraud" ) // MockModule is a mock of Module interface. @@ -38,10 +37,10 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // Get mocks base method. -func (m *MockModule) Get(arg0 context.Context, arg1 fraud.ProofType) ([]fraud0.Proof, error) { +func (m *MockModule) Get(arg0 context.Context, arg1 fraud0.ProofType) ([]fraud.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0, arg1) - ret0, _ := ret[0].([]fraud0.Proof) + ret0, _ := ret[0].([]fraud.Proof) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -53,10 +52,10 @@ func (mr *MockModuleMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { } // Subscribe mocks base method. -func (m *MockModule) Subscribe(arg0 context.Context, arg1 fraud.ProofType) (<-chan fraud0.Proof, error) { +func (m *MockModule) Subscribe(arg0 context.Context, arg1 fraud0.ProofType) (<-chan fraud.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Subscribe", arg0, arg1) - ret0, _ := ret[0].(<-chan fraud0.Proof) + ret0, _ := ret[0].(<-chan fraud.Proof) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 538169c6be..02529a8ef9 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -156,3 +155,18 @@ func (mr *MockModuleMockRecorder) SyncWait(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWait", reflect.TypeOf((*MockModule)(nil).SyncWait), arg0) } + +// WaitForHeight mocks base method. +func (m *MockModule) WaitForHeight(arg0 context.Context, arg1 uint64) (*header.ExtendedHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitForHeight", arg0, arg1) + ret0, _ := ret[0].(*header.ExtendedHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WaitForHeight indicates an expected call of WaitForHeight. +func (mr *MockModuleMockRecorder) WaitForHeight(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForHeight", reflect.TypeOf((*MockModule)(nil).WaitForHeight), arg0, arg1) +} diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index 41f8400101..cad8a51361 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -8,7 +8,7 @@ import ( logging "github.com/ipfs/go-log/v2" ) -const APIVersion = "v0.1.1" +const APIVersion = "v0.2.0" type module struct { tp Type diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index 39d500bc04..98df713429 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + node "github.com/celestiaorg/celestia-node/nodebuilder/node" auth "github.com/filecoin-project/go-jsonrpc/auth" gomock "github.com/golang/mock/gomock" - - node "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 1b26273c0f..586c6dab4b 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,12 +8,11 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 8398e0fd7b..814b9a0113 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,11 +9,11 @@ import ( reflect "reflect" math "cosmossdk.io/math" + blob "github.com/celestiaorg/celestia-node/blob" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" - types1 "github.com/tendermint/tendermint/proto/tendermint/types" - types2 "github.com/tendermint/tendermint/types" + types1 "github.com/tendermint/tendermint/types" ) // MockModule is a mock of Module interface. @@ -189,7 +189,7 @@ func (mr *MockModuleMockRecorder) QueryUnbonding(arg0, arg1 interface{}) *gomock } // SubmitPayForBlob mocks base method. -func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 math.Int, arg2 uint64, arg3 []*types1.Blob) (*types.TxResponse, error) { +func (m *MockModule) SubmitPayForBlob(arg0 context.Context, arg1 math.Int, arg2 uint64, arg3 []*blob.Blob) (*types.TxResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubmitPayForBlob", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*types.TxResponse) @@ -204,7 +204,7 @@ func (mr *MockModuleMockRecorder) SubmitPayForBlob(arg0, arg1, arg2, arg3 interf } // SubmitTx mocks base method. -func (m *MockModule) SubmitTx(arg0 context.Context, arg1 types2.Tx) (*types.TxResponse, error) { +func (m *MockModule) SubmitTx(arg0 context.Context, arg1 types1.Tx) (*types.TxResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubmitTx", arg0, arg1) ret0, _ := ret[0].(*types.TxResponse) diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 57f46a8fd6..c66205d594 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -5,8 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" - apptypes "github.com/celestiaorg/celestia-app/x/blob/types" - + "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/state" ) @@ -36,7 +35,9 @@ type Module interface { // Transfer sends the given amount of coins from default wallet of the node to the given account // address. - Transfer(ctx context.Context, to state.AccAddress, amount, fee state.Int, gasLimit uint64) (*state.TxResponse, error) + Transfer( + ctx context.Context, to state.AccAddress, amount, fee state.Int, gasLimit uint64, + ) (*state.TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in // a block. @@ -46,7 +47,7 @@ type Module interface { ctx context.Context, fee state.Int, gasLim uint64, - blobs []*apptypes.Blob, + blobs []*blob.Blob, ) (*state.TxResponse, error) // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. @@ -114,7 +115,7 @@ type API struct { ctx context.Context, fee state.Int, gasLim uint64, - blobs []*apptypes.Blob, + blobs []*blob.Blob, ) (*state.TxResponse, error) `perm:"write"` CancelUnbondingDelegation func( ctx context.Context, @@ -192,7 +193,7 @@ func (api *API) SubmitPayForBlob( ctx context.Context, fee state.Int, gasLim uint64, - blobs []*apptypes.Blob, + blobs []*blob.Blob, ) (*state.TxResponse, error) { return api.Internal.SubmitPayForBlob(ctx, fee, gasLim, blobs) } diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 12c36cb015..1c73c9170d 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -8,12 +8,11 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockGetter is a mock of Getter interface. diff --git a/state/core_access.go b/state/core_access.go index 712100d921..ca0947166d 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -21,10 +21,11 @@ import ( "google.golang.org/grpc/credentials/insecure" "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/x/blob" + appblob "github.com/celestiaorg/celestia-app/x/blob" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/header" ) @@ -159,17 +160,22 @@ func (ca *CoreAccessor) SubmitPayForBlob( ctx context.Context, fee Int, gasLim uint64, - blobs []*apptypes.Blob, + blobs []*blob.Blob, ) (*TxResponse, error) { if len(blobs) == 0 { return nil, errors.New("state: no blobs provided") } - response, err := blob.SubmitPayForBlob( + appblobs := make([]*apptypes.Blob, len(blobs)) + for i, blob := range blobs { + appblobs[i] = &blob.Blob + } + + response, err := appblob.SubmitPayForBlob( ctx, ca.signer, ca.coreConn, - blobs, + appblobs, apptypes.SetGasLimit(gasLim), withFee(fee), ) From c9443c6859808d1096f484eeb505a8ff9c3e12f9 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 13 Jun 2023 05:13:02 -0500 Subject: [PATCH 0647/1008] chore: mocha-2 support (#2326) Note that we're skipping mocha-1 because we reset the network after finding a state-breaking bug Friday Co-authored-by: Hlib Kanunnikov --- nodebuilder/p2p/bootstrap.go | 6 +++--- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 25f0a85120..c534aa4fba 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -44,9 +44,9 @@ var bootstrapList = map[Network][]string{ "/dns4/da-full-2-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWCs3wFmqwPn1u8pNU4BGsvLsob1ShTzvps8qEtTRuuuK5", }, Mocha: { - "/dns4/andromeda.celestia-devops.dev/tcp/2121/p2p/12D3KooWKvPXtV1yaQ6e3BRNUHa5Phh8daBwBi3KkGaSSkUPys6D", - "/dns4/libra.celestia-devops.dev/tcp/2121/p2p/12D3KooWK5aDotDcLsabBmWDazehQLMsDkRyARm1k7f1zGAXqbt4", - "/dns4/norma.celestia-devops.dev/tcp/2121/p2p/12D3KooWHYczJDVNfYVkLcNHPTDKCeiVvRhg8Q9JU3bE3m9eEVyY", + "/dns4/bootstr-mocha-1.celestia-mocha.com/tcp/2121/p2p/12D3KooWDRSJMbH3PS4dRDa11H7Tk615aqTUgkeEKz4pwd4sS6fN", + "/dns4/bootstr-mocha-2.celestia-mocha.com/tcp/2121/p2p/12D3KooWEk7cxtjQCC7kC84Uhs2j6dAHjdbwYnPcvUAqmj6Zsry2", + "/dns4/bootstr-mocha-3.celestia-mocha.com/tcp/2121/p2p/12D3KooWBE4QcFXZzENf2VRo6Y5LBvp9gzmpYRHKCvgGzEYj7Hdn", }, BlockspaceRace: { "/dns4/bootstr-incent-3.celestia.tools/tcp/2121/p2p/12D3KooWNzdKcHagtvvr6qtjcPTAdCN6ZBiBLH8FBHbihxqu4GZx", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index bc0fc3810d..0a36dc54cc 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -24,7 +24,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Arabica: "E5D620B5BE7873222DCD83464C285FD0F215C209393E7481F9A5979280AD6CA2", - Mocha: "8038B21032C941372ED601699857043C12E5CC7D5945DCEEA4567D11B5712526", + Mocha: "1181AF8EAE5DDF3CBBFF3BF3CC44C5B795DF5094F5A0CC0AE52921ECCA0AF3C8", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index ded3f0f1b1..dd04cd377c 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -14,7 +14,7 @@ const ( // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica-8" // Mocha testnet. See: celestiaorg/networks. - Mocha Network = "mocha" + Mocha Network = "mocha-2" // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. BlockspaceRace Network = "blockspacerace-0" // Private can be used to set up any private network, including local testing setups. From c41738a3f3d3743233e8b81f1c015f81dbd5eec3 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 13 Jun 2023 19:18:31 +0800 Subject: [PATCH 0648/1008] fix(nodebuilder): Fix dns lookup (#2332) ## Overview Cleanup core ip parsing logic Closes #2333 --------- Co-authored-by: Hlib Kanunnikov --- libs/utils/address.go | 9 ++++++--- libs/utils/address_test.go | 35 +++++++++++++++++++++++++++++------ nodebuilder/core/flags.go | 27 ++++++--------------------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/libs/utils/address.go b/libs/utils/address.go index 5971bf5a98..a8170e44b9 100644 --- a/libs/utils/address.go +++ b/libs/utils/address.go @@ -30,11 +30,14 @@ func ValidateAddr(addr string) (string, error) { } if ip := net.ParseIP(addr); ip == nil { - _, err = net.LookupHost(addr) + addrs, err := net.LookupHost(addr) if err != nil { - return addr, fmt.Errorf("could not resolve hostname or ip: %w", err) + return addr, fmt.Errorf("could not resolve %v: %w", addr, err) } + if len(addrs) == 0 { + return addr, fmt.Errorf("no IP addresses found for DNS record: %v", addr) + } + addr = addrs[0] } - return addr, nil } diff --git a/libs/utils/address_test.go b/libs/utils/address_test.go index c914a7d853..15452f4d1b 100644 --- a/libs/utils/address_test.go +++ b/libs/utils/address_test.go @@ -1,6 +1,7 @@ package utils import ( + "net" "testing" "github.com/stretchr/testify/require" @@ -13,8 +14,12 @@ func TestSanitizeAddr(t *testing.T) { }{ // Testcase: trims protocol prefix {addr: "http://celestia.org", want: "celestia.org"}, + // Testcase: protocol prefix trimmed already + {addr: "celestia.org", want: "celestia.org"}, // Testcase: trims protocol prefix, and trims port and trailing slash suffix {addr: "tcp://192.168.42.42:5050/", want: "192.168.42.42"}, + // Testcase: invariant ip + {addr: "192.168.42.42", want: "192.168.42.42"}, } for _, tt := range tests { @@ -27,23 +32,41 @@ func TestSanitizeAddr(t *testing.T) { } func TestValidateAddr(t *testing.T) { + type want struct { + addr string + unresolved bool + } var tests = []struct { addr string - want string + want want }{ // Testcase: ip is valid - {addr: "192.168.42.42:5050", want: "192.168.42.42"}, - // Testcase: hostname is valid - {addr: "https://celestia.org", want: "celestia.org"}, + {addr: "192.168.42.42:5050", want: want{addr: "192.168.42.42"}}, + // Testcase: ip is valid, no port + {addr: "192.168.42.42", want: want{addr: "192.168.42.42"}}, // Testcase: resolves localhost - {addr: "http://localhost:8080/", want: "localhost"}, + {addr: "http://localhost:8080/", want: want{unresolved: true}}, + // Testcase: hostname is valid + {addr: "https://celestia.org", want: want{unresolved: true}}, + // Testcase: hostname is valid, but no schema + {addr: "celestia.org", want: want{unresolved: true}}, } for _, tt := range tests { t.Run(tt.addr, func(t *testing.T) { got, err := ValidateAddr(tt.addr) require.NoError(t, err) - require.Equal(t, tt.want, got) + + // validate that returned value is ip + if ip := net.ParseIP(got); ip == nil { + t.Fatalf("empty ip") + } + + if tt.want.unresolved { + // unresolved addr has no addr to compare with + return + } + require.Equal(t, tt.want.addr, got) }) } } diff --git a/nodebuilder/core/flags.go b/nodebuilder/core/flags.go index 01ff031991..9cbed9b277 100644 --- a/nodebuilder/core/flags.go +++ b/nodebuilder/core/flags.go @@ -2,8 +2,6 @@ package core import ( "fmt" - "net" - "net/url" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -40,7 +38,10 @@ func Flags() *flag.FlagSet { } // ParseFlags parses Core flags from the given cmd and saves them to the passed config. -func ParseFlags(cmd *cobra.Command, cfg *Config) error { +func ParseFlags( + cmd *cobra.Command, + cfg *Config, +) error { coreIP := cmd.Flag(coreFlag).Value.String() if coreIP == "" { if cmd.Flag(coreGRPCFlag).Changed || cmd.Flag(coreRPCFlag).Changed { @@ -49,27 +50,11 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) error { return nil } - ip := net.ParseIP(coreIP) - if ip == nil { - u, err := url.Parse(coreIP) - if err != nil { - return fmt.Errorf("failed to parse url: %w", err) - } - ips, err := net.LookupIP(u.Host) - if err != nil { - return fmt.Errorf("failed to resolve DNS record: %v", err) - } - if len(ips) == 0 { - return fmt.Errorf("no IP addresses found for DNS record") - } - ip = ips[0] - } - rpc := cmd.Flag(coreRPCFlag).Value.String() grpc := cmd.Flag(coreGRPCFlag).Value.String() - cfg.IP = ip.String() + cfg.IP = coreIP cfg.RPCPort = rpc cfg.GRPCPort = grpc - return nil + return cfg.Validate() } From c157db61736592213108d226df2810eae9aca12d Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 14 Jun 2023 10:05:09 +0200 Subject: [PATCH 0649/1008] fix(rpc/client): stop overwriting existing clients (#2361) While working on #2356 I uncovered a bug: When using the `client.NewClient` constructor, it uses a global static struct to set the modules. This led to clients overwriting each other when multiple are initialized. --- api/rpc/client/client.go | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 9e7e577074..7ac8a55b3d 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -18,18 +18,11 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -// TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 -var client Client -var Modules = map[string]interface{}{ - "share": &client.Share.Internal, - "state": &client.State.Internal, - "header": &client.Header.Internal, - "fraud": &client.Fraud.Internal, - "das": &client.DAS.Internal, - "p2p": &client.P2P.Internal, - "node": &client.Node.Internal, - "blob": &client.Blob.Internal, -} +var ( + // staticClient is used for generating the OpenRPC spec. + staticClient Client + Modules = moduleMap(&staticClient) +) type Client struct { Fraud fraud.API @@ -61,7 +54,7 @@ func (m *multiClientCloser) closeAll() { } } -// Close closes the connections to all namespaces registered on the client. +// Close closes the connections to all namespaces registered on the staticClient. func (c *Client) Close() { c.closer.closeAll() } @@ -80,7 +73,8 @@ func NewClient(ctx context.Context, addr string, token string) (*Client, error) func newClient(ctx context.Context, addr string, authHeader http.Header) (*Client, error) { var multiCloser multiClientCloser - for name, module := range Modules { + var client Client + for name, module := range moduleMap(&client) { closer, err := jsonrpc.NewClient(ctx, addr, name, module, authHeader) if err != nil { return nil, err @@ -90,3 +84,17 @@ func newClient(ctx context.Context, addr string, authHeader http.Header) (*Clien return &client, nil } + +func moduleMap(client *Client) map[string]interface{} { + // TODO: this duplication of strings many times across the codebase can be avoided with issue #1176 + return map[string]interface{}{ + "share": &client.Share.Internal, + "state": &client.State.Internal, + "header": &client.Header.Internal, + "fraud": &client.Fraud.Internal, + "das": &client.DAS.Internal, + "p2p": &client.P2P.Internal, + "node": &client.Node.Internal, + "blob": &client.Blob.Internal, + } +} From 55db897aab533d2f00f444685aad6ccb15674f76 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 14 Jun 2023 10:10:55 +0200 Subject: [PATCH 0650/1008] fix(state): JSON marshaling for sdk Address wrapper (#2348) While debugging #2322 , I noticed that it is not only an RPC issue, but also a broken API one. All RPC methods that used the type `state.Address` were unusable, both via the client and via raw JSON calls. This is because the server was unable to marshal the address string value back into the interface type. To circumvent this, I have embedded the sdk.Address type in the same way that we embed the fraud proof type, to allow us to unmarshal it back into a concrete type. I have also added unit tests to fix this. In addition, it fixes the RPC parsing - so it closes #2322 . --- api/docgen/examples.go | 2 ++ api/gateway/state.go | 2 +- cmd/celestia/rpc.go | 16 +++------- nodebuilder/state/mocks/api.go | 7 +++-- state/address_test.go | 57 ++++++++++++++++++++++++++++++++++ state/core_access.go | 6 ++-- state/integration_test.go | 2 +- state/state.go | 34 ++++++++++++++++++-- 8 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 state/address_test.go diff --git a/api/docgen/examples.go b/api/docgen/examples.go index b19b06a7f8..80a8c64d93 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -83,6 +83,8 @@ func init() { } addToExampleValues(valAddr) + addToExampleValues(state.Address{Address: addr}) + var txResponse *state.TxResponse err = json.Unmarshal([]byte(exampleTxResponse), &txResponse) if err != nil { diff --git a/api/gateway/state.go b/api/gateway/state.go index 63ada8ebb2..c895e35bde 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -72,7 +72,7 @@ func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { } addr = valAddr.Bytes() } - bal, err = h.state.BalanceForAddress(r.Context(), addr) + bal, err = h.state.BalanceForAddress(r.Context(), state.Address{Address: addr}) } else { bal, err = h.state.Balance(r.Context()) } diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index de8c55e3f4..767fca872c 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" - "errors" "fmt" "io" "log" @@ -15,7 +14,6 @@ import ( "strconv" "strings" - "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" "github.com/celestiaorg/nmt/namespace" @@ -392,18 +390,12 @@ func sendJSONRPCRequest(namespace, method string, params []interface{}) { } func parseAddressFromString(addrStr string) (state.Address, error) { - var addr state.AccAddress - addr, err := types.AccAddressFromBech32(addrStr) + var address state.Address + err := address.UnmarshalJSON([]byte(addrStr)) if err != nil { - // first check if it is a validator address and can be converted - valAddr, err := types.ValAddressFromBech32(addrStr) - if err != nil { - return nil, errors.New("address must be a valid account or validator address ") - } - return valAddr, nil + return address, err } - - return addr, nil + return address, nil } func parseSignatureForHelpstring(methodSig reflect.StructField) string { diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 814b9a0113..dbd1d5dabe 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -10,6 +10,7 @@ import ( math "cosmossdk.io/math" blob "github.com/celestiaorg/celestia-node/blob" + state "github.com/celestiaorg/celestia-node/state" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" @@ -40,10 +41,10 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // AccountAddress mocks base method. -func (m *MockModule) AccountAddress(arg0 context.Context) (types.Address, error) { +func (m *MockModule) AccountAddress(arg0 context.Context) (state.Address, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AccountAddress", arg0) - ret0, _ := ret[0].(types.Address) + ret0, _ := ret[0].(state.Address) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -70,7 +71,7 @@ func (mr *MockModuleMockRecorder) Balance(arg0 interface{}) *gomock.Call { } // BalanceForAddress mocks base method. -func (m *MockModule) BalanceForAddress(arg0 context.Context, arg1 types.Address) (*types.Coin, error) { +func (m *MockModule) BalanceForAddress(arg0 context.Context, arg1 state.Address) (*types.Coin, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BalanceForAddress", arg0, arg1) ret0, _ := ret[0].(*types.Coin) diff --git a/state/address_test.go b/state/address_test.go new file mode 100644 index 0000000000..d701b38aa8 --- /dev/null +++ b/state/address_test.go @@ -0,0 +1,57 @@ +package state + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAddressMarshalling(t *testing.T) { + testCases := []struct { + name string + addressString string + addressFromStr func(string) (interface{}, error) + marshalJSON func(interface{}) ([]byte, error) + unmarshalJSON func([]byte) (interface{}, error) + }{ + { + name: "Account Address", + addressString: "celestia1377k5an3f94v6wyaceu0cf4nq6gk2jtpc46g7h", + addressFromStr: func(s string) (interface{}, error) { return sdk.AccAddressFromBech32(s) }, + marshalJSON: func(addr interface{}) ([]byte, error) { return addr.(sdk.AccAddress).MarshalJSON() }, + unmarshalJSON: func(b []byte) (interface{}, error) { + var addr sdk.AccAddress + err := addr.UnmarshalJSON(b) + return addr, err + }, + }, + { + name: "Validator Address", + addressString: "celestiavaloper1q3v5cugc8cdpud87u4zwy0a74uxkk6u4q4gx4p", + addressFromStr: func(s string) (interface{}, error) { return sdk.ValAddressFromBech32(s) }, + marshalJSON: func(addr interface{}) ([]byte, error) { return addr.(sdk.ValAddress).MarshalJSON() }, + unmarshalJSON: func(b []byte) (interface{}, error) { + var addr sdk.ValAddress + err := addr.UnmarshalJSON(b) + return addr, err + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + addr, err := tc.addressFromStr(tc.addressString) + require.NoError(t, err) + + addrBytes, err := tc.marshalJSON(addr) + assert.NoError(t, err) + assert.Equal(t, []byte("\""+tc.addressString+"\""), addrBytes) + + addrUnmarshalled, err := tc.unmarshalJSON(addrBytes) + assert.NoError(t, err) + assert.Equal(t, addr, addrUnmarshalled) + }) + } +} diff --git a/state/core_access.go b/state/core_access.go index ca0947166d..7b59f3e714 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -194,9 +194,9 @@ func (ca *CoreAccessor) SubmitPayForBlob( func (ca *CoreAccessor) AccountAddress(context.Context) (Address, error) { addr, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { - return nil, err + return Address{nil}, err } - return addr, nil + return Address{addr}, nil } func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { @@ -204,7 +204,7 @@ func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { if err != nil { return nil, err } - return ca.BalanceForAddress(ctx, addr) + return ca.BalanceForAddress(ctx, Address{addr}) } func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { diff --git a/state/integration_test.go b/state/integration_test.go index e7d2496397..8862de1bf8 100644 --- a/state/integration_test.go +++ b/state/integration_test.go @@ -110,7 +110,7 @@ func (s *IntegrationTestSuite) TestGetBalance() { require := s.Require() expectedBal := sdk.NewCoin(app.BondDenom, sdk.NewInt(int64(99999999999999999))) for _, acc := range s.accounts { - bal, err := s.accessor.BalanceForAddress(context.Background(), s.getAddress(acc)) + bal, err := s.accessor.BalanceForAddress(context.Background(), Address{s.getAddress(acc)}) require.NoError(err) require.Equal(&expectedBal, bal) } diff --git a/state/state.go b/state/state.go index 987a783239..d55bb6901c 100644 --- a/state/state.go +++ b/state/state.go @@ -1,6 +1,9 @@ package state import ( + "fmt" + "strings" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" coretypes "github.com/tendermint/tendermint/types" @@ -15,8 +18,11 @@ type Tx = coretypes.Tx // TxResponse is an alias to the TxResponse type from Cosmos-SDK. type TxResponse = sdk.TxResponse -// Address is an alias to the Address type from Cosmos-SDK. -type Address = sdk.Address +// Address is an alias to the Address type from Cosmos-SDK. It is embedded into a struct to provide +// a non-interface type for JSON serialization. +type Address struct { + sdk.Address +} // ValAddress is an alias to the ValAddress type from Cosmos-SDK. type ValAddress = sdk.ValAddress @@ -26,3 +32,27 @@ type AccAddress = sdk.AccAddress // Int is an alias to the Int type from Cosmos-SDK. type Int = math.Int + +func (a *Address) UnmarshalJSON(data []byte) error { + // To convert the string back to a concrete type, we have to determine the correct implementation + var addr AccAddress + addrString := strings.Trim(string(data), "\"") + addr, err := sdk.AccAddressFromBech32(addrString) + if err != nil { + // first check if it is a validator address and can be converted + valAddr, err := sdk.ValAddressFromBech32(addrString) + if err != nil { + return fmt.Errorf("address must be a valid account or validator address: %w", err) + } + a.Address = valAddr + return nil + } + + a.Address = addr + return nil +} + +func (a Address) MarshalJSON() ([]byte, error) { + // The address is marshaled into a simple string value + return []byte("\"" + a.Address.String() + "\""), nil +} From d77d0f2f22b107abb58585b3c02c47d7c1508092 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 14 Jun 2023 10:18:17 +0200 Subject: [PATCH 0651/1008] chore!(gateway): add deprecation notices (#2360) - Adds a warn log on startup if --gateway is passed - Deprecated endpoints are now disabled by default. This includes: - /balance - /submit_pfd - /query_delegation - /query_unbonding - /query_redelegations - Deprecated endpoints can be activated by passing `--gateway.deprecated-endpoints` - When using deprecated endpoints, the node will log a warning, giving the alternative RPC method to call in the future. The ugly config change/flag will be removed when fully deprecating the endpoints --- api/gateway/das.go | 1 + api/gateway/endpoints.go | 39 ++++++++++++++++------------- api/gateway/state.go | 10 ++++++++ nodebuilder/gateway/config.go | 7 +++--- nodebuilder/gateway/constructors.go | 3 ++- nodebuilder/gateway/flags.go | 16 +++++++++--- nodebuilder/gateway/module.go | 5 ++-- 7 files changed, 54 insertions(+), 27 deletions(-) diff --git a/api/gateway/das.go b/api/gateway/das.go index 565cbd7460..88dc97927c 100644 --- a/api/gateway/das.go +++ b/api/gateway/das.go @@ -10,6 +10,7 @@ const ( ) func (h *Handler) handleDASStateRequest(w http.ResponseWriter, r *http.Request) { + logDeprecation(dasStateEndpoint, "das.SamplingStats") stats, err := h.das.SamplingStats(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, dasStateEndpoint, err) diff --git a/api/gateway/endpoints.go b/api/gateway/endpoints.go index dfcb96bd06..0ae93b112c 100644 --- a/api/gateway/endpoints.go +++ b/api/gateway/endpoints.go @@ -5,21 +5,32 @@ import ( "net/http" ) -func (h *Handler) RegisterEndpoints(rpc *Server) { +func (h *Handler) RegisterEndpoints(rpc *Server, deprecatedEndpointsEnabled bool) { + if deprecatedEndpointsEnabled { + log.Warn("Deprecated endpoints will be removed from the gateway in the next release. Use the RPC instead.") + // state endpoints + rpc.RegisterHandlerFunc(balanceEndpoint, h.handleBalanceRequest, http.MethodGet) + rpc.RegisterHandlerFunc(submitPFBEndpoint, h.handleSubmitPFB, http.MethodPost) + + // staking queries + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryDelegationEndpoint, addrKey), h.handleQueryDelegation, + http.MethodGet) + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryUnbondingEndpoint, addrKey), h.handleQueryUnbonding, + http.MethodGet) + rpc.RegisterHandlerFunc(queryRedelegationsEndpoint, h.handleQueryRedelegations, + http.MethodPost) + + // DASer endpoints + // only register if DASer service is available + if h.das != nil { + rpc.RegisterHandlerFunc(dasStateEndpoint, h.handleDASStateRequest, http.MethodGet) + } + } + // state endpoints - rpc.RegisterHandlerFunc(balanceEndpoint, h.handleBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceRequest, http.MethodGet) rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) - rpc.RegisterHandlerFunc(submitPFBEndpoint, h.handleSubmitPFB, http.MethodPost) - - // staking queries - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryDelegationEndpoint, addrKey), h.handleQueryDelegation, - http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryUnbondingEndpoint, addrKey), h.handleQueryUnbonding, - http.MethodGet) - rpc.RegisterHandlerFunc(queryRedelegationsEndpoint, h.handleQueryRedelegations, - http.MethodPost) // share endpoints rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, nIDKey, heightKey), @@ -39,10 +50,4 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", headerByHeightEndpoint, heightKey), h.handleHeaderRequest, http.MethodGet) rpc.RegisterHandlerFunc(headEndpoint, h.handleHeadRequest, http.MethodGet) - - // DASer endpoints - // only register if DASer service is available - if h.das != nil { - rpc.RegisterHandlerFunc(dasStateEndpoint, h.handleDASStateRequest, http.MethodGet) - } } diff --git a/api/gateway/state.go b/api/gateway/state.go index c895e35bde..b584b00d36 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -74,6 +74,7 @@ func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { } bal, err = h.state.BalanceForAddress(r.Context(), state.Address{Address: addr}) } else { + logDeprecation(balanceEndpoint, "state.Balance") bal, err = h.state.Balance(r.Context()) } if err != nil { @@ -122,6 +123,7 @@ func (h *Handler) handleSubmitTx(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { + logDeprecation(submitPFBEndpoint, "blob.Submit or state.SubmitPayForBlob") // decode request var req submitPFBRequest err := json.NewDecoder(r.Body).Decode(&req) @@ -171,6 +173,7 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleQueryDelegation(w http.ResponseWriter, r *http.Request) { + logDeprecation(queryDelegationEndpoint, "state.QueryDelegation") // read and parse request vars := mux.Vars(r) addrStr, exists := vars[addrKey] @@ -202,6 +205,7 @@ func (h *Handler) handleQueryDelegation(w http.ResponseWriter, r *http.Request) } func (h *Handler) handleQueryUnbonding(w http.ResponseWriter, r *http.Request) { + logDeprecation(queryUnbondingEndpoint, "state.QueryUnbonding") // read and parse request vars := mux.Vars(r) addrStr, exists := vars[addrKey] @@ -233,6 +237,7 @@ func (h *Handler) handleQueryUnbonding(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleQueryRedelegations(w http.ResponseWriter, r *http.Request) { + logDeprecation(queryRedelegationsEndpoint, "state.QueryRedelegations") var req queryRedelegationsRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { @@ -264,3 +269,8 @@ func (h *Handler) handleQueryRedelegations(w http.ResponseWriter, r *http.Reques log.Errorw("writing response", "endpoint", queryRedelegationsEndpoint, "err", err) } } + +func logDeprecation(endpoint string, alternative string) { + log.Warn("The " + endpoint + " endpoint is deprecated and will be removed in the next release. Please " + + "use " + alternative + " from the RPC instead.") +} diff --git a/nodebuilder/gateway/config.go b/nodebuilder/gateway/config.go index 903a27489a..f85f207ceb 100644 --- a/nodebuilder/gateway/config.go +++ b/nodebuilder/gateway/config.go @@ -8,9 +8,10 @@ import ( ) type Config struct { - Address string - Port string - Enabled bool + Address string + Port string + Enabled bool + deprecatedEndpoints bool } func DefaultConfig() Config { diff --git a/nodebuilder/gateway/constructors.go b/nodebuilder/gateway/constructors.go index c28153b0a5..c771c12023 100644 --- a/nodebuilder/gateway/constructors.go +++ b/nodebuilder/gateway/constructors.go @@ -10,6 +10,7 @@ import ( // Handler constructs a new RPC Handler from the given services. func Handler( + cfg *Config, state state.Module, share share.Module, header header.Module, @@ -17,7 +18,7 @@ func Handler( serv *gateway.Server, ) { handler := gateway.NewHandler(state, share, header, daser) - handler.RegisterEndpoints(serv) + handler.RegisterEndpoints(serv, cfg.deprecatedEndpoints) handler.RegisterMiddleware(serv) } diff --git a/nodebuilder/gateway/flags.go b/nodebuilder/gateway/flags.go index cd13e47162..4d72a278e5 100644 --- a/nodebuilder/gateway/flags.go +++ b/nodebuilder/gateway/flags.go @@ -6,9 +6,10 @@ import ( ) var ( - enabledFlag = "gateway" - addrFlag = "gateway.addr" - portFlag = "gateway.port" + enabledFlag = "gateway" + addrFlag = "gateway.addr" + portFlag = "gateway.port" + deprecatedEndpoints = "gateway.deprecated-endpoints" ) // Flags gives a set of hardcoded node/gateway package flags. @@ -20,6 +21,11 @@ func Flags() *flag.FlagSet { false, "Enables the REST gateway", ) + flags.Bool( + deprecatedEndpoints, + false, + "Enables deprecated endpoints on the gateway. These will be removed in the next release.", + ) flags.String( addrFlag, "", @@ -40,6 +46,10 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) { if cmd.Flags().Changed(enabledFlag) && err == nil { cfg.Enabled = enabled } + deprecatedEndpointsEnabled, err := cmd.Flags().GetBool(deprecatedEndpoints) + if cmd.Flags().Changed(deprecatedEndpoints) && err == nil { + cfg.deprecatedEndpoints = deprecatedEndpointsEnabled + } addr, port := cmd.Flag(addrFlag), cmd.Flag(portFlag) if !cfg.Enabled && (addr.Changed || port.Changed) { log.Warn("custom address or port provided without enabling gateway, setting config values") diff --git a/nodebuilder/gateway/module.go b/nodebuilder/gateway/module.go index b3070e01a6..b727f4c04d 100644 --- a/nodebuilder/gateway/module.go +++ b/nodebuilder/gateway/module.go @@ -21,8 +21,6 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { if !cfg.Enabled { return fx.Options() } - // NOTE @distractedm1nd @renaynay: Remove whenever/if we decide to add auth to gateway - log.Warn("Gateway is enabled, however gateway endpoints are not authenticated. Use with caution!") baseComponents := fx.Options( fx.Supply(cfg), @@ -50,12 +48,13 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "gateway", baseComponents, fx.Invoke(func( + cfg *Config, state stateServ.Module, share shareServ.Module, header headerServ.Module, serv *gateway.Server, ) { - Handler(state, share, header, nil, serv) + Handler(cfg, state, share, header, nil, serv) }), ) default: From 162e43899704956fa5e778e08d49f729d6f5485b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:11:36 +0000 Subject: [PATCH 0652/1008] chore(deps): bump github.com/pyroscope-io/client from 0.7.0 to 0.7.1 (#2319) Bumps [github.com/pyroscope-io/client](https://github.com/pyroscope-io/client) from 0.7.0 to 0.7.1.

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/pyroscope-io/client&package-manager=go_modules&previous-version=0.7.0&new-version=0.7.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5656aac5b3..bc783271b6 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/prometheus/client_golang v1.14.0 - github.com/pyroscope-io/client v0.7.0 + github.com/pyroscope-io/client v0.7.1 github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 128a3b4b5e..ee2c193791 100644 --- a/go.sum +++ b/go.sum @@ -1772,8 +1772,8 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/pyroscope-io/client v0.7.0 h1:LWuuqPQ1oa6x7BnmUOuo/aGwdX85QGhWZUBYWWW3zdk= -github.com/pyroscope-io/client v0.7.0/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= +github.com/pyroscope-io/client v0.7.1 h1:yFRhj3vbgjBxehvxQmedmUWJQ4CAfCHhn+itPsuWsHw= +github.com/pyroscope-io/client v0.7.1/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= github.com/pyroscope-io/godeltaprof v0.1.0 h1:UBqtjt0yZi4jTxqZmLAs34XG6ycS3vUTlhEUSq4NHLE= github.com/pyroscope-io/godeltaprof v0.1.0/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKAvs5drr0511Ki8ic= From f291cf308352b69467e00362e14c3afad8b35983 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 14 Jun 2023 12:16:52 +0200 Subject: [PATCH 0653/1008] fix(header): GetByHeight requests for syncer head +1 shouldnt error (#2362) --- nodebuilder/header/service.go | 5 +++-- nodebuilder/tests/api_test.go | 38 +++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index e6d7d46b8f..f410c04f04 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -37,7 +37,8 @@ func newHeaderService( sub libhead.Subscriber[*header.ExtendedHeader], p2pServer *p2p.ExchangeServer[*header.ExtendedHeader], ex libhead.Exchange[*header.ExtendedHeader], - store libhead.Store[*header.ExtendedHeader]) Module { + store libhead.Store[*header.ExtendedHeader], +) Module { return &Service{ syncer: syncer, sub: sub, @@ -66,7 +67,7 @@ func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.Exten return nil, err case uint64(head.Height()) == height: return head, nil - case uint64(head.Height()) < height: + case uint64(head.Height())+1 < height: return nil, fmt.Errorf("header: given height is from the future: "+ "networkHeight: %d, requestedHeight: %d", head.Height(), height) } diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index f740dc12fa..13cf083eee 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -2,8 +2,8 @@ package tests import ( "context" - "fmt" "testing" + "time" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/libp2p/go-libp2p/core/host" @@ -19,6 +19,41 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) +func TestGetByHeight(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second)) + + // start a bridge node + bridge := sw.NewBridgeNode() + err := bridge.Start(ctx) + require.NoError(t, err) + + signer := bridge.AdminSigner + bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() + + jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) + require.NoError(t, err) + + client, err := client.NewClient(ctx, bridgeAddr, jwt) + require.NoError(t, err) + + // let a few blocks be produced + _, err = client.Header.WaitForHeight(ctx, 3) + require.NoError(t, err) + + networkHead, err := client.Header.NetworkHead(ctx) + require.NoError(t, err) + _, err = client.Header.GetByHeight(ctx, uint64(networkHead.Height()+1)) + require.Nil(t, err, "Requesting syncer.Head()+1 shouldn't return an error") + + networkHead, err = client.Header.NetworkHead(ctx) + require.NoError(t, err) + _, err = client.Header.GetByHeight(ctx, uint64(networkHead.Height()+2)) + require.ErrorContains(t, err, "given height is from the future") +} + // TestBlobRPC ensures that blobs can be submited via rpc func TestBlobRPC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) @@ -33,7 +68,6 @@ func TestBlobRPC(t *testing.T) { signer := bridge.AdminSigner bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() - fmt.Println("bridgeAddr: ", bridgeAddr) jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) require.NoError(t, err) From 2b8e151a3085fc5a4c81d162ef1f00697dbad34e Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 14 Jun 2023 13:13:05 +0200 Subject: [PATCH 0654/1008] fix(share/eds): fix test flake (#2364) --- share/eds/store.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/eds/store.go b/share/eds/store.go index f01e96a24b..f0d02a1141 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -119,6 +119,10 @@ func (s *Store) Start(ctx context.Context) error { // start Store only if DagStore succeeds ctx, cancel := context.WithCancel(context.Background()) s.cancel = cancel + // initialize empty gc result to avoid panic on access + s.lastGCResult.Store(&dagstore.GCResult{ + Shards: make(map[shard.Key]error), + }) go s.gc(ctx) return nil } @@ -132,10 +136,6 @@ func (s *Store) Stop(context.Context) error { // gc periodically removes all inactive or errored shards. func (s *Store) gc(ctx context.Context) { ticker := time.NewTicker(s.gcInterval) - // initialize empty gc result to avoid panic on access - s.lastGCResult.Store(&dagstore.GCResult{ - Shards: make(map[shard.Key]error), - }) for { select { case <-ctx.Done(): From 4c87c691ca1868f983bb45964b51c492db896ad8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:23:33 +0000 Subject: [PATCH 0655/1008] chore(deps): bump mheap/github-action-required-labels from 4 to 5 (#2354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [mheap/github-action-required-labels](https://github.com/mheap/github-action-required-labels) from 4 to 5.
    Release notes

    Sourced from mheap/github-action-required-labels's releases.

    v5

    Tag that always points to the latest commit in the v5.x.x series of releases

    v5.0.0

    What's Changed

    Full Changelog: https://github.com/mheap/github-action-required-labels/compare/v4.1.0...v5.0.0

    v4.1.1

    • Fix build step by switching to ubuntu-latest

    Full Changelog: https://github.com/mheap/github-action-required-labels/compare/v4.1.0...v4.1.1

    v4.1.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/mheap/github-action-required-labels/compare/v4.0.0...v4.1.0

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mheap/github-action-required-labels&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 314f2fcb14..bed2b3352c 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -12,7 +12,7 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@v4 + - uses: mheap/github-action-required-labels@v5 with: mode: minimum count: 1 From 400517e0e32700fe7bc90e1de6644631c51b4996 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 14 Jun 2023 13:31:48 +0200 Subject: [PATCH 0656/1008] feat!(nodbuilder): making lifecycle timeouts configurable (#2347) Closes #2315 Doing this proactively even though it is a good-first issue in case other dagstore startup issues are not fixed in time. Breaks the config. --- nodebuilder/config.go | 2 ++ nodebuilder/node.go | 16 ++-------------- nodebuilder/node/config.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 nodebuilder/node/config.go diff --git a/nodebuilder/config.go b/nodebuilder/config.go index 3607aa593e..670bbf9bbd 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -25,6 +25,7 @@ type ConfigLoader func() (*Config, error) // Config is main configuration structure for a Node. // It combines configuration units for all Node subsystems. type Config struct { + Node node.Config Core core.Config State state.Config P2P p2p.Config @@ -39,6 +40,7 @@ type Config struct { // NOTE: Currently, configs are identical, but this will change. func DefaultConfig(tp node.Type) *Config { commonConfig := &Config{ + Node: node.DefaultConfig(tp), Core: core.DefaultConfig(), State: state.DefaultConfig(), P2P: p2p.DefaultConfig(tp), diff --git a/nodebuilder/node.go b/nodebuilder/node.go index a6e3a3c3cc..19760831cf 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "strings" - "time" "github.com/cristalhq/jwt" "github.com/ipfs/go-blockservice" @@ -95,7 +94,7 @@ func NewWithConfig(tp node.Type, network p2p.Network, store Store, cfg *Config, // Start launches the Node and all its components and services. func (n *Node) Start(ctx context.Context) error { - to := timeout(n.Type) + to := n.Config.Node.StartupTimeout ctx, cancel := context.WithTimeout(ctx, to) defer cancel() @@ -141,7 +140,7 @@ func (n *Node) Run(ctx context.Context) error { // Canceling the given context earlier 'ctx' unblocks the Stop and aborts graceful shutdown forcing // remaining Modules/Services to close immediately. func (n *Node) Stop(ctx context.Context) error { - to := timeout(n.Type) + to := n.Config.Node.ShutdownTimeout ctx, cancel := context.WithTimeout(ctx, to) defer cancel() @@ -183,14 +182,3 @@ func newNode(opts ...fx.Option) (*Node, error) { // lifecycleFunc defines a type for common lifecycle funcs. type lifecycleFunc func(context.Context) error - -var DefaultLifecycleTimeout = time.Minute * 2 - -func timeout(tp node.Type) time.Duration { - switch tp { - case node.Light: - return time.Second * 20 - default: - return DefaultLifecycleTimeout - } -} diff --git a/nodebuilder/node/config.go b/nodebuilder/node/config.go new file mode 100644 index 0000000000..e44fe2f014 --- /dev/null +++ b/nodebuilder/node/config.go @@ -0,0 +1,38 @@ +package node + +import ( + "fmt" + "time" +) + +var defaultLifecycleTimeout = time.Minute * 2 + +type Config struct { + StartupTimeout time.Duration + ShutdownTimeout time.Duration +} + +// DefaultConfig returns the default node configuration for a given node type. +func DefaultConfig(tp Type) Config { + var timeout time.Duration + switch tp { + case Light: + timeout = time.Second * 20 + default: + timeout = defaultLifecycleTimeout + } + return Config{ + StartupTimeout: timeout, + ShutdownTimeout: timeout, + } +} + +func (c *Config) Validate() error { + if c.StartupTimeout == 0 { + return fmt.Errorf("invalid startup timeout: %v", c.StartupTimeout) + } + if c.ShutdownTimeout == 0 { + return fmt.Errorf("invalid shutdown timeout: %v", c.ShutdownTimeout) + } + return nil +} From 37d0af9141d2357349a463ecbb7459639cc8b8cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:41:58 +0000 Subject: [PATCH 0657/1008] chore(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.6.0 (#2353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.4.0 to 3.6.0.
    Release notes

    Sourced from golangci/golangci-lint-action's releases.

    v3.6.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/golangci/golangci-lint-action/compare/v3.5.0...v3.6.0

    v3.5.0

    What's Changed

    ... (truncated)

    Commits
    • 639cd34 tests: increase timeout
    • 569abaa fix: out-format (#770)
    • c57cc43 build(deps-dev): bump typescript from 5.0.4 to 5.1.3 (#764)
    • 322510a feat: support out-format as args (#769)
    • 185e7a2 feat: add install-mode (#768)
    • 5be60c7 docs: improve args examples
    • 825a50d chore: update workflow and doc
    • 8c13ec4 doc: Add custom configuration file path to args (#767)
    • 416b5d0 build(deps-dev): bump @​typescript-eslint/parser from 5.59.7 to 5.59.8 (#765)
    • 66a6080 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 5.59.7 to 5.59.8 ...
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=3.4.0&new-version=3.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index b519db7763..e6c4e5881b 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -23,7 +23,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.6.0 with: version: v1.52.2 From d0e4353db6ffa9e28e97a8df867298e417e42edf Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 14 Jun 2023 13:56:45 +0200 Subject: [PATCH 0658/1008] fix(swamp): use synchronized map as container for nodes (#2358) --- go.mod | 2 +- nodebuilder/tests/fraud_test.go | 3 +- nodebuilder/tests/swamp/swamp.go | 123 ++++++++++++------------------- nodebuilder/tests/sync_test.go | 3 +- 4 files changed, 49 insertions(+), 82 deletions(-) diff --git a/go.mod b/go.mod index bc783271b6..524d1943ba 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.9.0 + golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/sync v0.1.0 golang.org/x/text v0.9.0 google.golang.org/grpc v1.53.0 @@ -309,7 +310,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.16.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 2022107cb7..c6876cb624 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -84,8 +84,7 @@ func TestFraudProofBroadcasting(t *testing.T) { require.ErrorIs(t, err, context.DeadlineExceeded) syncCancel() - require.NoError(t, full.Stop(ctx)) - require.NoError(t, sw.RemoveNode(full, node.Full)) + sw.StopNode(ctx, full) full = sw.NewNodeWithStore(node.Full, store) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 5b99b577b1..1f21ac3bfa 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "fmt" "net" + "sync" "testing" "time" @@ -16,6 +17,7 @@ import ( ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" "go.uber.org/fx" + "golang.org/x/exp/maps" "github.com/celestiaorg/celestia-app/test/util/testnode" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" @@ -45,16 +47,16 @@ const DefaultTestTimeout = time.Minute * 5 // - Slices of created Bridge/Full/Light Nodes // - trustedHash taken from the CoreClient and shared between nodes type Swamp struct { - t *testing.T - Network mocknet.Mocknet - BridgeNodes []*nodebuilder.Node - FullNodes []*nodebuilder.Node - LightNodes []*nodebuilder.Node - comps *Config + t *testing.T + cfg *Config + Network mocknet.Mocknet ClientContext testnode.Context Accounts []string + nodesMu sync.Mutex + nodes map[*nodebuilder.Node]struct{} + genesis *header.ExtendedHeader } @@ -69,37 +71,38 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { option(ic) } - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - // Now, we are making an assumption that consensus mechanism is already tested out // so, we are not creating bridge nodes with each one containing its own core client // instead we are assigning all created BNs to 1 Core from the swamp cctx := core.StartTestNodeWithConfig(t, ic.TestConfig) swp := &Swamp{ t: t, + cfg: ic, Network: mocknet.New(), ClientContext: cctx, - comps: ic, Accounts: ic.Accounts, + nodes: map[*nodebuilder.Node]struct{}{}, } - swp.t.Cleanup(func() { - swp.stopAllNodes(ctx, swp.BridgeNodes, swp.FullNodes, swp.LightNodes) - }) - - swp.setupGenesis(ctx) + swp.t.Cleanup(swp.cleanup) + swp.setupGenesis() return swp } -// stopAllNodes goes through all received slices of Nodes and stops one-by-one -// this eliminates a manual clean-up in the test-cases itself in the end -func (s *Swamp) stopAllNodes(ctx context.Context, allNodes ...[]*nodebuilder.Node) { - for _, nodes := range allNodes { - for _, node := range nodes { - require.NoError(s.t, node.Stop(ctx)) - } - } +// cleanup frees up all the resources +// including stop of all created nodes +func (s *Swamp) cleanup() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(s.t, s.Network.Close()) + + s.nodesMu.Lock() + defer s.nodesMu.Unlock() + maps.DeleteFunc(s.nodes, func(nd *nodebuilder.Node, _ struct{}) bool { + require.NoError(s.t, nd.Stop(ctx)) + return true + }) } // GetCoreBlockHashByHeight returns a tendermint block's hash by provided height @@ -158,7 +161,10 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { // setupGenesis sets up genesis Header. // This is required to initialize and start correctly. -func (s *Swamp) setupGenesis(ctx context.Context) { +func (s *Swamp) setupGenesis() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + // ensure core has surpassed genesis block s.WaitTillHeight(ctx, 2) @@ -180,7 +186,7 @@ func (s *Swamp) setupGenesis(ctx context.Context) { func (s *Swamp) DefaultTestConfig(tp node.Type) *nodebuilder.Config { cfg := nodebuilder.DefaultConfig(tp) - ip, port, err := net.SplitHostPort(s.comps.App.GRPC.Address) + ip, port, err := net.SplitHostPort(s.cfg.App.GRPC.Address) require.NoError(s.t, err) cfg.Core.IP = ip @@ -228,36 +234,29 @@ func (s *Swamp) NewNodeWithConfig(nodeType node.Type, cfg *nodebuilder.Config, o } // NewNodeWithStore creates a new instance of Node with predefined Store. -// Afterwards, the instance is stored in the swamp's Nodes' slice according to the -// node's type provided from the user. func (s *Swamp) NewNodeWithStore( - t node.Type, + tp node.Type, store nodebuilder.Store, options ...fx.Option, ) *nodebuilder.Node { - var n *nodebuilder.Node - signer := apptypes.NewKeyringSigner(s.ClientContext.Keyring, s.Accounts[0], s.ClientContext.ChainID) options = append(options, state.WithKeyringSigner(signer), ) - switch t { + switch tp { case node.Bridge: options = append(options, coremodule.WithClient(s.ClientContext.Client), ) - n = s.newNode(node.Bridge, store, options...) - s.BridgeNodes = append(s.BridgeNodes, n) - case node.Full: - n = s.newNode(node.Full, store, options...) - s.FullNodes = append(s.FullNodes, n) - case node.Light: - n = s.newNode(node.Light, store, options...) - s.LightNodes = append(s.LightNodes, n) + default: } - return n + nd := s.newNode(tp, store, options...) + s.nodesMu.Lock() + s.nodes[nd] = struct{}{} + s.nodesMu.Unlock() + return nd } func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Option) *nodebuilder.Node { @@ -284,43 +283,13 @@ func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Opti return node } -// RemoveNode removes a node from the swamp's node slice -// this allows reusage of the same var in the test scenario -// if the user needs to stop and start the same node -func (s *Swamp) RemoveNode(n *nodebuilder.Node, t node.Type) error { - var err error - switch t { - case node.Light: - s.LightNodes, err = s.remove(n, s.LightNodes) - return err - case node.Bridge: - s.BridgeNodes, err = s.remove(n, s.BridgeNodes) - return err - case node.Full: - s.FullNodes, err = s.remove(n, s.FullNodes) - return err - default: - return fmt.Errorf("no such type or node") - } -} - -func (s *Swamp) remove(rn *nodebuilder.Node, sn []*nodebuilder.Node) ([]*nodebuilder.Node, error) { - if len(sn) == 1 { - return nil, nil - } - - initSize := len(sn) - for i := 0; i < len(sn); i++ { - if sn[i] == rn { - sn = append(sn[:i], sn[i+1:]...) - i-- - } - } - - if initSize <= len(sn) { - return sn, fmt.Errorf("cannot delete the node") - } - return sn, nil +// StopNode stops the node and removes from Swamp. +// TODO(@Wondertan): For clean and symmetrical API, we may want to add StartNode. +func (s *Swamp) StopNode(ctx context.Context, nd *nodebuilder.Node) { + s.nodesMu.Lock() + delete(s.nodes, nd) + s.nodesMu.Unlock() + require.NoError(s.t, nd.Stop(ctx)) } // Connect allows to connect peers after hard disconnection. diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 68f33bc61b..af311ba45c 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -121,8 +121,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) - require.NoError(t, light.Stop(ctx)) - require.NoError(t, sw.RemoveNode(light, node.Light)) + sw.StopNode(ctx, light) cfg = nodebuilder.DefaultConfig(node.Light) cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) From 7ef26dce43779715a17c14e0e0581bd8e350cea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 12:04:05 +0000 Subject: [PATCH 0659/1008] chore(deps): bump github.com/imdario/mergo from 0.3.15 to 0.3.16 (#2355) Bumps [github.com/imdario/mergo](https://github.com/imdario/mergo) from 0.3.15 to 0.3.16.
    Release notes

    Sourced from github.com/imdario/mergo's releases.

    Announcement: v1.0.0 will be released on June 18th

    This release doesn't contain code changes.

    After 10 years, with many corner cases covered, very few issues pending (at least, comparing them with the usage of the library as part of Docker, Kubernetes, Datadog's agent, etc.), and a very stable API, I think it's time to release a 1.0.0 version.

    This version will be released under a vanity URL: dario.cat/mergo

    PS: although I'll make sure that github.com/imdario/mergo will be available, I'm going to also change my GitHub handle, so expect for a few minutes to not be able to pull from github.com/imdario as I fork it from the new handle to the old one.

    PS2: I'm creating a discussion for this release to make sure we can have a conversation around the topic, and anything else about Mergo that you care about.

    Commits
    • 14fe2b1 fix: OpenSSF scorecard generation
    • 4cde94b fix: remove Travis link and fix tests actions
    • df62a52 chore: README and GitHub actions
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/imdario/mergo&package-manager=go_modules&previous-version=0.3.15&new-version=0.3.16)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 524d1943ba..9628a0c205 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/imdario/mergo v0.3.15 + github.com/imdario/mergo v0.3.16 github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 diff --git a/go.sum b/go.sum index ee2c193791..bf659652b4 100644 --- a/go.sum +++ b/go.sum @@ -929,8 +929,8 @@ github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= From 168ff5f0e330ad46d37c1fdbd003b8ddd405d1d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:10:10 +0200 Subject: [PATCH 0660/1008] chore(deps): bump celestiaorg/.github from 0.1.1 to 0.2.0 (#2317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [celestiaorg/.github](https://github.com/celestiaorg/.github) from 0.1.1 to 0.2.0.
    Release notes

    Sourced from celestiaorg/.github's releases.

    v0.2.0

    What's Changed

    Full Changelog: https://github.com/celestiaorg/.github/compare/v0.1.1...v0.2.0

    Commits
    • cb19bc0 feat: speed up amd64 availability & push amd64 on each commit (#60)
    • 5ff9239 SECURITY: minor update to scope language (#48)
    • 09f990f feat: TestGround GA (#57)
    • d51dd57 Add repository dispatch reusable workflow (#46)
    • fa412a8 chore(deps): bump actions/add-to-project from 0.4.1 to 0.5.0 (#55)
    • e3225bc chore(deps): bump actions/stale from 7 to 8 (#42)
    • 0a71110 copy over adr template (#40)
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=celestiaorg/.github&package-manager=github_actions&previous-version=0.1.1&new-version=0.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Ryan --- .github/workflows/ci_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 574f425c7b..654c3c0a34 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -24,7 +24,7 @@ on: jobs: # Dockerfile Linting hadolint: - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.1.1 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.0 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: celestiaorg/.github/.github/actions/yamllint@v0.1.1 + - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.0 markdown-lint: name: Markdown Lint @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@v0.1.1 + uses: celestiaorg/.github/.github/actions/version-release@v0.2.0 with: github-token: ${{secrets.GITHUB_TOKEN}} version-bump: ${{inputs.version}} From a4c66f49fafb6e69e943926a86d4413d67fd5027 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 15 Jun 2023 12:18:49 +0200 Subject: [PATCH 0661/1008] chore(nodebuilder/tests): Clean up sync_test tests (#2162) This PR is a part of the swamp cleanussy effort to clean up our integration tests and make them more deterministic + readable. This PR focuses on `sync_test.go` file. It also contains a change to fraud proof broadcasting test but I can happily split out into separate PR. --- nodebuilder/tests/fraud_test.go | 29 ++- nodebuilder/tests/p2p_test.go | 4 +- nodebuilder/tests/swamp/swamp.go | 39 ++- nodebuilder/tests/sync_test.go | 419 ++++++++++++++++++------------- 4 files changed, 299 insertions(+), 192 deletions(-) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index c6876cb624..f652724d55 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -34,17 +34,18 @@ Another note: this test disables share exchange to speed up test results. */ func TestFraudProofBroadcasting(t *testing.T) { t.Skip("requires BEFP generation on app side to work") + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) const ( - blocks = 15 - bsize = 2 - btime = time.Millisecond * 300 + blocks = 15 + blockSize = 2 + blockTime = time.Millisecond * 300 ) - sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) - fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(blockTime)) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, blockSize, blocks) + cfg := nodebuilder.DefaultConfig(node.Bridge) cfg.Share.UseShareExchange = false bridge := sw.NewNodeWithConfig( @@ -55,12 +56,13 @@ func TestFraudProofBroadcasting(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) - require.NoError(t, err) cfg = nodebuilder.DefaultConfig(node.Full) cfg.Share.UseShareExchange = false + addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + require.NoError(t, err) cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + store := nodebuilder.MockStore(t, cfg) full := sw.NewNodeWithStore(node.Full, store) @@ -72,9 +74,12 @@ func TestFraudProofBroadcasting(t *testing.T) { subscr, err := full.FraudServ.Subscribe(ctx, byzantine.BadEncoding) require.NoError(t, err) - p := <-subscr - require.Equal(t, 10, int(p.Height())) - + select { + case p := <-subscr: + require.Equal(t, 10, int(p.Height())) + case <-ctx.Done(): + t.Fatal("fraud proof was not received in time") + } // This is an obscure way to check if the Syncer was stopped. // If we cannot get a height header within a timeframe it means the syncer was stopped // FIXME: Eventually, this should be a check on service registry managing and keeping diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 39c3c985a2..613dface94 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -142,7 +142,7 @@ func TestBootstrapNodesFromBridgeNode(t *testing.T) { // ensure that the light node is connected to the full node assert.True(t, light.Host.Network().Connectedness(addrFull.ID) == network.Connected) - sw.Disconnect(t, light.Host.ID(), full.Host.ID()) + sw.Disconnect(t, light, full) require.NoError(t, full.Stop(ctx)) select { case <-ctx.Done(): @@ -206,7 +206,7 @@ func TestRestartNodeDiscovery(t *testing.T) { connectSub, err := nodes[0].Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) require.NoError(t, err) defer connectSub.Close() - sw.Disconnect(t, nodes[0].Host.ID(), nodes[1].Host.ID()) + sw.Disconnect(t, nodes[0], nodes[1]) require.NoError(t, node.Start(ctx)) // ensure that the last node is connected to one of the nodes diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 1f21ac3bfa..eabe33dc9c 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -51,6 +51,8 @@ type Swamp struct { cfg *Config Network mocknet.Mocknet + Bootstrappers []ma.Multiaddr + ClientContext testnode.Context Accounts []string @@ -210,6 +212,10 @@ func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { cfg.Header.TrustedPeers = []string{ "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", } + // add all bootstrappers in suite as trusted peers + for _, bootstrapper := range s.Bootstrappers { + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, bootstrapper.String()) + } store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Full, store, options...) @@ -222,6 +228,10 @@ func (s *Swamp) NewLightNode(options ...fx.Option) *nodebuilder.Node { cfg.Header.TrustedPeers = []string{ "/ip4/1.2.3.4/tcp/12345/p2p/12D3KooWNaJ1y1Yio3fFJEXCZyd1Cat3jmrPdgkYCrHfKD3Ce21p", } + // add all bootstrappers in suite as trusted peers + for _, bootstrapper := range s.Bootstrappers { + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, bootstrapper.String()) + } store := nodebuilder.MockStore(s.t, cfg) @@ -230,6 +240,10 @@ func (s *Swamp) NewLightNode(options ...fx.Option) *nodebuilder.Node { func (s *Swamp) NewNodeWithConfig(nodeType node.Type, cfg *nodebuilder.Config, options ...fx.Option) *nodebuilder.Node { store := nodebuilder.MockStore(s.t, cfg) + // add all bootstrappers in suite as trusted peers + for _, bootstrapper := range s.Bootstrappers { + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, bootstrapper.String()) + } return s.NewNodeWithStore(nodeType, store, options...) } @@ -293,10 +307,10 @@ func (s *Swamp) StopNode(ctx context.Context, nd *nodebuilder.Node) { } // Connect allows to connect peers after hard disconnection. -func (s *Swamp) Connect(t *testing.T, peerA, peerB peer.ID) { - _, err := s.Network.LinkPeers(peerA, peerB) +func (s *Swamp) Connect(t *testing.T, peerA, peerB *nodebuilder.Node) { + _, err := s.Network.LinkPeers(peerA.Host.ID(), peerB.Host.ID()) require.NoError(t, err) - _, err = s.Network.ConnectPeers(peerA, peerB) + _, err = s.Network.ConnectPeers(peerA.Host.ID(), peerB.Host.ID()) require.NoError(t, err) } @@ -304,7 +318,20 @@ func (s *Swamp) Connect(t *testing.T, peerA, peerB peer.ID) { // re-establish it. Order is very important here. We have to unlink peers first, and only after // that call disconnect. This is hard disconnect and peers will not be able to reconnect. // In order to reconnect peers again, please use swamp.Connect -func (s *Swamp) Disconnect(t *testing.T, peerA, peerB peer.ID) { - require.NoError(t, s.Network.UnlinkPeers(peerA, peerB)) - require.NoError(t, s.Network.DisconnectPeers(peerA, peerB)) +func (s *Swamp) Disconnect(t *testing.T, peerA, peerB *nodebuilder.Node) { + require.NoError(t, s.Network.UnlinkPeers(peerA.Host.ID(), peerB.Host.ID())) + require.NoError(t, s.Network.DisconnectPeers(peerA.Host.ID(), peerB.Host.ID())) +} + +// SetBootstrapper sets the given bootstrappers as the "bootstrappers" for the +// Swamp test suite. Every new full or light node created on the suite afterwards +// will automatically add the suite's bootstrappers as trusted peers to their config. +// NOTE: Bridge nodes do not automaatically add the bootstrappers as trusted peers. +// NOTE: Use `NewNodeWithStore` to avoid this automatic configuration. +func (s *Swamp) SetBootstrapper(t *testing.T, bootstrappers ...*nodebuilder.Node) { + for _, trusted := range bootstrappers { + addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(trusted.Host)) + require.NoError(t, err) + s.Bootstrappers = append(s.Bootstrappers, addrs[0]) + } } diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index af311ba45c..dfa3577599 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -5,73 +5,187 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" + ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/nodebuilder" - "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) // Common consts for tests producing filled blocks const ( - blocks = 20 - bsize = 16 - btime = time.Millisecond * 300 + numBlocks = 20 + bsize = 16 + btime = time.Millisecond * 300 ) /* -Test-Case: Sync a Light Node with a Bridge Node(includes DASing of non-empty blocks) +Test-Case: Header and block/sample sync against a Bridge Node of non-empty blocks. + Steps: 1. Create a Bridge Node(BN) 2. Start a BN 3. Check BN is synced to height 20 -4. Create a Light Node(LN) with a trusted peer + +Light node: +4. Create a Light Node (LN) with bridge as a trusted peer 5. Start a LN with a defined connection to the BN -6. Check LN is synced to height 30 +6. Check LN is header-synced to height 20 +7. Wait until LN has sampled height 20 +8. Wait for LN DASer to catch up to network head + +Full node: +4. Create a Full Node (FN) with bridge as a trusted peer +5. Start a FN with a defined connection to the BN +6. Check FN is header-synced to height 20 +7. Wait until FN has synced block at height 20 +8. Wait for FN DASer to catch up to network head */ -func TestSyncLightWithBridge(t *testing.T) { +func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) + // wait for core network to fill 20 blocks + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, numBlocks) + sw.WaitTillHeight(ctx, numBlocks) + // create a bridge node and set it as the bootstrapper for the suite bridge := sw.NewBridgeNode() - - sw.WaitTillHeight(ctx, 20) - + sw.SetBootstrapper(t, bridge) + // start bridge and wait for it to sync to 20 err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) require.NoError(t, err) + require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) + + t.Run("light sync against bridge", func(t *testing.T) { + // create a light node that is connected to the bridge node as + // a bootstrapper + light := sw.NewLightNode() + // start light node and wait for it to sync 20 blocks + err = light.Start(ctx) + require.NoError(t, err) + h, err = light.HeaderServ.WaitForHeight(ctx, numBlocks) + require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) + + // check that the light node has also sampled over the block at height 20 + err = light.ShareServ.SharesAvailable(ctx, h.DAH) + assert.NoError(t, err) + + // wait until the entire chain (up to network head) has been sampled + err = light.DASer.WaitCatchUp(ctx) + require.NoError(t, err) + }) + + t.Run("full sync against bridge", func(t *testing.T) { + // create a full node with bridge node as its bootstrapper + full := sw.NewFullNode() + // let full node sync 20 blocks + err = full.Start(ctx) + require.NoError(t, err) + h, err = full.HeaderServ.WaitForHeight(ctx, numBlocks) + require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) + + // check to ensure the full node can sync the 20th block's data + err = full.ShareServ.SharesAvailable(ctx, h.DAH) + assert.NoError(t, err) + + // wait for full node to sync up the blocks from genesis -> network head. + err = full.DASer.WaitCatchUp(ctx) + require.NoError(t, err) + }) + + // wait for the core block filling process to exit + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case err := <-fillDn: + require.NoError(t, err) + } +} - require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) +/* +Test-Case: Header and block/sample sync against a Bridge Node of empty blocks. - addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) - require.NoError(t, err) +Steps: +1. Create a Bridge Node(BN) +2. Start a BN +3. Check BN is synced to height 20 - cfg := nodebuilder.DefaultConfig(node.Light) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - light := sw.NewNodeWithConfig(node.Light, cfg) +Light node: +4. Create a Light Node (LN) with bridge as a trusted peer +5. Start a LN with a defined connection to the BN +6. Check LN is header-synced to height 20 +7. Wait until LN has sampled height 20 +8. Wait for LN DASer to catch up to network head + +Full node: +4. Create a Full Node (FN) with bridge as a trusted peer +5. Start a FN with a defined connection to the BN +6. Check FN is header-synced to height 20 +7. Wait until FN has synced block at height 20 +8. Wait for FN DASer to catch up to network head +*/ +func TestSyncAgainstBridge_EmptyChain(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) - err = light.Start(ctx) - require.NoError(t, err) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + sw.WaitTillHeight(ctx, numBlocks) - h, err = light.HeaderServ.WaitForHeight(ctx, 30) + // create bridge node and set it as the bootstrapper for the suite + bridge := sw.NewBridgeNode() + sw.SetBootstrapper(t, bridge) + // start bridge and wait for it to sync to 20 + err := bridge.Start(ctx) require.NoError(t, err) - - err = light.ShareServ.SharesAvailable(ctx, h.DAH) - assert.NoError(t, err) - - err = light.DASer.WaitCatchUp(ctx) + h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) require.NoError(t, err) - - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) - require.NoError(t, <-fillDn) + require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) + + t.Run("light sync against bridge", func(t *testing.T) { + // create a light node that is connected to the bridge node as + // a bootstrapper + light := sw.NewLightNode() + // start light node and wait for it to sync 20 blocks + err = light.Start(ctx) + require.NoError(t, err) + h, err = light.HeaderServ.WaitForHeight(ctx, numBlocks) + require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) + + // check that the light node has also sampled over the block at height 20 + err = light.ShareServ.SharesAvailable(ctx, h.DAH) + assert.NoError(t, err) + + // wait until the entire chain (up to network head) has been sampled + err = light.DASer.WaitCatchUp(ctx) + require.NoError(t, err) + }) + + t.Run("full sync against bridge", func(t *testing.T) { + // create a full node with bridge node as its bootstrapper + full := sw.NewFullNode() + // let full node sync 20 blocks + err = full.Start(ctx) + require.NoError(t, err) + h, err = full.HeaderServ.WaitForHeight(ctx, numBlocks) + require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) + + // check to ensure the full node can sync the 20th block's data + err = full.ShareServ.SharesAvailable(ctx, h.DAH) + assert.NoError(t, err) + + // wait for full node to sync up the blocks from genesis -> network head. + err = full.DASer.WaitCatchUp(ctx) + require.NoError(t, err) + }) } /* @@ -85,106 +199,57 @@ Steps: 3. Check BN is synced to height 20 4. Create a Light Node(LN) with a trusted peer 5. Start a LN with a defined connection to the BN -6. Check LN is synced to height 30 -7. Stop LN -8. Start LN +6. Check LN is synced to height 20 +7. Disconnect LN from BN for 3 seconds while BN continues broadcasting new blocks from core +8. Re-connect LN and let it sync up again 9. Check LN is synced to height 40 */ func TestSyncStartStopLightWithBridge(t *testing.T) { - sw := swamp.NewSwamp(t) - - bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) + defer cancel() - sw.WaitTillHeight(ctx, 50) + sw := swamp.NewSwamp(t) + // wait for core network to fill 20 blocks + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, numBlocks) + sw.WaitTillHeight(ctx, numBlocks) + // create bridge and set it as a bootstrapper + bridge := sw.NewBridgeNode() + sw.SetBootstrapper(t, bridge) + // and let bridge node sync up 20 blocks err := bridge.Start(ctx) require.NoError(t, err) - - h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) require.NoError(t, err) + require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) - require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) - - addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + // create a light node and connect it to the bridge node as a bootstrapper + light := sw.NewLightNode() + // start light node and let it sync to 20 + err = light.Start(ctx) require.NoError(t, err) - - cfg := nodebuilder.DefaultConfig(node.Light) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - light := sw.NewNodeWithConfig(node.Light, cfg) - require.NoError(t, light.Start(ctx)) - - h, err = light.HeaderServ.WaitForHeight(ctx, 30) + h, err = light.HeaderServ.WaitForHeight(ctx, numBlocks) require.NoError(t, err) - - require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) + require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) sw.StopNode(ctx, light) - cfg = nodebuilder.DefaultConfig(node.Light) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - light = sw.NewNodeWithConfig(node.Light, cfg) + light = sw.NewLightNode() require.NoError(t, light.Start(ctx)) + // ensure when light node comes back up, it can sync the remainder of the chain it + // missed while sleeping h, err = light.HeaderServ.WaitForHeight(ctx, 40) require.NoError(t, err) - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 40)) -} - -/* -Test-Case: Sync a Full Node with a Bridge Node(includes DASing of non-empty blocks) -Steps: -1. Create a Bridge Node(BN) -2. Start a BN -3. Check BN is synced to height 20 -4. Create a Full Node(FN) with a connection to BN as a trusted peer -5. Start a FN -6. Check FN is synced to height 30 -*/ -func TestSyncFullWithBridge(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) - - sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) - - bridge := sw.NewBridgeNode() - - sw.WaitTillHeight(ctx, 20) - - err := bridge.Start(ctx) - require.NoError(t, err) - - h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) - require.NoError(t, err) - - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) - addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) - require.NoError(t, err) - - cfg := nodebuilder.DefaultConfig(node.Full) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - cfg.Share.UseShareExchange = false - full := sw.NewNodeWithConfig(node.Full, cfg) - require.NoError(t, full.Start(ctx)) - - h, err = full.HeaderServ.WaitForHeight(ctx, 30) - require.NoError(t, err) - - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) - - err = full.ShareServ.SharesAvailable(ctx, h.DAH) - assert.NoError(t, err) - - err = full.DASer.WaitCatchUp(ctx) - require.NoError(t, err) - - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) - require.NoError(t, <-fillDn) + // wait for the core block filling process to exit + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case err := <-fillDn: + require.NoError(t, err) + } } /* @@ -198,59 +263,69 @@ Steps: 3. Check BN is synced to height 20 4. Create a Full Node(FN) with a connection to BN as a trusted peer 5. Start a FN -6. Check FN is synced to height 30 +6. Check FN is synced to network head 7. Create a Light Node(LN) with a connection to FN as a trusted peer -8. Start LN -9. Check LN is synced to height 50 +8. Ensure LN is NOT connected to BN and only connected to FN +9. Start LN +10. Check LN is synced to network head */ -func TestSyncLightWithFull(t *testing.T) { - sw := swamp.NewSwamp(t) - - bridge := sw.NewBridgeNode() - +func TestSyncLightAgainstFull(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) - sw.WaitTillHeight(ctx, 20) + sw := swamp.NewSwamp(t) + // wait for the core network to fill up 20 blocks + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, numBlocks) + sw.WaitTillHeight(ctx, numBlocks) + // create bridge and set it as a bootstrapper + bridge := sw.NewBridgeNode() + sw.SetBootstrapper(t, bridge) + // start a bridge node and wait for it to sync up 20 blocks err := bridge.Start(ctx) require.NoError(t, err) - - h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) + h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) - - addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + // create a FN with BN as a trusted peer + full := sw.NewFullNode() + // start FN and wait for it to sync up to head of BN + err = full.Start(ctx) require.NoError(t, err) - - cfg := nodebuilder.DefaultConfig(node.Full) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - full := sw.NewNodeWithConfig(node.Full, cfg) - require.NoError(t, full.Start(ctx)) - - h, err = full.HeaderServ.WaitForHeight(ctx, 30) + bridgeHead, err := bridge.HeaderServ.LocalHead(ctx) require.NoError(t, err) - - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) - - addrs, err = peer.AddrInfoToP2pAddrs(host.InfoFromHost(full.Host)) + _, err = full.HeaderServ.WaitForHeight(ctx, uint64(bridgeHead.Height())) require.NoError(t, err) - cfg = nodebuilder.DefaultConfig(node.Light) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - light := sw.NewNodeWithConfig(node.Light, cfg) + // reset suite bootstrapper list and set full node as a bootstrapper for + // LN to connect to + sw.Bootstrappers = make([]ma.Multiaddr, 0) + sw.SetBootstrapper(t, full) + // create an LN with FN as a trusted peer + light := sw.NewLightNode() + + // ensure there is no direct connection between LN and BN so that + // LN relies only on FN for syncing err = sw.Network.UnlinkPeers(bridge.Host.ID(), light.Host.ID()) require.NoError(t, err) + // start LN and wait for it to sync up to network head against the head of the FN err = light.Start(ctx) require.NoError(t, err) - - h, err = light.HeaderServ.WaitForHeight(ctx, 50) + fullHead, err := full.HeaderServ.LocalHead(ctx) + require.NoError(t, err) + _, err = light.HeaderServ.WaitForHeight(ctx, uint64(fullHead.Height())) require.NoError(t, err) - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 50)) + // wait for the core block filling process to exit + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case err := <-fillDn: + require.NoError(t, err) + } } /* @@ -264,54 +339,54 @@ Steps: 3. Check BN is synced to height 20 4. Create a Full Node(FN) with a connection to BN as a trusted peer 5. Start a FN -6. Check FN is synced to height 30 -7. Create a Light Node(LN) with a connection to BN, FN as trusted peers +6. Check FN is synced to network head +7. Create a Light Node(LN) with a connection to BN and FN as trusted peers 8. Start LN -9. Check LN is synced to height 50 +9. Check LN is synced to network head. */ func TestSyncLightWithTrustedPeers(t *testing.T) { - sw := swamp.NewSwamp(t) - - bridge := sw.NewBridgeNode() - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) - sw.WaitTillHeight(ctx, 20) + sw := swamp.NewSwamp(t) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, numBlocks) + sw.WaitTillHeight(ctx, numBlocks) + // create a BN and set as a bootstrapper + bridge := sw.NewBridgeNode() + sw.SetBootstrapper(t, bridge) + // let it sync to network head err := bridge.Start(ctx) require.NoError(t, err) - - h, err := bridge.HeaderServ.WaitForHeight(ctx, 20) + _, err = bridge.HeaderServ.WaitForHeight(ctx, numBlocks) require.NoError(t, err) - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 20)) + // create a FN with BN as trusted peer + full := sw.NewFullNode() - addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + // let FN sync to network head + err = full.Start(ctx) require.NoError(t, err) - - cfg := nodebuilder.DefaultConfig(node.Full) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - full := sw.NewNodeWithConfig(node.Full, cfg) - require.NoError(t, full.Start(ctx)) - - h, err = full.HeaderServ.WaitForHeight(ctx, 30) + err = full.HeaderServ.SyncWait(ctx) require.NoError(t, err) - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 30)) + // add full node as a bootstrapper for the suite + sw.SetBootstrapper(t, full) - addrs, err = peer.AddrInfoToP2pAddrs(host.InfoFromHost(full.Host)) - require.NoError(t, err) - - cfg = nodebuilder.DefaultConfig(node.Light) - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - light := sw.NewNodeWithConfig(node.Light, cfg) + // create a LN with both FN and BN as trusted peers + light := sw.NewLightNode() + // let LN sync to network head err = light.Start(ctx) require.NoError(t, err) - - h, err = light.HeaderServ.WaitForHeight(ctx, 50) + err = light.HeaderServ.SyncWait(ctx) require.NoError(t, err) - assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 50)) + // wait for the core block filling process to exit + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + case err := <-fillDn: + require.NoError(t, err) + } } From 9f304b42ea039a56bedee7f1833b9714b746c952 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 15 Jun 2023 19:52:59 +0800 Subject: [PATCH 0662/1008] feat(share): collect absence proof if namespace is not present in merkle tree (#2242) ## Overview Implements non-inclusion proof collector. Higher level integration (e.g. shrex) is coming as separate PR. --- share/get_test.go | 136 +++++++++++++++++++++++------------ share/getter.go | 4 +- share/getters/utils.go | 3 +- share/ipld/get.go | 37 +++++++--- share/ipld/namespace_data.go | 132 ++++++++++++++++++++++++---------- share/ipld/nmt.go | 19 +++++ 6 files changed, 234 insertions(+), 97 deletions(-) diff --git a/share/get_test.go b/share/get_test.go index 8eafe84cd8..25711e5a65 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -1,8 +1,12 @@ package share import ( + "bytes" "context" + "crypto/rand" + "errors" mrand "math/rand" + "sort" "strconv" "testing" "time" @@ -141,8 +145,8 @@ func removeRandShares(data [][]byte, d int) [][]byte { } func TestGetSharesByNamespace(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + t.Cleanup(cancel) bServ := mdutils.Bserv() var tests = []struct { @@ -168,6 +172,9 @@ func TestGetSharesByNamespace(t *testing.T) { for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) rowShares, _, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) + if errors.Is(err, ipld.ErrNamespaceOutsideRange) { + continue + } require.NoError(t, err) shares = append(shares, rowShares...) @@ -182,8 +189,8 @@ func TestGetSharesByNamespace(t *testing.T) { } func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + t.Cleanup(cancel) bServ := mdutils.Bserv() shares := RandShares(t, 16) @@ -191,11 +198,7 @@ func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { // set all shares to the same namespace id nid := shares[0][:NamespaceSize] - for i, nspace := range shares { - if i == len(shares) { - break - } - + for _, nspace := range shares { copy(nspace[:NamespaceSize], nid) } @@ -230,59 +233,53 @@ func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { } func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + t.Cleanup(cancel) bServ := mdutils.Bserv() shares := RandShares(t, 16) - minNid := make([]byte, NamespaceSize) - midNid := make([]byte, NamespaceSize) - maxNid := make([]byte, NamespaceSize) - - numberOfShares := len(shares) - - copy(minNid, shares[0][:NamespaceSize]) - copy(maxNid, shares[numberOfShares-1][:NamespaceSize]) - copy(midNid, shares[numberOfShares/2][:NamespaceSize]) - - // create min nid missing data by replacing first namespace id with second - minNidMissingData := make([]Share, len(shares)) - copy(minNidMissingData, shares) - copy(minNidMissingData[0][:NamespaceSize], shares[1][:NamespaceSize]) - - // create max nid missing data by replacing last namespace id with second last - maxNidMissingData := make([]Share, len(shares)) - copy(maxNidMissingData, shares) - copy(maxNidMissingData[numberOfShares-1][:NamespaceSize], shares[numberOfShares-2][:NamespaceSize]) + // set all shares to the same namespace id + nids, err := randomNids(5) + require.NoError(t, err) + minNid := nids[0] + minIncluded := nids[1] + midNid := nids[2] + maxIncluded := nids[3] + maxNid := nids[4] - // create mid nid missing data by replacing middle namespace id with the one after - midNidMissingData := make([]Share, len(shares)) - copy(midNidMissingData, shares) - copy(midNidMissingData[numberOfShares/2][:NamespaceSize], shares[(numberOfShares/2)+1][:NamespaceSize]) + secondNamespaceFrom := mrand.Intn(len(shares)-2) + 1 + for i, nspace := range shares { + if i < secondNamespaceFrom { + copy(nspace[:NamespaceSize], minIncluded) + continue + } + copy(nspace[:NamespaceSize], maxIncluded) + } var tests = []struct { name string data []Share missingNid []byte + isAbsence bool }{ - {name: "Namespace id less than the minimum namespace in data", data: minNidMissingData, missingNid: minNid}, - {name: "Namespace id greater than the maximum namespace in data", data: maxNidMissingData, missingNid: maxNid}, - {name: "Namespace id in range but still missing", data: midNidMissingData, missingNid: midNid}, + {name: "Namespace id less than the minimum namespace in data", data: shares, missingNid: minNid}, + {name: "Namespace id greater than the maximum namespace in data", data: shares, missingNid: maxNid}, + {name: "Namespace id in range but still missing", data: shares, missingNid: midNid, isAbsence: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { eds, err := AddShares(ctx, shares, bServ) require.NoError(t, err) - assertNoRowContainsNID(t, bServ, eds, tt.missingNid) + assertNoRowContainsNID(ctx, t, bServ, eds, tt.missingNid, tt.isAbsence) }) } } func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + t.Cleanup(cancel) bServ := mdutils.Bserv() shares := RandShares(t, 16) @@ -306,6 +303,9 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi rcid := ipld.MustCidFromNamespacedSha256(row) data := ipld.NewNamespaceData(len(shares), nid, ipld.WithLeaves()) err := data.CollectLeavesByNamespace(ctx, bServ, rcid) + if errors.Is(err, ipld.ErrNamespaceOutsideRange) { + continue + } assert.Nil(t, err) leaves := data.Leaves() for _, node := range leaves { @@ -317,7 +317,7 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi } func TestGetSharesWithProofsByNamespace(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) bServ := mdutils.Bserv() @@ -341,7 +341,7 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { } expected := tt.rawData[from] - nID := expected[:NamespaceSize] + nID := namespace.ID(expected[:NamespaceSize]) // change rawData to contain several shares with same nID for i := from; i <= to; i++ { @@ -356,6 +356,10 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { for _, row := range eds.RowRoots() { rcid := ipld.MustCidFromNamespacedSha256(row) rowShares, proof, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) + if ipld.NamespaceIsOutsideRange(row, row, nID) { + require.ErrorIs(t, err, ipld.ErrNamespaceOutsideRange) + continue + } require.NoError(t, err) if len(rowShares) > 0 { require.NotNil(t, proof) @@ -432,10 +436,12 @@ func TestBatchSize(t *testing.T) { } func assertNoRowContainsNID( + ctx context.Context, t *testing.T, bServ blockservice.BlockService, eds *rsmt2d.ExtendedDataSquare, nID namespace.ID, + isAbsent bool, ) { rowRootCount := len(eds.RowRoots()) // get all row root cids @@ -445,11 +451,47 @@ func assertNoRowContainsNID( } // for each row root cid check if the minNID exists - for _, rowCID := range rowRootCIDs { + var absentCount, foundAbsenceRows int + for _, rowRoot := range eds.RowRoots() { + var outsideRange bool + if !ipld.NamespaceIsOutsideRange(rowRoot, rowRoot, nID) { + // nID does belong to namespace range of the row + absentCount++ + } else { + outsideRange = true + } data := ipld.NewNamespaceData(rowRootCount, nID, ipld.WithProofs()) - err := data.CollectLeavesByNamespace(context.Background(), bServ, rowCID) - leaves := data.Leaves() - assert.Nil(t, leaves) - assert.Nil(t, err) + rootCID := ipld.MustCidFromNamespacedSha256(rowRoot) + err := data.CollectLeavesByNamespace(ctx, bServ, rootCID) + if outsideRange { + require.ErrorIs(t, err, ipld.ErrNamespaceOutsideRange) + continue + } + require.NoError(t, err) + + // if no error returned, check absence proof + foundAbsenceRows++ + verified := data.Proof().VerifyNamespace(sha256.New(), nID, nil, rowRoot) + require.True(t, verified) + } + + if isAbsent { + require.Equal(t, foundAbsenceRows, absentCount) + // there should be max 1 row that has namespace range containing nID + require.LessOrEqual(t, absentCount, 1) + } +} + +func randomNids(total int) ([]namespace.ID, error) { + namespaces := make([]namespace.ID, total) + for i := range namespaces { + nid := make([]byte, NamespaceSize) + _, err := rand.Read(nid) + if err != nil { + return nil, err + } + namespaces[i] = nid } + sort.Slice(namespaces, func(i, j int) bool { return bytes.Compare(namespaces[i], namespaces[j]) < 0 }) + return namespaces, nil } diff --git a/share/getter.go b/share/getter.go index f7a7b9c129..6ba729c71a 100644 --- a/share/getter.go +++ b/share/getter.go @@ -10,6 +10,8 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share/ipld" ) var ( @@ -58,7 +60,7 @@ type NamespacedRow struct { func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { originalRoots := make([][]byte, 0) for _, row := range root.RowRoots { - if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { + if !ipld.NamespaceIsOutsideRange(row, row, nID) { originalRoots = append(originalRoots, row) } } diff --git a/share/getters/utils.go b/share/getters/utils.go index c99a7689b8..c0b882f330 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -15,7 +15,6 @@ import ( "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/libs/utils" @@ -35,7 +34,7 @@ var ( func filterRootsByNamespace(root *share.Root, nID namespace.ID) []cid.Cid { rowRootCIDs := make([]cid.Cid, 0, len(root.RowRoots)) for _, row := range root.RowRoots { - if !nID.Less(nmt.MinNamespace(row, nID.Size())) && nID.LessOrEqual(nmt.MaxNamespace(row, nID.Size())) { + if !ipld.NamespaceIsOutsideRange(row, row, nID) { rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) } } diff --git a/share/ipld/get.go b/share/ipld/get.go index 70385f73f7..6d7de35c27 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -103,7 +103,7 @@ func GetLeaves(ctx context.Context, // this buffer ensures writes to 'jobs' are never blocking (bin-tree-feat) jobs := make(chan *job, (maxShares+1)/2) // +1 for the case where 'maxShares' is 1 - jobs <- &job{id: root, ctx: ctx} + jobs <- &job{cid: root, ctx: ctx} // total is an amount of routines spawned and total amount of nodes we process (bin-tree-feat) // so we can specify exact amount of loops we do, and wait for this amount // of routines to finish processing @@ -123,11 +123,11 @@ func GetLeaves(ctx context.Context, defer wg.Done() span.SetAttributes( - attribute.String("cid", j.id.String()), + attribute.String("cid", j.cid.String()), attribute.Int("pos", j.sharePos), ) - nd, err := GetNode(ctx, bGetter, j.id) + nd, err := GetNode(ctx, bGetter, j.cid) if err != nil { // we don't really care about errors here // just fetch as much as possible @@ -149,7 +149,7 @@ func GetLeaves(ctx context.Context, // send those to be processed select { case jobs <- &job{ - id: lnk.Cid, + cid: lnk.Cid, // calc position for children nodes (bin-tree-feat), // s.t. 'if' above knows where to put a share sharePos: j.sharePos*2 + i, @@ -213,7 +213,7 @@ func GetProof( // chanGroup implements an atomic wait group, closing a jobs chan // when fully done. type chanGroup struct { - jobs chan *job + jobs chan job counter int64 } @@ -233,8 +233,29 @@ func (w *chanGroup) done() { // job represents an encountered node to investigate during the `GetLeaves` // and `CollectLeavesByNamespace` routines. type job struct { - id cid.Cid + // we pass the context to job so that spans are tracked in a tree + // structure + ctx context.Context + // cid of the node that will be handled + cid cid.Cid + // sharePos represents potential share position in share slice sharePos int - depth int - ctx context.Context + // depth represents the number of edges present in path from the root node of a tree to that node + depth int + // isAbsent indicates if target namespaceID is not included, only collect absence proofs + isAbsent bool +} + +func (j job) next(direction direction, cid cid.Cid, isAbsent bool) job { + var i int + if direction == right { + i++ + } + return job{ + ctx: j.ctx, + cid: cid, + sharePos: j.sharePos*2 + i, + depth: j.depth + 1, + isAbsent: isAbsent, + } } diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index 13be26c06a..b38e5884b5 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -18,6 +18,9 @@ import ( "github.com/celestiaorg/nmt/namespace" ) +var ErrNamespaceOutsideRange = errors.New("share/ipld: " + + "target namespace id is outside of namespace range for the given root") + // Option is the functional option that is applied to the NamespaceData instance // to configure data that needs to be stored. type Option func(*NamespaceData) @@ -40,11 +43,15 @@ func WithProofs() Option { // NamespaceData stores all leaves under the given namespace with their corresponding proofs. type NamespaceData struct { - leaves []ipld.Node - proofs *proofCollector + leaves []ipld.Node + proofs *proofCollector + bounds fetchedBounds maxShares int nID namespace.ID + + isAbsentNamespace atomic.Bool + absenceProofLeaf ipld.Node } func NewNamespaceData(maxShares int, nID namespace.ID, options ...Option) *NamespaceData { @@ -63,7 +70,7 @@ func NewNamespaceData(maxShares int, nID namespace.ID, options ...Option) *Names return data } -func (n *NamespaceData) validate() error { +func (n *NamespaceData) validate(rootCid cid.Cid) error { if len(n.nID) != NamespaceSize { return fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(n.nID)) } @@ -71,6 +78,11 @@ func (n *NamespaceData) validate() error { if n.leaves == nil && n.proofs == nil { return errors.New("share/ipld: empty NamespaceData, nothing specified to retrieve") } + + root := NamespacedSha256FromCID(rootCid) + if NamespaceIsOutsideRange(root, root, n.nID) { + return ErrNamespaceOutsideRange + } return nil } @@ -78,6 +90,14 @@ func (n *NamespaceData) addLeaf(pos int, nd ipld.Node) { // bounds will be needed in `Proof` method n.bounds.update(int64(pos)) + if n.isAbsentNamespace.Load() { + if n.absenceProofLeaf != nil { + log.Fatal("there should be only one absence leaf") + } + n.absenceProofLeaf = nd + return + } + if n.leaves == nil { return } @@ -140,6 +160,16 @@ func (n *NamespaceData) Proof() *nmt.Proof { nodes[i] = NamespacedSha256FromCID(node) } + if n.isAbsentNamespace.Load() { + proof := nmt.NewAbsenceProof( + int(n.bounds.lowest), + int(n.bounds.highest)+1, + nodes, + NamespacedSha256FromCID(n.absenceProofLeaf.Cid()), + NMTIgnoreMaxNamespace, + ) + return &proof + } proof := nmt.NewInclusionProof( int(n.bounds.lowest), int(n.bounds.highest)+1, @@ -159,7 +189,7 @@ func (n *NamespaceData) CollectLeavesByNamespace( bGetter blockservice.BlockGetter, root cid.Cid, ) error { - if err := n.validate(); err != nil { + if err := n.validate(root); err != nil { return err } @@ -173,8 +203,8 @@ func (n *NamespaceData) CollectLeavesByNamespace( // buffer the jobs to avoid blocking, we only need as many // queued as the number of shares in the second-to-last layer - jobs := make(chan *job, (n.maxShares+1)/2) - jobs <- &job{id: root, ctx: ctx} + jobs := make(chan job, (n.maxShares+1)/2) + jobs <- job{cid: root, ctx: ctx} var wg chanGroup wg.jobs = jobs @@ -186,7 +216,7 @@ func (n *NamespaceData) CollectLeavesByNamespace( ) for { - var j *job + var j job var ok bool select { case j, ok = <-jobs: @@ -203,13 +233,13 @@ func (n *NamespaceData) CollectLeavesByNamespace( defer wg.done() span.SetAttributes( - attribute.String("cid", j.id.String()), + attribute.String("cid", j.cid.String()), attribute.Int("pos", j.sharePos), ) // if an error is likely to be returned or not depends on // the underlying impl of the blockservice, currently it is not a realistic probability - nd, err := GetNode(ctx, bGetter, j.id) + nd, err := GetNode(ctx, bGetter, j.cid) if err != nil { singleErr.Do(func() { retrievalErr = err @@ -235,38 +265,11 @@ func (n *NamespaceData) CollectLeavesByNamespace( } // this node has links in the namespace, so keep walking - for i, lnk := range links { - newJob := &job{ - id: lnk.Cid, - // sharePos represents potential share position in share slice - sharePos: j.sharePos*2 + i, - // depth represents the number of edges present in path from the root node of a tree to that node - depth: j.depth + 1, - // we pass the context to job so that spans are tracked in a tree - // structure - ctx: ctx, - } - // if the link's nID isn't in range we don't need to create a new job for it, - // but need to collect a proof - jobNid := NamespacedSha256FromCID(newJob.id) - - // proof is on the right side, if the nID is less than min namespace of jobNid - if n.nID.Less(nmt.MinNamespace(jobNid, n.nID.Size())) { - n.addProof(right, lnk.Cid, newJob.depth) - continue - } - - // proof is on the left side, if the nID is bigger than max namespace of jobNid - if !n.nID.LessOrEqual(nmt.MaxNamespace(jobNid, n.nID.Size())) { - n.addProof(left, lnk.Cid, newJob.depth) - continue - } - - // by passing the previous check, we know we will have one more node to process - // note: it is important to increase the counter before sending to the channel + newJobs := n.traverseLinks(j, links) + for _, j := range newJobs { wg.add(1) select { - case jobs <- newJob: + case jobs <- j: case <-ctx.Done(): return } @@ -275,6 +278,57 @@ func (n *NamespaceData) CollectLeavesByNamespace( } } +func (n *NamespaceData) traverseLinks(j job, links []*ipld.Link) []job { + if j.isAbsent { + return n.collectAbsenceProofs(j, links) + } + return n.collectNDWithProofs(j, links) +} + +func (n *NamespaceData) collectAbsenceProofs(j job, links []*ipld.Link) []job { + leftLink := links[0].Cid + rightLink := links[1].Cid + // traverse to the left node, while collecting right node as proof + n.addProof(right, rightLink, j.depth) + return []job{j.next(left, leftLink, j.isAbsent)} +} + +func (n *NamespaceData) collectNDWithProofs(j job, links []*ipld.Link) []job { + leftCid := links[0].Cid + rightCid := links[1].Cid + leftLink := NamespacedSha256FromCID(leftCid) + rightLink := NamespacedSha256FromCID(rightCid) + + var nextJobs []job + // check if target namespace is outside of boundaries of both links + if NamespaceIsOutsideRange(leftLink, rightLink, n.nID) { + log.Fatalf("target namespace outside of boundaries of links at depth: %v", j.depth) + } + + if !NamespaceIsAboveMax(leftLink, n.nID) { + // namespace is within the range of left link + nextJobs = append(nextJobs, j.next(left, leftCid, false)) + } else { + // proof is on the left side, if the nID is on the right side of the range of left link + n.addProof(left, leftCid, j.depth) + if NamespaceIsBelowMin(rightLink, n.nID) { + // namespace is not included in either links, convert to absence collector + n.isAbsentNamespace.Store(true) + nextJobs = append(nextJobs, j.next(right, rightCid, true)) + return nextJobs + } + } + + if !NamespaceIsBelowMin(rightLink, n.nID) { + // namespace is within the range of right link + nextJobs = append(nextJobs, j.next(right, rightCid, false)) + } else { + // proof is on the right side, if the nID is on the left side of the range of right link + n.addProof(right, rightCid, j.depth) + } + return nextJobs +} + type fetchedBounds struct { lowest int64 highest int64 diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 3923260555..df140ef8c7 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -20,6 +20,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" ) var ( @@ -181,3 +182,21 @@ func Translate(dah *da.DataAvailabilityHeader, row, col int) (cid.Cid, int) { func NamespacedSha256FromCID(cid cid.Cid) []byte { return cid.Hash()[cidPrefixSize:] } + +// NamespaceIsAboveMax checks if the target namespace is above the maximum namespace for a given +// node hash. +func NamespaceIsAboveMax(nodeHash []byte, target namespace.ID) bool { + return !target.LessOrEqual(nmt.MaxNamespace(nodeHash, target.Size())) +} + +// NamespaceIsBelowMin checks if the target namespace is below the minimum namespace for a given +// node hash. +func NamespaceIsBelowMin(nodeHash []byte, target namespace.ID) bool { + return target.Less(nmt.MinNamespace(nodeHash, target.Size())) +} + +// NamespaceIsOutsideRange checks if the target namespace is outside the range defined by the left +// and right nodes +func NamespaceIsOutsideRange(leftNodeHash, rightNodeHash []byte, target namespace.ID) bool { + return NamespaceIsBelowMin(leftNodeHash, target) || NamespaceIsAboveMax(rightNodeHash, target) +} From 403eb61123d7d0659d38f0224dda060b9c3dd8da Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 15 Jun 2023 16:51:05 +0200 Subject: [PATCH 0663/1008] deps: bump libp2p (#2359) Closes #2211 This release brings a whole set of important fixes. --- go.mod | 78 ++++++------ go.sum | 164 ++++++++++++++------------ share/p2p/discovery/discovery_test.go | 13 +- 3 files changed, 133 insertions(+), 122 deletions(-) diff --git a/go.mod b/go.mod index 9628a0c205..5f5fe104a0 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.3.0 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 - github.com/benbjohnson/clock v1.3.0 + github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc2 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 @@ -31,7 +31,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/imdario/mergo v0.3.16 github.com/ipfs/go-blockservice v0.5.0 - github.com/ipfs/go-cid v0.3.2 + github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 @@ -44,17 +44,17 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 - github.com/libp2p/go-libp2p v0.26.3 - github.com/libp2p/go-libp2p-kad-dht v0.21.0 + github.com/libp2p/go-libp2p v0.28.0 + github.com/libp2p/go-libp2p-kad-dht v0.21.1 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.6.1 - github.com/minio/sha256-simd v1.0.0 + github.com/minio/sha256-simd v1.0.1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.8.0 + github.com/multiformats/go-multiaddr v0.9.0 github.com/multiformats/go-multiaddr-dns v0.3.1 - github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 + github.com/multiformats/go-multihash v0.2.2 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/prometheus/client_golang v1.14.0 github.com/pyroscope-io/client v0.7.1 @@ -74,11 +74,11 @@ require ( go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.9.0 - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 - golang.org/x/sync v0.1.0 + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 + golang.org/x/sync v0.2.0 golang.org/x/text v0.9.0 google.golang.org/grpc v1.53.0 - google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 + google.golang.org/protobuf v1.30.0 ) require ( @@ -106,12 +106,12 @@ require ( github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chzyer/readline v1.5.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/confio/ics23/go v0.9.0 // indirect - github.com/containerd/cgroups v1.0.4 // indirect + github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha8 // indirect @@ -127,7 +127,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect @@ -146,7 +146,7 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -154,19 +154,19 @@ require ( github.com/go-openapi/spec v0.19.11 // indirect github.com/go-openapi/swag v0.19.11 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect + github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect @@ -185,11 +185,11 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect - github.com/huin/goupnp v1.0.3 // indirect + github.com/huin/goupnp v1.2.0 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -217,29 +217,29 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.15.15 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect - github.com/koron/go-ssdp v0.0.3 // indirect + github.com/koron/go-ssdp v0.0.4 // indirect github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect + github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-nat v0.1.0 // indirect + github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/libp2p/go-reuseport v0.2.0 // indirect + github.com/libp2p/go-reuseport v0.3.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.50 // indirect + github.com/miekg/dns v1.1.54 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -250,11 +250,11 @@ require ( github.com/mtibben/percent v0.2.1 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multicodec v0.8.0 // indirect + github.com/multiformats/go-multibase v0.2.0 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.5.1 // indirect + github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -265,15 +265,15 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.2.1 // indirect - github.com/quic-go/qtls-go1-20 v0.1.1 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/quic-go/quic-go v0.33.0 // indirect - github.com/quic-go/webtransport-go v0.5.2 // indirect + github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect @@ -307,15 +307,15 @@ require ( go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.16.1 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.9.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.103.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -324,7 +324,7 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.1.7 // indirect + lukechampine.com/blake3 v1.2.1 // indirect nhooyr.io/websocket v1.8.7 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index bf659652b4..e548acff6a 100644 --- a/go.sum +++ b/go.sum @@ -295,8 +295,9 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbE github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -382,14 +383,14 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= @@ -428,8 +429,8 @@ github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1 github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= -github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -492,11 +493,11 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6Uh github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= @@ -633,8 +634,8 @@ github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNV github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= @@ -670,8 +671,9 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -732,8 +734,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -794,8 +797,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= -github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -901,8 +904,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= -github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= +github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -920,8 +923,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= +github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= @@ -972,8 +976,8 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= -github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= +github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= +github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -1140,15 +1144,15 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= @@ -1157,8 +1161,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= -github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= -github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -1206,11 +1210,11 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= -github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= -github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= +github.com/libp2p/go-libp2p v0.28.0 h1:zO8cY98nJiPzZpFv5w5gqqb8aVzt4ukQ0nVOSaaKhJ8= +github.com/libp2p/go-libp2p v0.28.0/go.mod h1:s3Xabc9LSwOcnv9UD4nORnXKTsWkPMkIMB/JIGXVnzk= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= -github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= -github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= +github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= +github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= @@ -1255,8 +1259,8 @@ github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxn github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.21.0 h1:J0Yd22VA+sk0CJRGMgtfHvLVIkZDyJ3AJGiljywIw5U= -github.com/libp2p/go-libp2p-kad-dht v0.21.0/go.mod h1:Bhm9diAFmc6qcWAr084bHNL159srVZRKADdp96Qqd1I= +github.com/libp2p/go-libp2p-kad-dht v0.21.1 h1:xpfp8/t9+X2ip1l8Umap1/UGNnJ3RHJgKGAEsnRAlTo= +github.com/libp2p/go-libp2p-kad-dht v0.21.1/go.mod h1:Oy8wvbdjpB70eS5AaFaI68tOtrdo3KylTvXDjikxqFo= github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= @@ -1361,8 +1365,9 @@ github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0 github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= -github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= +github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= +github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= @@ -1378,8 +1383,8 @@ github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= -github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= -github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= +github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjPyvUw= +github.com/libp2p/go-reuseport v0.3.0/go.mod h1:laea40AimhtfEqysZ71UpYj4S+R9VpH8PgqLo7L+SwI= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= @@ -1468,8 +1473,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1488,8 +1493,8 @@ github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= +github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1507,8 +1512,9 @@ github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+ github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -1564,8 +1570,8 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= -github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= +github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= +github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -1584,13 +1590,13 @@ github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= -github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= +github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= +github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multicodec v0.8.0 h1:evBmgkbSQux+Ds2IgfhkO38Dl2GDtRW8/Rp6YiSHX/Q= -github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -1599,8 +1605,8 @@ github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= -github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6 h1:qLF997Rz0X1WvdcZ2r5CUkLZ2rvdiXwG1JRSrJZEAuE= -github.com/multiformats/go-multihash v0.2.2-0.20221030163302-608669da49b6/go.mod h1:kaHxr8TfO1cxIR/tYxgZ7e59HraJq8arEQQR8E/YNvI= +github.com/multiformats/go-multihash v0.2.2 h1:Uu7LWs/PmWby1gkj1S1DXx3zyd3aVabA4FiMKn/2tAc= +github.com/multiformats/go-multihash v0.2.2/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= @@ -1652,8 +1658,8 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= -github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= +github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1661,7 +1667,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1742,8 +1748,8 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1780,14 +1786,14 @@ github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKA github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A= -github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk= -github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= -github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= -github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= +github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= @@ -2079,10 +2085,10 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= -go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -2168,8 +2174,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2201,8 +2207,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2329,8 +2335,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2453,12 +2459,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -2558,8 +2565,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2818,8 +2825,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2872,8 +2879,9 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/share/p2p/discovery/discovery_test.go b/share/p2p/discovery/discovery_test.go index f0935086ef..06d88a9079 100644 --- a/share/p2p/discovery/discovery_test.go +++ b/share/p2p/discovery/discovery_test.go @@ -11,6 +11,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/p2p/discovery/routing" basic "github.com/libp2p/go-libp2p/p2p/host/basic" + "github.com/libp2p/go-libp2p/p2p/host/eventbus" swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,7 +22,7 @@ func TestDiscovery(t *testing.T) { discoveryRetryTimeout = time.Millisecond * 100 // defined in discovery.go - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30) t.Cleanup(cancel) tn := newTestnet(ctx, t) @@ -80,8 +81,9 @@ type testnet struct { } func newTestnet(ctx context.Context, t *testing.T) *testnet { - swarm := swarmt.GenSwarm(t, swarmt.OptDisableTCP) - hst, err := basic.NewHost(swarm, &basic.HostOpts{}) + bus := eventbus.NewBus() + swarm := swarmt.GenSwarm(t, swarmt.OptDisableTCP, swarmt.EventBus(bus)) + hst, err := basic.NewHost(swarm, &basic.HostOpts{EventBus: bus}) require.NoError(t, err) hst.Start() @@ -110,8 +112,9 @@ func (t *testnet) discovery(opts ...Option) *Discovery { } func (t *testnet) peer() (host.Host, discovery.Discovery) { - swarm := swarmt.GenSwarm(t.T, swarmt.OptDisableTCP) - hst, err := basic.NewHost(swarm, &basic.HostOpts{}) + bus := eventbus.NewBus() + swarm := swarmt.GenSwarm(t.T, swarmt.OptDisableTCP, swarmt.EventBus(bus)) + hst, err := basic.NewHost(swarm, &basic.HostOpts{EventBus: bus}) require.NoError(t.T, err) hst.Start() From d5b571de330e13f118507dde3fe8b36c864c9f2e Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 15 Jun 2023 17:55:54 +0200 Subject: [PATCH 0664/1008] feat(node): implement node.AuthVerify and node.AuthNew (#2370) --- api/rpc/server.go | 14 ++-------- libs/authtoken/authtoken.go | 18 ++++++++++++ nodebuilder/node.go | 3 +- nodebuilder/node/admin.go | 22 +++++++++------ nodebuilder/node/mocks/api.go | 4 +-- nodebuilder/node/module.go | 2 +- nodebuilder/node/node.go | 6 ++-- nodebuilder/node_test.go | 2 +- nodebuilder/tests/api_test.go | 52 +++++++++++++++++++++++++++++------ 9 files changed, 84 insertions(+), 39 deletions(-) diff --git a/api/rpc/server.go b/api/rpc/server.go index a4c8c21ce7..3357140e68 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "encoding/json" "net" "net/http" "reflect" @@ -15,6 +14,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/api/rpc/perms" + "github.com/celestiaorg/celestia-node/libs/authtoken" ) var log = logging.Logger("rpc") @@ -51,17 +51,7 @@ func NewServer(address, port string, secret jwt.Signer) *Server { // reached if a token is provided in the header of the request, otherwise only // methods with `read` permissions are accessible. func (s *Server) verifyAuth(_ context.Context, token string) ([]auth.Permission, error) { - tk, err := jwt.ParseAndVerifyString(token, s.auth) - if err != nil { - return nil, err - } - p := new(perms.JWTPayload) - err = json.Unmarshal(tk.RawClaims(), p) - if err != nil { - return nil, err - } - // check permissions - return p.Allow, nil + return authtoken.ExtractSignedPermissions(s.auth, token) } // RegisterService registers a service onto the RPC server. All methods on the service will then be diff --git a/libs/authtoken/authtoken.go b/libs/authtoken/authtoken.go index c86272790d..3d6645c972 100644 --- a/libs/authtoken/authtoken.go +++ b/libs/authtoken/authtoken.go @@ -1,12 +1,30 @@ package authtoken import ( + "encoding/json" + "github.com/cristalhq/jwt" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/celestiaorg/celestia-node/api/rpc/perms" ) +// ExtractSignedPermissions returns the permissions granted to the token by the passed signer. +// If the token isn't signed by the signer, it will not pass verification. +func ExtractSignedPermissions(signer jwt.Signer, token string) ([]auth.Permission, error) { + tk, err := jwt.ParseAndVerifyString(token, signer) + if err != nil { + return nil, err + } + p := new(perms.JWTPayload) + err = json.Unmarshal(tk.RawClaims(), p) + if err != nil { + return nil, err + } + return p.Allow, nil +} + +// NewSignedJWT returns a signed JWT token with the passed permissions and signer. func NewSignedJWT(signer jwt.Signer, permissions []auth.Permission) (string, error) { token, err := jwt.NewTokenBuilder(signer).Build(&perms.JWTPayload{ Allow: permissions, diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 19760831cf..33132dc5e4 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -6,7 +6,6 @@ import ( "fmt" "strings" - "github.com/cristalhq/jwt" "github.com/ipfs/go-blockservice" exchange "github.com/ipfs/go-ipfs-exchange-interface" logging "github.com/ipfs/go-log/v2" @@ -49,7 +48,6 @@ type Node struct { Network p2p.Network Bootstrappers p2p.Bootstrappers Config *Config - AdminSigner jwt.Signer // rpc components RPCServer *rpc.Server // not optional @@ -70,6 +68,7 @@ type Node struct { FraudServ fraud.Module // not optional BlobServ blob.Module // not optional DASer das.Module // not optional + AdminServ node.Module // not optional // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop start, stop lifecycleFunc diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index cad8a51361..c6c97625ef 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -2,21 +2,25 @@ package node import ( "context" - "fmt" + "github.com/cristalhq/jwt" "github.com/filecoin-project/go-jsonrpc/auth" logging "github.com/ipfs/go-log/v2" + + "github.com/celestiaorg/celestia-node/libs/authtoken" ) -const APIVersion = "v0.2.0" +const APIVersion = "v0.2.1" type module struct { - tp Type + tp Type + signer jwt.Signer } -func newModule(tp Type) Module { +func newModule(tp Type, signer jwt.Signer) Module { return &module{ - tp: tp, + tp: tp, + signer: signer, } } @@ -38,10 +42,10 @@ func (m *module) LogLevelSet(_ context.Context, name, level string) error { return logging.SetLogLevel(name, level) } -func (m *module) AuthVerify(context.Context, string) ([]auth.Permission, error) { - return []auth.Permission{}, fmt.Errorf("not implemented") +func (m *module) AuthVerify(_ context.Context, token string) ([]auth.Permission, error) { + return authtoken.ExtractSignedPermissions(m.signer, token) } -func (m *module) AuthNew(context.Context, []auth.Permission) ([]byte, error) { - return nil, fmt.Errorf("not implemented") +func (m *module) AuthNew(_ context.Context, permissions []auth.Permission) (string, error) { + return authtoken.NewSignedJWT(m.signer, permissions) } diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index 98df713429..d8789a771c 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -37,10 +37,10 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // AuthNew mocks base method. -func (m *MockModule) AuthNew(arg0 context.Context, arg1 []auth.Permission) ([]byte, error) { +func (m *MockModule) AuthNew(arg0 context.Context, arg1 []auth.Permission) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AuthNew", arg0, arg1) - ret0, _ := ret[0].([]byte) + ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/nodebuilder/node/module.go b/nodebuilder/node/module.go index e7ae88182e..5abfad8e5f 100644 --- a/nodebuilder/node/module.go +++ b/nodebuilder/node/module.go @@ -9,7 +9,7 @@ func ConstructModule(tp Type) fx.Option { return fx.Module( "node", fx.Provide(func(secret jwt.Signer) Module { - return newModule(tp) + return newModule(tp, secret) }), fx.Provide(secret), ) diff --git a/nodebuilder/node/node.go b/nodebuilder/node/node.go index c33f73ef58..18ce93615b 100644 --- a/nodebuilder/node/node.go +++ b/nodebuilder/node/node.go @@ -20,7 +20,7 @@ type Module interface { // AuthVerify returns the permissions assigned to the given token. AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) // AuthNew signs and returns a new token with the given permissions. - AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) + AuthNew(ctx context.Context, perms []auth.Permission) (string, error) } var _ Module = (*API)(nil) @@ -30,7 +30,7 @@ type API struct { Info func(context.Context) (Info, error) `perm:"admin"` LogLevelSet func(ctx context.Context, name, level string) error `perm:"admin"` AuthVerify func(ctx context.Context, token string) ([]auth.Permission, error) `perm:"admin"` - AuthNew func(ctx context.Context, perms []auth.Permission) ([]byte, error) `perm:"admin"` + AuthNew func(ctx context.Context, perms []auth.Permission) (string, error) `perm:"admin"` } } @@ -46,6 +46,6 @@ func (api *API) AuthVerify(ctx context.Context, token string) ([]auth.Permission return api.Internal.AuthVerify(ctx, token) } -func (api *API) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) { +func (api *API) AuthNew(ctx context.Context, perms []auth.Permission) (string, error) { return api.Internal.AuthNew(ctx, perms) } diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index bd5d1da811..e6775419c7 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -34,7 +34,7 @@ func TestLifecycle(t *testing.T) { require.NotNil(t, node.Host) require.NotNil(t, node.HeaderServ) require.NotNil(t, node.StateServ) - require.NotNil(t, node.AdminSigner) + require.NotNil(t, node.AdminServ) require.Equal(t, tt.tp, node.Type) ctx, cancel := context.WithCancel(context.Background()) diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 13cf083eee..37504ec3b3 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -13,29 +13,64 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/blob/blobtest" - "github.com/celestiaorg/celestia-node/libs/authtoken" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) -func TestGetByHeight(t *testing.T) { +func TestNodeModule(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second)) - // start a bridge node bridge := sw.NewBridgeNode() err := bridge.Start(ctx) require.NoError(t, err) - signer := bridge.AdminSigner bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() - jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) + writePerms := []auth.Permission{"public", "read", "write"} + adminPerms := []auth.Permission{"public", "read", "write", "admin"} + jwt, err := bridge.AdminServ.AuthNew(ctx, adminPerms) + require.NoError(t, err) + + client, err := client.NewClient(ctx, bridgeAddr, jwt) + require.NoError(t, err) + + info, err := client.Node.Info(ctx) + require.NoError(t, err) + require.Equal(t, info.APIVersion, node.APIVersion) + + perms, err := client.Node.AuthVerify(ctx, jwt) + require.NoError(t, err) + require.Equal(t, perms, adminPerms) + + writeJWT, err := client.Node.AuthNew(ctx, writePerms) + require.NoError(t, err) + + perms, err = client.Node.AuthVerify(ctx, writeJWT) + require.NoError(t, err) + require.Equal(t, perms, writePerms) + +} + +func TestGetByHeight(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second)) + + // start a bridge node + bridge := sw.NewBridgeNode() + err := bridge.Start(ctx) + require.NoError(t, err) + + adminPerms := []auth.Permission{"public", "read", "write", "admin"} + jwt, err := bridge.AdminServ.AuthNew(ctx, adminPerms) require.NoError(t, err) + bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() client, err := client.NewClient(ctx, bridgeAddr, jwt) require.NoError(t, err) @@ -66,12 +101,11 @@ func TestBlobRPC(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - signer := bridge.AdminSigner - bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() - - jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) + adminPerms := []auth.Permission{"public", "read", "write", "admin"} + jwt, err := bridge.AdminServ.AuthNew(ctx, adminPerms) require.NoError(t, err) + bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() client, err := client.NewClient(ctx, bridgeAddr, jwt) require.NoError(t, err) From 258dc78a2d29fece0782a7fe0f0cc1a95562c285 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Fri, 16 Jun 2023 05:55:39 -0400 Subject: [PATCH 0665/1008] fix: parseNamespace for <= 10 byte namespace IDs (#2325) Co-authored-by: Ryan --- cmd/celestia/rpc.go | 68 +++++++++++++++++++-------------- cmd/celestia/rpc_test.go | 82 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 cmd/celestia/rpc_test.go diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 767fca872c..18851dc797 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -16,6 +16,7 @@ import ( "github.com/spf13/cobra" + appns "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/api/rpc/client" @@ -112,17 +113,17 @@ func parseParams(method string, params []string) []interface{} { } parsedParams[0] = root // 2. NamespaceID - nID, err := parseNamespace(params[1]) + nID, err := parseV0NamespaceID(params[1]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) + panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) } parsedParams[1] = nID case "Submit": // 1. NamespaceID var err error - nID, err := parseNamespace(params[0]) + nID, err := parseV0NamespaceID(params[0]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) + panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) } // 2. Blob data var blobData []byte @@ -162,9 +163,9 @@ func parseParams(method string, params []string) []interface{} { } parsedParams[1] = num // 3. NamespaceID - nID, err := parseNamespace(params[2]) + nID, err := parseV0NamespaceID(params[2]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) + panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) } // 4. Blob data var blobData []byte @@ -201,9 +202,9 @@ func parseParams(method string, params []string) []interface{} { } parsedParams[0] = num // 2. NamespaceID - nID, err := parseNamespace(params[1]) + nID, err := parseV0NamespaceID(params[1]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) + panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) } parsedParams[1] = nID // 3: Commitment @@ -221,9 +222,9 @@ func parseParams(method string, params []string) []interface{} { } parsedParams[0] = num // 2. NamespaceID - nID, err := parseNamespace(params[1]) + nID, err := parseV0NamespaceID(params[1]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) + panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) } parsedParams[1] = []namespace.ID{nID} return parsedParams @@ -368,7 +369,7 @@ func sendJSONRPCRequest(namespace, method string, params []interface{}) { rawResponseJSON, err := parseJSON(string(responseBody)) if err != nil { - panic(err) + log.Fatalf("Error parsing JSON-RPC response: %v", err) } if printRequest { output, err := json.MarshalIndent(outputWithRequest{ @@ -418,32 +419,41 @@ func parseSignatureForHelpstring(methodSig reflect.StructField) string { return simplifiedSignature } -func parseNamespace(param string) (namespace.ID, error) { - var nID []byte - var err error +// parseV0NamespaceID parses a namespace ID from a base64 or hex string. The param +// is expected to be the user-specified portion of a v0 namespace ID (i.e. the +// last 10 bytes). +func parseV0NamespaceID(param string) (namespace.ID, error) { + userBytes, err := decodeToBytes(param) + if err != nil { + return nil, err + } + + if len(userBytes) > appns.NamespaceVersionZeroIDSize { + return nil, fmt.Errorf( + "namespace ID %v is too large to be a v0 namespace, want <= %v bytes", + userBytes, appns.NamespaceVersionZeroIDSize, + ) + } + // if the namespace ID is <= 10 bytes, left pad it with 0s + return share.NewNamespaceV0(userBytes) +} + +// decodeToBytes decodes a Base64 or hex input string into a byte slice. +func decodeToBytes(param string) ([]byte, error) { if strings.HasPrefix(param, "0x") { decoded, err := hex.DecodeString(param[2:]) if err != nil { return nil, fmt.Errorf("error decoding namespace ID: %w", err) } - nID = decoded - } else { - // otherwise, it's just a base64 string - nID, err = base64.StdEncoding.DecodeString(param) - if err != nil { - return nil, fmt.Errorf("error decoding namespace ID: %w", err) - } + return decoded, nil } - // if the namespace ID is 8 bytes, add v0 share + namespace prefix and zero pad - if len(nID) == 8 { - nID, err = share.NewNamespaceV0(nID) - if err != nil { - return nil, err - } + // otherwise, it's just a base64 string + decoded, err := base64.StdEncoding.DecodeString(param) + if err != nil { + return nil, fmt.Errorf("error decoding namespace ID: %w", err) } - return nID, nil + return decoded, nil } - func parseJSON(param string) (json.RawMessage, error) { var raw json.RawMessage err := json.Unmarshal([]byte(param), &raw) diff --git a/cmd/celestia/rpc_test.go b/cmd/celestia/rpc_test.go new file mode 100644 index 0000000000..9380a8b01e --- /dev/null +++ b/cmd/celestia/rpc_test.go @@ -0,0 +1,82 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/celestiaorg/nmt/namespace" +) + +func Test_parseNamespaceID(t *testing.T) { + type testCase struct { + name string + param string + want namespace.ID + wantErr bool + } + testCases := []testCase{ + { + param: "0x0c204d39600fddd3", + name: "8 byte hex encoded namespace ID gets left padded", + want: namespace.ID{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, + }, + wantErr: false, + }, + { + name: "10 byte hex encoded namespace ID", + param: "0x42690c204d39600fddd3", + want: namespace.ID{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, + }, + wantErr: false, + }, + { + name: "29 byte hex encoded namespace ID", + param: "0x0000000000000000000000000000000000000001010101010101010101", + want: namespace.ID{ + 0x0, // namespace version + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // v0 ID prefix + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // namespace ID + }, + wantErr: true, + }, + { + name: "11 byte hex encoded namespace ID returns error", + param: "0x42690c204d39600fddd3a3", + want: namespace.ID{}, + wantErr: true, + }, + { + name: "10 byte base64 encoded namespace ID", + param: "QmkMIE05YA/d0w==", + want: namespace.ID{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, + }, + wantErr: false, + }, + { + name: "not base64 or hex encoded namespace ID returns error", + param: "5748493939429", + want: namespace.ID{}, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := parseV0NamespaceID(tc.param) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + + } +} From fd1818446ff6f0e9f3c14d6cdf1996c33b7ecb3b Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 16 Jun 2023 19:34:24 +0800 Subject: [PATCH 0666/1008] add kind:testing label (#2375) ## Overview Add testing tag for pure testing PRs --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index bed2b3352c..b9d4351bbd 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -16,4 +16,4 @@ jobs: with: mode: minimum count: 1 - labels: "kind:fix, kind:misc, kind:break!, kind:refactor, kind:feat, kind:deps, kind:docs, kind:ci, kind:chore" # yamllint disable-line rule:line-length + labels: "kind:fix, kind:misc, kind:break!, kind:refactor, kind:feat, kind:deps, kind:docs, kind:ci, kind:chore, kind:testing" # yamllint disable-line rule:line-length From 6654cdf4994dbd381efd0d6a29688c731177c855 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:20:40 +0200 Subject: [PATCH 0667/1008] feat(libs/pidstore): Implement `PeerIDStore` (#2274) This PR implements a `PeerIDStore` that will be useful for both dumping useful peers inside of celestia-node's use of p2p.Exchange as well as inside of discovery. Related to https://github.com/celestiaorg/celestia-node/issues/1851 --------- Co-authored-by: Hlib Kanunnikov --- libs/pidstore/pidstore.go | 67 ++++++++++++++++++++++++++++++++++ libs/pidstore/pidstore_test.go | 59 ++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 libs/pidstore/pidstore.go create mode 100644 libs/pidstore/pidstore_test.go diff --git a/libs/pidstore/pidstore.go b/libs/pidstore/pidstore.go new file mode 100644 index 0000000000..2d4eb870a8 --- /dev/null +++ b/libs/pidstore/pidstore.go @@ -0,0 +1,67 @@ +package pidstore + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/peer" +) + +var ( + storePrefix = datastore.NewKey("pidstore") + peersKey = datastore.NewKey("peers") + + log = logging.Logger("pidstore") +) + +// PeerIDStore is used to store/load peers to/from disk. +type PeerIDStore struct { + ds datastore.Datastore +} + +// NewPeerIDStore creates a new peer ID store backed by the given datastore. +func NewPeerIDStore(ds datastore.Datastore) *PeerIDStore { + return &PeerIDStore{ + ds: namespace.Wrap(ds, storePrefix), + } +} + +// Load loads the peers from datastore and returns them. +func (p *PeerIDStore) Load(ctx context.Context) ([]peer.ID, error) { + log.Debug("Loading peers") + + bin, err := p.ds.Get(ctx, peersKey) + if err != nil { + return nil, fmt.Errorf("pidstore: loading peers from datastore: %w", err) + } + + var peers []peer.ID + err = json.Unmarshal(bin, &peers) + if err != nil { + return nil, fmt.Errorf("pidstore: unmarshalling peer IDs: %w", err) + } + + log.Infow("Loaded peers from disk", "amount", len(peers)) + return peers, nil +} + +// Put persists the given peer IDs to the datastore. +func (p *PeerIDStore) Put(ctx context.Context, peers []peer.ID) error { + log.Debugw("Persisting peers to disk", "amount", len(peers)) + + bin, err := json.Marshal(peers) + if err != nil { + return fmt.Errorf("pidstore: marshal peerlist: %w", err) + } + + if err = p.ds.Put(ctx, peersKey, bin); err != nil { + return fmt.Errorf("pidstore: error writing to datastore: %w", err) + } + + log.Infow("Persisted peers successfully", "amount", len(peers)) + return nil +} diff --git a/libs/pidstore/pidstore_test.go b/libs/pidstore/pidstore_test.go new file mode 100644 index 0000000000..eafceff3fe --- /dev/null +++ b/libs/pidstore/pidstore_test.go @@ -0,0 +1,59 @@ +package pidstore + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "testing" + "time" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPutLoad(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer t.Cleanup(cancel) + + peerstore := NewPeerIDStore(sync.MutexWrap(datastore.NewMapDatastore())) + + ids, err := generateRandomPeerList(10) + require.NoError(t, err) + + err = peerstore.Put(ctx, ids) + require.NoError(t, err) + + retrievedPeerlist, err := peerstore.Load(ctx) + require.NoError(t, err) + + assert.Equal(t, len(ids), len(retrievedPeerlist)) + assert.Equal(t, ids, retrievedPeerlist) +} + +func generateRandomPeerList(length int) ([]peer.ID, error) { + peerlist := make([]peer.ID, length) + for i := range peerlist { + key, err := rsa.GenerateKey(rand.Reader, 2096) + if err != nil { + return nil, err + } + + _, pubkey, err := crypto.KeyPairFromStdKey(key) + if err != nil { + return nil, err + } + + peerID, err := peer.IDFromPublicKey(pubkey) + if err != nil { + return nil, err + } + + peerlist[i] = peerID + } + + return peerlist, nil +} From 30f061699af303fd1a0a214fc60d520d83175b37 Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Wed, 21 Jun 2023 11:08:56 -0400 Subject: [PATCH 0668/1008] Update ci_release.yml (#2384) ## Overview I missed adding the `merge_group` trigger when enabling the merge queue. oops. Adding it now :-) ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- .github/workflows/ci_release.yml | 1 + .github/workflows/docker-build-publish.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 654c3c0a34..653add9256 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -1,5 +1,6 @@ name: CI and Release on: + merge_group: push: branches: - main diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 0ddebbd750..c922e8b901 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -2,6 +2,7 @@ name: Docker Build & Publish # Trigger on all push events, new semantic version tags, and all PRs on: + merge_group: push: branches: - "**" From de88cffff715980177e9a2cfee4686ca440106b3 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 21 Jun 2023 17:24:24 +0200 Subject: [PATCH 0669/1008] feat(gateway): enabling CORS * on gateway (#2373) Closes #2263 Co-authored-by: Hlib Kanunnikov --- api/gateway/middleware.go | 8 ++++++++ api/gateway/server_test.go | 31 +++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/api/gateway/middleware.go b/api/gateway/middleware.go index 498b9c5d64..2c88b34185 100644 --- a/api/gateway/middleware.go +++ b/api/gateway/middleware.go @@ -18,9 +18,17 @@ func (h *Handler) RegisterMiddleware(srv *Server) { setContentType, checkPostDisabled(h.state), wrapRequestContext, + enableCors, ) } +func enableCors(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + next.ServeHTTP(w, r) + }) +} + func setContentType(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") diff --git a/api/gateway/server_test.go b/api/gateway/server_test.go index e98d7a6091..cb8e3d17ae 100644 --- a/api/gateway/server_test.go +++ b/api/gateway/server_test.go @@ -12,8 +12,12 @@ import ( "github.com/stretchr/testify/require" ) +const ( + address = "localhost" + port = "0" +) + func TestServer(t *testing.T) { - address, port := "localhost", "0" server := NewServer(address, port) ctx, cancel := context.WithCancel(context.Background()) @@ -42,10 +46,33 @@ func TestServer(t *testing.T) { require.NoError(t, err) } +func TestCorsEnabled(t *testing.T) { + server := NewServer(address, port) + server.RegisterMiddleware(enableCors) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err := server.Start(ctx) + require.NoError(t, err) + + // register ping handler + ping := new(ping) + server.RegisterHandlerFunc("/ping", ping.ServeHTTP, http.MethodGet) + + url := fmt.Sprintf("http://%s/ping", server.ListenAddr()) + + resp, err := http.Get(url) + require.NoError(t, err) + defer resp.Body.Close() + + require.NoError(t, err) + require.Equal(t, resp.Header.Get("Access-Control-Allow-Origin"), "*") +} + // TestServer_contextLeakProtection tests to ensure a context // deadline was added by the context wrapper middleware server-side. func TestServer_contextLeakProtection(t *testing.T) { - address, port := "localhost", "0" server := NewServer(address, port) server.RegisterMiddleware(wrapRequestContext) From 9adb61eb1d3dbe54e2c7e13cbd51e9de9205fc47 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 21 Jun 2023 17:26:41 +0200 Subject: [PATCH 0670/1008] share: introduce Namespace type (#2367) Mostly copied from the app's Namespace type with multiple modifications. In preparation for https://github.com/celestiaorg/celestia-node/issues/2301. We want a more straightforward Namespace type from what the app provides: * Repsents 1:1 mapping of the serialized form of Namespace [version byte + namespace id] * So that it does not allocate on the hot path when getting data by namespace * and had simpler json serialization --- share/namespace.go | 151 ++++++++++++++++++++++++++++++++++++++++ share/namespace_test.go | 84 ++++++++++++++++++++++ share/nid.go | 1 + 3 files changed, 236 insertions(+) create mode 100644 share/namespace.go create mode 100644 share/namespace_test.go diff --git a/share/namespace.go b/share/namespace.go new file mode 100644 index 0000000000..0af1d1a60e --- /dev/null +++ b/share/namespace.go @@ -0,0 +1,151 @@ +package share + +import ( + "bytes" + "encoding/hex" + "fmt" + + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/celestiaorg/nmt/namespace" +) + +// Various reserved namespaces. +var ( + MaxReservedNamespace = Namespace(appns.MaxReservedNamespace.Bytes()) + ParitySharesNamespace = Namespace(appns.ParitySharesNamespace.Bytes()) + TailPaddingNamespace = Namespace(appns.TailPaddingNamespace.Bytes()) + ReservedPaddingNamespace = Namespace(appns.ReservedPaddingNamespace.Bytes()) + TxNamespace = Namespace(appns.TxNamespace.Bytes()) + PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) +) + +// Namespace represents namespace of a Share. +// Consists of version byte and namespace ID. +type Namespace []byte + +// NamespaceFromBytes converts bytes into Namespace and validates it. +func NamespaceFromBytes(b []byte) (Namespace, error) { + n := Namespace(b) + return n, n.Validate() +} + +// Version reports version of the Namespace. +func (n Namespace) Version() byte { + return n[appns.NamespaceVersionSize-1] +} + +// ID reports ID of the Namespace. +func (n Namespace) ID() namespace.ID { + return namespace.ID(n[appns.NamespaceVersionSize:]) +} + +// ToNMT converts the whole Namespace(both Version and ID parts) into NMT's namespace.ID +// NOTE: Once https://github.com/celestiaorg/nmt/issues/206 is closed Namespace should become NNT's +// type. +func (n Namespace) ToNMT() namespace.ID { + return namespace.ID(n) +} + +// ToAppNamespace converts the Namespace to App's definition of Namespace. +// TODO: Unify types between node and app +func (n Namespace) ToAppNamespace() appns.Namespace { + return appns.Namespace{Version: n.Version(), ID: n.ID()} +} + +// Len reports the total length of the namespace. +func (n Namespace) Len() int { + return len(n) +} + +// String stringifies the Namespace. +func (n Namespace) String() string { + return hex.EncodeToString(n) +} + +// Equals compares two Namespaces. +func (n Namespace) Equals(target Namespace) bool { + return bytes.Equal(n, target) +} + +// Validate checks if the namespace is correct. +func (n Namespace) Validate() error { + if n.Len() != NamespaceSize { + return fmt.Errorf("invalid namespace length: expected %d, got %d", NamespaceSize, n.Len()) + } + if n.Version() != appns.NamespaceVersionZero && n.Version() != appns.NamespaceVersionMax { + return fmt.Errorf("invalid namespace version %v", n.Version()) + } + if len(n.ID()) != appns.NamespaceIDSize { + return fmt.Errorf("invalid namespace id length: expected %d, got %d", appns.NamespaceIDSize, n.ID().Size()) + } + if n.Version() == appns.NamespaceVersionZero && !bytes.HasPrefix(n.ID(), appns.NamespaceVersionZeroPrefix) { + return fmt.Errorf("invalid namespace id: expect %d leading zeroes", len(appns.NamespaceVersionZeroPrefix)) + } + return nil +} + +// ValidateDataNamespace checks if the Namespace contains real/useful data. +func (n Namespace) ValidateDataNamespace() error { + if err := n.Validate(); err != nil { + return err + } + if n.Equals(ParitySharesNamespace) || n.Equals(TailPaddingNamespace) { + return fmt.Errorf("invalid data namespace(%s): parity and tail padding namespace are forbidden", n) + } + return nil +} + +// ValidateBlobNamespace checks if the Namespace is valid blob namespace. +func (n Namespace) ValidateBlobNamespace() error { + if err := n.ValidateDataNamespace(); err != nil { + return err + } + if bytes.Compare(n, MaxReservedNamespace) < 1 { + return fmt.Errorf("invalid blob namespace(%s): reserved namespaces are forbidden", n) + } + return nil +} + +// IsAboveMax checks if the namespace is above the maximum namespace of the given hash. +func (n Namespace) IsAboveMax(nodeHash []byte) bool { + return !n.IsLessOrEqual(nodeHash[n.Len() : n.Len()*2]) +} + +// IsBelowMin checks if the target namespace is below the minimum namespace of the given hash. +func (n Namespace) IsBelowMin(nodeHash []byte) bool { + return n.IsLess(nodeHash[:n.Len()]) +} + +// IsOutsideRange checks if the namespace is outside the min-max range of the given hashes. +func (n Namespace) IsOutsideRange(leftNodeHash, rightNodeHash []byte) bool { + return n.IsBelowMin(leftNodeHash) || n.IsAboveMax(rightNodeHash) +} + +// Repeat copies the Namespace t times. +func (n Namespace) Repeat(t int) []Namespace { + ns := make([]Namespace, t) + for i := 0; i < t; i++ { + ns[i] = n + } + return ns +} + +// IsLess reports if the Namespace is less than the target. +func (n Namespace) IsLess(target Namespace) bool { + return bytes.Compare(n, target) == -1 +} + +// IsLessOrEqual reports if the Namespace is less than the target. +func (n Namespace) IsLessOrEqual(target Namespace) bool { + return bytes.Compare(n, target) < 1 +} + +// IsGreater reports if the Namespace is greater than the target. +func (n Namespace) IsGreater(target Namespace) bool { + return bytes.Compare(n, target) == 1 +} + +// IsGreaterOrEqualThan reports if the Namespace is greater or equal than the target. +func (n Namespace) IsGreaterOrEqualThan(target Namespace) bool { + return bytes.Compare(n, target) > -1 +} diff --git a/share/namespace_test.go b/share/namespace_test.go new file mode 100644 index 0000000000..8cc61b379b --- /dev/null +++ b/share/namespace_test.go @@ -0,0 +1,84 @@ +package share + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + appns "github.com/celestiaorg/celestia-app/pkg/namespace" +) + +var ( + validID = append( + appns.NamespaceVersionZeroPrefix, + bytes.Repeat([]byte{1}, appns.NamespaceVersionZeroIDSize)..., + ) + tooShortID = append(appns.NamespaceVersionZeroPrefix, []byte{1}...) + tooLongID = append(appns.NamespaceVersionZeroPrefix, bytes.Repeat([]byte{1}, NamespaceSize)...) + invalidPrefixID = bytes.Repeat([]byte{1}, NamespaceSize) +) + +func TestFrom(t *testing.T) { + type testCase struct { + name string + bytes []byte + wantErr bool + want Namespace + } + validNamespace := []byte{} + validNamespace = append(validNamespace, appns.NamespaceVersionZero) + validNamespace = append(validNamespace, appns.NamespaceVersionZeroPrefix...) + validNamespace = append(validNamespace, bytes.Repeat([]byte{0x1}, appns.NamespaceVersionZeroIDSize)...) + parityNamespace := bytes.Repeat([]byte{0xFF}, NamespaceSize) + + testCases := []testCase{ + { + name: "valid namespace", + bytes: validNamespace, + wantErr: false, + want: append([]byte{appns.NamespaceVersionZero}, validID...), + }, + { + name: "parity namespace", + bytes: parityNamespace, + wantErr: false, + want: append([]byte{appns.NamespaceVersionMax}, bytes.Repeat([]byte{0xFF}, appns.NamespaceIDSize)...), + }, + { + name: "unsupported version", + bytes: append([]byte{1}, append( + appns.NamespaceVersionZeroPrefix, + bytes.Repeat([]byte{1}, NamespaceSize-len(appns.NamespaceVersionZeroPrefix))..., + )...), + wantErr: true, + }, + { + name: "unsupported id: too short", + bytes: append([]byte{appns.NamespaceVersionZero}, tooShortID...), + wantErr: true, + }, + { + name: "unsupported id: too long", + bytes: append([]byte{appns.NamespaceVersionZero}, tooLongID...), + wantErr: true, + }, + { + name: "unsupported id: invalid prefix", + bytes: append([]byte{appns.NamespaceVersionZero}, invalidPrefixID...), + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := NamespaceFromBytes(tc.bytes) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/share/nid.go b/share/nid.go index b7fd4e5836..7d960cc9e1 100644 --- a/share/nid.go +++ b/share/nid.go @@ -10,6 +10,7 @@ import ( // NewNamespaceV0 takes a variable size byte slice and creates a version 0 Namespace ID. // The byte slice must be <= 10 bytes. // If it is less than 10 bytes, it will be left padded to size 10 with 0s. +// TODO: Adapt for Namespace in the integration PR func NewNamespaceV0(subNId []byte) (namespace.ID, error) { if lnid := len(subNId); lnid > appns.NamespaceVersionZeroIDSize { return nil, fmt.Errorf("namespace id must be <= %v, but it was %v bytes", appns.NamespaceVersionZeroIDSize, lnid) From ce8ccb3677dd0498b5230ca5c3080a10003aa8d2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:28:02 +0200 Subject: [PATCH 0671/1008] feat(header): Check app version in eh validation and return err on mismatch (#2138) As recommended by @cmwaters, we should ensure (as a rudimentary check) that the headers being passed to us from core match in app versions as upgraded app versions can indicate breaking changes. We should error out if the app version does not match. This PR is still draft until celestia-app has a version out where they export the AppVersion as a const that we can depend on. Eventually we should support several versions via #2137 but for now, this is a good enough check. TODO: - [x] depend on const from app - [x] test --------- Co-authored-by: Hlib Kanunnikov --- go.mod | 2 +- go.sum | 4 ++-- header/header.go | 6 ++++++ header/headertest/verify_test.go | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5f5fe104a0..aef01d86a8 100644 --- a/go.mod +++ b/go.mod @@ -330,7 +330,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.14.0-sdk-v0.46.11 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27 diff --git a/go.sum b/go.sum index e548acff6a..065c88774f 100644 --- a/go.sum +++ b/go.sum @@ -346,8 +346,8 @@ github.com/celestiaorg/celestia-app v1.0.0-rc2 h1:/u7eespYtBpQtBSz3P8/rKfz9rW7QO github.com/celestiaorg/celestia-app v1.0.0-rc2/go.mod h1:uiTWKTtRpVwvSiFDl2zausrU1ZBHBWgk7z52pfzJqJU= github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27 h1:nmr9O5BflgNR1aWehs1ZFw4obA//M/+g+SrSMK9sOBA= github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27/go.mod h1:GVo91Wifg9KL/nFx9nPkpl0UIFdvvs4fhnly9GhGxZU= -github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11 h1:Rd5EvJx1nG3KurBspVN51RVmvif0Lp2UVURbG2ad3Cs= -github.com/celestiaorg/cosmos-sdk v1.13.0-sdk-v0.46.11/go.mod h1:xCG6OUkJy5KUMEg20Zk010lra9XjkmKS3+bk0wp7bd8= +github.com/celestiaorg/cosmos-sdk v1.14.0-sdk-v0.46.11 h1:xDLC0ZvIwj9S2gTs9b8EespilL/u/vOGcqJuHz1r/eA= +github.com/celestiaorg/cosmos-sdk v1.14.0-sdk-v0.46.11/go.mod h1:IwvD2nN3vEMkjxhTw/SF5tyJ0+x3GB0EdyQJyteK06U= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= diff --git a/header/header.go b/header/header.go index 25c3b0e5cf..e59d3802c1 100644 --- a/header/header.go +++ b/header/header.go @@ -10,6 +10,7 @@ import ( tmjson "github.com/tendermint/tendermint/libs/json" core "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" @@ -115,6 +116,11 @@ func (eh *ExtendedHeader) Validate() error { return fmt.Errorf("ValidateBasic error on RawHeader at height %d: %w", eh.Height(), err) } + if eh.RawHeader.Version.App != appconsts.LatestVersion { + return fmt.Errorf("app version mismatch, expected: %d, got %d", appconsts.LatestVersion, + eh.RawHeader.Version.App) + } + err = eh.Commit.ValidateBasic() if err != nil { return fmt.Errorf("ValidateBasic error on Commit at height %d: %w", eh.Height(), err) diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go index 78da779a54..177352a4cf 100644 --- a/header/headertest/verify_test.go +++ b/header/headertest/verify_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/celestiaorg/celestia-app/pkg/appconsts" libhead "github.com/celestiaorg/go-header" ) @@ -80,6 +81,13 @@ func TestVerify(t *testing.T) { }, err: true, }, + { + prepare: func() libhead.Header { + untrustedAdj.RawHeader.Version.App = appconsts.LatestVersion + 1 + return untrustedAdj + }, + err: true, + }, } for i, test := range tests { From 367fc66c6f9c68bcbea4906e335b7207d6854a31 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 21 Jun 2023 23:43:05 +0800 Subject: [PATCH 0672/1008] chore(share/getters): add unit test for ctxwithsplittimeout (#2374) ## Overview Adds extra safety. Co-authored-by: Hlib Kanunnikov --- share/getters/utils_test.go | 115 ++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/share/getters/utils_test.go b/share/getters/utils_test.go index 73e9400010..db8cd0f8cc 100644 --- a/share/getters/utils_test.go +++ b/share/getters/utils_test.go @@ -1,11 +1,14 @@ package getters import ( + "context" "errors" "fmt" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_ErrorContains(t *testing.T) { @@ -101,3 +104,115 @@ func Test_ErrorContains(t *testing.T) { }) } } + +func Test_ctxWithSplitTimeout(t *testing.T) { + type args struct { + ctxTimeout time.Duration + splitFactor []int + minTimeout time.Duration + } + tests := []struct { + name string + args args + want time.Duration + }{ + { + name: "ctxTimeout > minTimeout, splitFactor <= 0", + args: args{ + ctxTimeout: 3 * time.Minute, + splitFactor: []int{-1, 0}, + minTimeout: time.Minute, + }, + want: time.Minute, + }, + { + name: "ctxTimeout > minTimeout, splitFactor = 1", + args: args{ + ctxTimeout: 3 * time.Minute, + splitFactor: []int{1}, + minTimeout: time.Minute, + }, + want: 3 * time.Minute, + }, + { + name: "ctxTimeout > minTimeout, splitFactor = 2", + args: args{ + ctxTimeout: 3 * time.Minute, + splitFactor: []int{2}, + minTimeout: time.Minute, + }, + want: 3 * time.Minute / 2, + }, + { + name: "ctxTimeout > minTimeout, resulted timeout limited by minTimeout", + args: args{ + ctxTimeout: 3 * time.Minute, + splitFactor: []int{3, 4, 5}, + minTimeout: time.Minute, + }, + want: time.Minute, + }, + { + name: "ctxTimeout < minTimeout", + args: args{ + ctxTimeout: time.Minute, + splitFactor: []int{-1, 0, 1, 2, 3}, + minTimeout: 2 * time.Minute, + }, + want: time.Minute, + }, + { + name: "minTimeout = 0, splitFactor <= 1", + args: args{ + ctxTimeout: time.Minute, + splitFactor: []int{-1, 0, 1}, + minTimeout: 0, + }, + want: time.Minute, + }, + { + name: "minTimeout = 0, splitFactor > 1", + args: args{ + ctxTimeout: time.Minute, + splitFactor: []int{2}, + minTimeout: 0, + }, + want: time.Minute / 2, + }, + { + name: "no context timeout", + args: args{ + ctxTimeout: 0, + splitFactor: []int{-1, 0, 1, 2}, + minTimeout: time.Minute, + }, + want: 0, + }, + { + name: "no context timeout, minTimeout = 0", + args: args{ + ctxTimeout: 0, + splitFactor: []int{-1, 0, 1, 2}, + minTimeout: 0, + }, + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, sf := range tt.args.splitFactor { + ctx, cancel := context.WithTimeout(context.Background(), tt.args.ctxTimeout) + t.Cleanup(cancel) + got, _ := ctxWithSplitTimeout(ctx, sf, tt.args.minTimeout) + dl, ok := got.Deadline() + if !ok { + require.Equal(t, tt.want, 0) + continue + } + d := time.Until(dl) + require.True(t, d <= tt.want+time.Second) + require.True(t, d >= tt.want-time.Second) + } + }) + } +} From b222475b1d707e8c4e4cef9b67a5e47348c90bea Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:12:33 -0500 Subject: [PATCH 0673/1008] chore: bump celestia-app rc4 (#2371) ## Overview I'm getting a failed unit test, `TestBlobModule`, locally but I'm also getting that on `main` ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords Co-authored-by: Hlib Kanunnikov --- go.mod | 54 +++++++++++++++++--------------- go.sum | 99 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 79 insertions(+), 74 deletions(-) diff --git a/go.mod b/go.mod index aef01d86a8..31420f8745 100644 --- a/go.mod +++ b/go.mod @@ -6,17 +6,17 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/errors v1.0.0-beta.7 - cosmossdk.io/math v1.0.0-beta.3 + cosmossdk.io/math v1.0.0-rc.0 github.com/BurntSushi/toml v1.3.0 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc2 + github.com/celestiaorg/celestia-app v1.0.0-rc4 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.16.0 github.com/celestiaorg/rsmt2d v0.9.0 - github.com/cosmos/cosmos-sdk v0.46.11 + github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 github.com/dgraph-io/badger/v2 v2.2007.4 @@ -62,7 +62,7 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - github.com/tendermint/tendermint v0.34.24 + github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 @@ -77,20 +77,20 @@ require ( golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/sync v0.2.0 golang.org/x/text v0.9.0 - google.golang.org/grpc v1.53.0 + google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 ) require ( - cloud.google.com/go v0.107.0 // indirect - cloud.google.com/go/compute v1.15.1 // indirect + cloud.google.com/go v0.110.0 // indirect + cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.8.0 // indirect - cloud.google.com/go/storage v1.27.0 // indirect + cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/storage v1.28.1 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect + github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/StackExchange/wmi v1.2.1 // indirect @@ -116,10 +116,10 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha8 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.2 // indirect + github.com/cosmos/gogoproto v1.4.10 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.19.5 // indirect - github.com/cosmos/ibc-go/v6 v6.1.1 // indirect + github.com/cosmos/iavl v0.19.6 // indirect + github.com/cosmos/ibc-go/v6 v6.2.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect @@ -158,7 +158,7 @@ require ( github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/gateway v1.1.0 // indirect - github.com/golang/glog v1.0.0 // indirect + github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect @@ -168,7 +168,7 @@ require ( github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -221,7 +221,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect github.com/koron/go-ssdp v0.0.4 // indirect - github.com/lib/pq v1.10.6 // indirect + github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect @@ -259,14 +259,14 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect - github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect + github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -280,7 +280,7 @@ require ( github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/rs/cors v1.8.2 // indirect - github.com/rs/zerolog v1.27.0 // indirect + github.com/rs/zerolog v1.29.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil v3.21.6+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -289,7 +289,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.14.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tendermint/tm-db v0.6.7 // indirect @@ -312,14 +312,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.103.0 // indirect + google.golang.org/api v0.110.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -330,8 +330,10 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.14.0-sdk-v0.46.11 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27 + // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app + github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28 ) diff --git a/go.sum b/go.sum index 065c88774f..3bf1455309 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -75,8 +75,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -116,13 +116,13 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -179,8 +179,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -195,8 +196,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= -cosmossdk.io/math v1.0.0-beta.3 h1:TbZxSopz2LqjJ7aXYfn7nJSb8vNaBklW6BLpcei1qwM= -cosmossdk.io/math v1.0.0-beta.3/go.mod h1:3LYasri3Zna4XpbrTNdKsWmD5fHHkaNAod/mNT9XdE4= +cosmossdk.io/math v1.0.0-rc.0 h1:ml46ukocrAAoBpYKMidF0R2tQJ1Uxfns0yH8wqgMAFc= +cosmossdk.io/math v1.0.0-rc.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -223,8 +224,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= +github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -338,16 +339,17 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bufbuild/protocompile v0.1.0 h1:HjgJBI85hY/qmW5tw/66sNDZ7z0UDdVSi/5r40WHw4s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc2 h1:/u7eespYtBpQtBSz3P8/rKfz9rW7QOxkH8ebh8T4VxI= -github.com/celestiaorg/celestia-app v1.0.0-rc2/go.mod h1:uiTWKTtRpVwvSiFDl2zausrU1ZBHBWgk7z52pfzJqJU= -github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27 h1:nmr9O5BflgNR1aWehs1ZFw4obA//M/+g+SrSMK9sOBA= -github.com/celestiaorg/celestia-core v1.21.2-tm-v0.34.27/go.mod h1:GVo91Wifg9KL/nFx9nPkpl0UIFdvvs4fhnly9GhGxZU= -github.com/celestiaorg/cosmos-sdk v1.14.0-sdk-v0.46.11 h1:xDLC0ZvIwj9S2gTs9b8EespilL/u/vOGcqJuHz1r/eA= -github.com/celestiaorg/cosmos-sdk v1.14.0-sdk-v0.46.11/go.mod h1:IwvD2nN3vEMkjxhTw/SF5tyJ0+x3GB0EdyQJyteK06U= +github.com/celestiaorg/celestia-app v1.0.0-rc4 h1:S48+XMsLnk0GkNHrUc5c+rlyyHLIl0ZJ/zh92jmSSTk= +github.com/celestiaorg/celestia-app v1.0.0-rc4/go.mod h1:i+jE3Hh8IQxlZnE5XSBlYF0PCNPvD3v5g8KmY095PEA= +github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28 h1:idHJK9i4WCkYOf5PXVWZbOs8pWkCiHHQGI4MZr0iMtQ= +github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28/go.mod h1:LOxHW9nA++/9U8TgvTyKo9TO3F09sWv8asKQs00m73U= +github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 h1:vaQKgaOm0w58JAvOgn2iDohqjH7kvvRqVKiMcBDWifA= +github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13/go.mod h1:G9XkhOJZde36FH0kt/1ayg4ZaioZEQmmRfMa/zQig0I= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= @@ -441,7 +443,6 @@ github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -455,14 +456,14 @@ github.com/cosmos/cosmos-sdk/api v0.1.0/go.mod h1:CupqQBskAOiTXO1XDZ/wrtWzN/wTxU github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= -github.com/cosmos/gogoproto v1.4.2 h1:UeGRcmFW41l0G0MiefWhkPEVEwvu78SZsHBvI78dAYw= -github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= +github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= +github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= -github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= -github.com/cosmos/ibc-go/v6 v6.1.1 h1:oqqMNyjj6SLQF8rvgCaDGwfdITEIsbhs8F77/8xvRIo= -github.com/cosmos/ibc-go/v6 v6.1.1/go.mod h1:NL17FpFAaWjRFVb1T7LUKuOoMSsATPpu+Icc4zL5/Ik= +github.com/cosmos/iavl v0.19.6 h1:XY78yEeNPrEYyNCKlqr9chrwoeSDJ0bV2VjocTk//OU= +github.com/cosmos/iavl v0.19.6/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= +github.com/cosmos/ibc-go/v6 v6.2.0 h1:HKS5WNxQrlmjowHb73J9LqlNJfvTnvkbhXZ9QzNTU7Q= +github.com/cosmos/ibc-go/v6 v6.2.0/go.mod h1:+S3sxcNwOhgraYDJAhIFDg5ipXHaUnJrg7tOQqGyWlc= github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= @@ -697,8 +698,9 @@ github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -778,8 +780,8 @@ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPg github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -808,8 +810,9 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -1101,7 +1104,7 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b h1:izTof8BKh/nE1wrKOrloNA5q4odOarjf+Xpe+4qow98= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -1183,8 +1186,8 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= @@ -1701,15 +1704,16 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= +github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= +github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= @@ -1763,8 +1767,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1820,10 +1824,10 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= -github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= -github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= +github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= +github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1933,7 +1937,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= @@ -2318,8 +2321,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2634,8 +2637,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2759,8 +2762,8 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2807,8 +2810,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From f32c9039194037b15df85aef4f158b3e39c90e3d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 22 Jun 2023 16:16:24 +0800 Subject: [PATCH 0674/1008] feat(share/getter) Add support for Non-inclusion proofs (#2256) ## Overview This PR implements support for non-inclusion proof for getters and shrex protocol. Requires https://github.com/celestiaorg/celestia-node/pull/2242 to be merged first --- blob/service.go | 3 +- share/availability/light/availability_test.go | 5 +- share/get_test.go | 2 +- share/getter.go | 6 +- share/getters/cascade.go | 4 +- share/getters/getter_test.go | 12 +- share/getters/shrex.go | 13 +- share/getters/shrex_test.go | 50 ++++++-- share/getters/store.go | 4 - share/getters/utils.go | 8 +- share/ipld/namespace_data.go | 2 +- share/p2p/shrexnd/client.go | 50 ++++---- share/p2p/shrexnd/exchange_test.go | 7 +- share/p2p/shrexnd/pb/share.pb.go | 112 +++++++++++++----- share/p2p/shrexnd/pb/share.proto | 3 +- share/p2p/shrexnd/server.go | 32 ++--- 16 files changed, 197 insertions(+), 116 deletions(-) diff --git a/blob/service.go b/blob/service.go index 0b43493e27..0876a45a0c 100644 --- a/blob/service.go +++ b/blob/service.go @@ -193,8 +193,7 @@ func (s *Service) getByCommitment( namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header.DAH, nID) if err != nil { - if errors.Is(err, share.ErrNamespaceNotFound) || - errors.Is(err, share.ErrNotFound) { + if errors.Is(err, share.ErrNotFound) { err = ErrBlobNotFound } return nil, nil, err diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 4980af031e..ca72596816 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -141,8 +141,9 @@ func TestService_GetSharesByNamespaceNotFound(t *testing.T) { getter, root := GetterWithRandSquare(t, 1) root.RowRoots = nil - _, err := getter.GetSharesByNamespace(context.Background(), root, namespace.RandomNamespace().Bytes()) - assert.ErrorIs(t, err, share.ErrNamespaceNotFound) + emptyShares, err := getter.GetSharesByNamespace(context.Background(), root, namespace.RandomNamespace().Bytes()) + require.NoError(t, err) + require.Empty(t, emptyShares.Flatten()) } func BenchmarkService_GetSharesByNamespace(b *testing.B) { diff --git a/share/get_test.go b/share/get_test.go index 25711e5a65..1b9df69be3 100644 --- a/share/get_test.go +++ b/share/get_test.go @@ -237,7 +237,7 @@ func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { t.Cleanup(cancel) bServ := mdutils.Bserv() - shares := RandShares(t, 16) + shares := RandShares(t, 1024) // set all shares to the same namespace id nids, err := randomNids(5) diff --git a/share/getter.go b/share/getter.go index 6ba729c71a..15f384a2c4 100644 --- a/share/getter.go +++ b/share/getter.go @@ -17,9 +17,6 @@ import ( var ( // ErrNotFound is used to indicate that requested data could not be found. ErrNotFound = errors.New("share: data not found") - // ErrNamespaceNotFound is returned by GetSharesByNamespace when data for requested root does - // not include any shares from the given namespace - ErrNamespaceNotFound = errors.New("share: namespace not found in data") ) // Getter interface provides a set of accessors for shares by the Root. @@ -35,6 +32,9 @@ type Getter interface { // GetSharesByNamespace gets all shares from an EDS within the given namespace. // Shares are returned in a row-by-row order if the namespace spans multiple rows. + // Inclusion of returned data could be verified using Verify method on NamespacedShares. + // If no shares are found for target namespace non-inclusion could be also verified by calling + // Verify method. GetSharesByNamespace(context.Context, *Root, namespace.ID) (NamespacedShares, error) } diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 1a0d8fb274..01540de926 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -122,8 +122,8 @@ func cascadeGetters[V any]( getCtx, cancel := ctxWithSplitTimeout(ctx, len(getters)-i, 0) val, getErr := get(getCtx, getter) cancel() - if getErr == nil || errors.Is(getErr, share.ErrNamespaceNotFound) { - return val, getErr + if getErr == nil { + return val, nil } if errors.Is(getErr, errOperationNotSupported) { diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index c9bf82031a..20b7e25191 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -138,8 +138,9 @@ func TestStoreGetter(t *testing.T) { // nid not found nID = make([]byte, namespace.NamespaceSize) - _, err = sg.GetSharesByNamespace(ctx, &dah, nID) - require.ErrorIs(t, err, share.ErrNamespaceNotFound) + emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + require.NoError(t, err) + require.Empty(t, emptyShares.Flatten()) // root not found root := share.Root{} @@ -216,13 +217,14 @@ func TestIPLDGetter(t *testing.T) { // nid not found nID = make([]byte, namespace.NamespaceSize) emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, nID) - require.ErrorIs(t, err, share.ErrNamespaceNotFound) + require.NoError(t, err) require.Nil(t, emptyShares) // nid doesnt exist in root root := share.Root{} - _, err = sg.GetSharesByNamespace(ctx, &root, nID) - require.ErrorIs(t, err, share.ErrNamespaceNotFound) + emptyShares, err = sg.GetSharesByNamespace(ctx, &root, nID) + require.NoError(t, err) + require.Empty(t, emptyShares.Flatten()) }) } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 58a85abc0c..99da2f8758 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -191,7 +191,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( // verify that the namespace could exist inside the roots before starting network requests roots := filterRootsByNamespace(root, id) if len(roots) == 0 { - return nil, share.ErrNamespaceNotFound + return nil, nil } for { @@ -218,18 +218,15 @@ func (sg *ShrexGetter) GetSharesByNamespace( cancel() switch { case getErr == nil: - if getErr = nd.Verify(root, id); getErr != nil { + // both inclusion and non-inclusion cases needs verification + if verErr := nd.Verify(root, id); verErr != nil { + getErr = verErr setStatus(peers.ResultBlacklistPeer) break } setStatus(peers.ResultNoop) sg.metrics.recordNDAttempt(ctx, attempt, true) - return nd, getErr - case errors.Is(getErr, share.ErrNamespaceNotFound): - // TODO: will be merged with first case once non-inclusion proofs are ready - setStatus(peers.ResultNoop) - sg.metrics.recordNDAttempt(ctx, attempt, true) - return nd, getErr + return nd, nil case errors.Is(getErr, context.DeadlineExceeded), errors.Is(getErr, context.Canceled): setStatus(peers.ResultCooldownPeer) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index db60e0138a..72ac78e69b 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -15,8 +15,8 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/namespace" libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/nmt" nmtnamespace "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" @@ -24,6 +24,7 @@ import ( "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" @@ -88,12 +89,12 @@ func TestShrexGetter(t *testing.T) { require.ErrorIs(t, err, share.ErrNotFound) }) - t.Run("ND_namespace_not_found", func(t *testing.T) { + t.Run("ND_namespace_not_included", func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) // generate test data - eds, dah, nID := generateTestEDS(t) + eds, dah, maxNID := generateTestEDS(t) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), @@ -101,10 +102,43 @@ func TestShrexGetter(t *testing.T) { }) // corrupt NID - nID[4]++ + nID := make([]byte, ipld.NamespaceSize) + copy(nID, maxNID) + nID[ipld.NamespaceSize-1]-- // pray for last byte to not be 0x00 + // check for namespace to be between max and min namespace in root + require.Len(t, filterRootsByNamespace(&dah, maxNID), 1) - _, err := getter.GetSharesByNamespace(ctx, &dah, nID) - require.ErrorIs(t, err, share.ErrNamespaceNotFound) + emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, nID) + require.NoError(t, err) + // no shares should be returned + require.Empty(t, emptyShares.Flatten()) + require.Nil(t, emptyShares.Verify(&dah, nID)) + }) + + t.Run("ND_namespace_not_in_dah", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Cleanup(cancel) + + // generate test data + eds, dah, maxNID := generateTestEDS(t) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ + DataHash: dah.Hash(), + Height: 1, + }) + + // corrupt NID + nID := make([]byte, ipld.NamespaceSize) + copy(nID, maxNID) + nID[ipld.NamespaceSize-1]++ // pray for last byte to not be 0xFF + // check for namespace to be not in root + require.Len(t, filterRootsByNamespace(&dah, nID), 0) + + emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, nID) + require.NoError(t, err) + // no shares should be returned + require.Empty(t, emptyShares.Flatten()) + require.Nil(t, emptyShares.Verify(&dah, nID)) }) t.Run("EDS_Available", func(t *testing.T) { @@ -166,8 +200,8 @@ func newStore(t *testing.T) (*eds.Store, error) { func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, nmtnamespace.ID) { eds := share.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) - randNID := dah.RowRoots[(len(dah.RowRoots)-1)/2][:namespace.NamespaceSize] - return eds, dah, randNID + max := nmt.MaxNamespace(dah.RowRoots[(len(dah.RowRoots))/2-1], ipld.NamespaceSize) + return eds, dah, max } func testManager( diff --git a/share/getters/store.go b/share/getters/store.go index 91200b78f3..f39f41fc80 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -122,10 +122,6 @@ func (sg *StoreGetter) GetSharesByNamespace( // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) - if errors.Is(err, ipld.ErrNodeNotFound) { - // convert error to satisfy getter interface contract - err = share.ErrNotFound - } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) } diff --git a/share/getters/utils.go b/share/getters/utils.go index c0b882f330..79438204bd 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -59,7 +59,7 @@ func collectSharesByNamespace( rootCIDs := filterRootsByNamespace(root, nID) if len(rootCIDs) == 0 { - return nil, share.ErrNamespaceNotFound + return nil, nil } errGroup, ctx := errgroup.WithContext(ctx) @@ -83,12 +83,6 @@ func collectSharesByNamespace( if err := errGroup.Wait(); err != nil { return nil, err } - - // return ErrNamespaceNotFound if no shares are found for the namespace.ID - if len(rootCIDs) == 1 && len(shares[0].Shares) == 0 { - return nil, share.ErrNamespaceNotFound - } - return shares, nil } diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index b38e5884b5..f86a9803ac 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -137,7 +137,7 @@ func (n *NamespaceData) addProof(d direction, cid cid.Cid, depth int) { // Leaves returns retrieved leaves within the bounds in case `WithLeaves` option was passed, // otherwise nil will be returned. func (n *NamespaceData) Leaves() []ipld.Node { - if n.leaves == nil || n.noLeaves() { + if n.leaves == nil || n.noLeaves() || n.isAbsentNamespace.Load() { return nil } return n.leaves[n.bounds.lowest : n.bounds.highest+1] diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index ab407126de..b59bb347a7 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -56,7 +56,7 @@ func (c *Client) RequestND( ) (share.NamespacedShares, error) { shares, err := c.doRequest(ctx, root, nID, peer) if err == nil { - return shares, err + return shares, nil } if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { c.metrics.ObserveRequests(ctx, 1, p2p.StatusTimeout) @@ -71,7 +71,7 @@ func (c *Client) RequestND( return nil, context.DeadlineExceeded } } - if err != p2p.ErrNotFound && err != share.ErrNamespaceNotFound { + if err != p2p.ErrNotFound { log.Warnw("client-nd: peer returned err", "err", err) } return nil, err @@ -119,19 +119,11 @@ func (c *Client) doRequest( return nil, fmt.Errorf("client-nd: reading response: %w", err) } - if err = c.statusToErr(ctx, resp.Status); err != nil { - return nil, fmt.Errorf("client-nd: response code is not OK: %w", err) - } - - shares, err := convertToNamespacedShares(resp.Rows) - if err != nil { - return nil, fmt.Errorf("client-nd: converting response to shares: %w", err) - } - return shares, nil + return c.convertResponse(ctx, resp) } // convertToNamespacedShares converts proto Rows to share.NamespacedShares -func convertToNamespacedShares(rows []*pb.Row) (share.NamespacedShares, error) { +func convertToNamespacedShares(rows []*pb.Row) share.NamespacedShares { shares := make([]share.NamespacedRow, 0, len(rows)) for _, row := range rows { var proof *nmt.Proof @@ -150,7 +142,24 @@ func convertToNamespacedShares(rows []*pb.Row) (share.NamespacedShares, error) { Proof: proof, }) } - return shares, nil + return shares +} + +func convertToNonInclusionProofs(rows []*pb.Row) share.NamespacedShares { + shares := make([]share.NamespacedRow, 0, len(rows)) + for _, row := range rows { + proof := nmt.NewAbsenceProof( + int(row.Proof.Start), + int(row.Proof.End), + row.Proof.Nodes, + row.Proof.Hashleaf, + ipld.NMTIgnoreMaxNamespace, + ) + shares = append(shares, share.NamespacedRow{ + Proof: &proof, + }) + } + return shares } func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) { @@ -181,22 +190,23 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) } } -func (c *Client) statusToErr(ctx context.Context, code pb.StatusCode) error { - switch code { +func (c *Client) convertResponse( + ctx context.Context, resp pb.GetSharesByNamespaceResponse) (share.NamespacedShares, error) { + switch resp.Status { case pb.StatusCode_OK: c.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) - return nil + return convertToNamespacedShares(resp.Rows), nil + case pb.StatusCode_NAMESPACE_NOT_FOUND: + return convertToNonInclusionProofs(resp.Rows), nil case pb.StatusCode_NOT_FOUND: c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) - return p2p.ErrNotFound - case pb.StatusCode_NAMESPACE_NOT_FOUND: - return share.ErrNamespaceNotFound + return nil, p2p.ErrNotFound case pb.StatusCode_INVALID: log.Debug("client-nd: invalid request") fallthrough case pb.StatusCode_INTERNAL: fallthrough default: - return p2p.ErrInvalidResponse + return nil, p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 8c5a132fdc..25528595d7 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -49,8 +49,9 @@ func TestExchange_RequestND_NotFound(t *testing.T) { require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) randNID := dah.RowRoots[(len(dah.RowRoots)-1)/2][:namespace.NamespaceSize] - _, err := client.RequestND(ctx, &dah, randNID, server.host.ID()) - require.ErrorIs(t, err, share.ErrNamespaceNotFound) + emptyShares, err := client.RequestND(ctx, &dah, randNID, server.host.ID()) + require.NoError(t, err) + require.Empty(t, emptyShares.Flatten()) }) } @@ -119,7 +120,7 @@ func (m notFoundGetter) GetEDS( func (m notFoundGetter) GetSharesByNamespace( _ context.Context, _ *share.Root, _ nmtnamespace.ID, ) (share.NamespacedShares, error) { - return nil, share.ErrNamespaceNotFound + return nil, nil } func newStore(t *testing.T) *eds.Store { diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go index d902570410..7e19bebc09 100644 --- a/share/p2p/shrexnd/pb/share.pb.go +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -213,9 +213,10 @@ func (m *Row) GetProof() *Proof { } type Proof struct { - Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` - End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` - Nodes [][]byte `protobuf:"bytes,3,rep,name=Nodes,proto3" json:"Nodes,omitempty"` + Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + Nodes [][]byte `protobuf:"bytes,3,rep,name=nodes,proto3" json:"nodes,omitempty"` + Hashleaf []byte `protobuf:"bytes,4,opt,name=hashleaf,proto3" json:"hashleaf,omitempty"` } func (m *Proof) Reset() { *m = Proof{} } @@ -272,6 +273,13 @@ func (m *Proof) GetNodes() [][]byte { return nil } +func (m *Proof) GetHashleaf() []byte { + if m != nil { + return m.Hashleaf + } + return nil +} + func init() { proto.RegisterEnum("share.p2p.shrex.nd.StatusCode", StatusCode_name, StatusCode_value) proto.RegisterType((*GetSharesByNamespaceRequest)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceRequest") @@ -283,32 +291,33 @@ func init() { func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } var fileDescriptor_ed9f13149b0de397 = []byte{ - // 386 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xc1, 0xae, 0x93, 0x40, - 0x14, 0x86, 0x81, 0xb9, 0xc5, 0x7b, 0x0f, 0x68, 0xc8, 0x68, 0xbc, 0x98, 0x6b, 0x48, 0x65, 0xd5, - 0x68, 0x02, 0x09, 0x26, 0xee, 0x69, 0x8b, 0x4a, 0xac, 0xd3, 0x66, 0x5a, 0x75, 0x65, 0x08, 0x95, - 0x31, 0xb8, 0x90, 0x19, 0x99, 0x69, 0xaa, 0x6b, 0x5f, 0xc0, 0xc7, 0x72, 0xd9, 0xa5, 0x4b, 0xd3, - 0xbe, 0x88, 0x61, 0xa8, 0x76, 0x61, 0x77, 0xfc, 0xff, 0xf9, 0xce, 0x7f, 0xce, 0x21, 0x03, 0x43, - 0x59, 0x97, 0x2d, 0x8b, 0x45, 0x22, 0x62, 0x59, 0xb7, 0xec, 0x6b, 0x53, 0xc5, 0x62, 0x1d, 0x6b, - 0x33, 0x12, 0x2d, 0x57, 0x1c, 0xe3, 0xa3, 0x48, 0x44, 0xa4, 0x89, 0xa8, 0xa9, 0xc2, 0xf7, 0x70, - 0xf3, 0x82, 0xa9, 0x65, 0x57, 0x90, 0xe3, 0x6f, 0xa4, 0xfc, 0xcc, 0xa4, 0x28, 0x3f, 0x30, 0xca, - 0xbe, 0x6c, 0x98, 0x54, 0xf8, 0x06, 0xae, 0x5a, 0xce, 0x55, 0x51, 0x97, 0xb2, 0xf6, 0xcd, 0xa1, - 0x39, 0x72, 0xe9, 0x65, 0x67, 0xbc, 0x2c, 0x65, 0x8d, 0x1f, 0x81, 0xdb, 0xfc, 0x6d, 0x28, 0x3e, - 0x55, 0xbe, 0xa5, 0xeb, 0xce, 0x3f, 0x2f, 0xaf, 0xc2, 0xef, 0x26, 0x3c, 0x3c, 0x9f, 0x2f, 0x05, - 0x6f, 0x24, 0xc3, 0xcf, 0xc0, 0x96, 0xaa, 0x54, 0x1b, 0xa9, 0xd3, 0xef, 0x24, 0x41, 0xf4, 0xff, - 0x92, 0xd1, 0x52, 0x13, 0x13, 0x5e, 0x31, 0x7a, 0xa4, 0xf1, 0x13, 0xb8, 0x68, 0xf9, 0x56, 0xfa, - 0xd6, 0x10, 0x8d, 0x9c, 0xe4, 0xfa, 0x5c, 0x17, 0xe5, 0x5b, 0xaa, 0xa1, 0x90, 0x00, 0xa2, 0x7c, - 0x8b, 0xef, 0x83, 0xad, 0xb1, 0x6e, 0x16, 0x1a, 0xb9, 0xf4, 0xa8, 0x70, 0x0c, 0x03, 0xd1, 0x72, - 0xfe, 0x51, 0x1f, 0xe0, 0x24, 0x0f, 0xce, 0x85, 0x2d, 0x3a, 0x80, 0xf6, 0x5c, 0x98, 0xc1, 0x40, - 0x6b, 0x7c, 0x0f, 0x06, 0x52, 0x95, 0xad, 0xd2, 0xcb, 0x23, 0xda, 0x0b, 0xec, 0x01, 0x62, 0x4d, - 0xff, 0x3b, 0x10, 0xed, 0x3e, 0x3b, 0x8e, 0xf0, 0x8a, 0x49, 0x1f, 0xe9, 0xc1, 0xbd, 0x78, 0xfc, - 0x0e, 0xe0, 0x74, 0x19, 0x76, 0xe0, 0x56, 0x4e, 0xde, 0xa6, 0xb3, 0x7c, 0xea, 0x19, 0xd8, 0x06, - 0x6b, 0xfe, 0xca, 0x33, 0xf1, 0x6d, 0xb8, 0x22, 0xf3, 0x55, 0xf1, 0x7c, 0xfe, 0x86, 0x4c, 0x3d, - 0x0b, 0xbb, 0x70, 0x99, 0x93, 0x55, 0x46, 0x49, 0x3a, 0xf3, 0x10, 0xbe, 0x86, 0xbb, 0x24, 0x7d, - 0x9d, 0x2d, 0x17, 0xe9, 0x24, 0x2b, 0x4e, 0xd8, 0xc5, 0xd8, 0xff, 0xb9, 0x0f, 0xcc, 0xdd, 0x3e, - 0x30, 0x7f, 0xef, 0x03, 0xf3, 0xc7, 0x21, 0x30, 0x76, 0x87, 0xc0, 0xf8, 0x75, 0x08, 0x8c, 0xb5, - 0xad, 0x5f, 0xc2, 0xd3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xba, 0xc1, 0x4e, 0xec, 0x2d, 0x02, - 0x00, 0x00, + // 401 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0xe3, 0x6c, 0x62, 0xd2, 0xb1, 0x41, 0xd6, 0x80, 0xa8, 0xa1, 0xc8, 0x0a, 0x3e, 0x45, + 0x20, 0xd9, 0x92, 0x91, 0xb8, 0xbb, 0x6d, 0x00, 0x8b, 0xb2, 0xa9, 0x36, 0x05, 0x4e, 0xc8, 0xda, + 0xe2, 0xad, 0x8c, 0x04, 0xde, 0xc5, 0xbb, 0x55, 0xe0, 0xcc, 0x0b, 0xf0, 0x58, 0x1c, 0x7b, 0xe4, + 0x88, 0x92, 0x17, 0x41, 0x5e, 0xa7, 0xf4, 0x40, 0x6e, 0xfb, 0xcd, 0xfc, 0xe6, 0xdf, 0xa7, 0x85, + 0xa9, 0xae, 0x79, 0x2b, 0x52, 0x95, 0xa9, 0x54, 0xd7, 0xad, 0xf8, 0xd6, 0x54, 0xa9, 0x3a, 0x4f, + 0x6d, 0x30, 0x51, 0xad, 0x34, 0x12, 0x71, 0x2b, 0x32, 0x95, 0x58, 0x22, 0x69, 0xaa, 0xf8, 0x03, + 0x1c, 0xbc, 0x14, 0x66, 0xd9, 0x25, 0xf4, 0xe1, 0x77, 0xca, 0xbf, 0x08, 0xad, 0xf8, 0x47, 0xc1, + 0xc4, 0xd7, 0x4b, 0xa1, 0x0d, 0x1e, 0xc0, 0x5e, 0x2b, 0xa5, 0x29, 0x6b, 0xae, 0xeb, 0xd0, 0x99, + 0x3a, 0x33, 0x9f, 0x4d, 0xba, 0xc0, 0x2b, 0xae, 0x6b, 0x7c, 0x0c, 0x7e, 0x73, 0x5d, 0x50, 0x7e, + 0xaa, 0xc2, 0xa1, 0xcd, 0x7b, 0xff, 0x62, 0x45, 0x15, 0xff, 0x70, 0xe0, 0xd1, 0xee, 0xfe, 0x5a, + 0xc9, 0x46, 0x0b, 0x7c, 0x0e, 0xae, 0x36, 0xdc, 0x5c, 0x6a, 0xdb, 0xfd, 0x4e, 0x16, 0x25, 0xff, + 0x2f, 0x99, 0x2c, 0x2d, 0x71, 0x24, 0x2b, 0xc1, 0xb6, 0x34, 0x3e, 0x85, 0x51, 0x2b, 0x57, 0x3a, + 0x1c, 0x4e, 0xc9, 0xcc, 0xcb, 0xf6, 0x77, 0x55, 0x31, 0xb9, 0x62, 0x16, 0x8a, 0x29, 0x10, 0x26, + 0x57, 0x78, 0x1f, 0x5c, 0x8b, 0x75, 0xb3, 0xc8, 0xcc, 0x67, 0x5b, 0x85, 0x29, 0x8c, 0x55, 0x2b, + 0xe5, 0x85, 0x3d, 0xc0, 0xcb, 0x1e, 0xec, 0x6a, 0x76, 0xda, 0x01, 0xac, 0xe7, 0x62, 0x0e, 0x63, + 0xab, 0xf1, 0x1e, 0x8c, 0xb5, 0xe1, 0xad, 0xb1, 0xcb, 0x13, 0xd6, 0x0b, 0x0c, 0x80, 0x88, 0xa6, + 0xb7, 0x83, 0xb0, 0xee, 0xd9, 0x71, 0x8d, 0xac, 0x84, 0x0e, 0x89, 0x1d, 0xdc, 0x0b, 0x7c, 0x08, + 0x93, 0xce, 0xd7, 0xcf, 0x82, 0x5f, 0x84, 0xa3, 0xde, 0xdb, 0x6b, 0xfd, 0xe4, 0x3d, 0xc0, 0xcd, + 0xd5, 0xe8, 0xc1, 0xad, 0x82, 0xbe, 0xcb, 0x4f, 0x8a, 0xe3, 0x60, 0x80, 0x2e, 0x0c, 0x17, 0xaf, + 0x03, 0x07, 0x6f, 0xc3, 0x1e, 0x5d, 0x9c, 0x95, 0x2f, 0x16, 0x6f, 0xe9, 0x71, 0x30, 0x44, 0x1f, + 0x26, 0x05, 0x3d, 0x9b, 0x33, 0x9a, 0x9f, 0x04, 0x04, 0xf7, 0xe1, 0x2e, 0xcd, 0xdf, 0xcc, 0x97, + 0xa7, 0xf9, 0xd1, 0xbc, 0xbc, 0xc1, 0x46, 0x87, 0xe1, 0xaf, 0x75, 0xe4, 0x5c, 0xad, 0x23, 0xe7, + 0xcf, 0x3a, 0x72, 0x7e, 0x6e, 0xa2, 0xc1, 0xd5, 0x26, 0x1a, 0xfc, 0xde, 0x44, 0x83, 0x73, 0xd7, + 0xfe, 0x92, 0x67, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x3e, 0x78, 0x01, 0x49, 0x02, 0x00, + 0x00, } func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { @@ -454,6 +463,13 @@ func (m *Proof) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Hashleaf) > 0 { + i -= len(m.Hashleaf) + copy(dAtA[i:], m.Hashleaf) + i = encodeVarintShare(dAtA, i, uint64(len(m.Hashleaf))) + i-- + dAtA[i] = 0x22 + } if len(m.Nodes) > 0 { for iNdEx := len(m.Nodes) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Nodes[iNdEx]) @@ -559,6 +575,10 @@ func (m *Proof) Size() (n int) { n += 1 + l + sovShare(uint64(l)) } } + l = len(m.Hashleaf) + if l > 0 { + n += 1 + l + sovShare(uint64(l)) + } return n } @@ -1006,6 +1026,40 @@ func (m *Proof) Unmarshal(dAtA []byte) error { m.Nodes = append(m.Nodes, make([]byte, postIndex-iNdEx)) copy(m.Nodes[len(m.Nodes)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hashleaf", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthShare + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthShare + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hashleaf = append(m.Hashleaf[:0], dAtA[iNdEx:postIndex]...) + if m.Hashleaf == nil { + m.Hashleaf = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipShare(dAtA[iNdEx:]) diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto index 3d6a896641..09d77d29f3 100644 --- a/share/p2p/shrexnd/pb/share.proto +++ b/share/p2p/shrexnd/pb/share.proto @@ -28,5 +28,6 @@ message Row { message Proof { int64 start = 1; int64 end = 2; - repeated bytes Nodes = 3; + repeated bytes nodes = 3; + bytes hashleaf = 4; } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 67f64b8393..72c5231eff 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -136,9 +136,6 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre logger.Warn("server: nd not found") srv.respondNotFoundError(ctx, logger, stream) return - case errors.Is(err, share.ErrNamespaceNotFound): - srv.respondNamespaceNotFoundError(ctx, logger, stream) - return case err != nil: logger.Errorw("server: retrieving shares", "err", err) srv.respondInternalError(ctx, logger, stream) @@ -170,15 +167,6 @@ func (srv *Server) respondNotFoundError(ctx context.Context, srv.respond(ctx, logger, stream, resp) } -// respondNamespaceNotFoundError sends a namespace not found response to client -func (srv *Server) respondNamespaceNotFoundError(ctx context.Context, - logger *zap.SugaredLogger, stream network.Stream) { - resp := &pb.GetSharesByNamespaceResponse{ - Status: pb.StatusCode_NAMESPACE_NOT_FOUND, - } - srv.respond(ctx, logger, stream, resp) -} - // respondInternalError sends internal error response to client func (srv *Server) respondInternalError(ctx context.Context, logger *zap.SugaredLogger, stream network.Stream) { @@ -192,22 +180,26 @@ func (srv *Server) respondInternalError(ctx context.Context, func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNamespaceResponse { rows := make([]*pb.Row, 0, len(shares)) for _, row := range shares { - proof := &pb.Proof{ - Start: int64(row.Proof.Start()), - End: int64(row.Proof.End()), - Nodes: row.Proof.Nodes(), - } - row := &pb.Row{ Shares: row.Shares, - Proof: proof, + Proof: &pb.Proof{ + Start: int64(row.Proof.Start()), + End: int64(row.Proof.End()), + Nodes: row.Proof.Nodes(), + Hashleaf: row.Proof.LeafHash(), + }, } rows = append(rows, row) } + status := pb.StatusCode_OK + if len(shares) == 0 || (len(shares) == 1 && len(shares[0].Shares) == 0) { + status = pb.StatusCode_NAMESPACE_NOT_FOUND + } + return &pb.GetSharesByNamespaceResponse{ - Status: pb.StatusCode_OK, + Status: status, Rows: rows, } } From 8b28ad799688b5cbfe22dc2b4449f15c248badba Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:16:06 +0200 Subject: [PATCH 0675/1008] fix(api/gateway): Handle err from tx properly to avoid panic (#2393) Fixes a panic that can occur if txResp is nil and also returns actual error in the case of nil txResp. Reported by @tuxcanfly --- api/gateway/state.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/api/gateway/state.go b/api/gateway/state.go index b584b00d36..f97a7da38e 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -150,10 +150,15 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } // perform request - txResp, txerr := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*blob.Blob{constructedBlob}) - if txerr != nil && txResp == nil { - // no tx data to return - writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) + txResp, err := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*blob.Blob{constructedBlob}) + if err != nil { + if txResp == nil { + // no tx data to return + writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) + return + } + // if error returned, change status from 200 to 206 + w.WriteHeader(http.StatusPartialContent) } bs, err := json.Marshal(&txResp) @@ -162,10 +167,6 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { return } - // if error returned, change status from 200 to 206 - if txerr != nil { - w.WriteHeader(http.StatusPartialContent) - } _, err = w.Write(bs) if err != nil { log.Errorw("writing response", "endpoint", submitPFBEndpoint, "err", err) From 9264c1aec14f81e55f280aad670ab280260698d1 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Fri, 23 Jun 2023 15:43:58 -0500 Subject: [PATCH 0676/1008] chore: bump celestia-app to v1.0.0-rc5 (#2395) ## Overview bumps app to rc5 [full changelog](https://github.com/celestiaorg/celestia-app/compare/v1.0.0-rc4...v1.0.0-rc5) ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 31420f8745..0568717120 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.3.0 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc4 + github.com/celestiaorg/celestia-app v1.0.0-rc5 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 diff --git a/go.sum b/go.sum index 3bf1455309..425611c7df 100644 --- a/go.sum +++ b/go.sum @@ -344,8 +344,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc4 h1:S48+XMsLnk0GkNHrUc5c+rlyyHLIl0ZJ/zh92jmSSTk= -github.com/celestiaorg/celestia-app v1.0.0-rc4/go.mod h1:i+jE3Hh8IQxlZnE5XSBlYF0PCNPvD3v5g8KmY095PEA= +github.com/celestiaorg/celestia-app v1.0.0-rc5 h1:PjKIp91CQgqyP4MLI3KeF8E4MPHtBPlNoRPvGIMWjHw= +github.com/celestiaorg/celestia-app v1.0.0-rc5/go.mod h1:i+jE3Hh8IQxlZnE5XSBlYF0PCNPvD3v5g8KmY095PEA= github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28 h1:idHJK9i4WCkYOf5PXVWZbOs8pWkCiHHQGI4MZr0iMtQ= github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28/go.mod h1:LOxHW9nA++/9U8TgvTyKo9TO3F09sWv8asKQs00m73U= github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 h1:vaQKgaOm0w58JAvOgn2iDohqjH7kvvRqVKiMcBDWifA= From f79651c29cd4b9b4473139d92f0473c8667d76f6 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 26 Jun 2023 12:03:14 +0200 Subject: [PATCH 0677/1008] deps: bump libp2p (#2399) Change logs https://github.com/libp2p/go-libp2p/releases/tag/v0.28.1 --- go.mod | 2 +- go.sum | 4 ++-- nodebuilder/blob/mocks/api.go | 3 ++- nodebuilder/das/mocks/api.go | 3 ++- nodebuilder/fraud/mocks/api.go | 3 ++- nodebuilder/header/mocks/api.go | 3 ++- nodebuilder/node/mocks/api.go | 3 ++- nodebuilder/share/mocks/api.go | 3 ++- nodebuilder/state/mocks/api.go | 5 +++-- share/mocks/getter.go | 3 ++- 10 files changed, 20 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 0568717120..73509c56c3 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 - github.com/libp2p/go-libp2p v0.28.0 + github.com/libp2p/go-libp2p v0.28.1 github.com/libp2p/go-libp2p-kad-dht v0.21.1 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 diff --git a/go.sum b/go.sum index 425611c7df..28113d7847 100644 --- a/go.sum +++ b/go.sum @@ -1213,8 +1213,8 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= -github.com/libp2p/go-libp2p v0.28.0 h1:zO8cY98nJiPzZpFv5w5gqqb8aVzt4ukQ0nVOSaaKhJ8= -github.com/libp2p/go-libp2p v0.28.0/go.mod h1:s3Xabc9LSwOcnv9UD4nORnXKTsWkPMkIMB/JIGXVnzk= +github.com/libp2p/go-libp2p v0.28.1 h1:YurK+ZAI6cKfASLJBVFkpVBdl3wGhFi6fusOt725ii8= +github.com/libp2p/go-libp2p v0.28.1/go.mod h1:s3Xabc9LSwOcnv9UD4nORnXKTsWkPMkIMB/JIGXVnzk= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index f99d1d8168..b7c61aa450 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + blob "github.com/celestiaorg/celestia-node/blob" namespace "github.com/celestiaorg/nmt/namespace" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go index c4046e90e8..68ffaf3c8c 100644 --- a/nodebuilder/das/mocks/api.go +++ b/nodebuilder/das/mocks/api.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - das "github.com/celestiaorg/celestia-node/das" gomock "github.com/golang/mock/gomock" + + das "github.com/celestiaorg/celestia-node/das" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index ba88131695..399f8746e1 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + fraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraud0 "github.com/celestiaorg/go-fraud" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 02529a8ef9..7d6661ff5d 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index d8789a771c..14357316dc 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" - node "github.com/celestiaorg/celestia-node/nodebuilder/node" auth "github.com/filecoin-project/go-jsonrpc/auth" gomock "github.com/golang/mock/gomock" + + node "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 586c6dab4b..1b26273c0f 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,11 +8,12 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index dbd1d5dabe..754920dee2 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,12 +9,13 @@ import ( reflect "reflect" math "cosmossdk.io/math" - blob "github.com/celestiaorg/celestia-node/blob" - state "github.com/celestiaorg/celestia-node/state" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" + + blob "github.com/celestiaorg/celestia-node/blob" + state "github.com/celestiaorg/celestia-node/state" ) // MockModule is a mock of Module interface. diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 1c73c9170d..12c36cb015 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -8,11 +8,12 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" - gomock "github.com/golang/mock/gomock" ) // MockGetter is a mock of Getter interface. From 637072e8f6349a4919da9b5e5d8b4637c3058ecc Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 27 Jun 2023 15:17:02 +0200 Subject: [PATCH 0678/1008] fix(libs/utils): Use valid ip4 address (#2394) Fixes an issue where `localhost` was not accepted as a valid `--gateway.addr`. Uses net.ResolveIPAddr instead. Found by @tuxcanfly --- libs/utils/address.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/libs/utils/address.go b/libs/utils/address.go index a8170e44b9..ae52a03b16 100644 --- a/libs/utils/address.go +++ b/libs/utils/address.go @@ -29,15 +29,14 @@ func ValidateAddr(addr string) (string, error) { return addr, err } - if ip := net.ParseIP(addr); ip == nil { - addrs, err := net.LookupHost(addr) - if err != nil { - return addr, fmt.Errorf("could not resolve %v: %w", addr, err) - } - if len(addrs) == 0 { - return addr, fmt.Errorf("no IP addresses found for DNS record: %v", addr) - } - addr = addrs[0] + ip := net.ParseIP(addr) + if ip != nil { + return addr, nil } - return addr, nil + + resolved, err := net.ResolveIPAddr("ip4", addr) + if err != nil { + return addr, err + } + return resolved.String(), nil } From 0267e21fd1a500bb94960b6a1a1a38dae9a3084d Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 27 Jun 2023 15:17:40 +0200 Subject: [PATCH 0679/1008] feat: adding spans to shrex getter (#2404) Self explanatory --- share/getters/shrex.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 99da2f8758..08006073d6 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -12,10 +12,12 @@ import ( "go.opentelemetry.io/otel/metric/instrument" "go.opentelemetry.io/otel/metric/instrument/syncint64" "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p" "github.com/celestiaorg/celestia-node/share/p2p/peers" @@ -128,6 +130,13 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex attempt int err error ) + ctx, span := tracer.Start(ctx, "shrex/get-eds", trace.WithAttributes( + attribute.String("root", root.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + for { if ctx.Err() != nil { sg.metrics.recordEDSAttempt(ctx, attempt, false) @@ -187,6 +196,13 @@ func (sg *ShrexGetter) GetSharesByNamespace( attempt int err error ) + ctx, span := tracer.Start(ctx, "shrex/get-shares-by-namespace", trace.WithAttributes( + attribute.String("root", root.String()), + attribute.String("nid", hex.EncodeToString(id)), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() // verify that the namespace could exist inside the roots before starting network requests roots := filterRootsByNamespace(root, id) From ccae150a8999d124ab0d799f779a32568e518953 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 27 Jun 2023 15:37:55 +0200 Subject: [PATCH 0680/1008] refactor!(share): integrate new Namespace type (#2388) ## Notable Changes - `namespace.ID` to `share.Namespace` - Changes every comment namespace ID mentioning to just namespace. I renamed every such mention besides in ADRs. I don't want to touch ADRs here, as they need a more holistic re-review and up-to-date catchup. - Uses all the utility methods on the type, where suitable - Namespace constructor now only creates Blob Namespaces. For other reserved namespaces, the predefined globals should be used. - Uses namespace.ValidateDataNamespace everywhere data is requested. This is guarantees we verify the namespace are 100% valid and forbids requesting parity and padding namespaces. - Restricts PFBs for reserved namespaces - Reversed the dependency from `share -> share/ipld` to `share/ipld -> share` - NewBlobV0 constructor. Similar to NewNamespaceV0 - `sharetest` pkg for share related testing utilities - `edstest` pkg for eds related testing utilities ## Follow-ups - `blobtest` pkg to generate node's blob type ## Refs * Substitutes zombie PR https://github.com/celestiaorg/celestia-node/pull/2376. I push to the branch, but GH does not see commits. * Based on https://github.com/celestiaorg/celestia-node/pull/2367 * Closes https://github.com/celestiaorg/celestia-node/issues/2301 * Closes #2309 * Blocked on https://github.com/celestiaorg/celestia-node/pull/2256 --- api/docgen/examples.go | 4 +- api/gateway/endpoints.go | 8 +- api/gateway/share.go | 24 +- api/gateway/share_test.go | 9 +- api/gateway/state.go | 6 +- api/gateway/state_test.go | 2 +- blob/blob.go | 57 +++-- blob/blob_test.go | 10 +- blob/blobtest/testing.go | 8 +- blob/helper.go | 4 +- blob/service.go | 45 ++-- blob/service_test.go | 60 ++--- cmd/celestia/rpc.go | 53 ++--- cmd/celestia/rpc_test.go | 19 +- header/headertest/testing.go | 15 +- nodebuilder/blob/blob.go | 37 ++- nodebuilder/blob/mocks/api.go | 10 +- nodebuilder/share/constructors.go | 11 + nodebuilder/share/mocks/api.go | 3 +- nodebuilder/share/module.go | 2 +- nodebuilder/share/share.go | 7 +- nodebuilder/tests/api_test.go | 2 +- nodebuilder/tests/blob_test.go | 11 +- share/availability.go | 2 +- share/availability/light/availability_test.go | 30 +-- share/availability/test/testing.go | 6 +- share/doc.go | 2 +- share/eds/byzantine/bad_encoding.go | 8 +- share/eds/byzantine/bad_encoding_test.go | 32 ++- share/eds/byzantine/share_proof.go | 4 +- share/eds/byzantine/share_proof_test.go | 10 +- share/eds/eds.go | 14 +- share/eds/eds_test.go | 14 +- share/eds/edstest/testing.go | 31 +++ share/eds/ods_test.go | 2 +- share/eds/retriever.go | 2 +- share/eds/retriever_test.go | 46 +--- share/eds/store_test.go | 5 +- share/empty.go | 74 +++--- share/getter.go | 24 +- share/getters/cascade.go | 8 +- share/getters/getter_test.go | 91 ++++---- share/getters/ipld.go | 15 +- share/getters/shrex.go | 19 +- share/getters/shrex_test.go | 53 +++-- share/getters/store.go | 15 +- share/getters/tee.go | 8 +- share/getters/testing.go | 6 +- share/getters/utils.go | 31 +-- share/helpers.go | 58 +++++ share/{ => ipld}/add.go | 45 +--- share/ipld/get.go | 4 +- share/{get.go => ipld/get_shares.go} | 27 ++- .../{get_test.go => ipld/get_shares_test.go} | 214 +++++++++--------- share/ipld/namespace_data.go | 36 +-- share/ipld/nmt.go | 40 +--- share/ipld/nmt_test.go | 43 +--- share/mocks/getter.go | 3 +- share/namespace.go | 31 ++- share/namespace_test.go | 53 +++++ share/nid.go | 29 --- share/nid_test.go | 56 ----- share/p2p/shrexeds/exchange_test.go | 8 +- share/p2p/shrexnd/client.go | 15 +- share/p2p/shrexnd/exchange_test.go | 20 +- share/p2p/shrexnd/pb/share.pb.go | 77 ++++--- share/p2p/shrexnd/pb/share.proto | 2 +- share/p2p/shrexnd/server.go | 10 +- share/share.go | 21 +- share/sharetest/testing.go | 58 +++++ share/test_helpers.go | 65 ------ state/core_access.go | 7 +- 72 files changed, 915 insertions(+), 966 deletions(-) create mode 100644 share/eds/edstest/testing.go create mode 100644 share/helpers.go rename share/{ => ipld}/add.go (61%) rename share/{get.go => ipld/get_shares.go} (69%) rename share/{get_test.go => ipld/get_shares_test.go} (64%) delete mode 100644 share/nid.go delete mode 100644 share/nid_test.go create mode 100644 share/sharetest/testing.go delete mode 100644 share/test_helpers.go diff --git a/api/docgen/examples.go b/api/docgen/examples.go index 80a8c64d93..3456880c4f 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -133,13 +133,13 @@ func init() { } addToExampleValues(addrInfo) - namespace, err := share.NewNamespaceV0([]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10}) + namespace, err := share.NewBlobNamespaceV0([]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10}) if err != nil { panic(err) } addToExampleValues(namespace) - generatedBlob, err := blob.NewBlob(0, namespace, []byte("This is an example of some blob data")) + generatedBlob, err := blob.NewBlobV0(namespace, []byte("This is an example of some blob data")) if err != nil { panic(err) } diff --git a/api/gateway/endpoints.go b/api/gateway/endpoints.go index 0ae93b112c..9600138909 100644 --- a/api/gateway/endpoints.go +++ b/api/gateway/endpoints.go @@ -33,13 +33,13 @@ func (h *Handler) RegisterEndpoints(rpc *Server, deprecatedEndpointsEnabled bool rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) // share endpoints - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, nIDKey, heightKey), + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, namespaceKey, heightKey), h.handleSharesByNamespaceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", namespacedSharesEndpoint, nIDKey), + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", namespacedSharesEndpoint, namespaceKey), h.handleSharesByNamespaceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedDataEndpoint, nIDKey, heightKey), + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedDataEndpoint, namespaceKey, heightKey), h.handleDataByNamespaceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", namespacedDataEndpoint, nIDKey), + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", namespacedDataEndpoint, namespaceKey), h.handleDataByNamespaceRequest, http.MethodGet) // DAS endpoints diff --git a/api/gateway/share.go b/api/gateway/share.go index db5ed37286..c9dec071f3 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -10,7 +10,6 @@ import ( "github.com/gorilla/mux" "github.com/celestiaorg/celestia-app/pkg/shares" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/share" ) @@ -20,7 +19,7 @@ const ( namespacedDataEndpoint = "/namespaced_data" ) -var nIDKey = "nid" +var namespaceKey = "nid" // NamespacedSharesResponse represents the response to a // SharesByNamespace request. @@ -37,12 +36,12 @@ type NamespacedDataResponse struct { } func (h *Handler) handleSharesByNamespaceRequest(w http.ResponseWriter, r *http.Request) { - height, nID, err := parseGetByNamespaceArgs(r) + height, namespace, err := parseGetByNamespaceArgs(r) if err != nil { writeError(w, http.StatusBadRequest, namespacedSharesEndpoint, err) return } - shares, err := h.getShares(r.Context(), height, nID) + shares, err := h.getShares(r.Context(), height, namespace) if err != nil { writeError(w, http.StatusInternalServerError, namespacedSharesEndpoint, err) return @@ -62,12 +61,12 @@ func (h *Handler) handleSharesByNamespaceRequest(w http.ResponseWriter, r *http. } func (h *Handler) handleDataByNamespaceRequest(w http.ResponseWriter, r *http.Request) { - height, nID, err := parseGetByNamespaceArgs(r) + height, namespace, err := parseGetByNamespaceArgs(r) if err != nil { writeError(w, http.StatusBadRequest, namespacedDataEndpoint, err) return } - shares, err := h.getShares(r.Context(), height, nID) + shares, err := h.getShares(r.Context(), height, namespace) if err != nil { writeError(w, http.StatusInternalServerError, namespacedDataEndpoint, err) return @@ -91,13 +90,13 @@ func (h *Handler) handleDataByNamespaceRequest(w http.ResponseWriter, r *http.Re } } -func (h *Handler) getShares(ctx context.Context, height uint64, nID namespace.ID) ([]share.Share, error) { +func (h *Handler) getShares(ctx context.Context, height uint64, namespace share.Namespace) ([]share.Share, error) { header, err := h.header.GetByHeight(ctx, height) if err != nil { return nil, err } - shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, nID) + shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, namespace) if err != nil { return nil, err } @@ -124,7 +123,7 @@ func dataFromShares(input []share.Share) (data [][]byte, err error) { return data, nil } -func parseGetByNamespaceArgs(r *http.Request) (height uint64, nID namespace.ID, err error) { +func parseGetByNamespaceArgs(r *http.Request) (height uint64, namespace share.Namespace, err error) { vars := mux.Vars(r) // if a height was given, parse it, otherwise get namespaced shares/data from the latest header if strHeight, ok := vars[heightKey]; ok { @@ -133,11 +132,10 @@ func parseGetByNamespaceArgs(r *http.Request) (height uint64, nID namespace.ID, return 0, nil, err } } - hexNID := vars[nIDKey] - nID, err = hex.DecodeString(hexNID) + hexNamespace := vars[namespaceKey] + namespace, err = hex.DecodeString(hexNamespace) if err != nil { return 0, nil, err } - - return height, nID, nil + return height, namespace, namespace.ValidateForData() } diff --git a/api/gateway/share_test.go b/api/gateway/share_test.go index 16cf606680..9b12240f62 100644 --- a/api/gateway/share_test.go +++ b/api/gateway/share_test.go @@ -8,8 +8,9 @@ import ( coretypes "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/shares" + + "github.com/celestiaorg/celestia-node/share/sharetest" ) func Test_dataFromShares(t *testing.T) { @@ -19,13 +20,13 @@ func Test_dataFromShares(t *testing.T) { []byte("BEEEEAHP"), } - ns := namespace.RandomBlobNamespace() + ns := sharetest.RandV0Namespace() sss := shares.NewSparseShareSplitter() for _, data := range testData { b := coretypes.Blob{ Data: data, - NamespaceID: ns.ID, - NamespaceVersion: ns.Version, + NamespaceID: ns.ID(), + NamespaceVersion: ns.Version(), ShareVersion: appconsts.ShareVersionZero, } err := sss.Write(b) diff --git a/api/gateway/state.go b/api/gateway/state.go index f97a7da38e..69900b0bfc 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -9,8 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" - "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/state" ) @@ -131,7 +129,7 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) return } - nID, err := hex.DecodeString(req.NamespaceID) + namespace, err := hex.DecodeString(req.NamespaceID) if err != nil { writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) return @@ -143,7 +141,7 @@ func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { } fee := types.NewInt(req.Fee) - constructedBlob, err := blob.NewBlob(appconsts.DefaultShareVersion, nID, data) + constructedBlob, err := blob.NewBlobV0(namespace, data) if err != nil { writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) return diff --git a/api/gateway/state_test.go b/api/gateway/state_test.go index a613471a04..aa9196cc8d 100644 --- a/api/gateway/state_test.go +++ b/api/gateway/state_test.go @@ -34,7 +34,7 @@ func TestHandleSubmitPFB(t *testing.T) { mock.EXPECT().SubmitPayForBlob(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&txResponse, timedErr) - ns, err := share.NewNamespaceV0([]byte("abc")) + ns, err := share.NewBlobNamespaceV0([]byte("abc")) require.NoError(t, err) hexNs := hex.EncodeToString(ns[:]) diff --git a/blob/blob.go b/blob/blob.go index 9771714cb9..e9ad2b6255 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -5,11 +5,13 @@ import ( "encoding/json" "fmt" - appns "github.com/celestiaorg/celestia-app/pkg/namespace" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -101,41 +103,51 @@ type Blob struct { types.Blob `json:"blob"` Commitment Commitment `json:"commitment"` + + // the celestia-node's namespace type + // this is to avoid converting to and from app's type + namespace share.Namespace } -// NewBlob constructs a new blob from the provided namespace.ID and data. -func NewBlob(shareVersion uint8, namespace namespace.ID, data []byte) (*Blob, error) { - if len(namespace) != appns.NamespaceSize { - return nil, fmt.Errorf("invalid size of the namespace id. got:%d, want:%d", len(namespace), appns.NamespaceSize) - } +// NewBlobV0 constructs a new blob from the provided Namespace and data. +// The blob will be formatted as v0 shares. +func NewBlobV0(namespace share.Namespace, data []byte) (*Blob, error) { + return NewBlob(appconsts.ShareVersionZero, namespace, data) +} - ns, err := appns.New(namespace[appns.NamespaceVersionSize-1], namespace[appns.NamespaceVersionSize:]) - if err != nil { +// NewBlob constructs a new blob from the provided Namespace, data and share version. +func NewBlob(shareVersion uint8, namespace share.Namespace, data []byte) (*Blob, error) { + if len(data) == 0 || len(data) > appconsts.DefaultMaxBytes { + return nil, fmt.Errorf("blob data must be > 0 && <= %d, but it was %d bytes", appconsts.DefaultMaxBytes, len(data)) + } + if err := namespace.ValidateForBlob(); err != nil { return nil, err } - blob, err := types.NewBlob(ns, data, shareVersion) - if err != nil { - return nil, err + blob := tmproto.Blob{ + NamespaceId: namespace.ID(), + Data: data, + ShareVersion: uint32(shareVersion), + NamespaceVersion: uint32(namespace.Version()), } - com, err := types.CreateCommitment(blob) + com, err := types.CreateCommitment(&blob) if err != nil { return nil, err } - return &Blob{Blob: *blob, Commitment: com}, nil + return &Blob{Blob: blob, Commitment: com, namespace: namespace}, nil } // Namespace returns blob's namespace. -func (b *Blob) Namespace() namespace.ID { - return append([]byte{uint8(b.NamespaceVersion)}, b.NamespaceId...) +func (b *Blob) Namespace() share.Namespace { + return b.namespace } type jsonBlob struct { - Namespace namespace.ID `json:"namespace"` - Data []byte `json:"data"` - ShareVersion uint32 `json:"share_version"` - Commitment Commitment `json:"commitment"` + Namespace share.Namespace `json:"namespace"` + Data []byte `json:"data"` + ShareVersion uint32 `json:"share_version"` + Commitment Commitment `json:"commitment"` } func (b *Blob) MarshalJSON() ([]byte, error) { @@ -155,10 +167,11 @@ func (b *Blob) UnmarshalJSON(data []byte) error { return err } - b.Blob.NamespaceVersion = uint32(blob.Namespace[0]) - b.Blob.NamespaceId = blob.Namespace[1:] + b.Blob.NamespaceVersion = uint32(blob.Namespace.Version()) + b.Blob.NamespaceId = blob.Namespace.ID() b.Blob.Data = blob.Data b.Blob.ShareVersion = blob.ShareVersion b.Commitment = blob.Commitment + b.namespace = blob.Namespace return nil } diff --git a/blob/blob_test.go b/blob/blob_test.go index 3aabd6559b..85486ad125 100644 --- a/blob/blob_test.go +++ b/blob/blob_test.go @@ -8,14 +8,13 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" - appns "github.com/celestiaorg/celestia-app/pkg/namespace" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/blob/blobtest" ) func TestBlob(t *testing.T) { - appBlobs, err := blobtest.GenerateBlobs([]int{1}, false) + appBlobs, err := blobtest.GenerateV0Blobs([]int{1}, false) require.NoError(t, err) blob, err := convertBlobs(appBlobs...) require.NoError(t, err) @@ -42,12 +41,9 @@ func TestBlob(t *testing.T) { }, }, { - name: "verify nID", + name: "verify namespace", expectedRes: func(t *testing.T) { - ns, err := appns.New( - blob[0].Namespace()[appns.NamespaceVersionSize-1], - blob[0].Namespace()[appns.NamespaceVersionSize:], - ) + ns := blob[0].Namespace().ToAppNamespace() require.NoError(t, err) require.NoError(t, apptypes.ValidateBlobNamespace(ns)) }, diff --git a/blob/blobtest/testing.go b/blob/blobtest/testing.go index 395ef4167a..a22f22f790 100644 --- a/blob/blobtest/testing.go +++ b/blob/blobtest/testing.go @@ -11,14 +11,16 @@ import ( "github.com/celestiaorg/celestia-node/share" ) -func GenerateBlobs(sizes []int, sameNID bool) ([]types.Blob, error) { +// GenerateV0Blobs is a test utility producing v0 share formatted blobs with the +// requested size and random namespaces. +func GenerateV0Blobs(sizes []int, sameNamespace bool) ([]types.Blob, error) { blobs := make([]types.Blob, 0, len(sizes)) for _, size := range sizes { size := rawBlobSize(appconsts.FirstSparseShareContentSize * size) appBlob := testfactory.GenerateRandomBlob(size) - if !sameNID { - nid, err := share.NewNamespaceV0(tmrand.Bytes(7)) + if !sameNamespace { + nid, err := share.NewBlobNamespaceV0(tmrand.Bytes(7)) if err != nil { return nil, err } diff --git a/blob/helper.go b/blob/helper.go index 1fef41dc22..341080f42f 100644 --- a/blob/helper.go +++ b/blob/helper.go @@ -19,8 +19,8 @@ func SharesToBlobs(rawShares []share.Share) ([]*Blob, error) { } appShares := make([]shares.Share, 0, len(rawShares)) - for _, sh := range rawShares { - bShare, err := shares.NewShare(sh) + for _, shr := range rawShares { + bShare, err := shares.NewShare(shr) if err != nil { return nil, err } diff --git a/blob/service.go b/blob/service.go index 0876a45a0c..960f8c6c01 100644 --- a/blob/service.go +++ b/blob/service.go @@ -12,7 +12,6 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" @@ -72,8 +71,8 @@ func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { } // Get retrieves all the blobs for given namespaces at the given height by commitment. -func (s *Service) Get(ctx context.Context, height uint64, nID namespace.ID, commitment Commitment) (*Blob, error) { - blob, _, err := s.getByCommitment(ctx, height, nID, commitment) +func (s *Service) Get(ctx context.Context, height uint64, ns share.Namespace, commitment Commitment) (*Blob, error) { + blob, _, err := s.getByCommitment(ctx, height, ns, commitment) if err != nil { return nil, err } @@ -85,10 +84,10 @@ func (s *Service) Get(ctx context.Context, height uint64, nID namespace.ID, comm func (s *Service) GetProof( ctx context.Context, height uint64, - nID namespace.ID, + namespace share.Namespace, commitment Commitment, ) (*Proof, error) { - _, proof, err := s.getByCommitment(ctx, height, nID, commitment) + _, proof, err := s.getByCommitment(ctx, height, namespace, commitment) if err != nil { return nil, err } @@ -97,29 +96,29 @@ func (s *Service) GetProof( // GetAll returns all blobs under the given namespaces at the given height. // GetAll can return blobs and an error in case if some requests failed. -func (s *Service) GetAll(ctx context.Context, height uint64, nIDs []namespace.ID) ([]*Blob, error) { +func (s *Service) GetAll(ctx context.Context, height uint64, namespaces []share.Namespace) ([]*Blob, error) { header, err := s.headerGetter(ctx, height) if err != nil { return nil, err } var ( - resultBlobs = make([][]*Blob, len(nIDs)) - resultErr = make([]error, len(nIDs)) + resultBlobs = make([][]*Blob, len(namespaces)) + resultErr = make([]error, len(namespaces)) ) wg := sync.WaitGroup{} - for i, nID := range nIDs { + for i, namespace := range namespaces { wg.Add(1) - go func(i int, nID namespace.ID) { + go func(i int, namespace share.Namespace) { defer wg.Done() - blobs, err := s.getBlobs(ctx, nID, header.DAH) + blobs, err := s.getBlobs(ctx, namespace, header.DAH) if err != nil { - resultErr[i] = fmt.Errorf("getting blobs for nID(%s): %s", nID.String(), err) + resultErr[i] = fmt.Errorf("getting blobs for namespace(%s): %s", namespace.String(), err) return } resultBlobs[i] = blobs - }(i, nID) + }(i, namespace) } wg.Wait() @@ -143,7 +142,7 @@ func (s *Service) GetAll(ctx context.Context, height uint64, nIDs []namespace.ID func (s *Service) Included( ctx context.Context, height uint64, - nID namespace.ID, + namespace share.Namespace, proof *Proof, com Commitment, ) (bool, error) { @@ -156,7 +155,7 @@ func (s *Service) Included( // but we have to guarantee that all our stored subtree roots will be on the same height(e.g. one // level above shares). // TODO(@vgonkivs): rework the implementation to perform all verification without network requests. - _, resProof, err := s.getByCommitment(ctx, height, nID, com) + _, resProof, err := s.getByCommitment(ctx, height, namespace, com) switch err { case nil: case ErrBlobNotFound: @@ -172,12 +171,12 @@ func (s *Service) Included( func (s *Service) getByCommitment( ctx context.Context, height uint64, - nID namespace.ID, + namespace share.Namespace, commitment Commitment, ) (*Blob, *Proof, error) { log.Infow("requesting blob", "height", height, - "nID", nID.String()) + "namespace", namespace.String()) header, err := s.headerGetter(ctx, height) if err != nil { @@ -191,7 +190,7 @@ func (s *Service) getByCommitment( blobShare *shares.Share ) - namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header.DAH, nID) + namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header.DAH, namespace) if err != nil { if errors.Is(err, share.ErrNotFound) { err = ErrBlobNotFound @@ -209,8 +208,8 @@ func (s *Service) getByCommitment( // reconstruct the `blobShare` from the first rawShare in range // in order to get blob's length(first share will contain this info) if blobShare == nil { - for i, sh := range rawShares { - bShare, err := shares.NewShare(sh) + for i, shr := range rawShares { + bShare, err := shares.NewShare(shr) if err != nil { return nil, nil, err } @@ -278,10 +277,10 @@ func (s *Service) getByCommitment( return nil, nil, ErrBlobNotFound } -// getBlobs retrieves the DAH and fetches all shares from the requested namespace.ID and converts +// getBlobs retrieves the DAH and fetches all shares from the requested Namespace and converts // them to Blobs. -func (s *Service) getBlobs(ctx context.Context, nID namespace.ID, root *share.Root) ([]*Blob, error) { - namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, root, nID) +func (s *Service) getBlobs(ctx context.Context, namespace share.Namespace, root *share.Root) ([]*Blob, error) { + namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, root, namespace) if err != nil { return nil, err } diff --git a/blob/service_test.go b/blob/service_test.go index ee6e982fe8..51475178d2 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -14,17 +14,15 @@ import ( "github.com/stretchr/testify/require" tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/celestiaorg/celestia-app/pkg/appconsts" - appns "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/go-header/store" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/blob/blobtest" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" ) func TestBlobService_Get(t *testing.T) { @@ -37,12 +35,12 @@ func TestBlobService_Get(t *testing.T) { blobSize3 = 12 ) - appBlobs, err := blobtest.GenerateBlobs([]int{blobSize0, blobSize1}, false) + appBlobs, err := blobtest.GenerateV0Blobs([]int{blobSize0, blobSize1}, false) require.NoError(t, err) blobs0, err := convertBlobs(appBlobs...) require.NoError(t, err) - appBlobs, err = blobtest.GenerateBlobs([]int{blobSize2, blobSize3}, true) + appBlobs, err = blobtest.GenerateV0Blobs([]int{blobSize2, blobSize3}, true) require.NoError(t, err) blobs1, err := convertBlobs(appBlobs...) require.NoError(t, err) @@ -71,9 +69,9 @@ func TestBlobService_Get(t *testing.T) { }, }, { - name: "get all with the same nID", + name: "get all with the same namespace", doFn: func() (interface{}, error) { - b, err := service.GetAll(ctx, 1, []namespace.ID{blobs1[0].Namespace()}) + b, err := service.GetAll(ctx, 1, []share.Namespace{blobs1[0].Namespace()}) return b, err }, expectedResult: func(res interface{}, err error) { @@ -91,9 +89,9 @@ func TestBlobService_Get(t *testing.T) { }, }, { - name: "get all with different nIDs", + name: "get all with different namespaces", doFn: func() (interface{}, error) { - b, err := service.GetAll(ctx, 1, []namespace.ID{blobs0[0].Namespace(), blobs0[1].Namespace()}) + b, err := service.GetAll(ctx, 1, []share.Namespace{blobs0[0].Namespace(), blobs0[1].Namespace()}) return b, err }, expectedResult: func(res interface{}, err error) { @@ -126,7 +124,7 @@ func TestBlobService_Get(t *testing.T) { { name: "get invalid blob", doFn: func() (interface{}, error) { - appBlob, err := blobtest.GenerateBlobs([]int{10}, false) + appBlob, err := blobtest.GenerateV0Blobs([]int{10}, false) require.NoError(t, err) blob, err := convertBlobs(appBlob...) require.NoError(t, err) @@ -157,13 +155,13 @@ func TestBlobService_Get(t *testing.T) { proof, ok := res.(*Proof) assert.True(t, ok) - verifyFn := func(t *testing.T, rawShares [][]byte, proof *Proof, nID namespace.ID) { + verifyFn := func(t *testing.T, rawShares [][]byte, proof *Proof, namespace share.Namespace) { for _, row := range header.DAH.RowRoots { to := 0 for _, p := range *proof { from := to to = p.End() - p.Start() + from - eq := p.VerifyInclusion(sha256.New(), nID, rawShares[from:to], row) + eq := p.VerifyInclusion(sha256.New(), namespace.ToNMT(), rawShares[from:to], row) if eq == true { return } @@ -209,7 +207,7 @@ func TestBlobService_Get(t *testing.T) { { name: "not included", doFn: func() (interface{}, error) { - appBlob, err := blobtest.GenerateBlobs([]int{10}, false) + appBlob, err := blobtest.GenerateV0Blobs([]int{10}, false) require.NoError(t, err) blob, err := convertBlobs(appBlob...) require.NoError(t, err) @@ -256,8 +254,8 @@ func TestBlobService_Get(t *testing.T) { { name: "get all not found", doFn: func() (interface{}, error) { - nID := tmrand.Bytes(appconsts.NamespaceSize) - return service.GetAll(ctx, 1, []namespace.ID{nID}) + namespace := share.Namespace(tmrand.Bytes(share.NamespaceSize)) + return service.GetAll(ctx, 1, []share.Namespace{namespace}) }, expectedResult: func(i interface{}, err error) { blobs, ok := i.([]*Blob) @@ -297,23 +295,19 @@ func TestBlobService_Get(t *testing.T) { } } -// TestService_GetSingleBlobWithoutPadding creates two blobs with the same nID +// TestService_GetSingleBlobWithoutPadding creates two blobs with the same namespace // But to satisfy the rule of eds creating, padding namespace share is placed between // blobs. Test ensures that blob service will skip padding share and return the correct blob. func TestService_GetSingleBlobWithoutPadding(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - appBlob, err := blobtest.GenerateBlobs([]int{9, 5}, true) + appBlob, err := blobtest.GenerateV0Blobs([]int{9, 5}, true) require.NoError(t, err) blobs, err := convertBlobs(appBlob...) require.NoError(t, err) - ns1, err := appns.New(blobs[0].Namespace()[0], blobs[0].Namespace()[appns.NamespaceVersionSize:]) - require.NoError(t, err) - - ns2, err := appns.New(blobs[1].Namespace()[0], blobs[1].Namespace()[appns.NamespaceVersionSize:]) - require.NoError(t, err) + ns1, ns2 := blobs[0].Namespace().ToAppNamespace(), blobs[1].Namespace().ToAppNamespace() padding0, err := shares.NamespacePaddingShare(ns1) require.NoError(t, err) @@ -332,7 +326,7 @@ func TestService_GetSingleBlobWithoutPadding(t *testing.T) { batching := ds_sync.MutexWrap(ds.NewMapDatastore()) headerStore, err := store.NewStore[*header.ExtendedHeader](batching) require.NoError(t, err) - eds, err := share.AddShares(ctx, rawShares, bs) + eds, err := ipld.AddShares(ctx, rawShares, bs) require.NoError(t, err) h := headertest.ExtendedHeaderFromEDS(t, 1, eds) @@ -353,22 +347,12 @@ func TestService_GetAllWithoutPadding(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - appBlob, err := blobtest.GenerateBlobs([]int{9, 5}, true) + appBlob, err := blobtest.GenerateV0Blobs([]int{9, 5}, true) require.NoError(t, err) blobs, err := convertBlobs(appBlob...) require.NoError(t, err) - ns1, err := appns.New( - blobs[0].Namespace()[appns.NamespaceVersionSize-1], - blobs[0].Namespace()[appns.NamespaceVersionSize:], - ) - require.NoError(t, err) - - ns2, err := appns.New( - blobs[1].Namespace()[appns.NamespaceVersionSize-1], - blobs[1].Namespace()[appns.NamespaceVersionSize:], - ) - require.NoError(t, err) + ns1, ns2 := blobs[0].Namespace().ToAppNamespace(), blobs[1].Namespace().ToAppNamespace() padding0, err := shares.NamespacePaddingShare(ns1) require.NoError(t, err) @@ -393,7 +377,7 @@ func TestService_GetAllWithoutPadding(t *testing.T) { batching := ds_sync.MutexWrap(ds.NewMapDatastore()) headerStore, err := store.NewStore[*header.ExtendedHeader](batching) require.NoError(t, err) - eds, err := share.AddShares(ctx, rawShares, bs) + eds, err := ipld.AddShares(ctx, rawShares, bs) require.NoError(t, err) h := headertest.ExtendedHeaderFromEDS(t, 1, eds) @@ -406,7 +390,7 @@ func TestService_GetAllWithoutPadding(t *testing.T) { service := NewService(nil, getters.NewIPLDGetter(bs), fn) - _, err = service.GetAll(ctx, 1, []namespace.ID{blobs[0].Namespace(), blobs[1].Namespace()}) + _, err = service.GetAll(ctx, 1, []share.Namespace{blobs[0].Namespace(), blobs[1].Namespace()}) require.NoError(t, err) } @@ -417,7 +401,7 @@ func createService(ctx context.Context, t *testing.T, blobs []*Blob) *Service { require.NoError(t, err) rawShares, err := BlobsToShares(blobs...) require.NoError(t, err) - eds, err := share.AddShares(ctx, rawShares, bs) + eds, err := ipld.AddShares(ctx, rawShares, bs) require.NoError(t, err) h := headertest.ExtendedHeaderFromEDS(t, 1, eds) diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 18851dc797..169ca4b8d1 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -16,9 +16,6 @@ import ( "github.com/spf13/cobra" - appns "github.com/celestiaorg/celestia-app/pkg/namespace" - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/share" @@ -112,18 +109,18 @@ func parseParams(method string, params []string) []interface{} { panic(fmt.Errorf("couldn't parse share root as json: %v", err)) } parsedParams[0] = root - // 2. NamespaceID - nID, err := parseV0NamespaceID(params[1]) + // 2. Namespace + namespace, err := parseV0Namespace(params[1]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } - parsedParams[1] = nID + parsedParams[1] = namespace case "Submit": - // 1. NamespaceID + // 1. Namespace var err error - nID, err := parseV0NamespaceID(params[0]) + namespace, err := parseV0Namespace(params[0]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } // 2. Blob data var blobData []byte @@ -146,7 +143,7 @@ func parseParams(method string, params []string) []interface{} { panic("Error decoding blob data: base64 string could not be decoded.") } } - parsedBlob, err := blob.NewBlob(0, nID, blobData) + parsedBlob, err := blob.NewBlobV0(namespace, blobData) if err != nil { panic(fmt.Sprintf("Error creating blob: %v", err)) } @@ -162,10 +159,10 @@ func parseParams(method string, params []string) []interface{} { panic("Error parsing gas limit: uint64 could not be parsed.") } parsedParams[1] = num - // 3. NamespaceID - nID, err := parseV0NamespaceID(params[2]) + // 3. Namespace + namespace, err := parseV0Namespace(params[2]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } // 4. Blob data var blobData []byte @@ -188,7 +185,7 @@ func parseParams(method string, params []string) []interface{} { panic("Error decoding blob: base64 string could not be decoded.") } } - parsedBlob, err := blob.NewBlob(0, nID, blobData) + parsedBlob, err := blob.NewBlobV0(namespace, blobData) if err != nil { panic(fmt.Sprintf("Error creating blob: %v", err)) } @@ -202,11 +199,11 @@ func parseParams(method string, params []string) []interface{} { } parsedParams[0] = num // 2. NamespaceID - nID, err := parseV0NamespaceID(params[1]) + namespace, err := parseV0Namespace(params[1]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } - parsedParams[1] = nID + parsedParams[1] = namespace // 3: Commitment commitment, err := base64.StdEncoding.DecodeString(params[2]) if err != nil { @@ -221,12 +218,12 @@ func parseParams(method string, params []string) []interface{} { panic("Error parsing gas limit: uint64 could not be parsed.") } parsedParams[0] = num - // 2. NamespaceID - nID, err := parseV0NamespaceID(params[1]) + // 2. Namespace + namespace, err := parseV0Namespace(params[1]) if err != nil { - panic(fmt.Sprintf("Error parsing namespace ID: %v", err)) + panic(fmt.Sprintf("Error parsing namespace: %v", err)) } - parsedParams[1] = []namespace.ID{nID} + parsedParams[1] = []share.Namespace{namespace} return parsedParams case "QueryDelegation", "QueryUnbonding", "BalanceForAddress": var err error @@ -419,23 +416,17 @@ func parseSignatureForHelpstring(methodSig reflect.StructField) string { return simplifiedSignature } -// parseV0NamespaceID parses a namespace ID from a base64 or hex string. The param +// parseV0Namespace parses a namespace from a base64 or hex string. The param // is expected to be the user-specified portion of a v0 namespace ID (i.e. the // last 10 bytes). -func parseV0NamespaceID(param string) (namespace.ID, error) { +func parseV0Namespace(param string) (share.Namespace, error) { userBytes, err := decodeToBytes(param) if err != nil { return nil, err } - if len(userBytes) > appns.NamespaceVersionZeroIDSize { - return nil, fmt.Errorf( - "namespace ID %v is too large to be a v0 namespace, want <= %v bytes", - userBytes, appns.NamespaceVersionZeroIDSize, - ) - } // if the namespace ID is <= 10 bytes, left pad it with 0s - return share.NewNamespaceV0(userBytes) + return share.NewBlobNamespaceV0(userBytes) } // decodeToBytes decodes a Base64 or hex input string into a byte slice. diff --git a/cmd/celestia/rpc_test.go b/cmd/celestia/rpc_test.go index 9380a8b01e..53087646a7 100644 --- a/cmd/celestia/rpc_test.go +++ b/cmd/celestia/rpc_test.go @@ -5,21 +5,21 @@ import ( "github.com/stretchr/testify/assert" - "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/celestia-node/share" ) func Test_parseNamespaceID(t *testing.T) { type testCase struct { name string param string - want namespace.ID + want share.Namespace wantErr bool } testCases := []testCase{ { param: "0x0c204d39600fddd3", name: "8 byte hex encoded namespace ID gets left padded", - want: namespace.ID{ + want: share.Namespace{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, }, @@ -28,7 +28,7 @@ func Test_parseNamespaceID(t *testing.T) { { name: "10 byte hex encoded namespace ID", param: "0x42690c204d39600fddd3", - want: namespace.ID{ + want: share.Namespace{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, }, @@ -37,7 +37,7 @@ func Test_parseNamespaceID(t *testing.T) { { name: "29 byte hex encoded namespace ID", param: "0x0000000000000000000000000000000000000001010101010101010101", - want: namespace.ID{ + want: share.Namespace{ 0x0, // namespace version 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // v0 ID prefix 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // namespace ID @@ -47,13 +47,13 @@ func Test_parseNamespaceID(t *testing.T) { { name: "11 byte hex encoded namespace ID returns error", param: "0x42690c204d39600fddd3a3", - want: namespace.ID{}, + want: share.Namespace{}, wantErr: true, }, { name: "10 byte base64 encoded namespace ID", param: "QmkMIE05YA/d0w==", - want: namespace.ID{ + want: share.Namespace{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, }, @@ -62,14 +62,14 @@ func Test_parseNamespaceID(t *testing.T) { { name: "not base64 or hex encoded namespace ID returns error", param: "5748493939429", - want: namespace.ID{}, + want: share.Namespace{}, wantErr: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got, err := parseV0NamespaceID(tc.param) + got, err := parseV0Namespace(tc.param) if tc.wantErr { assert.Error(t, err) return @@ -77,6 +77,5 @@ func Test_parseNamespaceID(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tc.want, got) }) - } } diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 3e0da71d69..fbc71f92f9 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -26,7 +26,8 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" ) var log = logging.Logger("headertest") @@ -349,17 +350,15 @@ func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedData func CreateFraudExtHeader( t *testing.T, eh *header.ExtendedHeader, - dag blockservice.BlockService, + serv blockservice.BlockService, ) (*header.ExtendedHeader, *rsmt2d.ExtendedDataSquare) { - extended := share.RandEDS(t, 2) - shares := share.ExtractEDS(extended) - copy(shares[0][share.NamespaceSize:], shares[1][share.NamespaceSize:]) - extended, err := share.ImportShares(context.Background(), shares, dag) + square := edstest.RandByzantineEDS(t, 16) + err := ipld.ImportEDS(context.Background(), square, serv) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(extended) + dah := da.NewDataAvailabilityHeader(square) eh.DAH = &dah eh.RawHeader.DataHash = dah.Hash() - return eh, extended + return eh, square } type Subscriber struct { diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go index 7cbe312856..aae502824c 100644 --- a/nodebuilder/blob/blob.go +++ b/nodebuilder/blob/blob.go @@ -3,9 +3,8 @@ package blob import ( "context" - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/share" ) var _ Module = (*API)(nil) @@ -19,23 +18,23 @@ type Module interface { // Uses default wallet registered on the Node. Submit(_ context.Context, _ []*blob.Blob) (height uint64, _ error) // Get retrieves the blob by commitment under the given namespace and height. - Get(_ context.Context, height uint64, _ namespace.ID, _ blob.Commitment) (*blob.Blob, error) + Get(_ context.Context, height uint64, _ share.Namespace, _ blob.Commitment) (*blob.Blob, error) // GetAll returns all blobs under the given namespaces and height. - GetAll(_ context.Context, height uint64, _ []namespace.ID) ([]*blob.Blob, error) + GetAll(_ context.Context, height uint64, _ []share.Namespace) ([]*blob.Blob, error) // GetProof retrieves proofs in the given namespaces at the given height by commitment. - GetProof(_ context.Context, height uint64, _ namespace.ID, _ blob.Commitment) (*blob.Proof, error) + GetProof(_ context.Context, height uint64, _ share.Namespace, _ blob.Commitment) (*blob.Proof, error) // Included checks whether a blob's given commitment(Merkle subtree root) is included at // given height and under the namespace. - Included(_ context.Context, height uint64, _ namespace.ID, _ *blob.Proof, _ blob.Commitment) (bool, error) + Included(_ context.Context, height uint64, _ share.Namespace, _ *blob.Proof, _ blob.Commitment) (bool, error) } type API struct { Internal struct { - Submit func(context.Context, []*blob.Blob) (uint64, error) `perm:"write"` - Get func(context.Context, uint64, namespace.ID, blob.Commitment) (*blob.Blob, error) `perm:"read"` - GetAll func(context.Context, uint64, []namespace.ID) ([]*blob.Blob, error) `perm:"read"` - GetProof func(context.Context, uint64, namespace.ID, blob.Commitment) (*blob.Proof, error) `perm:"read"` - Included func(context.Context, uint64, namespace.ID, *blob.Proof, blob.Commitment) (bool, error) `perm:"read"` + Submit func(context.Context, []*blob.Blob) (uint64, error) `perm:"write"` + Get func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Blob, error) `perm:"read"` + GetAll func(context.Context, uint64, []share.Namespace) ([]*blob.Blob, error) `perm:"read"` + GetProof func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Proof, error) `perm:"read"` + Included func(context.Context, uint64, share.Namespace, *blob.Proof, blob.Commitment) (bool, error) `perm:"read"` } } @@ -46,31 +45,31 @@ func (api *API) Submit(ctx context.Context, blobs []*blob.Blob) (uint64, error) func (api *API) Get( ctx context.Context, height uint64, - nID namespace.ID, + namespace share.Namespace, commitment blob.Commitment, ) (*blob.Blob, error) { - return api.Internal.Get(ctx, height, nID, commitment) + return api.Internal.Get(ctx, height, namespace, commitment) } -func (api *API) GetAll(ctx context.Context, height uint64, nIDs []namespace.ID) ([]*blob.Blob, error) { - return api.Internal.GetAll(ctx, height, nIDs) +func (api *API) GetAll(ctx context.Context, height uint64, namespaces []share.Namespace) ([]*blob.Blob, error) { + return api.Internal.GetAll(ctx, height, namespaces) } func (api *API) GetProof( ctx context.Context, height uint64, - nID namespace.ID, + namespace share.Namespace, commitment blob.Commitment, ) (*blob.Proof, error) { - return api.Internal.GetProof(ctx, height, nID, commitment) + return api.Internal.GetProof(ctx, height, namespace, commitment) } func (api *API) Included( ctx context.Context, height uint64, - nID namespace.ID, + namespace share.Namespace, proof *blob.Proof, commitment blob.Commitment, ) (bool, error) { - return api.Internal.Included(ctx, height, nID, proof, commitment) + return api.Internal.Included(ctx, height, namespace, proof, commitment) } diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index b7c61aa450..5cd34b74b6 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -11,7 +11,7 @@ import ( gomock "github.com/golang/mock/gomock" blob "github.com/celestiaorg/celestia-node/blob" - namespace "github.com/celestiaorg/nmt/namespace" + share "github.com/celestiaorg/celestia-node/share" ) // MockModule is a mock of Module interface. @@ -38,7 +38,7 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // Get mocks base method. -func (m *MockModule) Get(arg0 context.Context, arg1 uint64, arg2 namespace.ID, arg3 blob.Commitment) (*blob.Blob, error) { +func (m *MockModule) Get(arg0 context.Context, arg1 uint64, arg2 share.Namespace, arg3 blob.Commitment) (*blob.Blob, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*blob.Blob) @@ -53,7 +53,7 @@ func (mr *MockModuleMockRecorder) Get(arg0, arg1, arg2, arg3 interface{}) *gomoc } // GetAll mocks base method. -func (m *MockModule) GetAll(arg0 context.Context, arg1 uint64, arg2 []namespace.ID) ([]*blob.Blob, error) { +func (m *MockModule) GetAll(arg0 context.Context, arg1 uint64, arg2 []share.Namespace) ([]*blob.Blob, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAll", arg0, arg1, arg2) ret0, _ := ret[0].([]*blob.Blob) @@ -68,7 +68,7 @@ func (mr *MockModuleMockRecorder) GetAll(arg0, arg1, arg2 interface{}) *gomock.C } // GetProof mocks base method. -func (m *MockModule) GetProof(arg0 context.Context, arg1 uint64, arg2 namespace.ID, arg3 blob.Commitment) (*blob.Proof, error) { +func (m *MockModule) GetProof(arg0 context.Context, arg1 uint64, arg2 share.Namespace, arg3 blob.Commitment) (*blob.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetProof", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*blob.Proof) @@ -83,7 +83,7 @@ func (mr *MockModuleMockRecorder) GetProof(arg0, arg1, arg2, arg3 interface{}) * } // Included mocks base method. -func (m *MockModule) Included(arg0 context.Context, arg1 uint64, arg2 namespace.ID, arg3 *blob.Proof, arg4 blob.Commitment) (bool, error) { +func (m *MockModule) Included(arg0 context.Context, arg1 uint64, arg2 share.Namespace, arg3 *blob.Proof, arg4 blob.Commitment) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Included", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(bool) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 1913b3d576..5cb0e41e53 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/filecoin-project/dagstore" + "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" @@ -18,6 +19,7 @@ import ( "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" disc "github.com/celestiaorg/celestia-node/share/p2p/discovery" ) @@ -60,6 +62,15 @@ func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { return err } +// ensureEmptyEDSInBS checks if the given DAG contains an empty block data square. +// If it does not, it stores an empty block. This optimization exists to prevent +// redundant storing of empty block data so that it is only stored once and returned +// upon request for a block with an empty data square. +func ensureEmptyEDSInBS(ctx context.Context, bServ blockservice.BlockService) error { + _, err := ipld.AddShares(ctx, share.EmptyBlockShares(), bServ) + return err +} + func lightGetter( shrexGetter *getters.ShrexGetter, ipldGetter *getters.IPLDGetter, diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 1b26273c0f..66baa23301 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -12,7 +12,6 @@ import ( da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" - namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" ) @@ -70,7 +69,7 @@ func (mr *MockModuleMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) * } // GetSharesByNamespace mocks base method. -func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 namespace.ID) (share.NamespacedShares, error) { +func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 share.Namespace) (share.NamespacedShares, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) ret0, _ := ret[0].(share.NamespacedShares) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 6dfb155bb0..b924cf8167 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -176,7 +176,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option } }), shrexGetterComponents, - fx.Invoke(share.EnsureEmptySquareExists), + fx.Invoke(ensureEmptyEDSInBS), fx.Provide(getters.NewIPLDGetter), fx.Provide(lightGetter), // shrexsub broadcaster stub for daser diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index 0c703f9a15..def3bb4d0f 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -3,7 +3,6 @@ package share import ( "context" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -40,7 +39,7 @@ type Module interface { GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) // GetSharesByNamespace gets all shares from an EDS within the given namespace. // Shares are returned in a row-by-row order if the namespace spans multiple rows. - GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) (share.NamespacedShares, error) + GetSharesByNamespace(ctx context.Context, root *share.Root, namespace share.Namespace) (share.NamespacedShares, error) } // API is a wrapper around Module for the RPC. @@ -61,7 +60,7 @@ type API struct { GetSharesByNamespace func( ctx context.Context, root *share.Root, - namespace namespace.ID, + namespace share.Namespace, ) (share.NamespacedShares, error) `perm:"public"` } } @@ -85,7 +84,7 @@ func (api *API) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedD func (api *API) GetSharesByNamespace( ctx context.Context, root *share.Root, - namespace namespace.ID, + namespace share.Namespace, ) (share.NamespacedShares, error) { return api.Internal.GetSharesByNamespace(ctx, root, namespace) } diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 37504ec3b3..1bc1c261de 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -109,7 +109,7 @@ func TestBlobRPC(t *testing.T) { client, err := client.NewClient(ctx, bridgeAddr, jwt) require.NoError(t, err) - appBlobs, err := blobtest.GenerateBlobs([]int{8}, false) + appBlobs, err := blobtest.GenerateV0Blobs([]int{8}, false) require.NoError(t, err) newBlob, err := blob.NewBlob( diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index 537e320c5a..6a7db50c7c 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -11,12 +11,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/blob/blobtest" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share" ) func TestBlobModule(t *testing.T) { @@ -24,9 +23,9 @@ func TestBlobModule(t *testing.T) { t.Cleanup(cancel) sw := swamp.NewSwamp(t) - appBlobs0, err := blobtest.GenerateBlobs([]int{8, 4}, true) + appBlobs0, err := blobtest.GenerateV0Blobs([]int{8, 4}, true) require.NoError(t, err) - appBlobs1, err := blobtest.GenerateBlobs([]int{4}, false) + appBlobs1, err := blobtest.GenerateV0Blobs([]int{4}, false) require.NoError(t, err) blobs := make([]*blob.Blob, 0, len(appBlobs0)+len(appBlobs1)) @@ -79,7 +78,7 @@ func TestBlobModule(t *testing.T) { { name: "GetAll", doFn: func(t *testing.T) { - newBlobs, err := fullNode.BlobServ.GetAll(ctx, height, []namespace.ID{blobs[0].Namespace()}) + newBlobs, err := fullNode.BlobServ.GetAll(ctx, height, []share.Namespace{blobs[0].Namespace()}) require.NoError(t, err) require.Len(t, newBlobs, len(appBlobs0)) require.True(t, bytes.Equal(blobs[0].Commitment, newBlobs[0].Commitment)) @@ -106,7 +105,7 @@ func TestBlobModule(t *testing.T) { { name: "Not Found", doFn: func(t *testing.T) { - appBlob, err := blobtest.GenerateBlobs([]int{4}, false) + appBlob, err := blobtest.GenerateV0Blobs([]int{4}, false) require.NoError(t, err) newBlob, err := blob.NewBlob( appBlob[0].ShareVersion, diff --git a/share/availability.go b/share/availability.go index 9538573114..b6b44271c8 100644 --- a/share/availability.go +++ b/share/availability.go @@ -4,7 +4,7 @@ import ( "context" "errors" - da "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/da" ) // ErrNotAvailable is returned whenever DA sampling fails. diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index ca72596816..1709c3f4b7 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -10,11 +10,11 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestSharesAvailable(t *testing.T) { @@ -81,23 +81,23 @@ func TestService_GetSharesByNamespace(t *testing.T) { t.Run("size: "+strconv.Itoa(tt.squareSize), func(t *testing.T) { getter, bServ := EmptyGetter() totalShares := tt.squareSize * tt.squareSize - randShares := share.RandShares(t, totalShares) + randShares := sharetest.RandShares(t, totalShares) idx1 := (totalShares - 1) / 2 idx2 := totalShares / 2 if tt.expectedShareCount > 1 { - // make it so that two rows have the same namespace ID - copy(randShares[idx2][:namespace.NamespaceSize], randShares[idx1][:namespace.NamespaceSize]) + // make it so that two rows have the same namespace + copy(share.GetNamespace(randShares[idx2]), share.GetNamespace(randShares[idx1])) } root := availability_test.FillBS(t, bServ, randShares) - randNID := randShares[idx1][:namespace.NamespaceSize] + randNamespace := share.GetNamespace(randShares[idx1]) - shares, err := getter.GetSharesByNamespace(context.Background(), root, randNID) + shares, err := getter.GetSharesByNamespace(context.Background(), root, randNamespace) require.NoError(t, err) - require.NoError(t, shares.Verify(root, randNID)) + require.NoError(t, shares.Verify(root, randNamespace)) flattened := shares.Flatten() assert.Len(t, flattened, tt.expectedShareCount) for _, value := range flattened { - assert.Equal(t, randNID, []byte(share.ID(value))) + assert.Equal(t, randNamespace, share.GetNamespace(value)) } if tt.expectedShareCount > 1 { // idx1 is always smaller than idx2 @@ -105,14 +105,14 @@ func TestService_GetSharesByNamespace(t *testing.T) { assert.Equal(t, randShares[idx2], flattened[1]) } }) - t.Run("last two rows of a 4x4 square that have the same namespace ID have valid NMT proofs", func(t *testing.T) { + t.Run("last two rows of a 4x4 square that have the same namespace have valid NMT proofs", func(t *testing.T) { squareSize := 4 totalShares := squareSize * squareSize getter, bServ := EmptyGetter() - randShares := share.RandShares(t, totalShares) - lastNID := randShares[totalShares-1][:namespace.NamespaceSize] + randShares := sharetest.RandShares(t, totalShares) + lastNID := share.GetNamespace(randShares[totalShares-1]) for i := totalShares / 2; i < totalShares; i++ { - copy(randShares[i][:namespace.NamespaceSize], lastNID) + copy(share.GetNamespace(randShares[i]), lastNID) } root := availability_test.FillBS(t, bServ, randShares) @@ -141,7 +141,7 @@ func TestService_GetSharesByNamespaceNotFound(t *testing.T) { getter, root := GetterWithRandSquare(t, 1) root.RowRoots = nil - emptyShares, err := getter.GetSharesByNamespace(context.Background(), root, namespace.RandomNamespace().Bytes()) + emptyShares, err := getter.GetSharesByNamespace(context.Background(), root, sharetest.RandV0Namespace()) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) } @@ -159,11 +159,11 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { t := &testing.T{} getter, root := GetterWithRandSquare(t, tt.amountShares) - randNID := root.RowRoots[(len(root.RowRoots)-1)/2][:8] + randNamespace := root.RowRoots[(len(root.RowRoots)-1)/2][:share.NamespaceSize] root.RowRoots[(len(root.RowRoots) / 2)] = root.RowRoots[(len(root.RowRoots)-1)/2] b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := getter.GetSharesByNamespace(context.Background(), root, randNID) + _, err := getter.GetSharesByNamespace(context.Background(), root, randNamespace) require.NoError(t, err) } }) diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index b2fb780bd4..19f63f114a 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -20,17 +20,19 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/sharetest" ) // RandFillBS fills the given BlockService with a random block of a given size. func RandFillBS(t *testing.T, n int, bServ blockservice.BlockService) *share.Root { - shares := share.RandShares(t, n*n) + shares := sharetest.RandShares(t, n*n) return FillBS(t, bServ, shares) } // FillBS fills the given BlockService with the given shares. func FillBS(t *testing.T, bServ blockservice.BlockService, shares []share.Share) *share.Root { - eds, err := share.AddShares(context.TODO(), shares, bServ) + eds, err := ipld.AddShares(context.TODO(), shares, bServ) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(eds) return &dah diff --git a/share/doc.go b/share/doc.go index 7ef8c9f7bf..97229932a7 100644 --- a/share/doc.go +++ b/share/doc.go @@ -4,7 +4,7 @@ block data. Though this package contains several useful methods for getting specific shares and/or sampling them at random, a particularly useful method is GetSharesByNamespace which retrieves -all shares of block data of the given namespace.ID from the block associated with the given +all shares of block data of the given Namespace from the block associated with the given DataAvailabilityHeader (DAH, but referred to as Root within this package). This package also contains declaration of the Availability interface. Implementations of diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 4e673ed8b8..0672096b25 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -143,17 +143,17 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { // verify that Merkle proofs correspond to particular shares. shares := make([][]byte, len(merkleRowRoots)) - for index, share := range p.Shares { - if share == nil { + for index, shr := range p.Shares { + if shr == nil { continue } // validate inclusion of the share into one of the DAHeader roots - if ok := share.Validate(ipld.MustCidFromNamespacedSha256(root)); !ok { + if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(root)); !ok { return fmt.Errorf("fraud: invalid proof: incorrect share received at index %d", index) } // NMTree commits the additional namespace while rsmt2d does not know about, so we trim it // this is ugliness from NMTWrapper that we have to embrace ¯\_(ツ)_/¯ - shares[index] = share.Share[ipld.NamespaceSize:] + shares[index] = share.GetData(shr.Share) } odsWidth := uint64(len(merkleRowRoots) / 2) diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 6e413b17a2..5f6adac595 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -3,8 +3,10 @@ package byzantine import ( "context" "testing" + "time" mdutils "github.com/ipfs/go-merkledag/test" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" core "github.com/tendermint/tendermint/types" @@ -12,10 +14,34 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/sharetest" ) +func TestBadEncodingFraudProof(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer t.Cleanup(cancel) + bServ := mdutils.Bserv() + + square := edstest.RandByzantineEDS(t, 16) + dah := da.NewDataAvailabilityHeader(square) + err := ipld.ImportEDS(ctx, square, bServ) + require.NoError(t, err) + + var errRsmt2d *rsmt2d.ErrByzantineData + err = square.Repair(dah.RowRoots, dah.ColumnRoots) + require.ErrorAs(t, err, &errRsmt2d) + + errByz := NewErrByzantine(ctx, bServ, &dah, errRsmt2d) + + befp := CreateBadEncodingProof([]byte("hash"), 0, errByz) + err = befp.Validate(&header.ExtendedHeader{ + DAH: &dah, + }) + assert.NoError(t, err) +} + // TestIncorrectBadEncodingFraudProof asserts that BEFP is not generated for the correct data func TestIncorrectBadEncodingFraudProof(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -24,9 +50,9 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { bServ := mdutils.Bserv() squareSize := 8 - shares := share.RandShares(t, squareSize*squareSize) + shares := sharetest.RandShares(t, squareSize*squareSize) - eds, err := share.AddShares(ctx, shares, bServ) + eds, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(eds) diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 86eaad54a8..b8e39ee1d3 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -45,8 +45,8 @@ func NewShareWithProof(index int, share share.Share, pathToLeaf []cid.Cid) *Shar func (s *ShareWithProof) Validate(root cid.Cid) bool { return s.Proof.VerifyInclusion( sha256.New(), // TODO(@Wondertan): This should be defined somewhere globally - share.ID(s.Share), - [][]byte{share.Data(s.Share)}, + share.GetNamespace(s.Share).ToNMT(), + [][]byte{share.GetData(s.Share)}, ipld.NamespacedSha256FromCID(root), ) } diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go index 9cffe6eb18..0f63d4f0c9 100644 --- a/share/eds/byzantine/share_proof_test.go +++ b/share/eds/byzantine/share_proof_test.go @@ -12,8 +12,8 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestGetProof(t *testing.T) { @@ -23,8 +23,8 @@ func TestGetProof(t *testing.T) { defer cancel() bServ := mdutils.Bserv() - shares := share.RandShares(t, width*width) - in, err := share.AddShares(ctx, shares, bServ) + shares := sharetest.RandShares(t, width*width) + in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(in) @@ -59,8 +59,8 @@ func TestGetProofs(t *testing.T) { defer cancel() bServ := mdutils.Bserv() - shares := share.RandShares(t, width*width) - in, err := share.AddShares(ctx, shares, bServ) + shares := sharetest.RandShares(t, width*width) + in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(in) diff --git a/share/eds/eds.go b/share/eds/eds.go index 4e96fd684e..239ba4a0ad 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -18,9 +18,7 @@ import ( "github.com/ipld/go-car/util" "github.com/minio/sha256-simd" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -114,7 +112,7 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. return &writingSession{ eds: eds, store: store, - hasher: nmt.NewNmtHasher(sha256.New(), ipld.NamespaceSize, ipld.NMTIgnoreMaxNamespace), + hasher: nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, ipld.NMTIgnoreMaxNamespace), w: w, }, nil } @@ -219,13 +217,13 @@ func getQuadrantCells(eds *rsmt2d.ExtendedDataSquare, i, j uint) [][]byte { // prependNamespace adds the namespace to the passed share if in the first quadrant, // otherwise it adds the ParitySharesNamespace to the beginning. -func prependNamespace(quadrant int, share []byte) []byte { - namespacedShare := make([]byte, 0, appconsts.NamespaceSize+appconsts.ShareSize) +func prependNamespace(quadrant int, shr share.Share) []byte { + namespacedShare := make([]byte, 0, share.NamespaceSize+share.Size) switch quadrant { case 0: - return append(append(namespacedShare, share[:ipld.NamespaceSize]...), share...) + return append(append(namespacedShare, share.GetNamespace(shr)...), shr...) case 1, 2, 3: - return append(append(namespacedShare, namespace.ParitySharesNamespace.Bytes()...), share...) + return append(append(namespacedShare, share.ParitySharesNamespace...), shr...) default: panic("invalid quadrant") } @@ -273,7 +271,7 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (eds *rsmt2d } // the stored first quadrant shares are wrapped with the namespace twice. // we cut it off here, because it is added again while importing to the tree below - shares[i] = block.RawData()[ipld.NamespaceSize:] + shares[i] = share.GetData(block.RawData()) } eds, err = rsmt2d.ComputeExtendedDataSquare( diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index ea0f06c138..0ef211ec6f 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -18,10 +18,10 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -57,7 +57,7 @@ func TestQuadrantOrder(t *testing.T) { res := quadrantOrder(eds) for _, s := range res { - require.Len(t, s, testShareSize+namespace.NamespaceSize) + require.Len(t, s, testShareSize+share.NamespaceSize) } for q := 0; q < 4; q++ { @@ -101,7 +101,7 @@ func TestWriteEDSStartsWithLeaves(t *testing.T) { block, err := reader.Next() require.NoError(t, err, "error getting first block") - require.Equal(t, block.RawData()[ipld.NamespaceSize:], eds.GetCell(0, 0)) + require.Equal(t, share.GetData(block.RawData()), eds.GetCell(0, 0)) } func TestWriteEDSIncludesRoots(t *testing.T) { @@ -193,7 +193,7 @@ func TestReadEDS(t *testing.T) { func TestReadEDSContentIntegrityMismatch(t *testing.T) { writeRandomEDS(t) - dah := da.NewDataAvailabilityHeader(share.RandEDS(t, 4)) + dah := da.NewDataAvailabilityHeader(edstest.RandEDS(t, 4)) f := openWrittenEDS(t) defer f.Close() @@ -208,7 +208,7 @@ func BenchmarkReadWriteEDS(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) b.Cleanup(cancel) for originalDataWidth := 4; originalDataWidth <= 64; originalDataWidth *= 2 { - eds := share.RandEDS(b, originalDataWidth) + eds := edstest.RandEDS(b, originalDataWidth) dah := da.NewDataAvailabilityHeader(eds) b.Run(fmt.Sprintf("Writing %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) { b.ReportAllocs() @@ -242,7 +242,7 @@ func writeRandomEDS(t *testing.T) *rsmt2d.ExtendedDataSquare { f, err := os.OpenFile("test.car", os.O_WRONLY|os.O_CREATE, 0600) require.NoError(t, err, "error opening file") - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) err = WriteEDS(ctx, eds, f) require.NoError(t, err, "error writing EDS to file") f.Close() @@ -276,7 +276,7 @@ func createTestData(t *testing.T, testDir string) { //nolint:unused f, err := os.OpenFile("example.car", os.O_WRONLY|os.O_CREATE, 0600) require.NoError(t, err, "opening file") - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) err = WriteEDS(ctx, eds, f) require.NoError(t, err, "writing EDS to file") f.Close() diff --git a/share/eds/edstest/testing.go b/share/eds/edstest/testing.go new file mode 100644 index 0000000000..2b6d9ef78d --- /dev/null +++ b/share/eds/edstest/testing.go @@ -0,0 +1,31 @@ +package edstest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/sharetest" +) + +func RandByzantineEDS(t *testing.T, size int) *rsmt2d.ExtendedDataSquare { + eds := RandEDS(t, size) + shares := share.ExtractEDS(eds) + copy(share.GetData(shares[0]), share.GetData(shares[1])) // corrupting eds + eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) + require.NoError(t, err, "failure to recompute the extended data square") + return eds +} + +// RandEDS generates EDS filled with the random data with the given size for original square. It +// uses require.TestingT to be able to take both a *testing.T and a *testing.B. +func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { + shares := sharetest.RandShares(t, size*size) + eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) + require.NoError(t, err, "failure to recompute the extended data square") + return eds +} diff --git a/share/eds/ods_test.go b/share/eds/ods_test.go index 75fad3022e..eb243a4022 100644 --- a/share/eds/ods_test.go +++ b/share/eds/ods_test.go @@ -52,7 +52,7 @@ func TestODSReader(t *testing.T) { assert.NoError(t, err) // check that original data from eds is same as data from reader - assert.Equal(t, original, block.RawData()[share.NamespaceSize:]) + assert.Equal(t, original, share.GetData(block.RawData())) } } diff --git a/share/eds/retriever.go b/share/eds/retriever.go index b2dcc4ff7a..b3cf363056 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -256,7 +256,7 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { // and go get shares of left or the right side of the whole col/row axis // the left or the right side of the tree represent some portion of the quadrant // which we put into the rs.square share-by-share by calculating shares' indexes using q.index - share.GetShares(ctx, rs.bget, nd.Links()[q.x].Cid, size, func(j int, share share.Share) { + ipld.GetShares(ctx, rs.bget, nd.Links()[q.x].Cid, size, func(j int, share share.Share) { // NOTE: Each share can appear twice here, for a Row and Col, respectively. // These shares are always equal, and we allow only the first one to be written // in the square. diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index e90216de13..ca689aa5ec 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -2,11 +2,9 @@ package eds import ( "context" - "errors" "testing" "time" - "github.com/ipfs/go-blockservice" mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,11 +14,11 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" + "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestRetriever_Retrieve(t *testing.T) { @@ -48,8 +46,8 @@ func TestRetriever_Retrieve(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { // generate EDS - shares := share.RandShares(t, tc.squareSize*tc.squareSize) - in, err := share.AddShares(ctx, shares, bServ) + shares := sharetest.RandShares(t, tc.squareSize*tc.squareSize) + in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) // limit with timeout, specifically retrieval @@ -70,8 +68,8 @@ func TestRetriever_ByzantineError(t *testing.T) { defer cancel() bserv := mdutils.Bserv() - shares := share.ExtractEDS(share.RandEDS(t, width)) - _, err := share.ImportShares(ctx, shares, bserv) + shares := share.ExtractEDS(edstest.RandEDS(t, width)) + _, err := ipld.ImportShares(ctx, shares, bserv) require.NoError(t, err) // corrupt shares so that eds erasure coding does not match @@ -109,8 +107,8 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { r := NewRetriever(bServ) // generate EDS - shares := share.RandShares(t, squareSize*squareSize) - in, err := share.AddShares(ctx, shares, bServ) + shares := sharetest.RandShares(t, squareSize*squareSize) + in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(in) @@ -126,31 +124,3 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { _, err = ses.Reconstruct(ctx) assert.NoError(t, err) } - -func TestFraudProofValidation(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer t.Cleanup(cancel) - bServ := mdutils.Bserv() - - var errByz *byzantine.ErrByzantine - faultHeader, err := generateByzantineError(ctx, t, bServ) - require.True(t, errors.As(err, &errByz)) - - p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(faultHeader.Height()), errByz) - err = p.Validate(faultHeader) - require.NoError(t, err) -} - -func generateByzantineError( - ctx context.Context, - t *testing.T, - bServ blockservice.BlockService, -) (*header.ExtendedHeader, error) { - store := headertest.NewStore(t) - h, err := store.GetByHeight(ctx, 1) - require.NoError(t, err) - - faultHeader, _ := headertest.CreateFraudExtHeader(t, h, bServ) - _, err = NewRetriever(bServ).Retrieve(ctx, faultHeader.DAH) - return faultHeader, err -} diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 6c6d7d10b2..b1dc3986b7 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -17,6 +17,7 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" ) func TestEDSStore(t *testing.T) { @@ -77,7 +78,7 @@ func TestEDSStore(t *testing.T) { original := eds.GetCell(uint(i), uint(j)) block, err := carReader.Next() assert.NoError(t, err) - assert.Equal(t, original, block.RawData()[share.NamespaceSize:]) + assert.Equal(t, original, share.GetData(block.RawData())) } } }) @@ -261,7 +262,7 @@ func newStore(t *testing.T) (*Store, error) { } func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) return eds, dah diff --git a/share/empty.go b/share/empty.go index 0b7ea2e775..b0e1e6e6ae 100644 --- a/share/empty.go +++ b/share/empty.go @@ -2,10 +2,8 @@ package share import ( "bytes" - "context" "fmt" - - "github.com/ipfs/go-blockservice" + "sync" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" @@ -13,19 +11,48 @@ import ( "github.com/celestiaorg/rsmt2d" ) +// EmptyRoot returns Root of the empty block EDS. +func EmptyRoot() *Root { + initEmpty() + return emptyBlockRoot +} + +// EmptyExtendedDataSquare returns the EDS of the empty block data square. +func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { + initEmpty() + return emptyBlockEDS +} + +// EmptyBlockShares returns the shares of the empty block. +func EmptyBlockShares() []Share { + initEmpty() + return emptyBlockShares +} + var ( - emptyRoot *Root - emptyEDS *rsmt2d.ExtendedDataSquare + emptyMu sync.Mutex + emptyBlockRoot *Root + emptyBlockEDS *rsmt2d.ExtendedDataSquare + emptyBlockShares []Share ) -func init() { +// initEmpty enables lazy initialization for constant empty block data. +func initEmpty() { + emptyMu.Lock() + defer emptyMu.Unlock() + if emptyBlockRoot != nil { + return + } + // compute empty block EDS and DAH for it - shares := emptyDataSquare() - eds, err := da.ExtendShares(shares) + result := shares.TailPaddingShares(appconsts.MinShareCount) + emptyBlockShares = shares.ToBytes(result) + + eds, err := da.ExtendShares(emptyBlockShares) if err != nil { panic(fmt.Errorf("failed to create empty EDS: %w", err)) } - emptyEDS = eds + emptyBlockEDS = eds dah := da.NewDataAvailabilityHeader(eds) minDAH := da.MinDataAvailabilityHeader() @@ -33,33 +60,8 @@ func init() { panic(fmt.Sprintf("mismatch in calculated minimum DAH and minimum DAH from celestia-app, "+ "expected %s, got %s", minDAH.String(), dah.String())) } - emptyRoot = &dah + emptyBlockRoot = &dah // precompute Hash, so it's cached internally to avoid potential races - emptyRoot.Hash() -} - -// EmptyRoot returns Root of an empty EDS. -func EmptyRoot() *Root { - return emptyRoot -} - -// EnsureEmptySquareExists checks if the given DAG contains an empty block data square. -// If it does not, it stores an empty block. This optimization exists to prevent -// redundant storing of empty block data so that it is only stored once and returned -// upon request for a block with an empty data square. Ref: header/constructors.go#L56 -func EnsureEmptySquareExists(ctx context.Context, bServ blockservice.BlockService) (*rsmt2d.ExtendedDataSquare, error) { - shares := emptyDataSquare() - return AddShares(ctx, shares, bServ) -} - -// EmptyExtendedDataSquare returns the EDS of the empty block data square. -func EmptyExtendedDataSquare() *rsmt2d.ExtendedDataSquare { - return emptyEDS -} - -// emptyDataSquare returns the minimum size data square filled with tail padding. -func emptyDataSquare() [][]byte { - result := shares.TailPaddingShares(appconsts.MinShareCount) - return shares.ToBytes(result) + emptyBlockRoot.Hash() } diff --git a/share/getter.go b/share/getter.go index 15f384a2c4..7caa954a24 100644 --- a/share/getter.go +++ b/share/getter.go @@ -8,10 +8,7 @@ import ( "github.com/minio/sha256-simd" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" - - "github.com/celestiaorg/celestia-node/share/ipld" ) var ( @@ -35,7 +32,7 @@ type Getter interface { // Inclusion of returned data could be verified using Verify method on NamespacedShares. // If no shares are found for target namespace non-inclusion could be also verified by calling // Verify method. - GetSharesByNamespace(context.Context, *Root, namespace.ID) (NamespacedShares, error) + GetSharesByNamespace(context.Context, *Root, Namespace) (NamespacedShares, error) } // NamespacedShares represents all shares with proofs within a specific namespace of an EDS. @@ -57,10 +54,10 @@ type NamespacedRow struct { } // Verify validates NamespacedShares by checking every row with nmt inclusion proof. -func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { - originalRoots := make([][]byte, 0) +func (ns NamespacedShares) Verify(root *Root, namespace Namespace) error { + var originalRoots [][]byte for _, row := range root.RowRoots { - if !ipld.NamespaceIsOutsideRange(row, row, nID) { + if !namespace.IsOutsideRange(row, row) { originalRoots = append(originalRoots, row) } } @@ -72,7 +69,7 @@ func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { for i, row := range ns { // verify row data against row hash from original root - if !row.verify(originalRoots[i], nID) { + if !row.verify(originalRoots[i], namespace) { return fmt.Errorf("row verification failed: row %d doesn't match original root: %s", i, root.String()) } } @@ -80,17 +77,18 @@ func (ns NamespacedShares) Verify(root *Root, nID namespace.ID) error { } // verify validates the row using nmt inclusion proof. -func (row *NamespacedRow) verify(rowRoot []byte, nID namespace.ID) bool { +func (row *NamespacedRow) verify(rowRoot []byte, namespace Namespace) bool { // construct nmt leaves from shares by prepending namespace leaves := make([][]byte, 0, len(row.Shares)) - for _, sh := range row.Shares { - leaves = append(leaves, append(sh[:NamespaceSize], sh...)) + for _, shr := range row.Shares { + leaves = append(leaves, append(GetNamespace(shr), shr...)) } // verify namespace return row.Proof.VerifyNamespace( sha256.New(), - nID, + namespace.ToNMT(), leaves, - rowRoot) + rowRoot, + ) } diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 01540de926..fe99bed4c3 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -2,13 +2,11 @@ package getters import ( "context" - "encoding/hex" "errors" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" @@ -67,16 +65,16 @@ func (cg *CascadeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d. func (cg *CascadeGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, - id namespace.ID, + namespace share.Namespace, ) (share.NamespacedShares, error) { ctx, span := tracer.Start(ctx, "cascade/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", hex.EncodeToString(id)), + attribute.String("namespace", namespace.String()), )) defer span.End() get := func(ctx context.Context, get share.Getter) (share.NamespacedShares, error) { - return get.GetSharesByNamespace(ctx, root, id) + return get.GetSharesByNamespace(ctx, root, namespace) } return cascadeGetters(ctx, cg.getters, get) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 20b7e25191..7af8af2f26 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -14,12 +14,14 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestTeeGetter(t *testing.T) { @@ -39,8 +41,8 @@ func TestTeeGetter(t *testing.T) { tg := NewTeeGetter(ig, edsStore) t.Run("TeesToEDSStore", func(t *testing.T) { - eds, dah := randomEDS(t) - _, err := share.ImportShares(ctx, eds.Flattened(), bServ) + randEds, dah := randomEDS(t) + _, err := ipld.ImportShares(ctx, randEds.Flattened(), bServ) require.NoError(t, err) // eds store doesn't have the EDS yet @@ -50,7 +52,7 @@ func TestTeeGetter(t *testing.T) { retrievedEDS, err := tg.GetEDS(ctx, &dah) require.NoError(t, err) - require.True(t, share.EqualEDS(eds, retrievedEDS)) + require.True(t, share.EqualEDS(randEds, retrievedEDS)) // eds store now has the EDS and it can be retrieved ok, err = edsStore.Has(ctx, dah.Hash()) @@ -58,22 +60,22 @@ func TestTeeGetter(t *testing.T) { assert.NoError(t, err) finalEDS, err := edsStore.Get(ctx, dah.Hash()) assert.NoError(t, err) - require.True(t, share.EqualEDS(eds, finalEDS)) + require.True(t, share.EqualEDS(randEds, finalEDS)) }) t.Run("ShardAlreadyExistsDoesntError", func(t *testing.T) { - eds, dah := randomEDS(t) - _, err := share.ImportShares(ctx, eds.Flattened(), bServ) + randEds, dah := randomEDS(t) + _, err := ipld.ImportShares(ctx, randEds.Flattened(), bServ) require.NoError(t, err) retrievedEDS, err := tg.GetEDS(ctx, &dah) require.NoError(t, err) - require.True(t, share.EqualEDS(eds, retrievedEDS)) + require.True(t, share.EqualEDS(randEds, retrievedEDS)) // no error should be returned, even though the EDS identified by the DAH already exists retrievedEDS, err = tg.GetEDS(ctx, &dah) require.NoError(t, err) - require.True(t, share.EqualEDS(eds, retrievedEDS)) + require.True(t, share.EqualEDS(randEds, retrievedEDS)) }) } @@ -92,16 +94,16 @@ func TestStoreGetter(t *testing.T) { sg := NewStoreGetter(edsStore) t.Run("GetShare", func(t *testing.T) { - eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), eds) + randEds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) - squareSize := int(eds.Width()) + squareSize := int(randEds.Width()) for i := 0; i < squareSize; i++ { for j := 0; j < squareSize; j++ { share, err := sg.GetShare(ctx, &dah, i, j) require.NoError(t, err) - assert.Equal(t, eds.GetCell(uint(i), uint(j)), share) + assert.Equal(t, randEds.GetCell(uint(i), uint(j)), share) } } @@ -112,13 +114,13 @@ func TestStoreGetter(t *testing.T) { }) t.Run("GetEDS", func(t *testing.T) { - eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), eds) + randEds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) retrievedEDS, err := sg.GetEDS(ctx, &dah) require.NoError(t, err) - assert.True(t, share.EqualEDS(eds, retrievedEDS)) + assert.True(t, share.EqualEDS(randEds, retrievedEDS)) // root not found root := share.Root{} @@ -127,24 +129,24 @@ func TestStoreGetter(t *testing.T) { }) t.Run("GetSharesByNamespace", func(t *testing.T) { - eds, nID, dah := randomEDSWithDoubledNamespace(t, 4) - err = edsStore.Put(ctx, dah.Hash(), eds) + randEds, namespace, dah := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) - shares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + shares, err := sg.GetSharesByNamespace(ctx, &dah, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(&dah, nID)) + require.NoError(t, shares.Verify(&dah, namespace)) assert.Len(t, shares.Flatten(), 2) - // nid not found - nID = make([]byte, namespace.NamespaceSize) - emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + // namespace not found + randNamespace := sharetest.RandV0Namespace() + emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, randNamespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) // root not found root := share.Root{} - _, err = sg.GetSharesByNamespace(ctx, &root, nID) + _, err = sg.GetSharesByNamespace(ctx, &root, namespace) require.ErrorIs(t, err, share.ErrNotFound) }) } @@ -168,16 +170,16 @@ func TestIPLDGetter(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), eds) + randEds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) - squareSize := int(eds.Width()) + squareSize := int(randEds.Width()) for i := 0; i < squareSize; i++ { for j := 0; j < squareSize; j++ { share, err := sg.GetShare(ctx, &dah, i, j) require.NoError(t, err) - assert.Equal(t, eds.GetCell(uint(i), uint(j)), share) + assert.Equal(t, randEds.GetCell(uint(i), uint(j)), share) } } @@ -191,47 +193,46 @@ func TestIPLDGetter(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), eds) + randEds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) retrievedEDS, err := sg.GetEDS(ctx, &dah) require.NoError(t, err) - assert.True(t, share.EqualEDS(eds, retrievedEDS)) + assert.True(t, share.EqualEDS(randEds, retrievedEDS)) }) t.Run("GetSharesByNamespace", func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - eds, nID, dah := randomEDSWithDoubledNamespace(t, 4) - err = edsStore.Put(ctx, dah.Hash(), eds) + randEds, namespace, dah := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) // first check that shares are returned correctly if they exist - shares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + shares, err := sg.GetSharesByNamespace(ctx, &dah, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(&dah, nID)) + require.NoError(t, shares.Verify(&dah, namespace)) assert.Len(t, shares.Flatten(), 2) - // nid not found - nID = make([]byte, namespace.NamespaceSize) - emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, nID) + // namespace not found + randNamespace := sharetest.RandV0Namespace() + emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, randNamespace) require.NoError(t, err) - require.Nil(t, emptyShares) + require.Empty(t, emptyShares.Flatten()) // nid doesnt exist in root root := share.Root{} - emptyShares, err = sg.GetSharesByNamespace(ctx, &root, nID) + emptyShares, err = sg.GetSharesByNamespace(ctx, &root, namespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) }) } func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) - return eds, dah } @@ -239,7 +240,7 @@ func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { // middle that share a namespace. func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedDataSquare, []byte, share.Root) { n := size * size - randShares := share.RandShares(t, n) + randShares := sharetest.RandShares(t, n) idx1 := (n - 1) / 2 idx2 := n / 2 @@ -251,7 +252,7 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData // D _ _ _ // _ _ _ _ // where the D shares have a common namespace. - copy(randShares[idx2][:share.NamespaceSize], randShares[idx1][:share.NamespaceSize]) + copy(share.GetNamespace(randShares[idx2]), share.GetNamespace(randShares[idx1])) eds, err := rsmt2d.ComputeExtendedDataSquare( randShares, @@ -261,5 +262,5 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData require.NoError(t, err, "failure to recompute the extended data square") dah := da.NewDataAvailabilityHeader(eds) - return eds, randShares[idx1][:share.NamespaceSize], dah + return eds, share.GetNamespace(randShares[idx1]), dah } diff --git a/share/getters/ipld.go b/share/getters/ipld.go index 04a1f4f728..92d1c55678 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -2,7 +2,6 @@ package getters import ( "context" - "encoding/hex" "errors" "fmt" "sync" @@ -12,7 +11,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" @@ -55,7 +53,7 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots)) + s, err := ipld.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots)) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound @@ -90,24 +88,23 @@ func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d func (ig *IPLDGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, - nID namespace.ID, + namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "ipld/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", hex.EncodeToString(nID)), + attribute.String("namespace", namespace.String()), )) defer func() { utils.SetStatusAndEnd(span, err) }() - err = verifyNIDSize(nID) - if err != nil { - return nil, fmt.Errorf("getter/ipld: invalid namespace ID: %w", err) + if err = namespace.ValidateForData(); err != nil { + return nil, err } // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) + shares, err = collectSharesByNamespace(ctx, blockGetter, root, namespace) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 08006073d6..bb5e7ca7a7 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -2,7 +2,6 @@ package getters import ( "context" - "encoding/hex" "errors" "fmt" "time" @@ -14,7 +13,6 @@ import ( "go.opentelemetry.io/otel/metric/unit" "go.opentelemetry.io/otel/trace" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" @@ -190,22 +188,25 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex func (sg *ShrexGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, - id namespace.ID, + namespace share.Namespace, ) (share.NamespacedShares, error) { + if err := namespace.ValidateForData(); err != nil { + return nil, err + } var ( attempt int err error ) ctx, span := tracer.Start(ctx, "shrex/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", hex.EncodeToString(id)), + attribute.String("namespace", namespace.String()), )) defer func() { utils.SetStatusAndEnd(span, err) }() // verify that the namespace could exist inside the roots before starting network requests - roots := filterRootsByNamespace(root, id) + roots := filterRootsByNamespace(root, namespace) if len(roots) == 0 { return nil, nil } @@ -221,7 +222,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( if getErr != nil { log.Debugw("nd: couldn't find peer", "hash", root.String(), - "nid", hex.EncodeToString(id), + "namespace", namespace.String(), "err", getErr, "finished (s)", time.Since(start)) sg.metrics.recordNDAttempt(ctx, attempt, false) @@ -230,12 +231,12 @@ func (sg *ShrexGetter) GetSharesByNamespace( reqStart := time.Now() reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) - nd, getErr := sg.ndClient.RequestND(reqCtx, root, id, peer) + nd, getErr := sg.ndClient.RequestND(reqCtx, root, namespace, peer) cancel() switch { case getErr == nil: // both inclusion and non-inclusion cases needs verification - if verErr := nd.Verify(root, id); verErr != nil { + if verErr := nd.Verify(root, namespace); verErr != nil { getErr = verErr setStatus(peers.ResultBlacklistPeer) break @@ -260,7 +261,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( } log.Debugw("nd: request failed", "hash", root.String(), - "nid", hex.EncodeToString(id), + "namespace", namespace.String(), "peer", peer.String(), "attempt", attempt, "err", getErr, diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 72ac78e69b..e1628d8725 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -17,14 +17,13 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/nmt" - nmtnamespace "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" @@ -62,16 +61,16 @@ func TestShrexGetter(t *testing.T) { t.Cleanup(cancel) // generate test data - eds, dah, nID := generateTestEDS(t) - require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + randEDS, dah, namespace := generateTestEDS(t) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), randEDS)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - got, err := getter.GetSharesByNamespace(ctx, &dah, nID) + got, err := getter.GetSharesByNamespace(ctx, &dah, namespace) require.NoError(t, err) - require.NoError(t, got.Verify(&dah, nID)) + require.NoError(t, got.Verify(&dah, namespace)) }) t.Run("ND_err_not_found", func(t *testing.T) { @@ -79,13 +78,13 @@ func TestShrexGetter(t *testing.T) { t.Cleanup(cancel) // generate test data - _, dah, nID := generateTestEDS(t) + _, dah, namespace := generateTestEDS(t) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - _, err := getter.GetSharesByNamespace(ctx, &dah, nID) + _, err := getter.GetSharesByNamespace(ctx, &dah, namespace) require.ErrorIs(t, err, share.ErrNotFound) }) @@ -94,7 +93,7 @@ func TestShrexGetter(t *testing.T) { t.Cleanup(cancel) // generate test data - eds, dah, maxNID := generateTestEDS(t) + eds, dah, maxNamespace := generateTestEDS(t) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), @@ -102,11 +101,11 @@ func TestShrexGetter(t *testing.T) { }) // corrupt NID - nID := make([]byte, ipld.NamespaceSize) - copy(nID, maxNID) - nID[ipld.NamespaceSize-1]-- // pray for last byte to not be 0x00 + nID := make([]byte, share.NamespaceSize) + copy(nID, maxNamespace) + nID[share.NamespaceSize-1]-- // check for namespace to be between max and min namespace in root - require.Len(t, filterRootsByNamespace(&dah, maxNID), 1) + require.Len(t, filterRootsByNamespace(&dah, maxNamespace), 1) emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, nID) require.NoError(t, err) @@ -120,25 +119,25 @@ func TestShrexGetter(t *testing.T) { t.Cleanup(cancel) // generate test data - eds, dah, maxNID := generateTestEDS(t) + eds, dah, maxNamesapce := generateTestEDS(t) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - // corrupt NID - nID := make([]byte, ipld.NamespaceSize) - copy(nID, maxNID) - nID[ipld.NamespaceSize-1]++ // pray for last byte to not be 0xFF + // corrupt namespace + namespace := make([]byte, share.NamespaceSize) + copy(namespace, maxNamesapce) + namespace[share.NamespaceSize-1]++ // check for namespace to be not in root - require.Len(t, filterRootsByNamespace(&dah, nID), 0) + require.Len(t, filterRootsByNamespace(&dah, namespace), 0) - emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, nID) + emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, namespace) require.NoError(t, err) // no shares should be returned require.Empty(t, emptyShares.Flatten()) - require.Nil(t, emptyShares.Verify(&dah, nID)) + require.Nil(t, emptyShares.Verify(&dah, namespace)) }) t.Run("EDS_Available", func(t *testing.T) { @@ -146,8 +145,8 @@ func TestShrexGetter(t *testing.T) { t.Cleanup(cancel) // generate test data - eds, dah, _ := generateTestEDS(t) - require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) + randEDS, dah, _ := generateTestEDS(t) + require.NoError(t, edsStore.Put(ctx, dah.Hash(), randEDS)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, @@ -155,7 +154,7 @@ func TestShrexGetter(t *testing.T) { got, err := getter.GetEDS(ctx, &dah) require.NoError(t, err) - require.Equal(t, eds.Flattened(), got.Flattened()) + require.Equal(t, randEDS.Flattened(), got.Flattened()) }) t.Run("EDS_ctx_deadline", func(t *testing.T) { @@ -197,10 +196,10 @@ func newStore(t *testing.T) (*eds.Store, error) { return eds.NewStore(tmpDir, ds) } -func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, nmtnamespace.ID) { - eds := share.RandEDS(t, 4) +func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, share.Namespace) { + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) - max := nmt.MaxNamespace(dah.RowRoots[(len(dah.RowRoots))/2-1], ipld.NamespaceSize) + max := nmt.MaxNamespace(dah.RowRoots[(len(dah.RowRoots))/2-1], share.NamespaceSize) return eds, dah, max } diff --git a/share/getters/store.go b/share/getters/store.go index f39f41fc80..0b88d89155 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -2,14 +2,12 @@ package getters import ( "context" - "encoding/hex" "errors" "fmt" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" @@ -58,7 +56,7 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) - s, err := share.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots)) + s, err := ipld.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots)) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound @@ -95,19 +93,18 @@ func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (data *rsmt func (sg *StoreGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, - nID namespace.ID, + namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "store/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", hex.EncodeToString(nID)), + attribute.String("namespace", namespace.String()), )) defer func() { utils.SetStatusAndEnd(span, err) }() - err = verifyNIDSize(nID) - if err != nil { - return nil, fmt.Errorf("getter/store: invalid namespace ID: %w", err) + if err = namespace.ValidateForData(); err != nil { + return nil, err } bs, err := sg.store.CARBlockstore(ctx, root.Hash()) @@ -121,7 +118,7 @@ func (sg *StoreGetter) GetSharesByNamespace( // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) - shares, err = collectSharesByNamespace(ctx, blockGetter, root, nID) + shares, err = collectSharesByNamespace(ctx, blockGetter, root, namespace) if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) } diff --git a/share/getters/tee.go b/share/getters/tee.go index 50e2e1b55d..213f05f90c 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -2,7 +2,6 @@ package getters import ( "context" - "encoding/hex" "errors" "fmt" @@ -10,7 +9,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" @@ -72,15 +70,15 @@ func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d. func (tg *TeeGetter) GetSharesByNamespace( ctx context.Context, root *share.Root, - id namespace.ID, + namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "tee/get-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", hex.EncodeToString(id)), + attribute.String("namespace", namespace.String()), )) defer func() { utils.SetStatusAndEnd(span, err) }() - return tg.getter.GetSharesByNamespace(ctx, root, id) + return tg.getter.GetSharesByNamespace(ctx, root, namespace) } diff --git a/share/getters/testing.go b/share/getters/testing.go index a90b937a51..4734557d7f 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -6,15 +6,15 @@ import ( "testing" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" ) // TestGetter provides a testing SingleEDSGetter and the root of the EDS it holds. func TestGetter(t *testing.T) (share.Getter, *share.Root) { - eds := share.RandEDS(t, 8) + eds := edstest.RandEDS(t, 8) dah := da.NewDataAvailabilityHeader(eds) return &SingleEDSGetter{ EDS: eds, @@ -46,7 +46,7 @@ func (seg *SingleEDSGetter) GetEDS(_ context.Context, root *share.Root) (*rsmt2d } // GetSharesByNamespace returns NamespacedShares from a kept EDS if the correct root is given. -func (seg *SingleEDSGetter) GetSharesByNamespace(context.Context, *share.Root, namespace.ID, +func (seg *SingleEDSGetter) GetSharesByNamespace(context.Context, *share.Root, share.Namespace, ) (share.NamespacedShares, error) { panic("SingleEDSGetter: GetSharesByNamespace is not implemented") } diff --git a/share/getters/utils.go b/share/getters/utils.go index 79438204bd..94b1cfe5ee 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -2,7 +2,6 @@ package getters import ( "context" - "encoding/hex" "errors" "fmt" "time" @@ -15,8 +14,6 @@ import ( "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" @@ -30,34 +27,33 @@ var ( ) // filterRootsByNamespace returns the row roots from the given share.Root that contain the passed -// namespace ID. -func filterRootsByNamespace(root *share.Root, nID namespace.ID) []cid.Cid { +// namespace. +func filterRootsByNamespace(root *share.Root, namespace share.Namespace) []cid.Cid { rowRootCIDs := make([]cid.Cid, 0, len(root.RowRoots)) for _, row := range root.RowRoots { - if !ipld.NamespaceIsOutsideRange(row, row, nID) { + if !namespace.IsOutsideRange(row, row) { rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) } } return rowRootCIDs } -// collectSharesByNamespace collects NamespaceShares within the given namespace ID from the given -// share.Root. +// collectSharesByNamespace collects NamespaceShares within the given namespace from share.Root. func collectSharesByNamespace( ctx context.Context, bg blockservice.BlockGetter, root *share.Root, - nID namespace.ID, + namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "collect-shares-by-namespace", trace.WithAttributes( attribute.String("root", root.String()), - attribute.String("nid", hex.EncodeToString(nID)), + attribute.String("namespace", namespace.String()), )) defer func() { utils.SetStatusAndEnd(span, err) }() - rootCIDs := filterRootsByNamespace(root, nID) + rootCIDs := filterRootsByNamespace(root, namespace) if len(rootCIDs) == 0 { return nil, nil } @@ -68,13 +64,13 @@ func collectSharesByNamespace( // shadow loop variables, to ensure correct values are captured i, rootCID := i, rootCID errGroup.Go(func() error { - row, proof, err := share.GetSharesByNamespace(ctx, bg, rootCID, nID, len(root.RowRoots)) + row, proof, err := ipld.GetSharesByNamespace(ctx, bg, rootCID, namespace, len(root.RowRoots)) shares[i] = share.NamespacedRow{ Shares: row, Proof: proof, } if err != nil { - return fmt.Errorf("retrieving nID %x for row %x: %w", nID, rootCID, err) + return fmt.Errorf("retrieving shares by namespace %s for row %x: %w", namespace.String(), rootCID, err) } return nil }) @@ -83,15 +79,8 @@ func collectSharesByNamespace( if err := errGroup.Wait(); err != nil { return nil, err } - return shares, nil -} -func verifyNIDSize(nID namespace.ID) error { - if len(nID) != share.NamespaceSize { - return fmt.Errorf("expected namespace ID of size %d, got %d", - share.NamespaceSize, len(nID)) - } - return nil + return shares, nil } // ctxWithSplitTimeout will split timeout stored in context by splitFactor and return the result if diff --git a/share/helpers.go b/share/helpers.go new file mode 100644 index 0000000000..0d729fd9b2 --- /dev/null +++ b/share/helpers.go @@ -0,0 +1,58 @@ +package share + +import ( + "bytes" + + "github.com/celestiaorg/rsmt2d" +) + +// TODO(Wondertan): All these helpers should be methods on rsmt2d.EDS + +// ExtractODS returns the original shares of the given ExtendedDataSquare. This +// is a helper function for circumstances where AddShares must be used after the EDS has already +// been generated. +func ExtractODS(eds *rsmt2d.ExtendedDataSquare) []Share { + origWidth := eds.Width() / 2 + origShares := make([][]byte, origWidth*origWidth) + for i := uint(0); i < origWidth; i++ { + row := eds.Row(i) + for j := uint(0); j < origWidth; j++ { + origShares[(i*origWidth)+j] = row[j] + } + } + return origShares +} + +// ExtractEDS takes an EDS and extracts all shares from it in a flattened slice(row by row). +func ExtractEDS(eds *rsmt2d.ExtendedDataSquare) []Share { + flattenedEDSSize := eds.Width() * eds.Width() + out := make([][]byte, flattenedEDSSize) + count := 0 + for i := uint(0); i < eds.Width(); i++ { + for _, share := range eds.Row(i) { + out[count] = share + count++ + } + } + return out +} + +// EqualEDS check whether two given EDSes are equal. +// TODO(Wondertan): Propose use of int by default instead of uint for the sake convenience and +// Golang practices +func EqualEDS(a *rsmt2d.ExtendedDataSquare, b *rsmt2d.ExtendedDataSquare) bool { + if a.Width() != b.Width() { + return false + } + + for i := uint(0); i < a.Width(); i++ { + ar, br := a.Row(i), b.Row(i) + for j := 0; j < len(ar); j++ { + if !bytes.Equal(ar[j], br[j]) { + return false + } + } + } + + return true +} diff --git a/share/add.go b/share/ipld/add.go similarity index 61% rename from share/add.go rename to share/ipld/add.go index 02016cadf6..fddfe8e99e 100644 --- a/share/add.go +++ b/share/ipld/add.go @@ -1,4 +1,4 @@ -package share +package ipld import ( "context" @@ -11,14 +11,14 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" - "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share" ) // AddShares erasures and extends shares to blockservice.BlockService using the provided // ipld.NodeAdder. func AddShares( ctx context.Context, - shares []Share, + shares []share.Share, adder blockservice.BlockService, ) (*rsmt2d.ExtendedDataSquare, error) { if len(shares) == 0 { @@ -26,12 +26,12 @@ func AddShares( } squareSize := int(utils.SquareSize(len(shares))) // create nmt adder wrapping batch adder with calculated size - batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) + batchAdder := NewNmtNodeAdder(ctx, adder, MaxSizeBatchOption(squareSize*2)) // create the nmt wrapper to generate row and col commitments // recompute the eds eds, err := rsmt2d.ComputeExtendedDataSquare( shares, - DefaultRSMT2DCodec(), + share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(squareSize), nmt.NodeVisitor(batchAdder.Visit)), ) @@ -55,11 +55,11 @@ func ImportShares( } squareSize := int(utils.SquareSize(len(shares))) // create nmt adder wrapping batch adder with calculated size - batchAdder := ipld.NewNmtNodeAdder(ctx, adder, ipld.MaxSizeBatchOption(squareSize*2)) + batchAdder := NewNmtNodeAdder(ctx, adder, MaxSizeBatchOption(squareSize*2)) // recompute the eds eds, err := rsmt2d.ImportExtendedDataSquare( shares, - DefaultRSMT2DCodec(), + share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(squareSize/2), nmt.NodeVisitor(batchAdder.Visit)), ) @@ -72,31 +72,8 @@ func ImportShares( return eds, batchAdder.Commit() } -// ExtractODS returns the original shares of the given ExtendedDataSquare. This -// is a helper function for circumstances where AddShares must be used after the EDS has already -// been generated. -func ExtractODS(eds *rsmt2d.ExtendedDataSquare) []Share { - origWidth := eds.Width() / 2 - origShares := make([][]byte, origWidth*origWidth) - for i := uint(0); i < origWidth; i++ { - row := eds.Row(i) - for j := uint(0); j < origWidth; j++ { - origShares[(i*origWidth)+j] = row[j] - } - } - return origShares -} - -// ExtractEDS takes an EDS and extracts all shares from it in a flattened slice(row by row). -func ExtractEDS(eds *rsmt2d.ExtendedDataSquare) []Share { - flattenedEDSSize := eds.Width() * eds.Width() - out := make([][]byte, flattenedEDSSize) - count := 0 - for i := uint(0); i < eds.Width(); i++ { - for _, share := range eds.Row(i) { - out[count] = share - count++ - } - } - return out +func ImportEDS(ctx context.Context, square *rsmt2d.ExtendedDataSquare, adder blockservice.BlockService) error { + shares := share.ExtractEDS(square) + _, err := ImportShares(ctx, shares, adder) + return err } diff --git a/share/ipld/get.go b/share/ipld/get.go index 6d7de35c27..35f601853d 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -12,6 +12,8 @@ import ( ipld "github.com/ipfs/go-ipld-format" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + + "github.com/celestiaorg/celestia-node/share" ) // NumWorkersLimit sets global limit for workers spawned by GetShares. @@ -26,7 +28,7 @@ import ( // // TODO(@Wondertan): This assumes we have parallelized DASer implemented. Sync the values once it is shipped. // TODO(@Wondertan): Allow configuration of values without global state. -var NumWorkersLimit = MaxSquareSize * MaxSquareSize / 2 * NumConcurrentSquares +var NumWorkersLimit = share.MaxSquareSize * share.MaxSquareSize / 2 * NumConcurrentSquares // NumConcurrentSquares limits the amount of squares that are fetched // concurrently/simultaneously. diff --git a/share/get.go b/share/ipld/get_shares.go similarity index 69% rename from share/get.go rename to share/ipld/get_shares.go index 9ec4c3c2c5..0bed240fdc 100644 --- a/share/get.go +++ b/share/ipld/get_shares.go @@ -1,4 +1,4 @@ -package share +package ipld import ( "context" @@ -8,9 +8,8 @@ import ( format "github.com/ipfs/go-ipld-format" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" - "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share" ) // GetShare fetches and returns the data for leaf `leafIndex` of root `rootCid`. @@ -20,8 +19,8 @@ func GetShare( rootCid cid.Cid, leafIndex int, totalLeafs int, // this corresponds to the extended square width -) (Share, error) { - nd, err := ipld.GetLeaf(ctx, bGetter, rootCid, leafIndex, totalLeafs) +) (share.Share, error) { + nd, err := GetLeaf(ctx, bGetter, rootCid, leafIndex, totalLeafs) if err != nil { return nil, err } @@ -32,30 +31,30 @@ func GetShare( // GetShares walks the tree of a given root and puts shares into the given 'put' func. // Does not return any error, and returns/unblocks only on success // (got all shares) or on context cancellation. -func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, shares int, put func(int, Share)) { +func GetShares(ctx context.Context, bg blockservice.BlockGetter, root cid.Cid, shares int, put func(int, share.Share)) { ctx, span := tracer.Start(ctx, "get-shares") defer span.End() putNode := func(i int, leaf format.Node) { put(i, leafToShare(leaf)) } - ipld.GetLeaves(ctx, bGetter, root, shares, putNode) + GetLeaves(ctx, bg, root, shares, putNode) } // GetSharesByNamespace walks the tree of a given root and returns its shares within the given -// namespace.ID. If a share could not be retrieved, err is not nil, and the returned array +// Namespace. If a share could not be retrieved, err is not nil, and the returned array // contains nil shares in place of the shares it was unable to retrieve. func GetSharesByNamespace( ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, - nID namespace.ID, + namespace share.Namespace, maxShares int, -) ([]Share, *nmt.Proof, error) { +) ([]share.Share, *nmt.Proof, error) { ctx, span := tracer.Start(ctx, "get-shares-by-namespace") defer span.End() - data := ipld.NewNamespaceData(maxShares, nID, ipld.WithLeaves(), ipld.WithProofs()) + data := NewNamespaceData(maxShares, namespace, WithLeaves(), WithProofs()) err := data.CollectLeavesByNamespace(ctx, bGetter, root) if err != nil { return nil, nil, err @@ -63,7 +62,7 @@ func GetSharesByNamespace( leaves := data.Leaves() - shares := make([]Share, len(leaves)) + shares := make([]share.Share, len(leaves)) for i, leaf := range leaves { if leaf != nil { shares[i] = leafToShare(leaf) @@ -73,8 +72,8 @@ func GetSharesByNamespace( } // leafToShare converts an NMT leaf into a Share. -func leafToShare(nd format.Node) Share { +func leafToShare(nd format.Node) share.Share { // * Additional namespace is prepended so that parity data can be identified with a parity // namespace, which we cut off - return nd.RawData()[NamespaceSize:] + return share.GetData(nd.RawData()) } diff --git a/share/get_test.go b/share/ipld/get_shares_test.go similarity index 64% rename from share/get_test.go rename to share/ipld/get_shares_test.go index 1b9df69be3..cd26f759b3 100644 --- a/share/get_test.go +++ b/share/ipld/get_shares_test.go @@ -1,9 +1,8 @@ -package share +package ipld import ( "bytes" "context" - "crypto/rand" "errors" mrand "math/rand" "sort" @@ -23,11 +22,12 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/libs/utils" - "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestGetShare(t *testing.T) { @@ -38,14 +38,14 @@ func TestGetShare(t *testing.T) { bServ := mdutils.Bserv() // generate random shares for the nmt - shares := RandShares(t, size*size) + shares := sharetest.RandShares(t, size*size) eds, err := AddShares(ctx, shares, bServ) require.NoError(t, err) for i, leaf := range shares { row := i / size pos := i - (size * row) - share, err := GetShare(ctx, bServ, ipld.MustCidFromNamespacedSha256(eds.RowRoots()[row]), pos, size*2) + share, err := GetShare(ctx, bServ, MustCidFromNamespacedSha256(eds.RowRoots()[row]), pos, size*2) require.NoError(t, err) assert.Equal(t, leaf, share) } @@ -58,12 +58,12 @@ func TestBlockRecovery(t *testing.T) { extendedShareCount := extendedSquareWidth * extendedSquareWidth // generate test data - quarterShares := RandShares(t, shareCount) - allShares := RandShares(t, shareCount) + quarterShares := sharetest.RandShares(t, shareCount) + allShares := sharetest.RandShares(t, shareCount) testCases := []struct { name string - shares []Share + shares []share.Share expectErr bool errString string d int // number of shares to delete @@ -79,25 +79,29 @@ func TestBlockRecovery(t *testing.T) { t.Run(tc.name, func(t *testing.T) { squareSize := utils.SquareSize(len(tc.shares)) - eds, err := rsmt2d.ComputeExtendedDataSquare(tc.shares, DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize)) + testEds, err := rsmt2d.ComputeExtendedDataSquare( + tc.shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(squareSize), + ) require.NoError(t, err) // calculate roots using the first complete square - rowRoots := eds.RowRoots() - colRoots := eds.ColRoots() + rowRoots := testEds.RowRoots() + colRoots := testEds.ColRoots() - flat := ExtractEDS(eds) + flat := share.ExtractEDS(testEds) // recover a partially complete square rdata := removeRandShares(flat, tc.d) - eds, err = rsmt2d.ImportExtendedDataSquare( + testEds, err = rsmt2d.ImportExtendedDataSquare( rdata, - DefaultRSMT2DCodec(), + share.DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize), ) require.NoError(t, err) - err = eds.Repair(rowRoots, colRoots) + err = testEds.Repair(rowRoots, colRoots) if tc.expectErr { require.Error(t, err) require.Contains(t, err.Error(), tc.errString) @@ -105,27 +109,27 @@ func TestBlockRecovery(t *testing.T) { } assert.NoError(t, err) - reds, err := rsmt2d.ImportExtendedDataSquare(rdata, DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize)) + reds, err := rsmt2d.ImportExtendedDataSquare(rdata, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize)) require.NoError(t, err) // check that the squares are equal - assert.Equal(t, ExtractEDS(eds), ExtractEDS(reds)) + assert.Equal(t, share.ExtractEDS(testEds), share.ExtractEDS(reds)) }) } } func Test_ConvertEDStoShares(t *testing.T) { squareWidth := 16 - shares := RandShares(t, squareWidth*squareWidth) + shares := sharetest.RandShares(t, squareWidth*squareWidth) // compute extended square - eds, err := rsmt2d.ComputeExtendedDataSquare( + testEds, err := rsmt2d.ComputeExtendedDataSquare( shares, - DefaultRSMT2DCodec(), + share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(squareWidth)), ) require.NoError(t, err) - resshares := ExtractODS(eds) + resshares := share.ExtractODS(testEds) require.Equal(t, shares, resshares) } @@ -150,29 +154,29 @@ func TestGetSharesByNamespace(t *testing.T) { bServ := mdutils.Bserv() var tests = []struct { - rawData []Share + rawData []share.Share }{ - {rawData: RandShares(t, 4)}, - {rawData: RandShares(t, 16)}, + {rawData: sharetest.RandShares(t, 4)}, + {rawData: sharetest.RandShares(t, 16)}, } for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - // choose random nID from rand shares + // choose random namespace from rand shares expected := tt.rawData[len(tt.rawData)/2] - nID := expected[:NamespaceSize] + namespace := share.GetNamespace(expected) - // change rawData to contain several shares with same nID + // change rawData to contain several shares with same namespace tt.rawData[(len(tt.rawData)/2)+1] = expected // put raw data in BlockService eds, err := AddShares(ctx, tt.rawData, bServ) require.NoError(t, err) - var shares []Share + var shares []share.Share for _, row := range eds.RowRoots() { - rcid := ipld.MustCidFromNamespacedSha256(row) - rowShares, _, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) - if errors.Is(err, ipld.ErrNamespaceOutsideRange) { + rcid := MustCidFromNamespacedSha256(row) + rowShares, _, err := GetSharesByNamespace(ctx, bServ, rcid, namespace, len(eds.RowRoots())) + if errors.Is(err, ErrNamespaceOutsideRange) { continue } require.NoError(t, err) @@ -193,13 +197,12 @@ func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { t.Cleanup(cancel) bServ := mdutils.Bserv() - shares := RandShares(t, 16) + shares := sharetest.RandShares(t, 16) // set all shares to the same namespace id - nid := shares[0][:NamespaceSize] - - for _, nspace := range shares { - copy(nspace[:NamespaceSize], nid) + namespace := share.GetNamespace(shares[0]) + for _, shr := range shares { + copy(share.GetNamespace(shr), namespace) } eds, err := AddShares(ctx, shares, bServ) @@ -208,28 +211,28 @@ func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { roots := eds.RowRoots() // remove the second share from the first row - rcid := ipld.MustCidFromNamespacedSha256(roots[0]) - node, err := ipld.GetNode(ctx, bServ, rcid) + rcid := MustCidFromNamespacedSha256(roots[0]) + node, err := GetNode(ctx, bServ, rcid) require.NoError(t, err) // Left side of the tree contains the original shares - data, err := ipld.GetNode(ctx, bServ, node.Links()[0].Cid) + data, err := GetNode(ctx, bServ, node.Links()[0].Cid) require.NoError(t, err) // Second share is the left side's right child - l, err := ipld.GetNode(ctx, bServ, data.Links()[0].Cid) + l, err := GetNode(ctx, bServ, data.Links()[0].Cid) require.NoError(t, err) - r, err := ipld.GetNode(ctx, bServ, l.Links()[1].Cid) + r, err := GetNode(ctx, bServ, l.Links()[1].Cid) require.NoError(t, err) err = bServ.DeleteBlock(ctx, r.Cid()) require.NoError(t, err) - namespaceData := ipld.NewNamespaceData(len(shares), nid, ipld.WithLeaves()) + namespaceData := NewNamespaceData(len(shares), namespace, WithLeaves()) err = namespaceData.CollectLeavesByNamespace(ctx, bServ, rcid) + require.Error(t, err) leaves := namespaceData.Leaves() assert.Nil(t, leaves[1]) assert.Equal(t, 4, len(leaves)) - require.Error(t, err) } func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { @@ -237,42 +240,42 @@ func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { t.Cleanup(cancel) bServ := mdutils.Bserv() - shares := RandShares(t, 1024) + shares := sharetest.RandShares(t, 1024) - // set all shares to the same namespace id - nids, err := randomNids(5) + // set all shares to the same namespace + namespaces, err := randomNamespaces(5) require.NoError(t, err) - minNid := nids[0] - minIncluded := nids[1] - midNid := nids[2] - maxIncluded := nids[3] - maxNid := nids[4] + minNamespace := namespaces[0] + minIncluded := namespaces[1] + midNamespace := namespaces[2] + maxIncluded := namespaces[3] + maxNamespace := namespaces[4] secondNamespaceFrom := mrand.Intn(len(shares)-2) + 1 - for i, nspace := range shares { + for i, shr := range shares { if i < secondNamespaceFrom { - copy(nspace[:NamespaceSize], minIncluded) + copy(share.GetNamespace(shr), minIncluded) continue } - copy(nspace[:NamespaceSize], maxIncluded) + copy(share.GetNamespace(shr), maxIncluded) } var tests = []struct { - name string - data []Share - missingNid []byte - isAbsence bool + name string + data []share.Share + missingNamespace share.Namespace + isAbsence bool }{ - {name: "Namespace id less than the minimum namespace in data", data: shares, missingNid: minNid}, - {name: "Namespace id greater than the maximum namespace in data", data: shares, missingNid: maxNid}, - {name: "Namespace id in range but still missing", data: shares, missingNid: midNid, isAbsence: true}, + {name: "Namespace less than the minimum namespace in data", data: shares, missingNamespace: minNamespace}, + {name: "Namespace greater than the maximum namespace in data", data: shares, missingNamespace: maxNamespace}, + {name: "Namespace in range but still missing", data: shares, missingNamespace: midNamespace, isAbsence: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { eds, err := AddShares(ctx, shares, bServ) require.NoError(t, err) - assertNoRowContainsNID(ctx, t, bServ, eds, tt.missingNid, tt.isAbsence) + assertNoRowContainsNID(ctx, t, bServ, eds, tt.missingNamespace, tt.isAbsence) }) } } @@ -282,10 +285,10 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi t.Cleanup(cancel) bServ := mdutils.Bserv() - shares := RandShares(t, 16) + shares := sharetest.RandShares(t, 16) // set all shares to the same namespace and data but the last one - nid := shares[0][:NamespaceSize] + namespace := share.GetNamespace(shares[0]) commonNamespaceData := shares[0] for i, nspace := range shares { @@ -300,10 +303,10 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi require.NoError(t, err) for _, row := range eds.RowRoots() { - rcid := ipld.MustCidFromNamespacedSha256(row) - data := ipld.NewNamespaceData(len(shares), nid, ipld.WithLeaves()) + rcid := MustCidFromNamespacedSha256(row) + data := NewNamespaceData(len(shares), namespace, WithLeaves()) err := data.CollectLeavesByNamespace(ctx, bServ, rcid) - if errors.Is(err, ipld.ErrNamespaceOutsideRange) { + if errors.Is(err, ErrNamespaceOutsideRange) { continue } assert.Nil(t, err) @@ -311,7 +314,7 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi for _, node := range leaves { // test that the data returned by collectLeavesByNamespace for nid // matches the commonNamespaceData that was copied across almost all data - assert.Equal(t, commonNamespaceData, node.RawData()[NamespaceSize:]) + assert.Equal(t, commonNamespaceData, share.GetData(node.RawData())) } } } @@ -322,11 +325,11 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { bServ := mdutils.Bserv() var tests = []struct { - rawData []Share + rawData []share.Share }{ - {rawData: RandShares(t, 4)}, - {rawData: RandShares(t, 16)}, - {rawData: RandShares(t, 64)}, + {rawData: sharetest.RandShares(t, 4)}, + {rawData: sharetest.RandShares(t, 16)}, + {rawData: sharetest.RandShares(t, 64)}, } for i, tt := range tests { @@ -341,9 +344,9 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { } expected := tt.rawData[from] - nID := namespace.ID(expected[:NamespaceSize]) + namespace := share.GetNamespace(expected) - // change rawData to contain several shares with same nID + // change rawData to contain several shares with same namespace for i := from; i <= to; i++ { tt.rawData[i] = expected } @@ -352,12 +355,12 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { eds, err := AddShares(ctx, tt.rawData, bServ) require.NoError(t, err) - var shares []Share + var shares []share.Share for _, row := range eds.RowRoots() { - rcid := ipld.MustCidFromNamespacedSha256(row) - rowShares, proof, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) - if ipld.NamespaceIsOutsideRange(row, row, nID) { - require.ErrorIs(t, err, ipld.ErrNamespaceOutsideRange) + rcid := MustCidFromNamespacedSha256(row) + rowShares, proof, err := GetSharesByNamespace(ctx, bServ, rcid, namespace, len(eds.RowRoots())) + if namespace.IsOutsideRange(row, row) { + require.ErrorIs(t, err, ErrNamespaceOutsideRange) continue } require.NoError(t, err) @@ -368,24 +371,24 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { // construct nodes from shares by prepending namespace var leaves [][]byte - for _, sh := range rowShares { - leaves = append(leaves, append(sh[:NamespaceSize], sh...)) + for _, shr := range rowShares { + leaves = append(leaves, append(share.GetNamespace(shr), shr...)) } // verify namespace verified := proof.VerifyNamespace( sha256.New(), - nID, + namespace.ToNMT(), leaves, - ipld.NamespacedSha256FromCID(rcid)) + NamespacedSha256FromCID(rcid)) require.True(t, verified) // verify inclusion verified = proof.VerifyInclusion( sha256.New(), - nID, + namespace.ToNMT(), rowShares, - ipld.NamespacedSha256FromCID(rcid)) + NamespacedSha256FromCID(rcid)) require.True(t, verified) } } @@ -409,7 +412,7 @@ func TestBatchSize(t *testing.T) { {"8", 8}, {"16", 16}, {"32", 32}, - // {"64", 64}, // test case too large for CI with race detector + {"64", 64}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -418,8 +421,8 @@ func TestBatchSize(t *testing.T) { bs := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) - eds := RandEDS(t, tt.origWidth) - _, err := AddShares(ctx, ExtractODS(eds), blockservice.New(bs, offline.Exchange(bs))) + randEds := edstest.RandEDS(t, tt.origWidth) + _, err := AddShares(ctx, share.ExtractODS(randEds), blockservice.New(bs, offline.Exchange(bs))) require.NoError(t, err) out, err := bs.AllKeysChan(ctx) @@ -430,7 +433,7 @@ func TestBatchSize(t *testing.T) { count++ } extendedWidth := tt.origWidth * 2 - assert.Equalf(t, count, ipld.BatchSize(extendedWidth), "batchSize(%v)", extendedWidth) + assert.Equalf(t, count, BatchSize(extendedWidth), "batchSize(%v)", extendedWidth) }) } } @@ -440,57 +443,52 @@ func assertNoRowContainsNID( t *testing.T, bServ blockservice.BlockService, eds *rsmt2d.ExtendedDataSquare, - nID namespace.ID, + namespace share.Namespace, isAbsent bool, ) { rowRootCount := len(eds.RowRoots()) // get all row root cids rowRootCIDs := make([]cid.Cid, rowRootCount) for i, rowRoot := range eds.RowRoots() { - rowRootCIDs[i] = ipld.MustCidFromNamespacedSha256(rowRoot) + rowRootCIDs[i] = MustCidFromNamespacedSha256(rowRoot) } - // for each row root cid check if the minNID exists + // for each row root cid check if the min namespace exists var absentCount, foundAbsenceRows int for _, rowRoot := range eds.RowRoots() { var outsideRange bool - if !ipld.NamespaceIsOutsideRange(rowRoot, rowRoot, nID) { - // nID does belong to namespace range of the row + if !namespace.IsOutsideRange(rowRoot, rowRoot) { + // namespace does belong to namespace range of the row absentCount++ } else { outsideRange = true } - data := ipld.NewNamespaceData(rowRootCount, nID, ipld.WithProofs()) - rootCID := ipld.MustCidFromNamespacedSha256(rowRoot) + data := NewNamespaceData(rowRootCount, namespace, WithProofs()) + rootCID := MustCidFromNamespacedSha256(rowRoot) err := data.CollectLeavesByNamespace(ctx, bServ, rootCID) if outsideRange { - require.ErrorIs(t, err, ipld.ErrNamespaceOutsideRange) + require.ErrorIs(t, err, ErrNamespaceOutsideRange) continue } require.NoError(t, err) // if no error returned, check absence proof foundAbsenceRows++ - verified := data.Proof().VerifyNamespace(sha256.New(), nID, nil, rowRoot) + verified := data.Proof().VerifyNamespace(sha256.New(), namespace.ToNMT(), nil, rowRoot) require.True(t, verified) } if isAbsent { require.Equal(t, foundAbsenceRows, absentCount) - // there should be max 1 row that has namespace range containing nID + // there should be max 1 row that has namespace range containing namespace require.LessOrEqual(t, absentCount, 1) } } -func randomNids(total int) ([]namespace.ID, error) { - namespaces := make([]namespace.ID, total) +func randomNamespaces(total int) ([]share.Namespace, error) { + namespaces := make([]share.Namespace, total) for i := range namespaces { - nid := make([]byte, NamespaceSize) - _, err := rand.Read(nid) - if err != nil { - return nil, err - } - namespaces[i] = nid + namespaces[i] = sharetest.RandV0Namespace() } sort.Slice(namespaces, func(i, j int) bool { return bytes.Compare(namespaces[i], namespaces[j]) < 0 }) return namespaces, nil diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index f86a9803ac..d776da219d 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -2,7 +2,6 @@ package ipld import ( "context" - "encoding/hex" "errors" "fmt" "sync" @@ -15,11 +14,12 @@ import ( "go.opentelemetry.io/otel/codes" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/share" ) var ErrNamespaceOutsideRange = errors.New("share/ipld: " + - "target namespace id is outside of namespace range for the given root") + "target namespace is outside of namespace range for the given root") // Option is the functional option that is applied to the NamespaceData instance // to configure data that needs to be stored. @@ -48,20 +48,20 @@ type NamespaceData struct { bounds fetchedBounds maxShares int - nID namespace.ID + namespace share.Namespace isAbsentNamespace atomic.Bool absenceProofLeaf ipld.Node } -func NewNamespaceData(maxShares int, nID namespace.ID, options ...Option) *NamespaceData { +func NewNamespaceData(maxShares int, namespace share.Namespace, options ...Option) *NamespaceData { data := &NamespaceData{ // we don't know where in the tree the leaves in the namespace are, // so we keep track of the bounds to return the correct slice // maxShares acts as a sentinel to know if we find any leaves bounds: fetchedBounds{int64(maxShares), 0}, maxShares: maxShares, - nID: nID, + namespace: namespace, } for _, opt := range options { @@ -71,8 +71,8 @@ func NewNamespaceData(maxShares int, nID namespace.ID, options ...Option) *Names } func (n *NamespaceData) validate(rootCid cid.Cid) error { - if len(n.nID) != NamespaceSize { - return fmt.Errorf("expected namespace ID of size %d, got %d", NamespaceSize, len(n.nID)) + if err := n.namespace.Validate(); err != nil { + return err } if n.leaves == nil && n.proofs == nil { @@ -80,7 +80,7 @@ func (n *NamespaceData) validate(rootCid cid.Cid) error { } root := NamespacedSha256FromCID(rootCid) - if NamespaceIsOutsideRange(root, root, n.nID) { + if n.namespace.IsOutsideRange(root, root) { return ErrNamespaceOutsideRange } return nil @@ -180,7 +180,7 @@ func (n *NamespaceData) Proof() *nmt.Proof { } // CollectLeavesByNamespace collects leaves and corresponding proof that could be used to verify -// leaves inclusion. It returns as many leaves from the given root with the given namespace.ID as +// leaves inclusion. It returns as many leaves from the given root with the given Namespace as // it can retrieve. If no shares are found, it returns error as nil. A // non-nil error means that only partial data is returned, because at least one share retrieval // failed. The following implementation is based on `GetShares`. @@ -197,7 +197,7 @@ func (n *NamespaceData) CollectLeavesByNamespace( defer span.End() span.SetAttributes( - attribute.String("namespace", hex.EncodeToString(n.nID)), + attribute.String("namespace", n.namespace.String()), attribute.String("root", root.String()), ) @@ -245,7 +245,7 @@ func (n *NamespaceData) CollectLeavesByNamespace( retrievalErr = err }) log.Errorw("could not retrieve IPLD node", - "nID", hex.EncodeToString(n.nID), + "namespace", n.namespace.String(), "pos", j.sharePos, "err", err, ) @@ -301,17 +301,17 @@ func (n *NamespaceData) collectNDWithProofs(j job, links []*ipld.Link) []job { var nextJobs []job // check if target namespace is outside of boundaries of both links - if NamespaceIsOutsideRange(leftLink, rightLink, n.nID) { + if n.namespace.IsOutsideRange(leftLink, rightLink) { log.Fatalf("target namespace outside of boundaries of links at depth: %v", j.depth) } - if !NamespaceIsAboveMax(leftLink, n.nID) { + if !n.namespace.IsAboveMax(leftLink) { // namespace is within the range of left link nextJobs = append(nextJobs, j.next(left, leftCid, false)) } else { - // proof is on the left side, if the nID is on the right side of the range of left link + // proof is on the left side, if the namespace is on the right side of the range of left link n.addProof(left, leftCid, j.depth) - if NamespaceIsBelowMin(rightLink, n.nID) { + if n.namespace.IsBelowMin(rightLink) { // namespace is not included in either links, convert to absence collector n.isAbsentNamespace.Store(true) nextJobs = append(nextJobs, j.next(right, rightCid, true)) @@ -319,11 +319,11 @@ func (n *NamespaceData) collectNDWithProofs(j job, links []*ipld.Link) []job { } } - if !NamespaceIsBelowMin(rightLink, n.nID) { + if !n.namespace.IsBelowMin(rightLink) { // namespace is within the range of right link nextJobs = append(nextJobs, j.next(right, rightCid, false)) } else { - // proof is on the right side, if the nID is on the left side of the range of right link + // proof is on the right side, if the namespace is on the left side of the range of right link n.addProof(right, rightCid, j.depth) } return nextJobs diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index df140ef8c7..485642dde4 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -11,7 +11,7 @@ import ( "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - blocks "github.com/ipfs/go-libipfs/blocks" + "github.com/ipfs/go-libipfs/blocks" logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" mhcore "github.com/multiformats/go-multihash/core" @@ -20,7 +20,8 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/share" ) var ( @@ -39,17 +40,14 @@ const ( // that contain an NMT node (inner and leaf nodes). sha256NamespaceFlagged = 0x7701 - // NamespaceSize is a system-wide size for NMT namespaces. - NamespaceSize = appconsts.NamespaceSize - // NmtHashSize is the size of a digest created by an NMT in bytes. - NmtHashSize = 2*NamespaceSize + sha256.Size + NmtHashSize = 2*share.NamespaceSize + sha256.Size // innerNodeSize is the size of data in inner nodes. innerNodeSize = NmtHashSize * 2 // leafNodeSize is the size of data in leaf nodes. - leafNodeSize = NamespaceSize + appconsts.ShareSize + leafNodeSize = share.NamespaceSize + appconsts.ShareSize // cidPrefixSize is the size of the prepended buffer of the CID encoding // for NamespacedSha256. For more information, see: @@ -57,21 +55,15 @@ const ( cidPrefixSize = 4 // NMTIgnoreMaxNamespace is currently used value for IgnoreMaxNamespace option in NMT. - // IgnoreMaxNamespace defines whether the largest possible namespace.ID MAX_NID should be 'ignored'. + // IgnoreMaxNamespace defines whether the largest possible Namespace MAX_NID should be 'ignored'. // If set to true, this allows for shorter proofs in particular use-cases. NMTIgnoreMaxNamespace = true ) -var ( - // MaxSquareSize is currently the maximum size supported for unerasured data in - // rsmt2d.ExtendedDataSquare. - MaxSquareSize = appconsts.SquareSizeUpperBound(appconsts.LatestVersion) -) - func init() { // required for Bitswap to hash and verify inbound data correctly mhcore.Register(sha256NamespaceFlagged, func() hash.Hash { - nh := nmt.NewNmtHasher(sha256.New(), NamespaceSize, true) + nh := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, true) nh.Reset() return nh }) @@ -182,21 +174,3 @@ func Translate(dah *da.DataAvailabilityHeader, row, col int) (cid.Cid, int) { func NamespacedSha256FromCID(cid cid.Cid) []byte { return cid.Hash()[cidPrefixSize:] } - -// NamespaceIsAboveMax checks if the target namespace is above the maximum namespace for a given -// node hash. -func NamespaceIsAboveMax(nodeHash []byte, target namespace.ID) bool { - return !target.LessOrEqual(nmt.MaxNamespace(nodeHash, target.Size())) -} - -// NamespaceIsBelowMin checks if the target namespace is below the minimum namespace for a given -// node hash. -func NamespaceIsBelowMin(nodeHash []byte, target namespace.ID) bool { - return target.Less(nmt.MinNamespace(nodeHash, target.Size())) -} - -// NamespaceIsOutsideRange checks if the target namespace is outside the range defined by the left -// and right nodes -func NamespaceIsOutsideRange(leftNodeHash, rightNodeHash []byte, target namespace.ID) bool { - return NamespaceIsBelowMin(leftNodeHash, target) || NamespaceIsAboveMax(rightNodeHash, target) -} diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index b52a75c150..aa125ab3c7 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -1,36 +1,32 @@ package ipld import ( - "bytes" - "crypto/rand" - "sort" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share/eds/edstest" ) // TestNamespaceFromCID checks that deriving the Namespaced hash from // the given CID works correctly. func TestNamespaceFromCID(t *testing.T) { var tests = []struct { - randData [][]byte + eds *rsmt2d.ExtendedDataSquare }{ // note that the number of shares must be a power of two - {randData: generateRandNamespacedRawData(4, appconsts.NamespaceSize, appconsts.ShareSize-appconsts.NamespaceSize)}, - {randData: generateRandNamespacedRawData(16, appconsts.NamespaceSize, appconsts.ShareSize-appconsts.NamespaceSize)}, + {eds: edstest.RandEDS(t, 4)}, + {eds: edstest.RandEDS(t, 16)}, } for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - // create DAH from rand data - eds, err := da.ExtendShares(tt.randData) - require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(eds) + dah := da.NewDataAvailabilityHeader(tt.eds) // check to make sure NamespacedHash is correctly derived from CID for _, row := range dah.RowRoots { c, err := CidFromNamespacedSha256(row) @@ -42,28 +38,3 @@ func TestNamespaceFromCID(t *testing.T) { }) } } - -// generateRandNamespacedRawData returns random namespaced raw data for testing -// purposes. Note that this does not check that total is a power of two. -func generateRandNamespacedRawData(total, nidSize, leafSize uint32) [][]byte { - data := make([][]byte, total) - for i := uint32(0); i < total; i++ { - nid := make([]byte, nidSize) - - _, _ = rand.Read(nid) - data[i] = nid - } - sortByteArrays(data) - for i := uint32(0); i < total; i++ { - d := make([]byte, leafSize) - - _, _ = rand.Read(d) - data[i] = append(data[i], d...) - } - - return data -} - -func sortByteArrays(src [][]byte) { - sort.Slice(src, func(i, j int) bool { return bytes.Compare(src[i], src[j]) < 0 }) -} diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 12c36cb015..2a1b84efd5 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -12,7 +12,6 @@ import ( da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" - namespace "github.com/celestiaorg/nmt/namespace" rsmt2d "github.com/celestiaorg/rsmt2d" ) @@ -70,7 +69,7 @@ func (mr *MockGetterMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) * } // GetSharesByNamespace mocks base method. -func (m *MockGetter) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 namespace.ID) (share.NamespacedShares, error) { +func (m *MockGetter) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 share.Namespace) (share.NamespacedShares, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) ret0, _ := ret[0].(share.NamespacedShares) diff --git a/share/namespace.go b/share/namespace.go index 0af1d1a60e..433529a57f 100644 --- a/share/namespace.go +++ b/share/namespace.go @@ -9,6 +9,9 @@ import ( "github.com/celestiaorg/nmt/namespace" ) +// NamespaceSize is a system-wide size for NMT namespaces. +const NamespaceSize = appns.NamespaceSize + // Various reserved namespaces. var ( MaxReservedNamespace = Namespace(appns.MaxReservedNamespace.Bytes()) @@ -17,12 +20,30 @@ var ( ReservedPaddingNamespace = Namespace(appns.ReservedPaddingNamespace.Bytes()) TxNamespace = Namespace(appns.TxNamespace.Bytes()) PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) + ISRNamespace = Namespace(appns.IntermediateStateRootsNamespace.Bytes()) ) // Namespace represents namespace of a Share. // Consists of version byte and namespace ID. type Namespace []byte +// NewBlobNamespaceV0 takes a variable size byte slice and creates a valid version 0 Blob Namespace. +// The byte slice must be <= 10 bytes. +// If it is less than 10 bytes, it will be left padded to size 10 with 0s. +// Use predefined namespaces above, if non-blob namespace is needed. +func NewBlobNamespaceV0(id []byte) (Namespace, error) { + if len(id) == 0 || len(id) > appns.NamespaceVersionZeroIDSize { + return nil, fmt.Errorf( + "namespace id must be > 0 && <= %d, but it was %d bytes", appns.NamespaceVersionZeroIDSize, len(id)) + } + + n := make(Namespace, NamespaceSize) + // version and zero padding are already set as zero, + // so simply copying subNID to the end is enough to comply the V0 spec + copy(n[len(n)-len(id):], id) + return n, n.ValidateForBlob() +} + // NamespaceFromBytes converts bytes into Namespace and validates it. func NamespaceFromBytes(b []byte) (Namespace, error) { n := Namespace(b) @@ -84,8 +105,8 @@ func (n Namespace) Validate() error { return nil } -// ValidateDataNamespace checks if the Namespace contains real/useful data. -func (n Namespace) ValidateDataNamespace() error { +// ValidateForData checks if the Namespace is of real/useful data. +func (n Namespace) ValidateForData() error { if err := n.Validate(); err != nil { return err } @@ -95,9 +116,9 @@ func (n Namespace) ValidateDataNamespace() error { return nil } -// ValidateBlobNamespace checks if the Namespace is valid blob namespace. -func (n Namespace) ValidateBlobNamespace() error { - if err := n.ValidateDataNamespace(); err != nil { +// ValidateForBlob checks if the Namespace is valid blob namespace. +func (n Namespace) ValidateForBlob() error { + if err := n.ValidateForData(); err != nil { return err } if bytes.Compare(n, MaxReservedNamespace) < 1 { diff --git a/share/namespace_test.go b/share/namespace_test.go index 8cc61b379b..62746e4b4e 100644 --- a/share/namespace_test.go +++ b/share/namespace_test.go @@ -19,6 +19,59 @@ var ( invalidPrefixID = bytes.Repeat([]byte{1}, NamespaceSize) ) +func TestNewNamespaceV0(t *testing.T) { + type testCase struct { + name string + subNid []byte + expected Namespace + wantErr bool + } + testCases := []testCase{ + { + name: "8 byte id, gets left padded", + subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + expected: Namespace{ + 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // filled zeros + 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, // id with left padding + wantErr: false, + }, + { + name: "10 byte id, no padding", + subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x9, 0x10}, + expected: Namespace{ + 0x0, // version + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // filled zeros + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10}, // id + wantErr: false, + }, + { + name: "11 byte id", + subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x9, 0x10, 0x11}, + expected: []byte{}, + wantErr: true, + }, + { + name: "nil id", + subNid: nil, + expected: []byte{}, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := NewBlobNamespaceV0(tc.subNid) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expected, got) + }) + } +} + func TestFrom(t *testing.T) { type testCase struct { name string diff --git a/share/nid.go b/share/nid.go deleted file mode 100644 index 7d960cc9e1..0000000000 --- a/share/nid.go +++ /dev/null @@ -1,29 +0,0 @@ -package share - -import ( - "fmt" - - appns "github.com/celestiaorg/celestia-app/pkg/namespace" - "github.com/celestiaorg/nmt/namespace" -) - -// NewNamespaceV0 takes a variable size byte slice and creates a version 0 Namespace ID. -// The byte slice must be <= 10 bytes. -// If it is less than 10 bytes, it will be left padded to size 10 with 0s. -// TODO: Adapt for Namespace in the integration PR -func NewNamespaceV0(subNId []byte) (namespace.ID, error) { - if lnid := len(subNId); lnid > appns.NamespaceVersionZeroIDSize { - return nil, fmt.Errorf("namespace id must be <= %v, but it was %v bytes", appns.NamespaceVersionZeroIDSize, lnid) - } - - id := make([]byte, appns.NamespaceIDSize) - leftPaddingOffset := appns.NamespaceVersionZeroIDSize - len(subNId) - copy(id[appns.NamespaceVersionZeroPrefixSize+leftPaddingOffset:], subNId) - - appID, err := appns.New(appns.NamespaceVersionZero, id) - if err != nil { - return nil, err - } - - return appID.Bytes(), nil -} diff --git a/share/nid_test.go b/share/nid_test.go deleted file mode 100644 index 8f83d430e3..0000000000 --- a/share/nid_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package share - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/celestiaorg/nmt/namespace" -) - -func TestNewNamespaceV0(t *testing.T) { - type testCase struct { - name string - subNid []byte - expected namespace.ID - wantErr bool - } - testCases := []testCase{ - { - name: "8 byte subNid, gets left padded", - subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - expected: namespace.ID{ - 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // filled zeros - 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, // id with left padding - wantErr: false, - }, - { - name: "10 byte subNid, no padding", - subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x9, 0x10}, - expected: namespace.ID{ - 0x0, // version - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // filled zeros - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10}, // id - wantErr: false, - }, - { - name: "11 byte subNid", - subNid: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x9, 0x10, 0x11}, - expected: []byte{}, - wantErr: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, err := NewNamespaceV0(tc.subNid) - if tc.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.Equal(t, tc.expected, got) - }) - } -} diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index b0e11e3587..21fe9f77a1 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -16,8 +16,8 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/p2p" ) @@ -34,7 +34,7 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is immediately available t.Run("EDS_Available", func(t *testing.T) { - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) err = store.Put(ctx, dah.Hash(), eds) require.NoError(t, err) @@ -47,7 +47,7 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is unavailable initially, but is found after multiple requests t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { storageDelay := time.Second - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) go func() { time.Sleep(storageDelay) @@ -76,7 +76,7 @@ func TestExchange_RequestEDS(t *testing.T) { t.Run("EDS_err_not_found", func(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) _, err := client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) require.ErrorIs(t, err, p2p.ErrNotFound) diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index b59bb347a7..7f12c21cf2 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -15,7 +15,6 @@ import ( "github.com/celestiaorg/go-libp2p-messenger/serde" "github.com/celestiaorg/nmt" - "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" @@ -51,10 +50,14 @@ func NewClient(params *Parameters, host host.Host) (*Client, error) { func (c *Client) RequestND( ctx context.Context, root *share.Root, - nID namespace.ID, + namespace share.Namespace, peer peer.ID, ) (share.NamespacedShares, error) { - shares, err := c.doRequest(ctx, root, nID, peer) + if err := namespace.ValidateForData(); err != nil { + return nil, err + } + + shares, err := c.doRequest(ctx, root, namespace, peer) if err == nil { return shares, nil } @@ -80,7 +83,7 @@ func (c *Client) RequestND( func (c *Client) doRequest( ctx context.Context, root *share.Root, - nID namespace.ID, + namespace share.Namespace, peerID peer.ID, ) (share.NamespacedShares, error) { stream, err := c.host.NewStream(ctx, peerID, c.protocolID) @@ -92,8 +95,8 @@ func (c *Client) doRequest( c.setStreamDeadlines(ctx, stream) req := &pb.GetSharesByNamespaceRequest{ - RootHash: root.Hash(), - NamespaceId: nID, + RootHash: root.Hash(), + Namespace: namespace, } _, err = serde.Write(stream, req) diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 25528595d7..e8d3e439c0 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -14,13 +14,13 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/namespace" - nmtnamespace "github.com/celestiaorg/nmt/namespace" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/p2p" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestExchange_RequestND_NotFound(t *testing.T) { @@ -35,8 +35,8 @@ func TestExchange_RequestND_NotFound(t *testing.T) { t.Cleanup(cancel) root := share.Root{} - nID := make([]byte, namespace.NamespaceSize) - _, err := client.RequestND(ctx, &root, nID, server.host.ID()) + namespace := sharetest.RandV0Namespace() + _, err := client.RequestND(ctx, &root, namespace, server.host.ID()) require.ErrorIs(t, err, p2p.ErrNotFound) }) @@ -44,12 +44,12 @@ func TestExchange_RequestND_NotFound(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - eds := share.RandEDS(t, 4) + eds := edstest.RandEDS(t, 4) dah := da.NewDataAvailabilityHeader(eds) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) - randNID := dah.RowRoots[(len(dah.RowRoots)-1)/2][:namespace.NamespaceSize] - emptyShares, err := client.RequestND(ctx, &dah, randNID, server.host.ID()) + randNamespace := dah.RowRoots[(len(dah.RowRoots)-1)/2][:share.NamespaceSize] + emptyShares, err := client.RequestND(ctx, &dah, randNamespace, server.host.ID()) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) }) @@ -92,13 +92,13 @@ func TestExchange_RequestND(t *testing.T) { // take server concurrency slots with blocked requests for i := 0; i < rateLimit; i++ { go func(i int) { - client.RequestND(ctx, nil, nil, server.host.ID()) //nolint:errcheck + client.RequestND(ctx, nil, sharetest.RandV0Namespace(), server.host.ID()) //nolint:errcheck }(i) } // wait until all server slots are taken wg.Wait() - _, err = client.RequestND(ctx, nil, nil, server.host.ID()) + _, err = client.RequestND(ctx, nil, sharetest.RandV0Namespace(), server.host.ID()) require.ErrorIs(t, err, p2p.ErrNotFound) }) } @@ -118,7 +118,7 @@ func (m notFoundGetter) GetEDS( } func (m notFoundGetter) GetSharesByNamespace( - _ context.Context, _ *share.Root, _ nmtnamespace.ID, + _ context.Context, _ *share.Root, _ share.Namespace, ) (share.NamespacedShares, error) { return nil, nil } diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go index 7e19bebc09..e4c7656b4e 100644 --- a/share/p2p/shrexnd/pb/share.pb.go +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -57,8 +57,8 @@ func (StatusCode) EnumDescriptor() ([]byte, []int) { } type GetSharesByNamespaceRequest struct { - RootHash []byte `protobuf:"bytes,1,opt,name=root_hash,json=rootHash,proto3" json:"root_hash,omitempty"` - NamespaceId []byte `protobuf:"bytes,2,opt,name=namespace_id,json=namespaceId,proto3" json:"namespace_id,omitempty"` + RootHash []byte `protobuf:"bytes,1,opt,name=root_hash,json=rootHash,proto3" json:"root_hash,omitempty"` + Namespace []byte `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` } func (m *GetSharesByNamespaceRequest) Reset() { *m = GetSharesByNamespaceRequest{} } @@ -101,9 +101,9 @@ func (m *GetSharesByNamespaceRequest) GetRootHash() []byte { return nil } -func (m *GetSharesByNamespaceRequest) GetNamespaceId() []byte { +func (m *GetSharesByNamespaceRequest) GetNamespace() []byte { if m != nil { - return m.NamespaceId + return m.Namespace } return nil } @@ -291,33 +291,32 @@ func init() { func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } var fileDescriptor_ed9f13149b0de397 = []byte{ - // 401 bytes of a gzipped FileDescriptorProto + // 396 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0xe3, 0x6c, 0x62, 0xd2, 0xb1, 0x41, 0xd6, 0x80, 0xa8, 0xa1, 0xc8, 0x0a, 0x3e, 0x45, - 0x20, 0xd9, 0x92, 0x91, 0xb8, 0xbb, 0x6d, 0x00, 0x8b, 0xb2, 0xa9, 0x36, 0x05, 0x4e, 0xc8, 0xda, - 0xe2, 0xad, 0x8c, 0x04, 0xde, 0xc5, 0xbb, 0x55, 0xe0, 0xcc, 0x0b, 0xf0, 0x58, 0x1c, 0x7b, 0xe4, - 0x88, 0x92, 0x17, 0x41, 0x5e, 0xa7, 0xf4, 0x40, 0x6e, 0xfb, 0xcd, 0xfc, 0xe6, 0xdf, 0xa7, 0x85, - 0xa9, 0xae, 0x79, 0x2b, 0x52, 0x95, 0xa9, 0x54, 0xd7, 0xad, 0xf8, 0xd6, 0x54, 0xa9, 0x3a, 0x4f, - 0x6d, 0x30, 0x51, 0xad, 0x34, 0x12, 0x71, 0x2b, 0x32, 0x95, 0x58, 0x22, 0x69, 0xaa, 0xf8, 0x03, - 0x1c, 0xbc, 0x14, 0x66, 0xd9, 0x25, 0xf4, 0xe1, 0x77, 0xca, 0xbf, 0x08, 0xad, 0xf8, 0x47, 0xc1, - 0xc4, 0xd7, 0x4b, 0xa1, 0x0d, 0x1e, 0xc0, 0x5e, 0x2b, 0xa5, 0x29, 0x6b, 0xae, 0xeb, 0xd0, 0x99, - 0x3a, 0x33, 0x9f, 0x4d, 0xba, 0xc0, 0x2b, 0xae, 0x6b, 0x7c, 0x0c, 0x7e, 0x73, 0x5d, 0x50, 0x7e, - 0xaa, 0xc2, 0xa1, 0xcd, 0x7b, 0xff, 0x62, 0x45, 0x15, 0xff, 0x70, 0xe0, 0xd1, 0xee, 0xfe, 0x5a, - 0xc9, 0x46, 0x0b, 0x7c, 0x0e, 0xae, 0x36, 0xdc, 0x5c, 0x6a, 0xdb, 0xfd, 0x4e, 0x16, 0x25, 0xff, - 0x2f, 0x99, 0x2c, 0x2d, 0x71, 0x24, 0x2b, 0xc1, 0xb6, 0x34, 0x3e, 0x85, 0x51, 0x2b, 0x57, 0x3a, - 0x1c, 0x4e, 0xc9, 0xcc, 0xcb, 0xf6, 0x77, 0x55, 0x31, 0xb9, 0x62, 0x16, 0x8a, 0x29, 0x10, 0x26, - 0x57, 0x78, 0x1f, 0x5c, 0x8b, 0x75, 0xb3, 0xc8, 0xcc, 0x67, 0x5b, 0x85, 0x29, 0x8c, 0x55, 0x2b, - 0xe5, 0x85, 0x3d, 0xc0, 0xcb, 0x1e, 0xec, 0x6a, 0x76, 0xda, 0x01, 0xac, 0xe7, 0x62, 0x0e, 0x63, - 0xab, 0xf1, 0x1e, 0x8c, 0xb5, 0xe1, 0xad, 0xb1, 0xcb, 0x13, 0xd6, 0x0b, 0x0c, 0x80, 0x88, 0xa6, - 0xb7, 0x83, 0xb0, 0xee, 0xd9, 0x71, 0x8d, 0xac, 0x84, 0x0e, 0x89, 0x1d, 0xdc, 0x0b, 0x7c, 0x08, - 0x93, 0xce, 0xd7, 0xcf, 0x82, 0x5f, 0x84, 0xa3, 0xde, 0xdb, 0x6b, 0xfd, 0xe4, 0x3d, 0xc0, 0xcd, - 0xd5, 0xe8, 0xc1, 0xad, 0x82, 0xbe, 0xcb, 0x4f, 0x8a, 0xe3, 0x60, 0x80, 0x2e, 0x0c, 0x17, 0xaf, - 0x03, 0x07, 0x6f, 0xc3, 0x1e, 0x5d, 0x9c, 0x95, 0x2f, 0x16, 0x6f, 0xe9, 0x71, 0x30, 0x44, 0x1f, - 0x26, 0x05, 0x3d, 0x9b, 0x33, 0x9a, 0x9f, 0x04, 0x04, 0xf7, 0xe1, 0x2e, 0xcd, 0xdf, 0xcc, 0x97, - 0xa7, 0xf9, 0xd1, 0xbc, 0xbc, 0xc1, 0x46, 0x87, 0xe1, 0xaf, 0x75, 0xe4, 0x5c, 0xad, 0x23, 0xe7, - 0xcf, 0x3a, 0x72, 0x7e, 0x6e, 0xa2, 0xc1, 0xd5, 0x26, 0x1a, 0xfc, 0xde, 0x44, 0x83, 0x73, 0xd7, - 0xfe, 0x92, 0x67, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x3e, 0x78, 0x01, 0x49, 0x02, 0x00, - 0x00, + 0x10, 0xc6, 0x6d, 0x6f, 0x62, 0x92, 0x49, 0x40, 0xd6, 0x80, 0xa8, 0xa1, 0x95, 0x15, 0xf9, 0x14, + 0x81, 0x64, 0x4b, 0x46, 0xe2, 0xee, 0xb6, 0x01, 0x22, 0xca, 0xa6, 0xda, 0x94, 0x3f, 0xb7, 0x68, + 0x8b, 0xb7, 0xf2, 0x01, 0xbc, 0x8b, 0x77, 0xa3, 0xc0, 0x99, 0x17, 0xe0, 0xb1, 0x38, 0xe6, 0xc8, + 0x11, 0x25, 0x2f, 0x82, 0xbc, 0x4e, 0x94, 0x03, 0xb9, 0xf9, 0x9b, 0xf9, 0xcd, 0x37, 0x9f, 0x47, + 0x0b, 0x23, 0x5d, 0xf2, 0x5a, 0xa4, 0x2a, 0x53, 0xa9, 0x2e, 0x6b, 0xf1, 0xbd, 0x2a, 0x52, 0x75, + 0x9b, 0xda, 0x62, 0xa2, 0x6a, 0x69, 0x24, 0xe2, 0x4e, 0x64, 0x2a, 0xb1, 0x44, 0x52, 0x15, 0xf1, + 0x27, 0x38, 0x7d, 0x2d, 0xcc, 0xbc, 0x69, 0xe8, 0xf3, 0x1f, 0x94, 0x7f, 0x15, 0x5a, 0xf1, 0xcf, + 0x82, 0x89, 0x6f, 0x4b, 0xa1, 0x0d, 0x9e, 0x42, 0xbf, 0x96, 0xd2, 0x2c, 0x4a, 0xae, 0xcb, 0xd0, + 0x1d, 0xb9, 0xe3, 0x21, 0xeb, 0x35, 0x85, 0x37, 0x5c, 0x97, 0x78, 0x06, 0xfd, 0x6a, 0x3f, 0x10, + 0x7a, 0xb6, 0x79, 0x28, 0xc4, 0x3f, 0x5d, 0x38, 0x3b, 0x6e, 0xad, 0x95, 0xac, 0xb4, 0xc0, 0x97, + 0xe0, 0x6b, 0xc3, 0xcd, 0x52, 0x5b, 0xe3, 0x07, 0x59, 0x94, 0xfc, 0x9f, 0x2f, 0x99, 0x5b, 0xe2, + 0x42, 0x16, 0x82, 0xed, 0x68, 0x7c, 0x0e, 0x9d, 0x5a, 0xae, 0x74, 0xe8, 0x8d, 0xc8, 0x78, 0x90, + 0x9d, 0x1c, 0x9b, 0x62, 0x72, 0xc5, 0x2c, 0x14, 0x53, 0x20, 0x4c, 0xae, 0xf0, 0x31, 0xf8, 0x16, + 0x6b, 0x76, 0x91, 0xf1, 0x90, 0xed, 0x14, 0xa6, 0xd0, 0x55, 0xb5, 0x94, 0x77, 0x36, 0xfe, 0x20, + 0x7b, 0x72, 0xcc, 0xec, 0xba, 0x01, 0x58, 0xcb, 0xc5, 0x1c, 0xba, 0x56, 0xe3, 0x23, 0xe8, 0x6a, + 0xc3, 0x6b, 0x63, 0xc3, 0x13, 0xd6, 0x0a, 0x0c, 0x80, 0x88, 0xaa, 0xb0, 0x6e, 0x84, 0x35, 0x9f, + 0x0d, 0x57, 0xc9, 0x42, 0xe8, 0x90, 0xd8, 0xc5, 0xad, 0xc0, 0xa7, 0xd0, 0x6b, 0x4e, 0xfa, 0x45, + 0xf0, 0xbb, 0xb0, 0xd3, 0x9e, 0x75, 0xaf, 0x9f, 0x7d, 0x04, 0x38, 0xfc, 0x35, 0x0e, 0xe0, 0xde, + 0x94, 0x7e, 0xc8, 0xaf, 0xa6, 0x97, 0x81, 0x83, 0x3e, 0x78, 0xb3, 0xb7, 0x81, 0x8b, 0xf7, 0xa1, + 0x4f, 0x67, 0x37, 0x8b, 0x57, 0xb3, 0xf7, 0xf4, 0x32, 0xf0, 0x70, 0x08, 0xbd, 0x29, 0xbd, 0x99, + 0x30, 0x9a, 0x5f, 0x05, 0x04, 0x4f, 0xe0, 0x21, 0xcd, 0xdf, 0x4d, 0xe6, 0xd7, 0xf9, 0xc5, 0x64, + 0x71, 0xc0, 0x3a, 0xe7, 0xe1, 0xef, 0x4d, 0xe4, 0xae, 0x37, 0x91, 0xfb, 0x77, 0x13, 0xb9, 0xbf, + 0xb6, 0x91, 0xb3, 0xde, 0x46, 0xce, 0x9f, 0x6d, 0xe4, 0xdc, 0xfa, 0xf6, 0x81, 0xbc, 0xf8, 0x17, + 0x00, 0x00, 0xff, 0xff, 0x0b, 0x93, 0xfd, 0x1b, 0x44, 0x02, 0x00, 0x00, } func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { @@ -340,10 +339,10 @@ func (m *GetSharesByNamespaceRequest) MarshalToSizedBuffer(dAtA []byte) (int, er _ = i var l int _ = l - if len(m.NamespaceId) > 0 { - i -= len(m.NamespaceId) - copy(dAtA[i:], m.NamespaceId) - i = encodeVarintShare(dAtA, i, uint64(len(m.NamespaceId))) + if len(m.Namespace) > 0 { + i -= len(m.Namespace) + copy(dAtA[i:], m.Namespace) + i = encodeVarintShare(dAtA, i, uint64(len(m.Namespace))) i-- dAtA[i] = 0x12 } @@ -513,7 +512,7 @@ func (m *GetSharesByNamespaceRequest) Size() (n int) { if l > 0 { n += 1 + l + sovShare(uint64(l)) } - l = len(m.NamespaceId) + l = len(m.Namespace) if l > 0 { n += 1 + l + sovShare(uint64(l)) } @@ -653,7 +652,7 @@ func (m *GetSharesByNamespaceRequest) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NamespaceId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -680,9 +679,9 @@ func (m *GetSharesByNamespaceRequest) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NamespaceId = append(m.NamespaceId[:0], dAtA[iNdEx:postIndex]...) - if m.NamespaceId == nil { - m.NamespaceId = []byte{} + m.Namespace = append(m.Namespace[:0], dAtA[iNdEx:postIndex]...) + if m.Namespace == nil { + m.Namespace = []byte{} } iNdEx = postIndex default: diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto index 09d77d29f3..306865d17f 100644 --- a/share/p2p/shrexnd/pb/share.proto +++ b/share/p2p/shrexnd/pb/share.proto @@ -4,7 +4,7 @@ package share.p2p.shrex.nd; message GetSharesByNamespaceRequest{ bytes root_hash = 1; - bytes namespace_id = 2; + bytes namespace = 2; } message GetSharesByNamespaceResponse{ diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 72c5231eff..d61e6b2e68 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -17,7 +17,6 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" ) @@ -100,7 +99,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre stream.Reset() //nolint:errcheck return } - logger = logger.With("namespaceId", hex.EncodeToString(req.NamespaceId), "hash", share.DataHash(req.RootHash).String()) + logger = logger.With("namespace", hex.EncodeToString(req.Namespace), "hash", share.DataHash(req.RootHash).String()) logger.Debugw("server: new request") err = stream.CloseRead() @@ -130,7 +129,7 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre return } - shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.NamespaceId) + shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.Namespace) switch { case errors.Is(err, share.ErrNotFound): logger.Warn("server: nd not found") @@ -148,13 +147,12 @@ func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stre // validateRequest checks correctness of the request func validateRequest(req pb.GetSharesByNamespaceRequest) error { - if len(req.NamespaceId) != ipld.NamespaceSize { - return fmt.Errorf("incorrect namespace id length: %v", len(req.NamespaceId)) + if err := share.Namespace(req.Namespace).ValidateForData(); err != nil { + return err } if len(req.RootHash) != sha256.Size { return fmt.Errorf("incorrect root hash length: %v", len(req.RootHash)) } - return nil } diff --git a/share/share.go b/share/share.go index 0178054a9f..02ccd73909 100644 --- a/share/share.go +++ b/share/share.go @@ -4,32 +4,23 @@ import ( "bytes" "fmt" - "go.opentelemetry.io/otel" - "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/nmt/namespace" - - "github.com/celestiaorg/celestia-node/share/ipld" ) var ( - tracer = otel.Tracer("share") - // DefaultRSMT2DCodec sets the default rsmt2d.Codec for shares. DefaultRSMT2DCodec = appconsts.DefaultCodec ) const ( - // NamespaceSize is a system-wide size for NMT namespaces. - NamespaceSize = appconsts.NamespaceSize - // Size is a system-wide size of a share, including both data and namespace ID + // Size is a system-wide size of a share, including both data and namespace GetNamespace Size = appconsts.ShareSize ) var ( // MaxSquareSize is currently the maximum size supported for unerasured data in // rsmt2d.ExtendedDataSquare. - MaxSquareSize = ipld.MaxSquareSize + MaxSquareSize = appconsts.SquareSizeUpperBound(appconsts.LatestVersion) ) // Share contains the raw share data without the corresponding namespace. @@ -38,13 +29,13 @@ var ( // on it. type Share = []byte -// ID gets the namespace ID from the share. -func ID(s Share) namespace.ID { +// GetNamespace slices Namespace out of the Share. +func GetNamespace(s Share) Namespace { return s[:NamespaceSize] } -// Data gets data from the share. -func Data(s Share) []byte { +// GetData slices out data of the Share. +func GetData(s Share) []byte { return s[NamespaceSize:] } diff --git a/share/sharetest/testing.go b/share/sharetest/testing.go new file mode 100644 index 0000000000..50eb0eb003 --- /dev/null +++ b/share/sharetest/testing.go @@ -0,0 +1,58 @@ +package sharetest + +import ( + "bytes" + "math/rand" + "sort" + "sync" + "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/namespace" + + "github.com/celestiaorg/celestia-node/share" +) + +// RandShares generate 'total' amount of shares filled with random data. It uses require.TestingT +// to be able to take both a *testing.T and a *testing.B. +func RandShares(t require.TestingT, total int) []share.Share { + if total&(total-1) != 0 { + t.Errorf("total must be power of 2: %d", total) + t.FailNow() + } + + shares := make([]share.Share, total) + for i := range shares { + shr := make([]byte, share.Size) + copy(share.GetNamespace(shr), RandV0Namespace()) + rndMu.Lock() + _, err := rnd.Read(share.GetData(shr)) + rndMu.Unlock() + require.NoError(t, err) + shares[i] = shr + } + sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) + + return shares +} + +// RandV0Namespace generates random valid data namespace for testing purposes. +func RandV0Namespace() share.Namespace { + rb := make([]byte, namespace.NamespaceVersionZeroIDSize) + rndMu.Lock() + rnd.Read(rb) + rndMu.Unlock() + for { + namespace, _ := share.NewBlobNamespaceV0(rb) + if err := namespace.ValidateForData(); err != nil { + continue + } + return namespace + } +} + +var ( + rnd = rand.New(rand.NewSource(time.Now().Unix())) //nolint:gosec + rndMu sync.Mutex +) diff --git a/share/test_helpers.go b/share/test_helpers.go deleted file mode 100644 index c02bfc55ac..0000000000 --- a/share/test_helpers.go +++ /dev/null @@ -1,65 +0,0 @@ -package share - -import ( - "bytes" - "crypto/rand" - "sort" - - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-app/pkg/namespace" - "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/rsmt2d" -) - -// EqualEDS check whether two given EDSes are equal. -// TODO(Wondertan): Move to rsmt2d -// TODO(Wondertan): Propose use of int by default instead of uint for the sake convenience and -// Golang practices -func EqualEDS(a *rsmt2d.ExtendedDataSquare, b *rsmt2d.ExtendedDataSquare) bool { - if a.Width() != b.Width() { - return false - } - - for i := uint(0); i < a.Width(); i++ { - ar, br := a.Row(i), b.Row(i) - for j := 0; j < len(ar); j++ { - if !bytes.Equal(ar[j], br[j]) { - return false - } - } - } - - return true -} - -// RandEDS generates EDS filled with the random data with the given size for original square. It -// uses require.TestingT to be able to take both a *testing.T and a *testing.B. -func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { - shares := RandShares(t, size*size) - // recompute the eds - eds, err := rsmt2d.ComputeExtendedDataSquare(shares, DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) - require.NoError(t, err, "failure to recompute the extended data square") - return eds -} - -// RandShares generate 'total' amount of shares filled with random data. It uses require.TestingT -// to be able to take both a *testing.T and a *testing.B. -func RandShares(t require.TestingT, total int) []Share { - if total&(total-1) != 0 { - t.Errorf("total must be power of 2: %d", total) - t.FailNow() - } - - shares := make([]Share, total) - for i := range shares { - share := make([]byte, Size) - copy(share[:NamespaceSize], namespace.RandomNamespace().Bytes()) - _, err := rand.Read(share[NamespaceSize:]) - require.NoError(t, err) - shares[i] = share - } - sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) - - return shares -} diff --git a/state/core_access.go b/state/core_access.go index 7b59f3e714..3fefaa3ed9 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -167,8 +167,11 @@ func (ca *CoreAccessor) SubmitPayForBlob( } appblobs := make([]*apptypes.Blob, len(blobs)) - for i, blob := range blobs { - appblobs[i] = &blob.Blob + for i, b := range blobs { + if err := b.Namespace().ValidateForBlob(); err != nil { + return nil, err + } + appblobs[i] = &b.Blob } response, err := appblob.SubmitPayForBlob( From 4d986940d662649eba4b0d3daa85268318204ff2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 27 Jun 2023 15:43:55 +0200 Subject: [PATCH 0681/1008] chore(go.mod): Bump nmt (#2405) Self-explanatory --- go.mod | 2 +- go.sum | 8 ++++---- share/eds/eds.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 73509c56c3..3d38242260 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.7 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/nmt v0.16.0 + github.com/celestiaorg/nmt v0.17.0 github.com/celestiaorg/rsmt2d v0.9.0 github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/cosmos-sdk/api v0.1.0 diff --git a/go.sum b/go.sum index 28113d7847..d8cfbe3806 100644 --- a/go.sum +++ b/go.sum @@ -362,8 +362,8 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.16.0 h1:4CX6d1Uwf1C+tGcAWskPve0HCDTnI4Ey8ffjiDwcGH0= -github.com/celestiaorg/nmt v0.16.0/go.mod h1:GfwIvQPhUakn1modWxJ+rv8dUjJzuXg5H+MLFM1o7nY= +github.com/celestiaorg/nmt v0.17.0 h1:/k8YLwJvuHgT/jQ435zXKaDX811+sYEMXL4B/vYdSLU= +github.com/celestiaorg/nmt v0.17.0/go.mod h1:ZndCeAR4l9lxm7W51ouoyTo1cxhtFgK+4DpEIkxRA3A= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.9.0 h1:kon78I748ZqjNzI8OAqPN+2EImuZuanj/6gTh8brX3o= @@ -1950,14 +1950,14 @@ github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= diff --git a/share/eds/eds.go b/share/eds/eds.go index 239ba4a0ad..544f3c2438 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -35,7 +35,7 @@ var ErrEmptySquare = errors.New("share: importing empty data") type writingSession struct { eds *rsmt2d.ExtendedDataSquare store bstore.Blockstore // caches inner nodes (proofs) while we walk the nmt tree. - hasher *nmt.Hasher + hasher *nmt.NmtHasher w io.Writer } From 9ee50806244e5f2eeb296c3353cc8344f9f78fea Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:32:15 +0800 Subject: [PATCH 0682/1008] tests(share/eds): add store basic benchmarks (#2407) ## Overview Implements basic benchmarks for eds store implementation. --- share/eds/store_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index b1dc3986b7..7f2f548f63 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -253,6 +253,49 @@ func Test_CachedAccessor(t *testing.T) { assert.Equal(t, firstBlock, secondBlock) } +func BenchmarkStore(b *testing.B) { + ctx, cancel := context.WithCancel(context.Background()) + b.Cleanup(cancel) + + tmpDir := b.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := NewStore(tmpDir, ds) + require.NoError(b, err) + err = edsStore.Start(ctx) + require.NoError(b, err) + + // BenchmarkStore/bench_put_128-10 10 3231859283 ns/op (~3sec) + b.Run("bench put 128", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + // pause the timer for initializing test data + b.StopTimer() + eds := edstest.RandEDS(b, 128) + dah := da.NewDataAvailabilityHeader(eds) + b.StartTimer() + + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(b, err) + } + }) + + // BenchmarkStore/bench_read_128-10 14 78970661 ns/op (~70ms) + b.Run("bench read 128", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + // pause the timer for initializing test data + b.StopTimer() + eds := edstest.RandEDS(b, 128) + dah := da.NewDataAvailabilityHeader(eds) + _ = edsStore.Put(ctx, dah.Hash(), eds) + b.StartTimer() + + _, err := edsStore.Get(ctx, dah.Hash()) + require.NoError(b, err) + } + }) +} + func newStore(t *testing.T) (*Store, error) { t.Helper() From 061b78881d7d3b968fafaff5ec2744f75b775954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Fri, 30 Jun 2023 13:22:10 +0200 Subject: [PATCH 0683/1008] chore!: bump arabica-9 (#2417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello team! This PR contains the updates for arabica-9: - the genesis hash - chain-id - bootstrapper multiaddresses thanks in advance! 🚀 Jose Ramon Mañes Signed-off-by: Jose Ramon Mañes --- nodebuilder/p2p/bootstrap.go | 8 ++++---- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index c534aa4fba..376d95fb14 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -38,10 +38,10 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ Arabica: { - "/dns4/da-bridge-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWDXkXARv79Dtn5xrGBgJePtCzCsEwWR7eGWnx9ZCyUyD6", - "/dns4/da-bridge-arabica-8-2.celestia-arabica.com/tcp/2121/p2p/12D3KooWPu8qKmmNgYFMBsTkLBa1m3D9Cy9ReCAoQLqxEn9MHD1i", - "/dns4/da-full-1-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWEmeFodzypdTBTcw8Yub6WZRT4h1UgFtwCwwq6wS5Dtqm", - "/dns4/da-full-2-arabica-8.celestia-arabica.com/tcp/2121/p2p/12D3KooWCs3wFmqwPn1u8pNU4BGsvLsob1ShTzvps8qEtTRuuuK5", + "/dns4/da-bridge-arabica-9.celestia-arabica.com/tcp/2121/p2p/12D3KooWBLvsfkbovAH74DbGGxHPpVW7DkvKdbQxhorrkv9tfGZU", + "/dns4/da-bridge-arabica-9-2.celestia-arabica.com/tcp/2121/p2p/12D3KooWNjJSk8JcY7VoLEjGGUz8CXp9Bxt495zXmdmccjaMPgHf", + "/dns4/da-full-1-arabica-9.celestia-arabica.com/tcp/2121/p2p/12D3KooWFUK2Z4WPsQN3p5n8tgBigxP32gbmABUet2UMby2Ha9ZK", + "/dns4/da-full-2-arabica-9.celestia-arabica.com/tcp/2121/p2p/12D3KooWKnmwsimoghxUT1DXr7f8yXbWCfmXDk4UGbQDsAks9XsN", }, Mocha: { "/dns4/bootstr-mocha-1.celestia-mocha.com/tcp/2121/p2p/12D3KooWDRSJMbH3PS4dRDa11H7Tk615aqTUgkeEKz4pwd4sS6fN", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 0a36dc54cc..e027cfdb1f 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "E5D620B5BE7873222DCD83464C285FD0F215C209393E7481F9A5979280AD6CA2", + Arabica: "7A5FABB19713D732D967B1DA84FA0DF5E87A7B62302D783F78743E216C1A3550", Mocha: "1181AF8EAE5DDF3CBBFF3BF3CC44C5B795DF5094F5A0CC0AE52921ECCA0AF3C8", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index dd04cd377c..276fc83501 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-8" + Arabica Network = "arabica-9" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha-2" // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. From 44786bb2e6d6c98f853d185413cbb66567fa5aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Fri, 30 Jun 2023 13:53:55 +0200 Subject: [PATCH 0684/1008] feat: update Go base alpine - add branch in makefile (#2414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello team! This PR contains a couple of things: - Update the base version of Go Alpine to `golang:1.20-alpine3.18` - In the Makefile, I've added the following line: ```Makefile main.semanticVersion=$(shell git describe --tags --dirty=-dev 2>/dev/null || git rev-parse --abbrev-ref HEAD)'" ``` Basically what it does is, in case this commit doesn't contain any tag, it uses the branch name. At the moment we are having some issues using the flag `version`, the output command is: ![Screenshot 2023-06-29 at 17 24 22](https://github.com/celestiaorg/celestia-node/assets/32740567/b6b6f948-fcec-4157-920e-0a9863b8d11f) With this change, we'll have something like: ![Screenshot 2023-06-29 at 17 08 17](https://github.com/celestiaorg/celestia-node/assets/32740567/7d2de807-f014-444d-8917-6ec45a9d9750) *Already tested* Let me know what you think about it and if it's worth it 😊 Thanks in advance! 🚀 Jose Ramon Mañes --------- Signed-off-by: Jose Ramon Mañes Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- Dockerfile | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9346c559d1..6e9934dcb1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.20-alpine3.17 as builder +FROM docker.io/golang:1.20-alpine3.18 as builder # hadolint ignore=DL3018 RUN apk update && apk add --no-cache \ diff --git a/Makefile b/Makefile index 8f4d1b42a9..310e65cbe0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL=/usr/bin/env bash PROJECTNAME=$(shell basename "$(PWD)") -LDFLAGS=-ldflags="-X 'main.buildTime=$(shell date)' -X 'main.lastCommit=$(shell git rev-parse HEAD)' -X 'main.semanticVersion=$(shell git describe --tags --dirty=-dev)'" +LDFLAGS=-ldflags="-X 'main.buildTime=$(shell date)' -X 'main.lastCommit=$(shell git rev-parse HEAD)' -X 'main.semanticVersion=$(shell git describe --tags --dirty=-dev 2>/dev/null || git rev-parse --abbrev-ref HEAD)'" ifeq (${PREFIX},) PREFIX := /usr/local endif From 1a37094a633ba62dbf95eba03d0bcd23d18494d1 Mon Sep 17 00:00:00 2001 From: Mustafa Al-Bassam Date: Mon, 3 Jul 2023 11:10:46 +0100 Subject: [PATCH 0685/1008] doc(share/p2p/shrexnd): Improve accuracy of RequestND description (#2418) `RequestND` doesn't actually verify the inclusion proofs; this is done later in `GetSharesByNamespace`: https://github.com/celestiaorg/celestia-node/blob/main/share/getters/shrex.go#L237 --- share/p2p/shrexnd/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 7f12c21cf2..74162f5981 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -46,7 +46,7 @@ func NewClient(params *Parameters, host host.Host) (*Client, error) { } // RequestND requests namespaced data from the given peer. -// Returns valid data with its verified inclusion against the share.Root. +// Returns shares with unverified inclusion proofs against the share.Root. func (c *Client) RequestND( ctx context.Context, root *share.Root, From beaf6dbdc73fd43b73a98578330a7a5ad422c3c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:28:28 +0200 Subject: [PATCH 0686/1008] chore(deps): bump alpine from 3.18.0 to 3.18.2 (#2380) Bumps alpine from 3.18.0 to 3.18.2. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.18.0&new-version=3.18.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6e9934dcb1..2d07babc94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ COPY . . RUN make build && make cel-key -FROM docker.io/alpine:3.18.0 +FROM docker.io/alpine:3.18.2 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From 0d10cedaa0d0d117b18b618d1d3ccbe456097495 Mon Sep 17 00:00:00 2001 From: Alex Kiss Date: Wed, 5 Jul 2023 22:50:29 +0100 Subject: [PATCH 0687/1008] chore(nodebuilder/p2p)! Upgrade to mocha-3 (#2432) ## Overview Update to mocha-3 chain name when init is called. ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index e027cfdb1f..1fb6b10a55 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -24,7 +24,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Arabica: "7A5FABB19713D732D967B1DA84FA0DF5E87A7B62302D783F78743E216C1A3550", - Mocha: "1181AF8EAE5DDF3CBBFF3BF3CC44C5B795DF5094F5A0CC0AE52921ECCA0AF3C8", + Mocha: "79A97034D569C4199A867439B1B7B77D4E1E1D9697212755E1CE6D920CDBB541", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 276fc83501..b1c3a5fbb7 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -14,7 +14,7 @@ const ( // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica-9" // Mocha testnet. See: celestiaorg/networks. - Mocha Network = "mocha-2" + Mocha Network = "mocha-3" // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. BlockspaceRace Network = "blockspacerace-0" // Private can be used to set up any private network, including local testing setups. From 387c160d76bcf74b2d3e4ecc7be017fe419ed3b3 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 6 Jul 2023 13:05:11 +0300 Subject: [PATCH 0688/1008] share/befp: add benchmarks for fraud proof creation and validation (#2400) ## Overview Add benchmarks for BEFP creation and validation BEFP was created for the first row. ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- header/headertest/testing.go | 2 +- share/eds/retriever_test.go | 122 +++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/header/headertest/testing.go b/header/headertest/testing.go index fbc71f92f9..ceeb9bb164 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -352,7 +352,7 @@ func CreateFraudExtHeader( eh *header.ExtendedHeader, serv blockservice.BlockService, ) (*header.ExtendedHeader, *rsmt2d.ExtendedDataSquare) { - square := edstest.RandByzantineEDS(t, 16) + square := edstest.RandByzantineEDS(t, len(eh.DAH.RowRoots)) err := ipld.ImportEDS(context.Background(), square, serv) require.NoError(t, err) dah := da.NewDataAvailabilityHeader(square) diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index ca689aa5ec..c90697862f 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -2,9 +2,12 @@ package eds import ( "context" + "errors" + "fmt" "testing" "time" + "github.com/ipfs/go-blockservice" mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,6 +17,8 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/eds/edstest" @@ -124,3 +129,120 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { _, err = ses.Reconstruct(ctx) assert.NoError(t, err) } + +func TestFraudProofValidation(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer t.Cleanup(cancel) + bServ := mdutils.Bserv() + + odsSize := []int{2, 4, 16, 32, 64, 128} + for _, size := range odsSize { + t.Run(fmt.Sprintf("ods size:%d", size), func(t *testing.T) { + var errByz *byzantine.ErrByzantine + faultHeader, err := generateByzantineError(ctx, t, size, bServ) + require.True(t, errors.As(err, &errByz)) + + p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(faultHeader.Height()), errByz) + err = p.Validate(faultHeader) + require.NoError(t, err) + }) + } +} + +func generateByzantineError( + ctx context.Context, + t *testing.T, + odsSize int, + bServ blockservice.BlockService, +) (*header.ExtendedHeader, error) { + eds := edstest.RandByzantineEDS(t, odsSize) + err := ipld.ImportEDS(ctx, eds, bServ) + require.NoError(t, err) + h := headertest.ExtendedHeaderFromEDS(t, 1, eds) + _, err = NewRetriever(bServ).Retrieve(ctx, h.DAH) + + return h, err +} + +/* +BenchmarkBEFPValidation/ods_size:2 31273 38819 ns/op 68052 B/op 366 allocs/op +BenchmarkBEFPValidation/ods_size:4 14664 80439 ns/op 135892 B/op 894 allocs/op +BenchmarkBEFPValidation/ods_size:16 2850 386178 ns/op 587890 B/op 4945 allocs/op +BenchmarkBEFPValidation/ods_size:32 1399 874490 ns/op 1233399 B/op 11284 allocs/op +BenchmarkBEFPValidation/ods_size:64 619 2047540 ns/op 2578008 B/op 25364 allocs/op +BenchmarkBEFPValidation/ods_size:128 259 4934375 ns/op 5418406 B/op 56345 allocs/op +*/ +func BenchmarkBEFPValidation(b *testing.B) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer b.Cleanup(cancel) + bServ := mdutils.Bserv() + r := NewRetriever(bServ) + t := &testing.T{} + odsSize := []int{2, 4, 16, 32, 64, 128} + for _, size := range odsSize { + b.Run(fmt.Sprintf("ods size:%d", size), func(b *testing.B) { + b.ResetTimer() + b.StopTimer() + eds := edstest.RandByzantineEDS(t, size) + err := ipld.ImportEDS(ctx, eds, bServ) + require.NoError(t, err) + h := headertest.ExtendedHeaderFromEDS(t, 1, eds) + _, err = r.Retrieve(ctx, h.DAH) + var errByz *byzantine.ErrByzantine + require.ErrorAs(t, err, &errByz) + b.StartTimer() + + for i := 0; i < b.N; i++ { + b.ReportAllocs() + p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(h.Height()), errByz) + err = p.Validate(h) + require.NoError(b, err) + } + }) + } +} + +/* +BenchmarkNewErrByzantineData/ods_size:2 29605 38846 ns/op 49518 B/op 579 allocs/op +BenchmarkNewErrByzantineData/ods_size:4 11380 105302 ns/op 134967 B/op 1571 allocs/op +BenchmarkNewErrByzantineData/ods_size:16 1902 631086 ns/op 830199 B/op 9601 allocs/op +BenchmarkNewErrByzantineData/ods_size:32 756 1530985 ns/op 1985272 B/op 22901 allocs/op +BenchmarkNewErrByzantineData/ods_size:64 340 3445544 ns/op 4767053 B/op 54704 allocs/op +BenchmarkNewErrByzantineData/ods_size:128 132 8740678 ns/op 11991093 B/op 136584 allocs/op +*/ +func BenchmarkNewErrByzantineData(b *testing.B) { + odsSize := []int{2, 4, 16, 32, 64, 128} + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + bServ := mdutils.Bserv() + r := NewRetriever(bServ) + t := &testing.T{} + for _, size := range odsSize { + b.Run(fmt.Sprintf("ods size:%d", size), func(b *testing.B) { + b.StopTimer() + eds := edstest.RandByzantineEDS(t, size) + err := ipld.ImportEDS(ctx, eds, bServ) + require.NoError(t, err) + h := headertest.ExtendedHeaderFromEDS(t, 1, eds) + ses, err := r.newSession(ctx, h.DAH) + require.NoError(t, err) + + select { + case <-ctx.Done(): + b.Fatal(ctx.Err()) + case <-ses.Done(): + } + + _, err = ses.Reconstruct(ctx) + assert.NoError(t, err) + var errByz *rsmt2d.ErrByzantineData + require.ErrorAs(t, err, &errByz) + b.StartTimer() + + for i := 0; i < b.N; i++ { + err = byzantine.NewErrByzantine(ctx, bServ, h.DAH, errByz) + require.NotNil(t, err) + } + }) + } +} From 37123381add941b02c7de2e087bb8fdb581bd4ad Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 6 Jul 2023 16:25:48 +0200 Subject: [PATCH 0689/1008] deps: bump go-header (#2436) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3d38242260..36ed440e4a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc5 github.com/celestiaorg/go-fraud v0.1.0 - github.com/celestiaorg/go-header v0.2.7 + github.com/celestiaorg/go-header v0.2.10 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.17.0 github.com/celestiaorg/rsmt2d v0.9.0 diff --git a/go.sum b/go.sum index d8cfbe3806..471f6c47d2 100644 --- a/go.sum +++ b/go.sum @@ -354,8 +354,8 @@ github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPc github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= -github.com/celestiaorg/go-header v0.2.7 h1:r0X9Dl7lqBkQpwG3ekQHC61n/QdwO6epuIxDkQ4YX4o= -github.com/celestiaorg/go-header v0.2.7/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= +github.com/celestiaorg/go-header v0.2.10 h1:KaULxmQ/WJGBeN+rEzTqEEwLD3adkqeD3RM+H8GRHkg= +github.com/celestiaorg/go-header v0.2.10/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= From e1b4c9c545cd3fc20651d171885399893ca0043b Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Thu, 6 Jul 2023 10:45:52 -0500 Subject: [PATCH 0690/1008] chore: bump celestia-app to v1.0.0-rc8 (#2434) ## Overview this PR bumps node to rc8 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- blob/helper.go | 2 +- core/eds.go | 20 ++++------------- core/eds_test.go | 17 ++++++++++----- core/exchange.go | 4 ++-- core/header_test.go | 2 +- core/listener.go | 2 +- go.mod | 22 +++++++++---------- go.sum | 44 +++++++++++++++++++------------------- nodebuilder/p2p/genesis.go | 2 +- 9 files changed, 55 insertions(+), 60 deletions(-) diff --git a/blob/helper.go b/blob/helper.go index 341080f42f..35f0abc540 100644 --- a/blob/helper.go +++ b/blob/helper.go @@ -74,7 +74,7 @@ func BlobsToShares(blobs ...*Blob) ([]share.Share, error) { return val <= 0 }) - rawShares, err := shares.SplitBlobs(0, nil, b, false) + rawShares, err := shares.SplitBlobs(b...) if err != nil { return nil, err } diff --git a/core/eds.go b/core/eds.go index dc9b2b4a9e..c87d947e35 100644 --- a/core/eds.go +++ b/core/eds.go @@ -7,9 +7,7 @@ import ( "github.com/filecoin-project/dagstore" "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/square" + "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -19,22 +17,12 @@ import ( // extendBlock extends the given block data, returning the resulting // ExtendedDataSquare (EDS). If there are no transactions in the block, // nil is returned in place of the eds. -func extendBlock(data types.Data) (*rsmt2d.ExtendedDataSquare, error) { - if len(data.Txs) == 0 && data.SquareSize == uint64(1) { +func extendBlock(data types.Data, appVersion uint64) (*rsmt2d.ExtendedDataSquare, error) { + if app.IsEmptyBlock(data, appVersion) { return nil, nil } - sqr, err := square.Construct(data.Txs.ToSliceOfBytes(), appconsts.LatestVersion, share.MaxSquareSize) - if err != nil { - return nil, err - } - - shares := make([][]byte, len(sqr)) - for i, s := range sqr { - shares[i] = s.ToBytes() - } - - return da.ExtendShares(shares) + return app.ExtendBlock(data, appVersion) } // storeEDS will only store extended block if it is not empty and doesn't already exist. diff --git a/core/eds_test.go b/core/eds_test.go index 6bc04c96c4..2cc6f1c7cc 100644 --- a/core/eds_test.go +++ b/core/eds_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/types" + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" @@ -21,7 +23,7 @@ func TestTrulyEmptySquare(t *testing.T) { SquareSize: 1, } - eds, err := extendBlock(data) + eds, err := extendBlock(data, appconsts.LatestVersion) require.NoError(t, err) assert.Nil(t, eds) } @@ -32,14 +34,19 @@ func TestTrulyEmptySquare(t *testing.T) { // construction of the square is deterministic, and the rules which dictate the // square size do not allow for empty block data. However, should that ever // occur, we need to ensure that the correct data root is generated. -func TestNonEmptySquareWithZeroTxs(t *testing.T) { +func TestEmptySquareWithZeroTxs(t *testing.T) { data := types.Data{ - Txs: []types.Tx{}, - SquareSize: 16, + Txs: []types.Tx{}, } - eds, err := extendBlock(data) + eds, err := extendBlock(data, appconsts.LatestVersion) + require.Nil(t, eds) + require.NoError(t, err) + + // force extend the square using an empty block and compare with the min DAH + eds, err = app.ExtendBlock(data, appconsts.LatestVersion) require.NoError(t, err) + dah := da.NewDataAvailabilityHeader(eds) assert.Equal(t, share.EmptyRoot().Hash(), dah.Hash()) } diff --git a/core/exchange.go b/core/exchange.go index 70ca47c56f..a3e98c0307 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -105,7 +105,7 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende } // extend block data - eds, err := extendBlock(block.Data) + eds, err := extendBlock(block.Data, block.Header.Version.App) if err != nil { return nil, fmt.Errorf("extending block data for height %d: %w", &block.Height, err) } @@ -142,7 +142,7 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 log.Debugw("fetched signed block from core", "height", b.Header.Height) // extend block data - eds, err := extendBlock(b.Data) + eds, err := extendBlock(b.Data, b.Header.Version.App) if err != nil { return nil, fmt.Errorf("extending block data for height %d: %w", b.Header.Height, err) } diff --git a/core/header_test.go b/core/header_test.go index 57f53d3661..6315fbc143 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -30,7 +30,7 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { comm, val, err := fetcher.GetBlockInfo(ctx, &height) require.NoError(t, err) - eds, err := extendBlock(b.Data) + eds, err := extendBlock(b.Data, b.Header.Version.App) require.NoError(t, err) headerExt, err := header.MakeExtendedHeader(ctx, &b.Header, comm, val, eds) diff --git a/core/listener.go b/core/listener.go index 24d83cda12..7ca408e4fb 100644 --- a/core/listener.go +++ b/core/listener.go @@ -150,7 +150,7 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS attribute.Int64("height", b.Header.Height), ) // extend block data - eds, err := extendBlock(b.Data) + eds, err := extendBlock(b.Data, b.Header.Version.App) if err != nil { return fmt.Errorf("extending block data: %w", err) } diff --git a/go.mod b/go.mod index 36ed440e4a..65d1436afc 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-rc.0 - github.com/BurntSushi/toml v1.3.0 + github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc5 + github.com/celestiaorg/celestia-app v1.0.0-rc8 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.10 github.com/celestiaorg/go-libp2p-messenger v0.2.0 @@ -77,15 +77,15 @@ require ( golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/sync v0.2.0 golang.org/x/text v0.9.0 - google.golang.org/grpc v1.55.0 - google.golang.org/protobuf v1.30.0 + google.golang.org/grpc v1.56.1 + google.golang.org/protobuf v1.31.0 ) require ( cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.12.0 // indirect + cloud.google.com/go/iam v0.13.0 // indirect cloud.google.com/go/storage v1.28.1 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -169,7 +169,7 @@ require ( github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect + github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -312,14 +312,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.110.0 // indirect + google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -335,5 +335,5 @@ replace ( github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.23.0-tm-v0.34.28 ) diff --git a/go.sum b/go.sum index 471f6c47d2..94ac7c3259 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -116,8 +116,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -221,8 +221,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= -github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= @@ -344,10 +344,10 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc5 h1:PjKIp91CQgqyP4MLI3KeF8E4MPHtBPlNoRPvGIMWjHw= -github.com/celestiaorg/celestia-app v1.0.0-rc5/go.mod h1:i+jE3Hh8IQxlZnE5XSBlYF0PCNPvD3v5g8KmY095PEA= -github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28 h1:idHJK9i4WCkYOf5PXVWZbOs8pWkCiHHQGI4MZr0iMtQ= -github.com/celestiaorg/celestia-core v1.22.0-tm-v0.34.28/go.mod h1:LOxHW9nA++/9U8TgvTyKo9TO3F09sWv8asKQs00m73U= +github.com/celestiaorg/celestia-app v1.0.0-rc8 h1:bBQY8tR3DCc8uA7c2ney4AeWwEn9Ob1lc1QryGKVF2M= +github.com/celestiaorg/celestia-app v1.0.0-rc8/go.mod h1:X0R6s+LvfusZu+jBj/2SbTm4Nb/H1R2MD1CnR4fwQno= +github.com/celestiaorg/celestia-core v1.23.0-tm-v0.34.28 h1:G7/rq6xTnuFf3XsVZEcl/Sa6vtagm9NQNhaUaSgjvy0= +github.com/celestiaorg/celestia-core v1.23.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 h1:vaQKgaOm0w58JAvOgn2iDohqjH7kvvRqVKiMcBDWifA= github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13/go.mod h1:G9XkhOJZde36FH0kt/1ayg4ZaioZEQmmRfMa/zQig0I= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= @@ -824,8 +824,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -2321,8 +2321,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2637,8 +2637,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2762,8 +2762,8 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2810,8 +2810,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2828,8 +2828,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 1fb6b10a55..cbe5628d9f 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -24,7 +24,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Arabica: "7A5FABB19713D732D967B1DA84FA0DF5E87A7B62302D783F78743E216C1A3550", - Mocha: "79A97034D569C4199A867439B1B7B77D4E1E1D9697212755E1CE6D920CDBB541", + Mocha: "831B81ADDC5CE999EBB0C150B778F76DAAD9E09DF75FACF164B1F11DCE93E2E1", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", } From eaa58358b21f9ffbb580de48071e2315e55a2b9c Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 6 Jul 2023 19:07:02 +0200 Subject: [PATCH 0691/1008] deps: bump go-header (#2438) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 65d1436afc..70e8de6cc5 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc8 github.com/celestiaorg/go-fraud v0.1.0 - github.com/celestiaorg/go-header v0.2.10 + github.com/celestiaorg/go-header v0.2.11 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.17.0 github.com/celestiaorg/rsmt2d v0.9.0 diff --git a/go.sum b/go.sum index 94ac7c3259..ac183c1c6d 100644 --- a/go.sum +++ b/go.sum @@ -354,8 +354,8 @@ github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPc github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= -github.com/celestiaorg/go-header v0.2.10 h1:KaULxmQ/WJGBeN+rEzTqEEwLD3adkqeD3RM+H8GRHkg= -github.com/celestiaorg/go-header v0.2.10/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= +github.com/celestiaorg/go-header v0.2.11 h1:dLpuUfpGNxFfJNw3Ar3SqWc+AeyT1DlTP5mLjx9Ths8= +github.com/celestiaorg/go-header v0.2.11/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= From 579a06078fc858887a4425a615434e11534c170e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Wed, 12 Jul 2023 17:29:54 +0200 Subject: [PATCH 0692/1008] feat: use context in the common pipeline (#2447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello team! I've created a new version in the common pipeline, this one should fix the issues with the forks and we'll have the version output that we're currently missing when using the docker containers. If this works, we could close the PR: #2421 I've tested on my end and seems to be working crossed_fingers PR where we've done the fix: celestiaorg/.github#65 Thanks in advance! rocket Jose Ramon Mañes celestiaorg/devops#370 --- .github/workflows/docker-build-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index c922e8b901..2ce01c81df 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -18,6 +18,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.0 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.2 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From 60a949e8d06ee142244fb2f20e622d34db02b462 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 12 Jul 2023 17:35:35 +0200 Subject: [PATCH 0693/1008] fix(header): temporary disable skipping verification (#2456) Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- header/headertest/verify_test.go | 8 -------- header/verify.go | 8 -------- nodebuilder/p2p/genesis.go | 2 +- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go index 177352a4cf..33bcf72642 100644 --- a/header/headertest/verify_test.go +++ b/header/headertest/verify_test.go @@ -37,14 +37,6 @@ func TestVerify(t *testing.T) { }, err: true, }, - { - prepare: func() libhead.Header { - untrusted := *untrustedNonAdj - untrusted.Commit = NewTestSuite(t, 2).Commit(RandRawHeader(t)) - return &untrusted - }, - err: true, - }, { prepare: func() libhead.Header { untrusted := *untrustedAdj diff --git a/header/verify.go b/header/verify.go index 18e7f91ea6..827f6c1d1b 100644 --- a/header/verify.go +++ b/header/verify.go @@ -5,8 +5,6 @@ import ( "fmt" "time" - "github.com/tendermint/tendermint/light" - libhead "github.com/celestiaorg/go-header" ) @@ -47,12 +45,6 @@ func (eh *ExtendedHeader) Verify(untrusted libhead.Header) error { return nil } - // Ensure that untrusted commit has enough of trusted commit's power. - err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel) - if err != nil { - return &libhead.VerifyError{Reason: err} - } - return nil } diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index cbe5628d9f..1fb6b10a55 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -24,7 +24,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Arabica: "7A5FABB19713D732D967B1DA84FA0DF5E87A7B62302D783F78743E216C1A3550", - Mocha: "831B81ADDC5CE999EBB0C150B778F76DAAD9E09DF75FACF164B1F11DCE93E2E1", + Mocha: "79A97034D569C4199A867439B1B7B77D4E1E1D9697212755E1CE6D920CDBB541", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", } From c8fa853ee92494de98781b679f494f17c9ad87a2 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 12 Jul 2023 18:54:07 +0300 Subject: [PATCH 0694/1008] share/befp: rework collecting and verifying the BEFP (#2408) * Use an orthogonal axis to collect and verify BEFP instead of collecting and verifying it against a single Merkle Root(where ErrByzantine occurred) * collect only 1/2 of the shares instead of all available shares from the row/col --- share/eds/byzantine/bad_encoding.go | 58 +++++++++++++++++------------ share/eds/byzantine/byzantine.go | 46 +++++++++++++++++------ share/eds/byzantine/share_proof.go | 34 ++++++++++++----- 3 files changed, 94 insertions(+), 44 deletions(-) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 0672096b25..ae77026acf 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -2,7 +2,6 @@ package byzantine import ( "bytes" - "errors" "fmt" "github.com/celestiaorg/celestia-app/pkg/wrapper" @@ -113,50 +112,58 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { func (p *BadEncodingProof) Validate(hdr libhead.Header) error { header, ok := hdr.(*header.ExtendedHeader) if !ok { - panic(fmt.Sprintf("invalid header type: expected %T, got %T", header, hdr)) + panic(fmt.Sprintf("invalid header type received during BEFP validation: expected %T, got %T", header, hdr)) } if header.Height() != int64(p.BlockHeight) { - return errors.New("fraud: incorrect block height") + return fmt.Errorf("incorrect block height during BEFP validation: expected %d, got %d", + p.BlockHeight, header.Height(), + ) } - merkleRowRoots := header.DAH.RowRoots - merkleColRoots := header.DAH.ColumnRoots - if len(merkleRowRoots) != len(merkleColRoots) { + + if len(header.DAH.RowRoots) != len(header.DAH.ColumnRoots) { // NOTE: This should never happen as callers of this method should not feed it with a // malformed extended header. panic(fmt.Sprintf( - "fraud: invalid extended header: length of row and column roots do not match. (rowRoots=%d) (colRoots=%d)", - len(merkleRowRoots), - len(merkleColRoots)), + "invalid extended header: length of row and column roots do not match. (rowRoots=%d) (colRoots=%d)", + len(header.DAH.RowRoots), + len(header.DAH.ColumnRoots)), ) } - if int(p.Index) >= len(merkleRowRoots) { - return fmt.Errorf("fraud: invalid proof: index out of bounds (%d >= %d)", int(p.Index), len(merkleRowRoots)) + + // merkleRoots are the roots against which we are going to check the inclusion of the received + // shares. Changing the order of the roots to prove the shares relative to the orthogonal axis, + // because inside the rsmt2d library rsmt2d.Row = 0 and rsmt2d.Col = 1 + merkleRoots := header.DAH.RowRoots + if p.Axis == rsmt2d.Row { + merkleRoots = header.DAH.ColumnRoots } - if len(merkleRowRoots) != len(p.Shares) { - return fmt.Errorf("fraud: invalid proof: incorrect number of shares %d != %d", len(p.Shares), len(merkleRowRoots)) + if int(p.Index) >= len(merkleRoots) { + return fmt.Errorf("invalid %s proof: index out of bounds (%d >= %d)", + BadEncoding, int(p.Index), len(merkleRoots), + ) } - - root := merkleRowRoots[p.Index] - if p.Axis == rsmt2d.Col { - root = merkleColRoots[p.Index] + if len(p.Shares) != len(merkleRoots) { + return fmt.Errorf("invalid %s proof: incorrect number of shares %d != %d", + BadEncoding, len(p.Shares), len(merkleRoots), + ) } // verify that Merkle proofs correspond to particular shares. - shares := make([][]byte, len(merkleRowRoots)) + shares := make([][]byte, len(merkleRoots)) for index, shr := range p.Shares { if shr == nil { continue } // validate inclusion of the share into one of the DAHeader roots - if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(root)); !ok { - return fmt.Errorf("fraud: invalid proof: incorrect share received at index %d", index) + if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(merkleRoots[index])); !ok { + return fmt.Errorf("invalid %s proof: incorrect share received at index %d", BadEncoding, index) } // NMTree commits the additional namespace while rsmt2d does not know about, so we trim it // this is ugliness from NMTWrapper that we have to embrace ¯\_(ツ)_/¯ shares[index] = share.GetData(shr.Share) } - odsWidth := uint64(len(merkleRowRoots) / 2) + odsWidth := uint64(len(merkleRoots) / 2) codec := share.DefaultRSMT2DCodec() // rebuild a row or col. @@ -183,10 +190,15 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { return err } + // root is a merkle root of the row/col where ErrByzantine occurred + root := header.DAH.RowRoots[p.Index] + if p.Axis == rsmt2d.Col { + root = header.DAH.ColumnRoots[p.Index] + } + // comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block. if bytes.Equal(expectedRoot, root) { - return errors.New("fraud: invalid proof: recomputed Merkle root matches the DAH's row/column root") + return fmt.Errorf("invalid %s proof: recomputed Merkle root matches the DAH's row/column root", BadEncoding) } - return nil } diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index b9c8ef414f..0fcd78273e 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ipfs/go-blockservice" + "golang.org/x/sync/errgroup" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" @@ -35,17 +36,41 @@ func NewErrByzantine( dah *da.DataAvailabilityHeader, errByz *rsmt2d.ErrByzantineData, ) *ErrByzantine { - root := [][][]byte{ - dah.RowRoots, + // changing the order to collect proofs against an orthogonal axis + roots := [][][]byte{ dah.ColumnRoots, - }[errByz.Axis][errByz.Index] - sharesWithProof, err := GetProofsForShares( - ctx, - bGetter, - ipld.MustCidFromNamespacedSha256(root), - errByz.Shares, - ) - if err != nil { + dah.RowRoots, + }[errByz.Axis] + + sharesWithProof := make([]*ShareWithProof, len(errByz.Shares)) + sharesAmount := 0 + + errGr, ctx := errgroup.WithContext(ctx) + for index, share := range errByz.Shares { + // skip further shares if we already requested half of them, which is enough to recompute the row + // or col + if sharesAmount == len(dah.RowRoots)/2 { + break + } + + if share == nil { + continue + } + sharesAmount++ + + index := index + errGr.Go(func() error { + share, err := getProofsAt( + ctx, bGetter, + ipld.MustCidFromNamespacedSha256(roots[index]), + int(errByz.Index), len(errByz.Shares), + ) + sharesWithProof[index] = share + return err + }) + } + + if err := errGr.Wait(); err != nil { // Fatal as rsmt2d proved that error is byzantine, // but we cannot properly collect the proof, // so verification will fail and thus services won't be stopped @@ -53,7 +78,6 @@ func NewErrByzantine( // TODO(@Wondertan): Find a better way to handle log.Fatalw("getting proof for ErrByzantine", "err", err) } - return &ErrByzantine{ Index: uint32(errByz.Index), Shares: sharesWithProof, diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index b8e39ee1d3..d6aa9dad51 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -78,24 +78,38 @@ func GetProofsForShares( proofs := make([]*ShareWithProof, len(shares)) for index, share := range shares { if share != nil { - proof := make([]cid.Cid, 0) - // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same - // tree. Add options that will control what data will be fetched. - s, err := ipld.GetLeaf(ctx, bGetter, root, index, len(shares)) + proof, err := getProofsAt(ctx, bGetter, root, index, len(shares)) if err != nil { return nil, err } - proof, err = ipld.GetProof(ctx, bGetter, root, proof, index, len(shares)) - if err != nil { - return nil, err - } - proofs[index] = NewShareWithProof(index, s.RawData(), proof) + proofs[index] = proof } } - return proofs, nil } +func getProofsAt( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + index, + total int, +) (*ShareWithProof, error) { + proof := make([]cid.Cid, 0) + // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same + // tree. Add options that will control what data will be fetched. + node, err := ipld.GetLeaf(ctx, bGetter, root, index, total) + if err != nil { + return nil, err + } + + proof, err = ipld.GetProof(ctx, bGetter, root, proof, index, total) + if err != nil { + return nil, err + } + return NewShareWithProof(index, node.RawData(), proof), nil +} + func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { shares := make([]*ShareWithProof, len(protoShares)) for i, share := range protoShares { From 7fe4fee1b1602c20dffdae69f4e15c820d0f6bc8 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:32:54 -0500 Subject: [PATCH 0695/1008] chore: bump celestia app v1.0.0-rc9 (#2458) Co-authored-by: Rootul Patel Co-authored-by: Wondertan --- core/eds_test.go | 3 +- go.mod | 8 ++-- go.sum | 16 ++++---- header/header.go | 10 ++++- header/headertest/testing.go | 6 ++- nodebuilder/share/constructors.go | 7 +++- nodebuilder/share/share_test.go | 3 +- nodebuilder/tests/swamp/config.go | 6 +-- share/availability/light/availability_test.go | 3 +- share/availability/test/testing.go | 3 +- share/eds/byzantine/bad_encoding_test.go | 8 ++-- share/eds/byzantine/share_proof_test.go | 6 ++- share/eds/eds.go | 24 ++++++++++-- share/eds/eds_test.go | 36 +++++++++++++----- share/eds/ods_test.go | 14 ++++++- share/eds/retriever.go | 8 ++-- share/eds/retriever_test.go | 9 +++-- share/eds/store_test.go | 11 ++++-- share/empty.go | 5 ++- share/getters/getter_test.go | 6 ++- share/getters/shrex_test.go | 3 +- share/getters/testing.go | 9 ++++- share/ipld/add.go | 10 ++++- share/ipld/get_shares_test.go | 38 +++++++++++++------ share/ipld/nmt_test.go | 3 +- share/p2p/shrexeds/exchange_test.go | 11 ++++-- share/p2p/shrexnd/exchange_test.go | 3 +- 27 files changed, 188 insertions(+), 81 deletions(-) diff --git a/core/eds_test.go b/core/eds_test.go index 2cc6f1c7cc..6a2026ee58 100644 --- a/core/eds_test.go +++ b/core/eds_test.go @@ -47,6 +47,7 @@ func TestEmptySquareWithZeroTxs(t *testing.T) { eds, err = app.ExtendBlock(data, appconsts.LatestVersion) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) assert.Equal(t, share.EmptyRoot().Hash(), dah.Hash()) } diff --git a/go.mod b/go.mod index 70e8de6cc5..f7af093ece 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,12 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc8 + github.com/celestiaorg/celestia-app v1.0.0-rc9 github.com/celestiaorg/go-fraud v0.1.0 github.com/celestiaorg/go-header v0.2.11 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.17.0 - github.com/celestiaorg/rsmt2d v0.9.0 + github.com/celestiaorg/rsmt2d v0.10.0 github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 @@ -330,10 +330,10 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.23.0-tm-v0.34.28 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 ) diff --git a/go.sum b/go.sum index ac183c1c6d..7d3cc73274 100644 --- a/go.sum +++ b/go.sum @@ -344,12 +344,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc8 h1:bBQY8tR3DCc8uA7c2ney4AeWwEn9Ob1lc1QryGKVF2M= -github.com/celestiaorg/celestia-app v1.0.0-rc8/go.mod h1:X0R6s+LvfusZu+jBj/2SbTm4Nb/H1R2MD1CnR4fwQno= -github.com/celestiaorg/celestia-core v1.23.0-tm-v0.34.28 h1:G7/rq6xTnuFf3XsVZEcl/Sa6vtagm9NQNhaUaSgjvy0= -github.com/celestiaorg/celestia-core v1.23.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= -github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 h1:vaQKgaOm0w58JAvOgn2iDohqjH7kvvRqVKiMcBDWifA= -github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13/go.mod h1:G9XkhOJZde36FH0kt/1ayg4ZaioZEQmmRfMa/zQig0I= +github.com/celestiaorg/celestia-app v1.0.0-rc9 h1:6xDYE+OziXO/rLeYy/MutnJpE8M2sIPryZ/ifSWUmdc= +github.com/celestiaorg/celestia-app v1.0.0-rc9/go.mod h1:aGFnIIdA30DtFzznYbcfMdNnXiUebfEUkkrQu8imC3I= +github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHVxcTzhzQW44KgTcooR3OxnK4= +github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= +github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13 h1:N1PrCWcYkaODeIQyyVBmDKDTwiQWZ31bgtTEYIGeby8= +github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13/go.mod h1:xpBZc/OYZ736hp0IZlBGNUhEgCD9C+bKs8yNLZibyv0= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= @@ -366,8 +366,8 @@ github.com/celestiaorg/nmt v0.17.0 h1:/k8YLwJvuHgT/jQ435zXKaDX811+sYEMXL4B/vYdSL github.com/celestiaorg/nmt v0.17.0/go.mod h1:ZndCeAR4l9lxm7W51ouoyTo1cxhtFgK+4DpEIkxRA3A= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= -github.com/celestiaorg/rsmt2d v0.9.0 h1:kon78I748ZqjNzI8OAqPN+2EImuZuanj/6gTh8brX3o= -github.com/celestiaorg/rsmt2d v0.9.0/go.mod h1:E06nDxfoeBDltWRvTR9dLviiUZI5/6mLXAuhSJzz3Iw= +github.com/celestiaorg/rsmt2d v0.10.0 h1:8dprr6CW5mCk5YPnbiLdirojw9YsJOE+XB+GORb8sT0= +github.com/celestiaorg/rsmt2d v0.10.0/go.mod h1:BiCZkCJfhDHUEOJKXUeu+CudjluecKvRTqHcuxKvodc= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= diff --git a/header/header.go b/header/header.go index e59d3802c1..0e386be8b5 100644 --- a/header/header.go +++ b/header/header.go @@ -75,12 +75,18 @@ func MakeExtendedHeader( vals *core.ValidatorSet, eds *rsmt2d.ExtendedDataSquare, ) (*ExtendedHeader, error) { - var dah DataAvailabilityHeader + var ( + dah DataAvailabilityHeader + err error + ) switch eds { case nil: dah = EmptyDAH() default: - dah = da.NewDataAvailabilityHeader(eds) + dah, err = da.NewDataAvailabilityHeader(eds) + } + if err != nil { + return nil, err } eh := &ExtendedHeader{ diff --git a/header/headertest/testing.go b/header/headertest/testing.go index ceeb9bb164..b20d389452 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -325,7 +325,8 @@ func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader { valSet, vals := RandValidatorSet(10, 10) gen := RandRawHeader(t) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) gen.DataHash = dah.Hash() gen.ValidatorsHash = valSet.Hash() @@ -355,7 +356,8 @@ func CreateFraudExtHeader( square := edstest.RandByzantineEDS(t, len(eh.DAH.RowRoots)) err := ipld.ImportEDS(context.Background(), square, serv) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(square) + dah, err := da.NewDataAvailabilityHeader(square) + require.NoError(t, err) eh.DAH = &dah eh.RawHeader.DataHash = dah.Hash() return eh, square diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 5cb0e41e53..a1b7e39713 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -53,9 +53,12 @@ func newModule(getter share.Getter, avail share.Availability) Module { // ensureEmptyCARExists adds an empty EDS to the provided EDS store. func ensureEmptyCARExists(ctx context.Context, store *eds.Store) error { emptyEDS := share.EmptyExtendedDataSquare() - emptyDAH := da.NewDataAvailabilityHeader(emptyEDS) + emptyDAH, err := da.NewDataAvailabilityHeader(emptyEDS) + if err != nil { + return err + } - err := store.Put(ctx, emptyDAH.Hash(), emptyEDS) + err = store.Put(ctx, emptyDAH.Hash(), emptyEDS) if errors.Is(err, dagstore.ErrShardExists) { return nil } diff --git a/nodebuilder/share/share_test.go b/nodebuilder/share/share_test.go index 388fd9af07..7c440a6dbf 100644 --- a/nodebuilder/share/share_test.go +++ b/nodebuilder/share/share_test.go @@ -27,7 +27,8 @@ func Test_EmptyCARExists(t *testing.T) { require.NoError(t, err) eds := share.EmptyExtendedDataSquare() - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) // add empty EDS to store err = ensureEmptyCARExists(ctx, edsStore) diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index 630920609f..e6c06b40cc 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -15,8 +15,8 @@ type Config struct { // 100ms func DefaultConfig() *Config { cfg := core.DefaultTestConfig() - // target height duration lower than this tend to be flakier - cfg.Tendermint.Consensus.TargetHeightDuration = 200 * time.Millisecond + // timeout commit lower than this tend to be flakier + cfg.Tendermint.Consensus.TimeoutCommit = 200 * time.Millisecond return &Config{ cfg, } @@ -31,7 +31,7 @@ func WithBlockTime(t time.Duration) Option { // for empty block c.Tendermint.Consensus.CreateEmptyBlocksInterval = t // for filled block - c.Tendermint.Consensus.TargetHeightDuration = t + c.Tendermint.Consensus.TimeoutCommit = t c.Tendermint.Consensus.SkipTimeoutCommit = false } } diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 1709c3f4b7..48813a33f9 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -132,7 +132,8 @@ func TestGetShares(t *testing.T) { eds, err := getter.GetEDS(ctx, dah) require.NoError(t, err) - gotDAH := da.NewDataAvailabilityHeader(eds) + gotDAH, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) require.True(t, dah.Equals(&gotDAH)) } diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 19f63f114a..27d6669061 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -34,7 +34,8 @@ func RandFillBS(t *testing.T, n int, bServ blockservice.BlockService) *share.Roo func FillBS(t *testing.T, bServ blockservice.BlockService, shares []share.Share) *share.Root { eds, err := ipld.AddShares(context.TODO(), shares, bServ) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) return &dah } diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 5f6adac595..49cf64c2c2 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -25,8 +25,9 @@ func TestBadEncodingFraudProof(t *testing.T) { bServ := mdutils.Bserv() square := edstest.RandByzantineEDS(t, 16) - dah := da.NewDataAvailabilityHeader(square) - err := ipld.ImportEDS(ctx, square, bServ) + dah, err := da.NewDataAvailabilityHeader(square) + require.NoError(t, err) + err = ipld.ImportEDS(ctx, square, bServ) require.NoError(t, err) var errRsmt2d *rsmt2d.ErrByzantineData @@ -55,7 +56,8 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { eds, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) // get an arbitrary row row := uint(squareSize / 2) diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go index 0f63d4f0c9..db1db64f80 100644 --- a/share/eds/byzantine/share_proof_test.go +++ b/share/eds/byzantine/share_proof_test.go @@ -27,7 +27,8 @@ func TestGetProof(t *testing.T) { in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(in) + dah, err := da.NewDataAvailabilityHeader(in) + require.NoError(t, err) var tests = []struct { roots [][]byte }{ @@ -63,7 +64,8 @@ func TestGetProofs(t *testing.T) { in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(in) + dah, err := da.NewDataAvailabilityHeader(in) + require.NoError(t, err) for _, root := range dah.ColumnRoots { rootCid := ipld.MustCidFromNamespacedSha256(root) data := make([][]byte, 0, in.Width()) diff --git a/share/eds/eds.go b/share/eds/eds.go index 544f3c2438..cc775491c9 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -102,7 +102,10 @@ func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io. return nil, fmt.Errorf("recomputing data square: %w", err) } // compute roots - eds.RowRoots() + _, err = eds.RowRoots() + if err != nil { + return nil, fmt.Errorf("computing row roots: %w", err) + } // commit the batch to DAG err = batchAdder.Commit() if err != nil { @@ -231,8 +234,18 @@ func prependNamespace(quadrant int, shr share.Share) []byte { // rootsToCids converts the EDS's Row and Column roots to CIDs. func rootsToCids(eds *rsmt2d.ExtendedDataSquare) ([]cid.Cid, error) { - var err error - roots := append(eds.RowRoots(), eds.ColRoots()...) + rowRoots, err := eds.RowRoots() + if err != nil { + return nil, err + } + colRoots, err := eds.ColRoots() + if err != nil { + return nil, err + } + + roots := make([][]byte, 0, len(rowRoots)+len(colRoots)) + roots = append(roots, rowRoots...) + roots = append(roots, colRoots...) rootCids := make([]cid.Cid, len(roots)) for i, r := range roots { rootCids[i], err = ipld.CidFromNamespacedSha256(r) @@ -283,7 +296,10 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (eds *rsmt2d return nil, fmt.Errorf("share: computing eds: %w", err) } - newDah := da.NewDataAvailabilityHeader(eds) + newDah, err := da.NewDataAvailabilityHeader(eds) + if err != nil { + return nil, err + } if !bytes.Equal(newDah.Hash(), root) { return nil, fmt.Errorf( "share: content integrity mismatch: imported root %s doesn't match expected root %s", diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 0ef211ec6f..af870f60b3 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -167,14 +167,25 @@ func TestInnerNodeBatchSize(t *testing.T) { func TestReadWriteRoundtrip(t *testing.T) { eds := writeRandomEDS(t) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) f := openWrittenEDS(t) defer f.Close() loaded, err := ReadEDS(context.Background(), f, dah.Hash()) require.NoError(t, err, "error reading EDS from file") - require.Equal(t, eds.RowRoots(), loaded.RowRoots()) - require.Equal(t, eds.ColRoots(), loaded.ColRoots()) + + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + loadedRowRoots, err := loaded.RowRoots() + require.NoError(t, err) + require.Equal(t, rowRoots, loadedRowRoots) + + colRoots, err := eds.ColRoots() + require.NoError(t, err) + loadedColRoots, err := loaded.ColRoots() + require.NoError(t, err) + require.Equal(t, colRoots, loadedColRoots) } func TestReadEDS(t *testing.T) { @@ -187,17 +198,22 @@ func TestReadEDS(t *testing.T) { loaded, err := ReadEDS(context.Background(), f, dah.Hash()) require.NoError(t, err, "error reading EDS from file") - require.Equal(t, dah.RowRoots, loaded.RowRoots()) - require.Equal(t, dah.ColumnRoots, loaded.ColRoots()) + rowRoots, err := loaded.RowRoots() + require.NoError(t, err) + require.Equal(t, dah.RowRoots, rowRoots) + colRoots, err := loaded.ColRoots() + require.NoError(t, err) + require.Equal(t, dah.ColumnRoots, colRoots) } func TestReadEDSContentIntegrityMismatch(t *testing.T) { writeRandomEDS(t) - dah := da.NewDataAvailabilityHeader(edstest.RandEDS(t, 4)) + dah, err := da.NewDataAvailabilityHeader(edstest.RandEDS(t, 4)) + require.NoError(t, err) f := openWrittenEDS(t) defer f.Close() - _, err := ReadEDS(context.Background(), f, dah.Hash()) + _, err = ReadEDS(context.Background(), f, dah.Hash()) require.ErrorContains(t, err, "share: content integrity mismatch: imported root") } @@ -209,7 +225,8 @@ func BenchmarkReadWriteEDS(b *testing.B) { b.Cleanup(cancel) for originalDataWidth := 4; originalDataWidth <= 64; originalDataWidth *= 2 { eds := edstest.RandEDS(b, originalDataWidth) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) b.Run(fmt.Sprintf("Writing %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { @@ -280,7 +297,8 @@ func createTestData(t *testing.T, testDir string) { //nolint:unused err = WriteEDS(ctx, eds, f) require.NoError(t, err, "writing EDS to file") f.Close() - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) header, err := json.MarshalIndent(dah, "", "") require.NoError(t, err, "marshaling example root") diff --git a/share/eds/ods_test.go b/share/eds/ods_test.go index eb243a4022..5b6ed5568b 100644 --- a/share/eds/ods_test.go +++ b/share/eds/ods_test.go @@ -89,6 +89,16 @@ func TestODSReaderReconstruction(t *testing.T) { // reconstruct EDS from ODSReader loaded, err := ReadEDS(ctx, odsR, dah.Hash()) assert.NoError(t, err) - require.Equal(t, eds.RowRoots(), loaded.RowRoots()) - require.Equal(t, eds.ColRoots(), loaded.ColRoots()) + + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + loadedRowRoots, err := loaded.RowRoots() + require.NoError(t, err) + require.Equal(t, rowRoots, loadedRowRoots) + + colRoots, err := eds.ColRoots() + require.NoError(t, err) + loadedColRoots, err := loaded.ColRoots() + require.NoError(t, err) + require.Equal(t, colRoots, loadedColRoots) } diff --git a/share/eds/retriever.go b/share/eds/retriever.go index b3cf363056..2483a37a92 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -127,7 +127,7 @@ func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHead return &tree } - square, err := rsmt2d.ImportExtendedDataSquare(make([][]byte, size*size), share.DefaultRSMT2DCodec(), treeFn) + square, err := rsmt2d.NewExtendedDataSquare(share.DefaultRSMT2DCodec(), treeFn, uint(size), share.Size) if err != nil { return nil, err } @@ -283,10 +283,12 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { if rs.isReconstructed() { return } - if rs.square.GetCell(uint(x), uint(y)) != nil { + if err := rs.square.SetCell(uint(x), uint(y), share); err != nil { + // safe to ignore as: + // * share size already verified + // * the same share might come from either Row or Col return } - rs.square.SetCell(uint(x), uint(y), share) // if we have >= 1/4 of the square we can start trying to Reconstruct // TODO(@Wondertan): This is not an ideal way to know when to start // reconstruction and can cause idle reconstruction tries in some cases, diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index c90697862f..2277f894a1 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -59,7 +59,8 @@ func TestRetriever_Retrieve(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Minute*5) // the timeout is big for the max size which is long defer cancel() - dah := da.NewDataAvailabilityHeader(in) + dah, err := da.NewDataAvailabilityHeader(in) + require.NoError(t, err) out, err := r.Retrieve(ctx, &dah) require.NoError(t, err) assert.True(t, share.EqualEDS(in, out)) @@ -93,7 +94,8 @@ func TestRetriever_ByzantineError(t *testing.T) { require.NoError(t, err) // ensure we rcv an error - dah := da.NewDataAvailabilityHeader(attackerEDS) + dah, err := da.NewDataAvailabilityHeader(attackerEDS) + require.NoError(t, err) r := NewRetriever(bserv) _, err = r.Retrieve(ctx, &dah) var errByz *byzantine.ErrByzantine @@ -116,7 +118,8 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { in, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) - dah := da.NewDataAvailabilityHeader(in) + dah, err := da.NewDataAvailabilityHeader(in) + require.NoError(t, err) ses, err := r.newSession(ctx, &dah) require.NoError(t, err) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 7f2f548f63..491b63c48a 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -271,7 +271,8 @@ func BenchmarkStore(b *testing.B) { // pause the timer for initializing test data b.StopTimer() eds := edstest.RandEDS(b, 128) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) b.StartTimer() err = edsStore.Put(ctx, dah.Hash(), eds) @@ -286,11 +287,12 @@ func BenchmarkStore(b *testing.B) { // pause the timer for initializing test data b.StopTimer() eds := edstest.RandEDS(b, 128) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) _ = edsStore.Put(ctx, dah.Hash(), eds) b.StartTimer() - _, err := edsStore.Get(ctx, dah.Hash()) + _, err = edsStore.Get(ctx, dah.Hash()) require.NoError(b, err) } }) @@ -306,7 +308,8 @@ func newStore(t *testing.T) (*Store, error) { func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) return eds, dah } diff --git a/share/empty.go b/share/empty.go index b0e1e6e6ae..07d48f2f07 100644 --- a/share/empty.go +++ b/share/empty.go @@ -54,7 +54,10 @@ func initEmpty() { } emptyBlockEDS = eds - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + if err != nil { + panic(fmt.Errorf("failed to create empty DAH: %w", err)) + } minDAH := da.MinDataAvailabilityHeader() if !bytes.Equal(minDAH.Hash(), dah.Hash()) { panic(fmt.Sprintf("mismatch in calculated minimum DAH and minimum DAH from celestia-app, "+ diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 7af8af2f26..571129f029 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -232,7 +232,8 @@ func TestIPLDGetter(t *testing.T) { func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) return eds, dah } @@ -260,7 +261,8 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData wrapper.NewConstructor(uint64(size)), ) require.NoError(t, err, "failure to recompute the extended data square") - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) return eds, share.GetNamespace(randShares[idx1]), dah } diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index e1628d8725..0dbe7e44db 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -198,7 +198,8 @@ func newStore(t *testing.T) (*eds.Store, error) { func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, share.Namespace) { eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) max := nmt.MaxNamespace(dah.RowRoots[(len(dah.RowRoots))/2-1], share.NamespaceSize) return eds, dah, max } diff --git a/share/getters/testing.go b/share/getters/testing.go index 4734557d7f..3a15bb5bf2 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -7,6 +7,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" + "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" @@ -15,7 +16,8 @@ import ( // TestGetter provides a testing SingleEDSGetter and the root of the EDS it holds. func TestGetter(t *testing.T) (share.Getter, *share.Root) { eds := edstest.RandEDS(t, 8) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) return &SingleEDSGetter{ EDS: eds, }, &dah @@ -52,7 +54,10 @@ func (seg *SingleEDSGetter) GetSharesByNamespace(context.Context, *share.Root, s } func (seg *SingleEDSGetter) checkRoot(root *share.Root) error { - dah := da.NewDataAvailabilityHeader(seg.EDS) + dah, err := da.NewDataAvailabilityHeader(seg.EDS) + if err != nil { + return err + } if !root.Equals(&dah) { return fmt.Errorf("unknown EDS: have %s, asked %s", dah.String(), root.String()) } diff --git a/share/ipld/add.go b/share/ipld/add.go index fddfe8e99e..5807a320fd 100644 --- a/share/ipld/add.go +++ b/share/ipld/add.go @@ -39,7 +39,10 @@ func AddShares( return nil, fmt.Errorf("failure to recompute the extended data square: %w", err) } // compute roots - eds.RowRoots() + _, err = eds.RowRoots() + if err != nil { + return nil, err + } // commit the batch to ipfs return eds, batchAdder.Commit() } @@ -67,7 +70,10 @@ func ImportShares( return nil, fmt.Errorf("failure to recompute the extended data square: %w", err) } // compute roots - eds.RowRoots() + _, err = eds.RowRoots() + if err != nil { + return nil, err + } // commit the batch to DAG return eds, batchAdder.Commit() } diff --git a/share/ipld/get_shares_test.go b/share/ipld/get_shares_test.go index cd26f759b3..7ee7704fc0 100644 --- a/share/ipld/get_shares_test.go +++ b/share/ipld/get_shares_test.go @@ -45,7 +45,9 @@ func TestGetShare(t *testing.T) { for i, leaf := range shares { row := i / size pos := i - (size * row) - share, err := GetShare(ctx, bServ, MustCidFromNamespacedSha256(eds.RowRoots()[row]), pos, size*2) + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + share, err := GetShare(ctx, bServ, MustCidFromNamespacedSha256(rowRoots[row]), pos, size*2) require.NoError(t, err) assert.Equal(t, leaf, share) } @@ -87,8 +89,10 @@ func TestBlockRecovery(t *testing.T) { require.NoError(t, err) // calculate roots using the first complete square - rowRoots := testEds.RowRoots() - colRoots := testEds.ColRoots() + rowRoots, err := testEds.RowRoots() + require.NoError(t, err) + colRoots, err := testEds.ColRoots() + require.NoError(t, err) flat := share.ExtractEDS(testEds) @@ -173,9 +177,11 @@ func TestGetSharesByNamespace(t *testing.T) { require.NoError(t, err) var shares []share.Share - for _, row := range eds.RowRoots() { + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + for _, row := range rowRoots { rcid := MustCidFromNamespacedSha256(row) - rowShares, _, err := GetSharesByNamespace(ctx, bServ, rcid, namespace, len(eds.RowRoots())) + rowShares, _, err := GetSharesByNamespace(ctx, bServ, rcid, namespace, len(rowRoots)) if errors.Is(err, ErrNamespaceOutsideRange) { continue } @@ -208,7 +214,8 @@ func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { eds, err := AddShares(ctx, shares, bServ) require.NoError(t, err) - roots := eds.RowRoots() + roots, err := eds.RowRoots() + require.NoError(t, err) // remove the second share from the first row rcid := MustCidFromNamespacedSha256(roots[0]) @@ -302,7 +309,10 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi eds, err := AddShares(ctx, shares, bServ) require.NoError(t, err) - for _, row := range eds.RowRoots() { + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + + for _, row := range rowRoots { rcid := MustCidFromNamespacedSha256(row) data := NewNamespaceData(len(shares), namespace, WithLeaves()) err := data.CollectLeavesByNamespace(ctx, bServ, rcid) @@ -356,9 +366,11 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { require.NoError(t, err) var shares []share.Share - for _, row := range eds.RowRoots() { + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + for _, row := range rowRoots { rcid := MustCidFromNamespacedSha256(row) - rowShares, proof, err := GetSharesByNamespace(ctx, bServ, rcid, namespace, len(eds.RowRoots())) + rowShares, proof, err := GetSharesByNamespace(ctx, bServ, rcid, namespace, len(rowRoots)) if namespace.IsOutsideRange(row, row) { require.ErrorIs(t, err, ErrNamespaceOutsideRange) continue @@ -446,16 +458,18 @@ func assertNoRowContainsNID( namespace share.Namespace, isAbsent bool, ) { - rowRootCount := len(eds.RowRoots()) + rowRoots, err := eds.RowRoots() + require.NoError(t, err) + rowRootCount := len(rowRoots) // get all row root cids rowRootCIDs := make([]cid.Cid, rowRootCount) - for i, rowRoot := range eds.RowRoots() { + for i, rowRoot := range rowRoots { rowRootCIDs[i] = MustCidFromNamespacedSha256(rowRoot) } // for each row root cid check if the min namespace exists var absentCount, foundAbsenceRows int - for _, rowRoot := range eds.RowRoots() { + for _, rowRoot := range rowRoots { var outsideRange bool if !namespace.IsOutsideRange(rowRoot, rowRoot) { // namespace does belong to namespace range of the row diff --git a/share/ipld/nmt_test.go b/share/ipld/nmt_test.go index aa125ab3c7..77268d7112 100644 --- a/share/ipld/nmt_test.go +++ b/share/ipld/nmt_test.go @@ -26,7 +26,8 @@ func TestNamespaceFromCID(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - dah := da.NewDataAvailabilityHeader(tt.eds) + dah, err := da.NewDataAvailabilityHeader(tt.eds) + require.NoError(t, err) // check to make sure NamespacedHash is correctly derived from CID for _, row := range dah.RowRoots { c, err := CidFromNamespacedSha256(row) diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 21fe9f77a1..b1d2e0ad18 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -35,7 +35,8 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is immediately available t.Run("EDS_Available", func(t *testing.T) { eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) err = store.Put(ctx, dah.Hash(), eds) require.NoError(t, err) @@ -48,7 +49,8 @@ func TestExchange_RequestEDS(t *testing.T) { t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { storageDelay := time.Second eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) go func() { time.Sleep(storageDelay) err = store.Put(ctx, dah.Hash(), eds) @@ -77,8 +79,9 @@ func TestExchange_RequestEDS(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) - _, err := client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) + _, err = client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) require.ErrorIs(t, err, p2p.ErrNotFound) }) diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index e8d3e439c0..8d1dad17c0 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -45,7 +45,8 @@ func TestExchange_RequestND_NotFound(t *testing.T) { t.Cleanup(cancel) eds := edstest.RandEDS(t, 4) - dah := da.NewDataAvailabilityHeader(eds) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) randNamespace := dah.RowRoots[(len(dah.RowRoots)-1)/2][:share.NamespaceSize] From 889f56953afa23e21f02c52cf4d6f4c9a8dd92a1 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 12 Jul 2023 21:04:44 +0300 Subject: [PATCH 0696/1008] fix(lint): fix importing (#2467) --- share/getters/testing.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/getters/testing.go b/share/getters/testing.go index 3a15bb5bf2..71c6231f3c 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -5,9 +5,10 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" - "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" From e41f62662cf1844ba52d7a93d03cf79b6e363897 Mon Sep 17 00:00:00 2001 From: Javed Khan Date: Wed, 12 Jul 2023 13:31:12 -0700 Subject: [PATCH 0697/1008] cmd: rpc.go fix error parsing height message (#2469) Fixing the error message returned by the rpc cli client, when an invalid height is passed to the `Get`, `GetAll` commands of the Blob module. --- cmd/celestia/rpc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 169ca4b8d1..64b21aaf86 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -195,7 +195,7 @@ func parseParams(method string, params []string) []interface{} { // 1. Height num, err := strconv.ParseUint(params[0], 10, 64) if err != nil { - panic("Error parsing gas limit: uint64 could not be parsed.") + panic("Error parsing height: uint64 could not be parsed.") } parsedParams[0] = num // 2. NamespaceID @@ -215,7 +215,7 @@ func parseParams(method string, params []string) []interface{} { // 1. Height num, err := strconv.ParseUint(params[0], 10, 64) if err != nil { - panic("Error parsing gas limit: uint64 could not be parsed.") + panic("Error parsing height: uint64 could not be parsed.") } parsedParams[0] = num // 2. Namespace From 85cec8e82a53b1154034e162f16cac8441406c5c Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 13 Jul 2023 14:26:43 +0200 Subject: [PATCH 0698/1008] fix(nodebuilder): fix nil pointer exception and simplify synchronization (#2466) We hit an infamous use case here where nil is not nil. Also, I simplified locking here while thinking that's a race condition. --- nodebuilder/store.go | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 046dd41bbd..f0957a16b6 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -104,24 +104,18 @@ func (f *fsStore) PutConfig(cfg *Config) error { } func (f *fsStore) Keystore() (_ keystore.Keystore, err error) { - f.lock.RLock() - defer f.lock.RUnlock() if f.keys == nil { return nil, fmt.Errorf("node: no Keystore found") } return f.keys, nil } -func (f *fsStore) Datastore() (_ datastore.Batching, err error) { - f.lock.RLock() +func (f *fsStore) Datastore() (datastore.Batching, error) { + f.dataMu.Lock() + defer f.dataMu.Unlock() if f.data != nil { - f.lock.RUnlock() return f.data, nil } - f.lock.RUnlock() - - f.lock.Lock() - defer f.lock.Unlock() opts := dsbadger.DefaultOptions // this should be copied @@ -144,29 +138,31 @@ func (f *fsStore) Datastore() (_ datastore.Batching, err error) { // TODO(@Wondertan): Make configurable with more conservative defaults for Light Node opts.MaxTableSize = 64 << 20 - f.data, err = dsbadger.NewDatastore(dataPath(f.path), &opts) + ds, err := dsbadger.NewDatastore(dataPath(f.path), &opts) if err != nil { return nil, fmt.Errorf("node: can't open Badger Datastore: %w", err) } - return f.data, nil + f.data = ds + return ds, nil } func (f *fsStore) Close() (err error) { err = errors.Join(err, f.dirLock.Unlock()) + f.dataMu.Lock() if f.data != nil { err = errors.Join(err, f.data.Close()) } + f.dataMu.Unlock() return } type fsStore struct { path string - data datastore.Batching - keys keystore.Keystore - - lock sync.RWMutex // protects all the fields + dataMu sync.Mutex + data datastore.Batching + keys keystore.Keystore dirLock *fslock.Locker // protects directory } From eea06684bf5b012489c2400c305aa64793eb51b0 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Thu, 13 Jul 2023 10:04:18 -0400 Subject: [PATCH 0699/1008] docs: clarify usage of `len(merkleRowRoots)` (#2431) Closes https://github.com/celestiaorg/celestia-node/issues/2392 Note: this doesn't introduce a new variable name (the proposed solution in the issue). Happy to do so if reviewers request. --- share/eds/byzantine/bad_encoding.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index ae77026acf..9d6cbff229 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -143,6 +143,10 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { ) } if len(p.Shares) != len(merkleRoots) { + // Since p.Shares should contain all the shares from either a row or a + // column, it should exactly match the number of row roots. In this + // context, the number of row roots is the width of the extended data + // square. return fmt.Errorf("invalid %s proof: incorrect number of shares %d != %d", BadEncoding, len(p.Shares), len(merkleRoots), ) From 360ba6b447504b9c2f89931463b994e8b0b5b8c3 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 13 Jul 2023 16:50:48 +0200 Subject: [PATCH 0700/1008] chore(share/p2p/discovery): demote disc peers log to debug (#2473) self explaanory --- share/p2p/discovery/discovery.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index 96eda8bd78..9779d72195 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -251,7 +251,10 @@ func (d *Discovery) discover(ctx context.Context) bool { log.Debugw("reached soft peer limit, skipping discovery", "size", size) return true } - log.Infow("discovering peers", "want", want) + // TODO @renaynay: eventually, have a mechanism to catch if wanted amount of peers + // has not been discovered in X amount of time so that users are warned of degraded + // FN connectivity. + log.Debugw("discovering peers", "want", want) // we use errgroup as it provide limits var wg errgroup.Group From 11dced407aad141b11acab623d987620529bba09 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 13 Jul 2023 23:02:29 +0800 Subject: [PATCH 0701/1008] chore(deps): bump otel, go-header, and go-fraud (#2472) --- cmd/flags_misc.go | 8 +- das/metrics.go | 135 ++++++++++++++++----------------- go.mod | 34 +++++---- go.sum | 67 ++++++++-------- nodebuilder/node/metrics.go | 53 ++++++------- nodebuilder/settings.go | 12 +-- share/getters/shrex.go | 30 ++++---- share/p2p/discovery/metrics.go | 73 +++++++++--------- share/p2p/metrics.go | 25 +++--- share/p2p/peers/metrics.go | 127 ++++++++++++++++--------------- state/metrics.go | 30 ++++---- 11 files changed, 296 insertions(+), 298 deletions(-) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 4483e17201..26f99985b8 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -13,6 +13,7 @@ import ( flag "github.com/spf13/pflag" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" @@ -210,15 +211,16 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e opts = append(opts, otlptracehttp.WithInsecure()) } - exp, err := otlptracehttp.New(cmd.Context(), opts...) + client := otlptracehttp.NewClient(opts...) + exporter, err := otlptrace.New(ctx, client) if err != nil { - return ctx, err + return ctx, fmt.Errorf("creating OTLP trace exporter: %w", err) } tp = tracesdk.NewTracerProvider( tracesdk.WithSampler(tracesdk.AlwaysSample()), // Always be sure to batch in production. - tracesdk.WithBatcher(exp), + tracesdk.WithBatcher(exporter), // Record information about this application in a Resource. tracesdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, diff --git a/das/metrics.go b/das/metrics.go index 1dcf5c8165..42b472d909 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -6,11 +6,9 @@ import ( "sync/atomic" "time" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncfloat64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric" "github.com/celestiaorg/celestia-node/header" ) @@ -22,73 +20,70 @@ const ( ) var ( - meter = global.MeterProvider().Meter("das") + meter = otel.Meter("das") ) type metrics struct { - sampled syncint64.Counter - sampleTime syncfloat64.Histogram - getHeaderTime syncfloat64.Histogram - newHead syncint64.Counter + sampled metric.Int64Counter + sampleTime metric.Float64Histogram + getHeaderTime metric.Float64Histogram + newHead metric.Int64Counter lastSampledTS uint64 } func (d *DASer) InitMetrics() error { - sampled, err := meter.SyncInt64().Counter("das_sampled_headers_counter", - instrument.WithDescription("sampled headers counter")) + sampled, err := meter.Int64Counter("das_sampled_headers_counter", + metric.WithDescription("sampled headers counter")) if err != nil { return err } - sampleTime, err := meter.SyncFloat64().Histogram("das_sample_time_hist", - instrument.WithDescription("duration of sampling a single header")) + sampleTime, err := meter.Float64Histogram("das_sample_time_hist", + metric.WithDescription("duration of sampling a single header")) if err != nil { return err } - getHeaderTime, err := meter.SyncFloat64().Histogram("das_get_header_time_hist", - instrument.WithDescription("duration of getting header from header store")) + getHeaderTime, err := meter.Float64Histogram("das_get_header_time_hist", + metric.WithDescription("duration of getting header from header store")) if err != nil { return err } - newHead, err := meter.SyncInt64().Counter("das_head_updated_counter", - instrument.WithDescription("amount of times DAS'er advanced network head")) + newHead, err := meter.Int64Counter("das_head_updated_counter", + metric.WithDescription("amount of times DAS'er advanced network head")) if err != nil { return err } - lastSampledTS, err := meter.AsyncInt64().Gauge("das_latest_sampled_ts", - instrument.WithDescription("latest sampled timestamp")) + lastSampledTS, err := meter.Int64ObservableGauge("das_latest_sampled_ts", + metric.WithDescription("latest sampled timestamp")) if err != nil { return err } - busyWorkers, err := meter.AsyncInt64().Gauge("das_busy_workers_amount", - instrument.WithDescription("number of active parallel workers in DAS'er")) + busyWorkers, err := meter.Int64ObservableGauge("das_busy_workers_amount", + metric.WithDescription("number of active parallel workers in DAS'er")) if err != nil { return err } - networkHead, err := meter.AsyncInt64().Gauge("das_network_head", - instrument.WithDescription("most recent network head")) + networkHead, err := meter.Int64ObservableGauge("das_network_head", + metric.WithDescription("most recent network head")) if err != nil { return err } - sampledChainHead, err := meter.AsyncInt64().Gauge("das_sampled_chain_head", - instrument.WithDescription("height of the sampled chain - all previous headers have been successfully sampled")) + sampledChainHead, err := meter.Int64ObservableGauge("das_sampled_chain_head", + metric.WithDescription("height of the sampled chain - all previous headers have been successfully sampled")) if err != nil { return err } - totalSampled, err := meter. - AsyncInt64(). - Gauge( - "das_total_sampled_headers", - instrument.WithDescription("total sampled headers gauge"), - ) + totalSampled, err := meter.Int64ObservableGauge("das_total_sampled_headers", + metric.WithDescription("total sampled headers gauge"), + ) if err != nil { return err } @@ -100,36 +95,38 @@ func (d *DASer) InitMetrics() error { newHead: newHead, } - err = meter.RegisterCallback( - []instrument.Asynchronous{ - lastSampledTS, - busyWorkers, - networkHead, - sampledChainHead, - totalSampled, - }, - func(ctx context.Context) { - stats, err := d.sampler.stats(ctx) - if err != nil { - log.Errorf("observing stats: %s", err.Error()) - } - - for jobType, amount := range stats.workersByJobType() { - busyWorkers.Observe(ctx, amount, - attribute.String(jobTypeLabel, string(jobType))) - } - - networkHead.Observe(ctx, int64(stats.NetworkHead)) - sampledChainHead.Observe(ctx, int64(stats.SampledChainHead)) - - if ts := atomic.LoadUint64(&d.sampler.metrics.lastSampledTS); ts != 0 { - lastSampledTS.Observe(ctx, int64(ts)) - } - - totalSampled.Observe(ctx, int64(stats.totalSampled())) - }, - ) + callback := func(ctx context.Context, observer metric.Observer) error { + stats, err := d.sampler.stats(ctx) + if err != nil { + log.Errorf("observing stats: %s", err.Error()) + return err + } + + for jobType, amount := range stats.workersByJobType() { + observer.ObserveInt64(busyWorkers, amount, + metric.WithAttributes( + attribute.String(jobTypeLabel, string(jobType)), + )) + } + + observer.ObserveInt64(networkHead, int64(stats.NetworkHead)) + observer.ObserveInt64(sampledChainHead, int64(stats.SampledChainHead)) + + if ts := atomic.LoadUint64(&d.sampler.metrics.lastSampledTS); ts != 0 { + observer.ObserveInt64(lastSampledTS, int64(ts)) + } + observer.ObserveInt64(totalSampled, int64(stats.totalSampled())) + return nil + } + + _, err = meter.RegisterCallback(callback, + lastSampledTS, + busyWorkers, + networkHead, + sampledChainHead, + totalSampled, + ) if err != nil { return fmt.Errorf("registering metrics callback: %w", err) } @@ -153,16 +150,18 @@ func (m *metrics) observeSample( ctx = context.Background() } m.sampleTime.Record(ctx, sampleTime.Seconds(), - attribute.Bool(failedLabel, err != nil), - attribute.Int(headerWidthLabel, len(h.DAH.RowRoots)), - attribute.String(jobTypeLabel, string(jobType)), - ) + metric.WithAttributes( + attribute.Bool(failedLabel, err != nil), + attribute.Int(headerWidthLabel, len(h.DAH.RowRoots)), + attribute.String(jobTypeLabel, string(jobType)), + )) m.sampled.Add(ctx, 1, - attribute.Bool(failedLabel, err != nil), - attribute.Int(headerWidthLabel, len(h.DAH.RowRoots)), - attribute.String(jobTypeLabel, string(jobType)), - ) + metric.WithAttributes( + attribute.Bool(failedLabel, err != nil), + attribute.Int(headerWidthLabel, len(h.DAH.RowRoots)), + attribute.String(jobTypeLabel, string(jobType)), + )) atomic.StoreUint64(&m.lastSampledTS, uint64(time.Now().UTC().Unix())) } diff --git a/go.mod b/go.mod index f7af093ece..eae1f5842b 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc9 - github.com/celestiaorg/go-fraud v0.1.0 - github.com/celestiaorg/go-header v0.2.11 + github.com/celestiaorg/go-fraud v0.1.2 + github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.17.0 github.com/celestiaorg/rsmt2d v0.10.0 @@ -63,13 +63,14 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 - go.opentelemetry.io/otel v1.13.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 - go.opentelemetry.io/otel/metric v0.34.0 - go.opentelemetry.io/otel/sdk v1.11.2 - go.opentelemetry.io/otel/sdk/metric v0.34.0 - go.opentelemetry.io/otel/trace v1.13.0 + go.opentelemetry.io/otel v1.16.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 + go.opentelemetry.io/otel/metric v1.16.0 + go.opentelemetry.io/otel/sdk v1.16.0 + go.opentelemetry.io/otel/sdk/metric v0.39.0 + go.opentelemetry.io/otel/trace v1.16.0 go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 @@ -103,7 +104,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect github.com/celestiaorg/quantum-gravity-bridge v1.3.0 // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -174,7 +175,7 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect @@ -304,22 +305,23 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 7d3cc73274..7ef5215bfd 100644 --- a/go.sum +++ b/go.sum @@ -352,10 +352,10 @@ github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13 h1:N1PrCWcYkaODeIQyyVBmDK github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13/go.mod h1:xpBZc/OYZ736hp0IZlBGNUhEgCD9C+bKs8yNLZibyv0= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= -github.com/celestiaorg/go-fraud v0.1.0 h1:v6mZvlmf2J5ELZfPnrtmmOvKbaYIUs/erDWPO8NbZyY= -github.com/celestiaorg/go-fraud v0.1.0/go.mod h1:yoNM35cKMAkt5Mi/Qx3Wi9bnPilLi8n6RpHZVglTUDs= -github.com/celestiaorg/go-header v0.2.11 h1:dLpuUfpGNxFfJNw3Ar3SqWc+AeyT1DlTP5mLjx9Ths8= -github.com/celestiaorg/go-header v0.2.11/go.mod h1:i9OpY70+PJ1xPw1IgMfF0Pk6vBD6VWPmjY3bgubJBcU= +github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= +github.com/celestiaorg/go-fraud v0.1.2/go.mod h1:kHZXQY+6gd1kYkoWRFFKgWyrLPWRgDN3vd1Ll9gE/oo= +github.com/celestiaorg/go-header v0.2.12 h1:3H9nir20+MTY1vXbLxOUOV05ZspotR6JOiZGKxACHCQ= +github.com/celestiaorg/go-header v0.2.12/go.mod h1:NhiWq97NtAYyRBu8quzYOUghQULjgOzO2Ql0iVEFOf0= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= @@ -371,8 +371,8 @@ github.com/celestiaorg/rsmt2d v0.10.0/go.mod h1:BiCZkCJfhDHUEOJKXUeu+CudjluecKvR github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -854,8 +854,9 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= @@ -2054,31 +2055,31 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= -go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y= -go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0 h1:kpskzLZ60cJ48SJ4uxWa6waBL+4kSV6nVK8rP+QM8Wg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.34.0/go.mod h1:4+x3i62TEegDHuzNva0bMcAN8oUi5w4liGb1d/VgPYo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0 h1:t4Ajxj8JGjxkqoBtbkCOY2cDUl9RwiNE9LPQavooi9U= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.34.0/go.mod h1:WO7omosl4P7JoanH9NgInxDxEn2F2M5YinIh8EyeT8w= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 h1:fqR1kli93643au1RKo0Uma3d2aPQKT+WBKfTSBaKbOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2/go.mod h1:5Qn6qvgkMsLDX+sYK64rHb1FPhpn0UtxF+ouX1uhyJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 h1:Us8tbCmuN16zAnK5TC69AtODLycKbwnskQzaB6DfFhc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2/go.mod h1:GZWSQQky8AgdJj50r1KJm8oiQiIPaAX7uZCFQX9GzC8= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 h1:f6BwB2OACc3FCbYVznctQ9V6KK7Vq6CjmYXJ7DeSs4E= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0/go.mod h1:UqL5mZ3qs6XYhDnZaW1Ps4upD+PX6LipH40AoeuIlwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 h1:IZXpCEtI7BbX01DRQEWTGDkvjMB6hEhiEZXS+eg2YqY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0/go.mod h1:xY111jIZtWb+pUUgT4UiiSonAaY2cD2Ts5zvuKLki3o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h1:hGXzO5bhhSHZnKvrDaXB82Y9DRFour0Nz/KrBh7reWw= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8= -go.opentelemetry.io/otel/metric v0.34.0/go.mod h1:ZFuI4yQGNCupurTXCwkeD/zHBt+C2bR7bw5JqUm/AP8= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= -go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= -go.opentelemetry.io/otel/sdk/metric v0.34.0 h1:7ElxfQpXCFZlRTvVRTkcUvK8Gt5DC8QzmzsLsO2gdzo= -go.opentelemetry.io/otel/sdk/metric v0.34.0/go.mod h1:l4r16BIqiqPy5rd14kkxllPy/fOI4tWo1jkpD9Z3ffQ= +go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= +go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= +go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= -go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= -go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -2321,8 +2322,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2762,8 +2763,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= +google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= diff --git a/nodebuilder/node/metrics.go b/nodebuilder/node/metrics.go index 625e8425e8..7d722524e8 100644 --- a/nodebuilder/node/metrics.go +++ b/nodebuilder/node/metrics.go @@ -4,11 +4,11 @@ import ( "context" "time" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" ) -var meter = global.MeterProvider().Meter("node") +var meter = otel.Meter("node") var ( timeStarted time.Time @@ -17,37 +17,34 @@ var ( // WithMetrics registers node metrics. func WithMetrics() error { - nodeStartTS, err := meter. - AsyncFloat64(). - Gauge( - "node_start_ts", - instrument.WithDescription("timestamp when the node was started"), - ) + nodeStartTS, err := meter.Int64ObservableGauge( + "node_start_ts", + metric.WithDescription("timestamp when the node was started"), + ) if err != nil { return err } - totalNodeRunTime, err := meter. - AsyncFloat64(). - Counter( - "node_runtime_counter_in_seconds", - instrument.WithDescription("total time the node has been running"), - ) + totalNodeRunTime, err := meter.Float64ObservableCounter( + "node_runtime_counter_in_seconds", + metric.WithDescription("total time the node has been running"), + ) if err != nil { return err } - return meter.RegisterCallback( - []instrument.Asynchronous{nodeStartTS, totalNodeRunTime}, - func(ctx context.Context) { - if !nodeStarted { - // Observe node start timestamp - timeStarted = time.Now() - nodeStartTS.Observe(ctx, float64(timeStarted.Unix())) - nodeStarted = true - } - - totalNodeRunTime.Observe(ctx, time.Since(timeStarted).Seconds()) - }, - ) + callback := func(ctx context.Context, observer metric.Observer) error { + if !nodeStarted { + // Observe node start timestamp + timeStarted = time.Now() + observer.ObserveInt64(nodeStartTS, timeStarted.Unix()) + nodeStarted = true + } + + observer.ObserveFloat64(totalNodeRunTime, time.Since(timeStarted).Seconds()) + return nil + } + + _, err = meter.RegisterCallback(callback, nodeStartTS, totalNodeRunTime) + return err } diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index bea3c78ad2..66b02c34e4 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -7,9 +7,9 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/pyroscope-io/client/pyroscope" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/sdk/metric" + sdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "go.uber.org/fx" @@ -119,9 +119,9 @@ func initializeMetrics( return err } - provider := metric.NewMeterProvider( - metric.WithReader(metric.NewPeriodicReader(exp, metric.WithTimeout(2*time.Second))), - metric.WithResource(resource.NewWithAttributes( + provider := sdk.NewMeterProvider( + sdk.WithReader(sdk.NewPeriodicReader(exp, sdk.WithTimeout(2*time.Second))), + sdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNamespaceKey.String(fmt.Sprintf("Celestia-%s", nodeType.String())), semconv.ServiceNameKey.String(fmt.Sprintf("semver-%s", buildInfo.SemanticVersion)), @@ -132,6 +132,6 @@ func initializeMetrics( return provider.Shutdown(ctx) }, }) - global.SetMeterProvider(provider) + otel.SetMeterProvider(provider) return nil } diff --git a/share/getters/shrex.go b/share/getters/shrex.go index bb5e7ca7a7..c754d73c1d 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -6,11 +6,9 @@ import ( "fmt" "time" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncint64" - "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/rsmt2d" @@ -32,11 +30,11 @@ const ( defaultMinAttemptsCount = 3 ) -var meter = global.MeterProvider().Meter("shrex/getter") +var meter = otel.Meter("shrex/getter") type metrics struct { - edsAttempts syncint64.Histogram - ndAttempts syncint64.Histogram + edsAttempts metric.Int64Histogram + ndAttempts metric.Int64Histogram } func (m *metrics) recordEDSAttempt(ctx context.Context, attemptCount int, success bool) { @@ -46,7 +44,9 @@ func (m *metrics) recordEDSAttempt(ctx context.Context, attemptCount int, succes if ctx.Err() != nil { ctx = context.Background() } - m.edsAttempts.Record(ctx, int64(attemptCount), attribute.Bool("success", success)) + m.edsAttempts.Record(ctx, int64(attemptCount), + metric.WithAttributes( + attribute.Bool("success", success))) } func (m *metrics) recordNDAttempt(ctx context.Context, attemptCount int, success bool) { @@ -56,23 +56,23 @@ func (m *metrics) recordNDAttempt(ctx context.Context, attemptCount int, success if ctx.Err() != nil { ctx = context.Background() } - m.ndAttempts.Record(ctx, int64(attemptCount), attribute.Bool("success", success)) + m.ndAttempts.Record(ctx, int64(attemptCount), + metric.WithAttributes( + attribute.Bool("success", success))) } func (sg *ShrexGetter) WithMetrics() error { - edsAttemptHistogram, err := meter.SyncInt64().Histogram( + edsAttemptHistogram, err := meter.Int64Histogram( "getters_shrex_eds_attempts_per_request", - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription("Number of attempts per shrex/eds request"), + metric.WithDescription("Number of attempts per shrex/eds request"), ) if err != nil { return err } - ndAttemptHistogram, err := meter.SyncInt64().Histogram( + ndAttemptHistogram, err := meter.Int64Histogram( "getters_shrex_nd_attempts_per_request", - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription("Number of attempts per shrex/nd request"), + metric.WithDescription("Number of attempts per shrex/nd request"), ) if err != nil { return err diff --git a/share/p2p/discovery/metrics.go b/share/p2p/discovery/metrics.go index b6adbb1984..99c9bb4548 100644 --- a/share/p2p/discovery/metrics.go +++ b/share/p2p/discovery/metrics.go @@ -5,11 +5,9 @@ import ( "fmt" "github.com/libp2p/go-libp2p/core/peer" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric" ) const ( @@ -28,18 +26,18 @@ const ( ) var ( - meter = global.MeterProvider().Meter("share_discovery") + meter = otel.Meter("share_discovery") ) type handlePeerResult string type metrics struct { - peersAmount asyncint64.Gauge - discoveryResult syncint64.Counter // attributes: enough_peers[bool],is_canceled[bool] - handlePeerResult syncint64.Counter // attributes: result[string] - advertise syncint64.Counter // attributes: failed[bool] - peerAdded syncint64.Counter - peerRemoved syncint64.Counter + peersAmount metric.Int64ObservableGauge + discoveryResult metric.Int64Counter // attributes: enough_peers[bool],is_canceled[bool] + handlePeerResult metric.Int64Counter // attributes: result[string] + advertise metric.Int64Counter // attributes: failed[bool] + peerAdded metric.Int64Counter + peerRemoved metric.Int64Counter } // WithMetrics turns on metric collection in discoery. @@ -54,44 +52,44 @@ func (d *Discovery) WithMetrics() error { } func initMetrics(d *Discovery) (*metrics, error) { - peersAmount, err := meter.AsyncInt64().Gauge("discovery_amount_of_peers", - instrument.WithDescription("amount of peers in discovery set")) + peersAmount, err := meter.Int64ObservableGauge("discovery_amount_of_peers", + metric.WithDescription("amount of peers in discovery set")) if err != nil { return nil, err } - discoveryResult, err := meter.SyncInt64().Counter("discovery_find_peers_result", - instrument.WithDescription("result of find peers run")) + discoveryResult, err := meter.Int64Counter("discovery_find_peers_result", + metric.WithDescription("result of find peers run")) if err != nil { return nil, err } - handlePeerResultCounter, err := meter.SyncInt64().Counter("discovery_handler_peer_result", - instrument.WithDescription("result handling found peer")) + handlePeerResultCounter, err := meter.Int64Counter("discovery_handler_peer_result", + metric.WithDescription("result handling found peer")) if err != nil { return nil, err } - advertise, err := meter.SyncInt64().Counter("discovery_advertise_event", - instrument.WithDescription("advertise events counter")) + advertise, err := meter.Int64Counter("discovery_advertise_event", + metric.WithDescription("advertise events counter")) if err != nil { return nil, err } - peerAdded, err := meter.SyncInt64().Counter("discovery_add_peer", - instrument.WithDescription("add peer to discovery set counter")) + peerAdded, err := meter.Int64Counter("discovery_add_peer", + metric.WithDescription("add peer to discovery set counter")) if err != nil { return nil, err } - peerRemoved, err := meter.SyncInt64().Counter("discovery_remove_peer", - instrument.WithDescription("remove peer from discovery set counter")) + peerRemoved, err := meter.Int64Counter("discovery_remove_peer", + metric.WithDescription("remove peer from discovery set counter")) if err != nil { return nil, err } - backOffSize, err := meter.AsyncInt64().Gauge("discovery_backoff_amount", - instrument.WithDescription("amount of peers in backoff")) + backOffSize, err := meter.Int64ObservableGauge("discovery_backoff_amount", + metric.WithDescription("amount of peers in backoff")) if err != nil { return nil, err } @@ -105,16 +103,12 @@ func initMetrics(d *Discovery) (*metrics, error) { peerRemoved: peerRemoved, } - err = meter.RegisterCallback( - []instrument.Asynchronous{ - peersAmount, - backOffSize, - }, - func(ctx context.Context) { - peersAmount.Observe(ctx, int64(d.set.Size())) - backOffSize.Observe(ctx, int64(d.connector.Size())) - }, - ) + callback := func(ctx context.Context, observer metric.Observer) error { + observer.ObserveInt64(peersAmount, int64(d.set.Size())) + observer.ObserveInt64(backOffSize, int64(d.connector.Size())) + return nil + } + _, err = meter.RegisterCallback(callback, peersAmount, backOffSize) if err != nil { return nil, fmt.Errorf("registering metrics callback: %w", err) } @@ -130,7 +124,8 @@ func (m *metrics) observeFindPeers(ctx context.Context, isEnoughPeers bool) { } m.discoveryResult.Add(ctx, 1, - attribute.Bool(discoveryEnoughPeersKey, isEnoughPeers)) + metric.WithAttributes( + attribute.Bool(discoveryEnoughPeersKey, isEnoughPeers))) } func (m *metrics) observeHandlePeer(ctx context.Context, result handlePeerResult) { @@ -142,7 +137,8 @@ func (m *metrics) observeHandlePeer(ctx context.Context, result handlePeerResult } m.handlePeerResult.Add(ctx, 1, - attribute.String(handlePeerResultKey, string(result))) + metric.WithAttributes( + attribute.String(handlePeerResultKey, string(result)))) } func (m *metrics) observeAdvertise(ctx context.Context, err error) { @@ -154,7 +150,8 @@ func (m *metrics) observeAdvertise(ctx context.Context, err error) { } m.advertise.Add(ctx, 1, - attribute.Bool(advertiseFailedKey, err != nil)) + metric.WithAttributes( + attribute.Bool(advertiseFailedKey, err != nil))) } func (m *metrics) observeOnPeersUpdate(_ peer.ID, isAdded bool) { diff --git a/share/p2p/metrics.go b/share/p2p/metrics.go index 87c1e2eeb0..1942be5d6b 100644 --- a/share/p2p/metrics.go +++ b/share/p2p/metrics.go @@ -4,14 +4,12 @@ import ( "context" "fmt" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/syncint64" - "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/metric" ) -var meter = global.MeterProvider().Meter("shrex/eds") +var meter = otel.Meter("shrex/eds") type status string @@ -24,7 +22,7 @@ const ( ) type Metrics struct { - totalRequestCounter syncint64.Counter + totalRequestCounter metric.Int64Counter } // ObserveRequests increments the total number of requests sent with the given status as an @@ -36,14 +34,16 @@ func (m *Metrics) ObserveRequests(ctx context.Context, count int64, status statu if ctx.Err() != nil { ctx = context.Background() } - m.totalRequestCounter.Add(ctx, count, attribute.String("status", string(status))) + m.totalRequestCounter.Add(ctx, count, + metric.WithAttributes( + attribute.String("status", string(status)), + )) } func InitClientMetrics(protocol string) (*Metrics, error) { - totalRequestCounter, err := meter.SyncInt64().Counter( + totalRequestCounter, err := meter.Int64Counter( fmt.Sprintf("shrex_%s_client_total_requests", protocol), - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription(fmt.Sprintf("Total count of sent shrex/%s requests", protocol)), + metric.WithDescription(fmt.Sprintf("Total count of sent shrex/%s requests", protocol)), ) if err != nil { return nil, err @@ -55,10 +55,9 @@ func InitClientMetrics(protocol string) (*Metrics, error) { } func InitServerMetrics(protocol string) (*Metrics, error) { - totalRequestCounter, err := meter.SyncInt64().Counter( + totalRequestCounter, err := meter.Int64Counter( fmt.Sprintf("shrex_%s_server_total_responses", protocol), - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription(fmt.Sprintf("Total count of sent shrex/%s responses", protocol)), + metric.WithDescription(fmt.Sprintf("Total count of sent shrex/%s responses", protocol)), ) if err != nil { return nil, err diff --git a/share/p2p/peers/metrics.go b/share/p2p/peers/metrics.go index bf4d544d9f..95d1ce65d9 100644 --- a/share/p2p/peers/metrics.go +++ b/share/p2p/peers/metrics.go @@ -8,11 +8,9 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/instrument/asyncint64" - "go.opentelemetry.io/otel/metric/instrument/syncint64" + "go.opentelemetry.io/otel/metric" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -52,7 +50,7 @@ const ( ) var ( - meter = global.MeterProvider().Meter("shrex_peer_manager") + meter = otel.Meter("shrex_peer_manager") ) type blacklistPeerReason string @@ -64,63 +62,63 @@ type poolStatus string type peerSource string type metrics struct { - getPeer syncint64.Counter // attributes: source, is_instant - getPeerWaitTimeHistogram syncint64.Histogram // attributes: source - getPeerPoolSizeHistogram syncint64.Histogram // attributes: source - doneResult syncint64.Counter // attributes: source, done_result - validationResult syncint64.Counter // attributes: validation_result - - shrexPools asyncint64.Gauge // attributes: pool_status - fullNodesPool asyncint64.Gauge // attributes: pool_status + getPeer metric.Int64Counter // attributes: source, is_instant + getPeerWaitTimeHistogram metric.Int64Histogram // attributes: source + getPeerPoolSizeHistogram metric.Int64Histogram // attributes: source + doneResult metric.Int64Counter // attributes: source, done_result + validationResult metric.Int64Counter // attributes: validation_result + + shrexPools metric.Int64ObservableGauge // attributes: pool_status + fullNodesPool metric.Int64ObservableGauge // attributes: pool_status blacklistedPeersByReason sync.Map - blacklistedPeers asyncint64.Gauge // attributes: blacklist_reason + blacklistedPeers metric.Int64ObservableGauge // attributes: blacklist_reason } func initMetrics(manager *Manager) (*metrics, error) { - getPeer, err := meter.SyncInt64().Counter("peer_manager_get_peer_counter", - instrument.WithDescription("get peer counter")) + getPeer, err := meter.Int64Counter("peer_manager_get_peer_counter", + metric.WithDescription("get peer counter")) if err != nil { return nil, err } - getPeerWaitTimeHistogram, err := meter.SyncInt64().Histogram("peer_manager_get_peer_ms_time_hist", - instrument.WithDescription("get peer time histogram(ms), observed only for async get(is_instant = false)")) + getPeerWaitTimeHistogram, err := meter.Int64Histogram("peer_manager_get_peer_ms_time_hist", + metric.WithDescription("get peer time histogram(ms), observed only for async get(is_instant = false)")) if err != nil { return nil, err } - getPeerPoolSizeHistogram, err := meter.SyncInt64().Histogram("peer_manager_get_peer_pool_size_hist", - instrument.WithDescription("amount of available active peers in pool at time when get was called")) + getPeerPoolSizeHistogram, err := meter.Int64Histogram("peer_manager_get_peer_pool_size_hist", + metric.WithDescription("amount of available active peers in pool at time when get was called")) if err != nil { return nil, err } - doneResult, err := meter.SyncInt64().Counter("peer_manager_done_result_counter", - instrument.WithDescription("done results counter")) + doneResult, err := meter.Int64Counter("peer_manager_done_result_counter", + metric.WithDescription("done results counter")) if err != nil { return nil, err } - validationResult, err := meter.SyncInt64().Counter("peer_manager_validation_result_counter", - instrument.WithDescription("validation result counter")) + validationResult, err := meter.Int64Counter("peer_manager_validation_result_counter", + metric.WithDescription("validation result counter")) if err != nil { return nil, err } - shrexPools, err := meter.AsyncInt64().Gauge("peer_manager_pools_gauge", - instrument.WithDescription("pools amount")) + shrexPools, err := meter.Int64ObservableGauge("peer_manager_pools_gauge", + metric.WithDescription("pools amount")) if err != nil { return nil, err } - fullNodesPool, err := meter.AsyncInt64().Gauge("peer_manager_full_nodes_gauge", - instrument.WithDescription("full nodes pool peers amount")) + fullNodesPool, err := meter.Int64ObservableGauge("peer_manager_full_nodes_gauge", + metric.WithDescription("full nodes pool peers amount")) if err != nil { return nil, err } - blacklisted, err := meter.AsyncInt64().Gauge("peer_manager_blacklisted_peers", - instrument.WithDescription("blacklisted peers amount")) + blacklisted, err := meter.Int64ObservableGauge("peer_manager_blacklisted_peers", + metric.WithDescription("blacklisted peers amount")) if err != nil { return nil, err } @@ -136,33 +134,31 @@ func initMetrics(manager *Manager) (*metrics, error) { blacklistedPeers: blacklisted, } - err = meter.RegisterCallback( - []instrument.Asynchronous{ - shrexPools, - fullNodesPool, - blacklisted, - }, - func(ctx context.Context) { - for poolStatus, count := range manager.shrexPools() { - shrexPools.Observe(ctx, count, - attribute.String(poolStatusKey, string(poolStatus))) - } - - fullNodesPool.Observe(ctx, int64(manager.fullNodes.len()), - attribute.String(peerStatusKey, string(peerStatusActive))) - fullNodesPool.Observe(ctx, int64(manager.fullNodes.cooldown.len()), - attribute.String(peerStatusKey, string(peerStatusCooldown))) - - metrics.blacklistedPeersByReason.Range(func(key, value any) bool { - reason := key.(blacklistPeerReason) - amount := value.(int) - blacklisted.Observe(ctx, int64(amount), - attribute.String(blacklistPeerReasonKey, string(reason))) - return true - }) - }, - ) + callback := func(ctx context.Context, observer metric.Observer) error { + for poolStatus, count := range manager.shrexPools() { + observer.ObserveInt64(shrexPools, count, + metric.WithAttributes( + attribute.String(poolStatusKey, string(poolStatus)))) + } + observer.ObserveInt64(fullNodesPool, int64(manager.fullNodes.len()), + metric.WithAttributes( + attribute.String(peerStatusKey, string(peerStatusActive)))) + observer.ObserveInt64(fullNodesPool, int64(manager.fullNodes.cooldown.len()), + metric.WithAttributes( + attribute.String(peerStatusKey, string(peerStatusCooldown)))) + + metrics.blacklistedPeersByReason.Range(func(key, value any) bool { + reason := key.(blacklistPeerReason) + amount := value.(int) + observer.ObserveInt64(blacklisted, int64(amount), + metric.WithAttributes( + attribute.String(blacklistPeerReasonKey, string(reason)))) + return true + }) + return nil + } + _, err = meter.RegisterCallback(callback, shrexPools, fullNodesPool, blacklisted) if err != nil { return nil, fmt.Errorf("registering metrics callback: %w", err) } @@ -180,17 +176,20 @@ func (m *metrics) observeGetPeer( ctx = context.Background() } m.getPeer.Add(ctx, 1, - attribute.String(sourceKey, string(source)), - attribute.Bool(isInstantKey, waitTime == 0)) + metric.WithAttributes( + attribute.String(sourceKey, string(source)), + attribute.Bool(isInstantKey, waitTime == 0))) if source == sourceShrexSub { m.getPeerPoolSizeHistogram.Record(ctx, int64(poolSize), - attribute.String(sourceKey, string(source))) + metric.WithAttributes( + attribute.String(sourceKey, string(source)))) } // record wait time only for async gets if waitTime > 0 { m.getPeerWaitTimeHistogram.Record(ctx, waitTime.Milliseconds(), - attribute.String(sourceKey, string(source))) + metric.WithAttributes( + attribute.String(sourceKey, string(source)))) } } @@ -201,8 +200,9 @@ func (m *metrics) observeDoneResult(source peerSource, result result) { ctx := context.Background() m.doneResult.Add(ctx, 1, - attribute.String(sourceKey, string(source)), - attribute.String(doneResultKey, string(result))) + metric.WithAttributes( + attribute.String(sourceKey, string(source)), + attribute.String(doneResultKey, string(result)))) } // validationObserver is a middleware that observes validation results as metrics @@ -230,7 +230,8 @@ func (m *metrics) validationObserver(validator shrexsub.ValidatorFn) shrexsub.Va } m.validationResult.Add(ctx, 1, - attribute.String(validationResultKey, resStr)) + metric.WithAttributes( + attribute.String(validationResultKey, resStr))) return res } } diff --git a/state/metrics.go b/state/metrics.go index e465e2833d..169023b5f9 100644 --- a/state/metrics.go +++ b/state/metrics.go @@ -3,32 +3,28 @@ package state import ( "context" - "go.opentelemetry.io/otel/metric/global" - "go.opentelemetry.io/otel/metric/instrument" - "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" ) -var meter = global.MeterProvider().Meter("state") +var meter = otel.Meter("state") func WithMetrics(ca *CoreAccessor) { - pfbCounter, _ := meter.AsyncInt64().Counter( + pfbCounter, _ := meter.Int64ObservableCounter( "pfb_count", - instrument.WithUnit(unit.Dimensionless), - instrument.WithDescription("Total count of submitted PayForBlob transactions"), + metric.WithDescription("Total count of submitted PayForBlob transactions"), ) - lastPfbTimestamp, _ := meter.AsyncInt64().Counter( + lastPfbTimestamp, _ := meter.Int64ObservableCounter( "last_pfb_timestamp", - instrument.WithUnit(unit.Milliseconds), - instrument.WithDescription("Timestamp of the last submitted PayForBlob transaction"), + metric.WithDescription("Timestamp of the last submitted PayForBlob transaction"), ) - err := meter.RegisterCallback( - []instrument.Asynchronous{pfbCounter, lastPfbTimestamp}, - func(ctx context.Context) { - pfbCounter.Observe(ctx, ca.payForBlobCount) - lastPfbTimestamp.Observe(ctx, ca.lastPayForBlob) - }, - ) + callback := func(ctx context.Context, observer metric.Observer) error { + observer.ObserveInt64(pfbCounter, ca.payForBlobCount) + observer.ObserveInt64(lastPfbTimestamp, ca.lastPayForBlob) + return nil + } + _, err := meter.RegisterCallback(callback, pfbCounter, lastPfbTimestamp) if err != nil { panic(err) } From 05e00e8cfbc574d23ad586af81de14e404f123d5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 14 Jul 2023 10:48:49 +0300 Subject: [PATCH 0702/1008] chore: move versioning outside the main package (#2468) --- Makefile | 3 ++- cmd/celestia/util.go | 6 ------ cmd/celestia/version.go | 21 +++++++-------------- cmd/env.go | 11 ----------- cmd/flags_misc.go | 3 ++- nodebuilder/node/buildInfo.go | 28 +++++++++++++++++++++++++++- nodebuilder/node_test.go | 2 +- nodebuilder/settings.go | 4 ++-- 8 files changed, 41 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 310e65cbe0..2567b225d3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ SHELL=/usr/bin/env bash PROJECTNAME=$(shell basename "$(PWD)") -LDFLAGS=-ldflags="-X 'main.buildTime=$(shell date)' -X 'main.lastCommit=$(shell git rev-parse HEAD)' -X 'main.semanticVersion=$(shell git describe --tags --dirty=-dev 2>/dev/null || git rev-parse --abbrev-ref HEAD)'" +versioningPath := "github.com/celestiaorg/celestia-node/nodebuilder/node" +LDFLAGS=-ldflags="-X '$(versioningPath).buildTime=$(shell date)' -X '$(versioningPath).lastCommit=$(shell git rev-parse HEAD)' -X '$(versioningPath).semanticVersion=$(shell git describe --tags --dirty=-dev 2>/dev/null || git rev-parse --abbrev-ref HEAD)'" ifeq (${PREFIX},) PREFIX := /usr/local endif diff --git a/cmd/celestia/util.go b/cmd/celestia/util.go index 85505c3a60..a38860d1f7 100644 --- a/cmd/celestia/util.go +++ b/cmd/celestia/util.go @@ -26,12 +26,6 @@ func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) err return err } ctx = cmdnode.WithNetwork(ctx, parsedNetwork) - ctx = cmdnode.WithNodeBuildInfo(ctx, &node.BuildInfo{ - LastCommit: lastCommit, - SemanticVersion: semanticVersion, - SystemVersion: systemVersion, - GolangVersion: golangVersion, - }) // loads existing config into the environment ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) diff --git a/cmd/celestia/version.go b/cmd/celestia/version.go index 462f17b474..f0d379e7a7 100644 --- a/cmd/celestia/version.go +++ b/cmd/celestia/version.go @@ -2,18 +2,10 @@ package main import ( "fmt" - "runtime" "github.com/spf13/cobra" -) - -var ( - buildTime string - lastCommit string - semanticVersion string - systemVersion = fmt.Sprintf("%s/%s", runtime.GOARCH, runtime.GOOS) - golangVersion = runtime.Version() + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) var versionCmd = &cobra.Command{ @@ -24,9 +16,10 @@ var versionCmd = &cobra.Command{ } func printBuildInfo(_ *cobra.Command, _ []string) { - fmt.Printf("Semantic version: %s\n", semanticVersion) - fmt.Printf("Commit: %s\n", lastCommit) - fmt.Printf("Build Date: %s\n", buildTime) - fmt.Printf("System version: %s\n", systemVersion) - fmt.Printf("Golang version: %s\n", golangVersion) + buildInfo := node.GetBuildInfo() + fmt.Printf("Semantic version: %s\n", buildInfo.SemanticVersion) + fmt.Printf("Commit: %s\n", buildInfo.LastCommit) + fmt.Printf("Build Date: %s\n", buildInfo.BuildTime) + fmt.Printf("System version: %s\n", buildInfo.SystemVersion) + fmt.Printf("Golang version: %s\n", buildInfo.GolangVersion) } diff --git a/cmd/env.go b/cmd/env.go index ca915d884f..f9860a2de8 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -38,11 +38,6 @@ func NodeConfig(ctx context.Context) nodebuilder.Config { return cfg } -// NodeInfo reads the node build inforamtion from the context. -func NodeInfo(ctx context.Context) node.BuildInfo { - return ctx.Value(buildInfo{}).(node.BuildInfo) -} - // WithNodeType sets the node type in the given context. func WithNodeType(ctx context.Context, tp node.Type) context.Context { return context.WithValue(ctx, nodeTypeKey{}, tp) @@ -78,16 +73,10 @@ func WithNodeConfig(ctx context.Context, config *nodebuilder.Config) context.Con return context.WithValue(ctx, configKey{}, *config) } -// WithNodeConfig sets the node config build information. -func WithNodeBuildInfo(ctx context.Context, info *node.BuildInfo) context.Context { - return context.WithValue(ctx, buildInfo{}, *info) -} - type ( optionsKey struct{} configKey struct{} storePathKey struct{} nodeTypeKey struct{} networkKey struct{} - buildInfo struct{} ) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 26f99985b8..9dbb35ebf4 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -22,6 +22,7 @@ import ( "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -264,7 +265,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e opts = append(opts, otlpmetrichttp.WithInsecure()) } - ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx), NodeInfo(ctx))) + ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx), node.GetBuildInfo())) } ok, err = cmd.Flags().GetBool(p2pMetrics) diff --git a/nodebuilder/node/buildInfo.go b/nodebuilder/node/buildInfo.go index 5f5bdde28e..53d8554d4d 100644 --- a/nodebuilder/node/buildInfo.go +++ b/nodebuilder/node/buildInfo.go @@ -1,9 +1,35 @@ package node -// BuildInfo stores all necessary information for the current build. +import ( + "fmt" + "runtime" +) + +var ( + buildTime string + lastCommit string + semanticVersion string + + systemVersion = fmt.Sprintf("%s/%s", runtime.GOARCH, runtime.GOOS) + golangVersion = runtime.Version() +) + +// BuildInfo represents all necessary information about current build. type BuildInfo struct { + BuildTime string LastCommit string SemanticVersion string SystemVersion string GolangVersion string } + +// GetBuildInfo returns information about current build. +func GetBuildInfo() *BuildInfo { + return &BuildInfo{ + buildTime, + lastCommit, + semanticVersion, + systemVersion, + golangVersion, + } +} diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index e6775419c7..5ef86a0479 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -81,7 +81,7 @@ func TestLifecycle_WithMetrics(t *testing.T) { otlpmetrichttp.WithInsecure(), }, tt.tp, - node.BuildInfo{}, + &node.BuildInfo{}, ), ) require.NotNil(t, node) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 66b02c34e4..ace222179b 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -62,7 +62,7 @@ func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { } // WithMetrics enables metrics exporting for the node. -func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type, buildInfo node.BuildInfo) fx.Option { +func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type, buildInfo *node.BuildInfo) fx.Option { baseComponents := fx.Options( fx.Supply(metricOpts), fx.Supply(buildInfo), @@ -111,7 +111,7 @@ func initializeMetrics( lc fx.Lifecycle, peerID peer.ID, nodeType node.Type, - buildInfo node.BuildInfo, + buildInfo *node.BuildInfo, opts []otlpmetrichttp.Option, ) error { exp, err := otlpmetrichttp.New(ctx, opts...) From b59c21c32275b26729208324831874befb7b3d7d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 14 Jul 2023 23:38:20 +0800 Subject: [PATCH 0703/1008] refactor(share/availability): don't log error canceled operation (#2475) Small cleanup to prevent node from spamming error availability logs on shutdown. --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/availability/full/availability.go | 5 +++-- share/availability/light/availability.go | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index cfa5bd0c39..4b19183be1 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -72,13 +72,14 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro _, err := fa.getter.GetEDS(ctx, root) if err != nil { + if errors.Is(err, context.Canceled) { + return err + } log.Errorw("availability validation failed", "root", root.String(), "err", err.Error()) var byzantineErr *byzantine.ErrByzantine if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { return share.ErrNotAvailable } - - return err } return err } diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 761671b955..cc2e08129e 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -84,13 +84,13 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo } if err != nil { - if !errors.Is(err, context.Canceled) { - log.Errorw("availability validation failed", "root", dah.String(), "err", err.Error()) + if errors.Is(err, context.Canceled) { + return err } + log.Errorw("availability validation failed", "root", dah.String(), "err", err.Error()) if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) { return share.ErrNotAvailable } - return err } } From 9c40a7f379ca4cefa738e265e7d318b2e5245ef2 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 14 Jul 2023 21:56:41 +0300 Subject: [PATCH 0704/1008] feat(nodebuilder): Invoke traces from fx (#2477) --- cmd/flags_misc.go | 35 +++--------------------- nodebuilder/node_test.go | 1 - nodebuilder/settings.go | 58 +++++++++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 9dbb35ebf4..4a11977b86 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -11,18 +11,11 @@ import ( otelpyroscope "github.com/pyroscope-io/otel-profiling-go" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" - "go.opentelemetry.io/otel/sdk/resource" - tracesdk "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.11.0" - "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/celestia-node/logs" "github.com/celestiaorg/celestia-node/nodebuilder" - "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -201,7 +194,6 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e } if ok { - var tp trace.TracerProvider opts := []otlptracehttp.Option{ otlptracehttp.WithCompression(otlptracehttp.GzipCompression), otlptracehttp.WithEndpoint(cmd.Flag(tracingEndpointFlag).Value.String()), @@ -212,31 +204,13 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e opts = append(opts, otlptracehttp.WithInsecure()) } - client := otlptracehttp.NewClient(opts...) - exporter, err := otlptrace.New(ctx, client) - if err != nil { - return ctx, fmt.Errorf("creating OTLP trace exporter: %w", err) - } - - tp = tracesdk.NewTracerProvider( - tracesdk.WithSampler(tracesdk.AlwaysSample()), - // Always be sure to batch in production. - tracesdk.WithBatcher(exporter), - // Record information about this application in a Resource. - tracesdk.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", NodeType(ctx).String())), - // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey - )), - ) - + pyroOpts := make([]otelpyroscope.Option, 0) ok, err = cmd.Flags().GetBool(pyroscopeTracing) if err != nil { panic(err) } if ok { - tp = otelpyroscope.NewTracerProvider( - tp, + pyroOpts = append(pyroOpts, otelpyroscope.WithAppName("celestia.da-node"), otelpyroscope.WithPyroscopeURL(cmd.Flag(pyroscopeEndpoint).Value.String()), otelpyroscope.WithRootSpanOnly(true), @@ -245,8 +219,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e otelpyroscope.WithProfileBaselineURL(true), ) } - - otel.SetTracerProvider(tp) + ctx = WithNodeOptions(ctx, nodebuilder.WithTraces(opts, pyroOpts)) } ok, err = cmd.Flags().GetBool(metricsFlag) @@ -265,7 +238,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e opts = append(opts, otlpmetrichttp.WithInsecure()) } - ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx), node.GetBuildInfo())) + ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx))) } ok, err = cmd.Flags().GetBool(p2pMetrics) diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 5ef86a0479..f481162b5c 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -81,7 +81,6 @@ func TestLifecycle_WithMetrics(t *testing.T) { otlpmetrichttp.WithInsecure(), }, tt.tp, - &node.BuildInfo{}, ), ) require.NotNil(t, node) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index ace222179b..9a9f7fcf47 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -7,11 +7,16 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/pyroscope-io/client/pyroscope" + otelpyroscope "github.com/pyroscope-io/otel-profiling-go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" sdk "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.11.0" + "go.opentelemetry.io/otel/trace" "go.uber.org/fx" "github.com/celestiaorg/go-fraud" @@ -62,10 +67,9 @@ func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { } // WithMetrics enables metrics exporting for the node. -func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type, buildInfo *node.BuildInfo) fx.Option { +func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Option { baseComponents := fx.Options( fx.Supply(metricOpts), - fx.Supply(buildInfo), fx.Invoke(initializeMetrics), fx.Invoke(state.WithMetrics), fx.Invoke(fraud.WithMetrics), @@ -105,13 +109,55 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type, buildIn return opts } +func WithTraces(opts []otlptracehttp.Option, pyroOpts []otelpyroscope.Option) fx.Option { + options := fx.Options( + fx.Supply(opts), + fx.Supply(pyroOpts), + fx.Invoke(initializeTraces), + ) + return options +} + +func initializeTraces( + ctx context.Context, + nodeType node.Type, + peerID peer.ID, + network p2p.Network, + opts []otlptracehttp.Option, + pyroOpts []otelpyroscope.Option, +) error { + client := otlptracehttp.NewClient(opts...) + exporter, err := otlptrace.New(ctx, client) + if err != nil { + return fmt.Errorf("creating OTLP trace exporter: %w", err) + } + + var tp trace.TracerProvider + tp = tracesdk.NewTracerProvider( + tracesdk.WithSampler(tracesdk.AlwaysSample()), + // Always be sure to batch in production. + tracesdk.WithBatcher(exporter), + // Record information about this application in a Resource. + tracesdk.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNamespaceKey.String(nodeType.String()), + semconv.ServiceNameKey.String(fmt.Sprintf("%s/%s", network.String(), peerID.String()))), + )) + + if len(pyroOpts) > 0 { + tp = otelpyroscope.NewTracerProvider(tp, pyroOpts...) + } + otel.SetTracerProvider(tp) + return nil +} + // initializeMetrics initializes the global meter provider. func initializeMetrics( ctx context.Context, lc fx.Lifecycle, peerID peer.ID, nodeType node.Type, - buildInfo *node.BuildInfo, + network p2p.Network, opts []otlpmetrichttp.Option, ) error { exp, err := otlpmetrichttp.New(ctx, opts...) @@ -123,10 +169,8 @@ func initializeMetrics( sdk.WithReader(sdk.NewPeriodicReader(exp, sdk.WithTimeout(2*time.Second))), sdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNamespaceKey.String(fmt.Sprintf("Celestia-%s", nodeType.String())), - semconv.ServiceNameKey.String(fmt.Sprintf("semver-%s", buildInfo.SemanticVersion)), - semconv.ServiceInstanceIDKey.String(peerID.String()), - ))) + semconv.ServiceNamespaceKey.String(nodeType.String()), + semconv.ServiceNameKey.String(fmt.Sprintf("%s/%s", network.String(), peerID.String()))))) lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { return provider.Shutdown(ctx) From 03976abcc05925cb92df2966241688ceb31448d1 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 17 Jul 2023 17:49:14 +0300 Subject: [PATCH 0705/1008] fix: remove root from traces (#2483) --- share/eds/retriever.go | 2 -- share/eds/store.go | 11 +++++------ share/getters/cascade.go | 6 +----- share/getters/ipld.go | 6 +----- share/getters/shrex.go | 5 +---- share/getters/store.go | 6 +----- share/getters/tee.go | 6 +----- share/getters/utils.go | 1 - share/ipld/namespace_data.go | 1 - 9 files changed, 10 insertions(+), 34 deletions(-) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 2483a37a92..0f52a903bc 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -64,7 +64,6 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader defer span.End() span.SetAttributes( attribute.Int("size", len(dah.RowRoots)), - attribute.String("data_hash", dah.String()), ) log.Debugw("retrieving data square", "data_hash", dah.String(), "size", len(dah.RowRoots)) @@ -248,7 +247,6 @@ func (rs *retrievalSession) doRequest(ctx context.Context, q *quadrant) { nd, err := ipld.GetNode(ctx, rs.bget, root) if err != nil { rs.span.RecordError(err, trace.WithAttributes( - attribute.String("requesting-root", root.String()), attribute.Int("root-index", i), )) return diff --git a/share/eds/store.go b/share/eds/store.go index f0d02a1141..0683df0d99 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -168,7 +168,6 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext } ctx, span := tracer.Start(ctx, "store/put", trace.WithAttributes( - attribute.String("root", root.String()), attribute.Int("width", int(square.Width())), )) defer func() { @@ -214,7 +213,7 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext // The shard is cached in the Store, so subsequent calls to GetCAR with the same root will use the // same reader. The cache is responsible for closing the underlying reader. func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { - ctx, span := tracer.Start(ctx, "store/get-car", trace.WithAttributes(attribute.String("root", root.String()))) + ctx, span := tracer.Start(ctx, "store/get-car") defer span.End() key := root.String() @@ -251,7 +250,7 @@ func (s *Store) CARBlockstore( // GetDAH returns the DataAvailabilityHeader for the EDS identified by DataHash. func (s *Store) GetDAH(ctx context.Context, root share.DataHash) (*share.Root, error) { - ctx, span := tracer.Start(ctx, "store/get-dah", trace.WithAttributes(attribute.String("root", root.String()))) + ctx, span := tracer.Start(ctx, "store/get-dah") defer span.End() key := shard.KeyFromString(root.String()) @@ -330,7 +329,7 @@ func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessor // Remove removes EDS from Store by the given share.Root hash and cleans up all // the indexing. func (s *Store) Remove(ctx context.Context, root share.DataHash) (err error) { - ctx, span := tracer.Start(ctx, "store/remove", trace.WithAttributes(attribute.String("root", root.String()))) + ctx, span := tracer.Start(ctx, "store/remove") defer func() { utils.SetStatusAndEnd(span, err) }() @@ -371,7 +370,7 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) (err error) { // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by // recomputing it. func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.ExtendedDataSquare, err error) { - ctx, span := tracer.Start(ctx, "store/get", trace.WithAttributes(attribute.String("root", root.String()))) + ctx, span := tracer.Start(ctx, "store/get") defer func() { utils.SetStatusAndEnd(span, err) }() @@ -389,7 +388,7 @@ func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.Exten // Has checks if EDS exists by the given share.Root hash. func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { - _, span := tracer.Start(ctx, "store/has", trace.WithAttributes(attribute.String("root", root.String()))) + _, span := tracer.Start(ctx, "store/has") defer span.End() key := root.String() diff --git a/share/getters/cascade.go b/share/getters/cascade.go index fe99bed4c3..ef59946666 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -33,7 +33,6 @@ func NewCascadeGetter(getters []share.Getter) *CascadeGetter { // GetShare gets a share from any of registered share.Getters in cascading order. func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { ctx, span := tracer.Start(ctx, "cascade/get-share", trace.WithAttributes( - attribute.String("root", root.String()), attribute.Int("row", row), attribute.Int("col", col), )) @@ -48,9 +47,7 @@ func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, co // GetEDS gets a full EDS from any of registered share.Getters in cascading order. func (cg *CascadeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - ctx, span := tracer.Start(ctx, "cascade/get-eds", trace.WithAttributes( - attribute.String("root", root.String()), - )) + ctx, span := tracer.Start(ctx, "cascade/get-eds") defer span.End() get := func(ctx context.Context, get share.Getter) (*rsmt2d.ExtendedDataSquare, error) { @@ -68,7 +65,6 @@ func (cg *CascadeGetter) GetSharesByNamespace( namespace share.Namespace, ) (share.NamespacedShares, error) { ctx, span := tracer.Start(ctx, "cascade/get-shares-by-namespace", trace.WithAttributes( - attribute.String("root", root.String()), attribute.String("namespace", namespace.String()), )) defer span.End() diff --git a/share/getters/ipld.go b/share/getters/ipld.go index 92d1c55678..af6e7673ba 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -41,7 +41,6 @@ func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter { func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { var err error ctx, span := tracer.Start(ctx, "ipld/get-share", trace.WithAttributes( - attribute.String("root", dah.String()), attribute.Int("row", row), attribute.Int("col", col), )) @@ -66,9 +65,7 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in } func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { - ctx, span := tracer.Start(ctx, "ipld/get-eds", trace.WithAttributes( - attribute.String("root", root.String()), - )) + ctx, span := tracer.Start(ctx, "ipld/get-eds") defer func() { utils.SetStatusAndEnd(span, err) }() @@ -91,7 +88,6 @@ func (ig *IPLDGetter) GetSharesByNamespace( namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "ipld/get-shares-by-namespace", trace.WithAttributes( - attribute.String("root", root.String()), attribute.String("namespace", namespace.String()), )) defer func() { diff --git a/share/getters/shrex.go b/share/getters/shrex.go index c754d73c1d..5a2f696854 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -128,9 +128,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex attempt int err error ) - ctx, span := tracer.Start(ctx, "shrex/get-eds", trace.WithAttributes( - attribute.String("root", root.String()), - )) + ctx, span := tracer.Start(ctx, "shrex/get-eds") defer func() { utils.SetStatusAndEnd(span, err) }() @@ -198,7 +196,6 @@ func (sg *ShrexGetter) GetSharesByNamespace( err error ) ctx, span := tracer.Start(ctx, "shrex/get-shares-by-namespace", trace.WithAttributes( - attribute.String("root", root.String()), attribute.String("namespace", namespace.String()), )) defer func() { diff --git a/share/getters/store.go b/share/getters/store.go index 0b88d89155..28c6c6d021 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -36,7 +36,6 @@ func NewStoreGetter(store *eds.Store) *StoreGetter { func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { var err error ctx, span := tracer.Start(ctx, "store/get-share", trace.WithAttributes( - attribute.String("root", dah.String()), attribute.Int("row", row), attribute.Int("col", col), )) @@ -70,9 +69,7 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i // GetEDS gets the EDS identified by the given root from the EDS store. func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (data *rsmt2d.ExtendedDataSquare, err error) { - ctx, span := tracer.Start(ctx, "store/get-eds", trace.WithAttributes( - attribute.String("root", root.String()), - )) + ctx, span := tracer.Start(ctx, "store/get-eds") defer func() { utils.SetStatusAndEnd(span, err) }() @@ -96,7 +93,6 @@ func (sg *StoreGetter) GetSharesByNamespace( namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "store/get-shares-by-namespace", trace.WithAttributes( - attribute.String("root", root.String()), attribute.String("namespace", namespace.String()), )) defer func() { diff --git a/share/getters/tee.go b/share/getters/tee.go index 213f05f90c..fe43c971b7 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -35,7 +35,6 @@ func NewTeeGetter(getter share.Getter, store *eds.Store) *TeeGetter { func (tg *TeeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share share.Share, err error) { ctx, span := tracer.Start(ctx, "tee/get-share", trace.WithAttributes( - attribute.String("root", root.String()), attribute.Int("row", row), attribute.Int("col", col), )) @@ -47,9 +46,7 @@ func (tg *TeeGetter) GetShare(ctx context.Context, root *share.Root, row, col in } func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { - ctx, span := tracer.Start(ctx, "tee/get-eds", trace.WithAttributes( - attribute.String("root", root.String()), - )) + ctx, span := tracer.Start(ctx, "tee/get-eds") defer func() { utils.SetStatusAndEnd(span, err) }() @@ -73,7 +70,6 @@ func (tg *TeeGetter) GetSharesByNamespace( namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "tee/get-shares-by-namespace", trace.WithAttributes( - attribute.String("root", root.String()), attribute.String("namespace", namespace.String()), )) defer func() { diff --git a/share/getters/utils.go b/share/getters/utils.go index 94b1cfe5ee..5305c5c737 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -46,7 +46,6 @@ func collectSharesByNamespace( namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "collect-shares-by-namespace", trace.WithAttributes( - attribute.String("root", root.String()), attribute.String("namespace", namespace.String()), )) defer func() { diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index d776da219d..38dfdb2169 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -198,7 +198,6 @@ func (n *NamespaceData) CollectLeavesByNamespace( span.SetAttributes( attribute.String("namespace", n.namespace.String()), - attribute.String("root", root.String()), ) // buffer the jobs to avoid blocking, we only need as many From 6a6c68c42ff940467476c0a6876e7d986b0e84aa Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 17 Jul 2023 18:30:59 +0300 Subject: [PATCH 0706/1008] feat(share/byzantine): handle additional cases in befp validation (#2383) Co-authored-by: Hlib Kanunnikov --- share/eds/byzantine/bad_encoding.go | 45 +++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 9d6cbff229..95f2a7fd03 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -2,6 +2,7 @@ package byzantine import ( "bytes" + "errors" "fmt" "github.com/celestiaorg/celestia-app/pkg/wrapper" @@ -114,6 +115,7 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { if !ok { panic(fmt.Sprintf("invalid header type received during BEFP validation: expected %T, got %T", header, hdr)) } + if header.Height() != int64(p.BlockHeight) { return fmt.Errorf("incorrect block height during BEFP validation: expected %d, got %d", p.BlockHeight, header.Height(), @@ -137,11 +139,13 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { if p.Axis == rsmt2d.Row { merkleRoots = header.DAH.ColumnRoots } + if int(p.Index) >= len(merkleRoots) { return fmt.Errorf("invalid %s proof: index out of bounds (%d >= %d)", BadEncoding, int(p.Index), len(merkleRoots), ) } + if len(p.Shares) != len(merkleRoots) { // Since p.Shares should contain all the shares from either a row or a // column, it should exactly match the number of row roots. In this @@ -152,6 +156,22 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { ) } + odsWidth := uint64(len(merkleRoots) / 2) + amount := uint64(0) + for _, share := range p.Shares { + if share == nil { + continue + } + amount++ + if amount == odsWidth { + break + } + } + + if amount < odsWidth { + return errors.New("fraud: invalid proof: not enough shares provided to reconstruct row/col") + } + // verify that Merkle proofs correspond to particular shares. shares := make([][]byte, len(merkleRoots)) for index, shr := range p.Shares { @@ -167,17 +187,24 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { shares[index] = share.GetData(shr.Share) } - odsWidth := uint64(len(merkleRoots) / 2) codec := share.DefaultRSMT2DCodec() - // rebuild a row or col. + // We can conclude that the proof is valid in case we proved the inclusion of `Shares` but + // the row/col can't be reconstructed, or the building of NMTree fails. rebuiltShares, err := codec.Decode(shares) if err != nil { - return err + log.Infow("failed to decode shares at height", + "height", header.Height(), "err", err, + ) + return nil } + rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0:odsWidth]) if err != nil { - return err + log.Infow("failed to encode shares at height", + "height", header.Height(), "err", err, + ) + return nil } copy(rebuiltShares[odsWidth:], rebuiltExtendedShares) @@ -185,13 +212,19 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { for _, share := range rebuiltShares { err = tree.Push(share) if err != nil { - return err + log.Infow("failed to build a tree from the reconstructed shares at height", + "height", header.Height(), "err", err, + ) + return nil } } expectedRoot, err := tree.Root() if err != nil { - return err + log.Infow("failed to build a tree root at height", + "height", header.Height(), "err", err, + ) + return nil } // root is a merkle root of the row/col where ErrByzantine occurred From c5565885d68f515d2c90ceee014372e1cfe3c3a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:35:46 +0000 Subject: [PATCH 0707/1008] chore(deps): bump celestiaorg/.github from 0.2.0 to 0.2.2 (#2484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [celestiaorg/.github](https://github.com/celestiaorg/.github) from 0.2.0 to 0.2.2.
    Release notes

    Sourced from celestiaorg/.github's releases.

    Release v0.2.2

    0.2.2 (2023-07-11)

    Overview

    This release fixes a bug in the docker reusable workflow that was causing it to fail on forks.

    What's Changed

    Full Changelog: https://github.com/celestiaorg/.github/compare/v0.2.1...v0.2.2

    v0.2.1

    What's Changed

    Full Changelog: https://github.com/celestiaorg/.github/compare/v0.2.0...v0.2.1

    Commits
    • 84d7d3c Revert "Remove failing docker publish line" (#64)
    • 911f77b feat: use context - seems like it fixes the ldflags issue (#65)
    • 5f647a9 Remove failing docker publish line (#62)
    • d3fbccf chore(deps): bump necojackarc/auto-request-review from 0.10.0 to 0.12.0 (#61)
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=celestiaorg/.github&package-manager=github_actions&previous-version=0.2.0&new-version=0.2.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 653add9256..e10a032681 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -25,7 +25,7 @@ on: jobs: # Dockerfile Linting hadolint: - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.0 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.2 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.0 + - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.2 markdown-lint: name: Markdown Lint @@ -59,7 +59,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@v0.2.0 + uses: celestiaorg/.github/.github/actions/version-release@v0.2.2 with: github-token: ${{secrets.GITHUB_TOKEN}} version-bump: ${{inputs.version}} From 4556e6f73fea197c2a472da765279164d73d0db8 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 20 Jul 2023 17:46:56 +0200 Subject: [PATCH 0708/1008] fix(nodebuilder/tests/swamp): Bump timeout (#2504) Necessary to get core client to produce blocks in swamp at the moment. Found by @rootulp --- nodebuilder/tests/swamp/swamp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index eabe33dc9c..3532635883 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -164,7 +164,7 @@ func (s *Swamp) createPeer(ks keystore.Keystore) host.Host { // setupGenesis sets up genesis Header. // This is required to initialize and start correctly. func (s *Swamp) setupGenesis() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() // ensure core has surpassed genesis block From 8a06f313afb2fcb27295ccd4fbade61daff4edce Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 24 Jul 2023 17:54:48 +0300 Subject: [PATCH 0709/1008] dep: update nmt version (#2509) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index eae1f5842b..4275047ede 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/celestiaorg/go-fraud v0.1.2 github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/nmt v0.17.0 + github.com/celestiaorg/nmt v0.18.1 github.com/celestiaorg/rsmt2d v0.10.0 github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/cosmos-sdk/api v0.1.0 diff --git a/go.sum b/go.sum index 7ef5215bfd..a6d7f9e346 100644 --- a/go.sum +++ b/go.sum @@ -362,8 +362,8 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.17.0 h1:/k8YLwJvuHgT/jQ435zXKaDX811+sYEMXL4B/vYdSLU= -github.com/celestiaorg/nmt v0.17.0/go.mod h1:ZndCeAR4l9lxm7W51ouoyTo1cxhtFgK+4DpEIkxRA3A= +github.com/celestiaorg/nmt v0.18.1 h1:zU3apzW4y0fs0ilQA74XnEYW8FvRv0CUK2LXK66L3rA= +github.com/celestiaorg/nmt v0.18.1/go.mod h1:0l8q6UYRju1xNrxtvV6NwPdW3lfsN6KuZ0htRnModdc= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.10.0 h1:8dprr6CW5mCk5YPnbiLdirojw9YsJOE+XB+GORb8sT0= From 6a7275d137be3cdf9cdd5f17e3bb7aef6537d9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:14:38 +0200 Subject: [PATCH 0710/1008] feat: new metrics - deployment dashboard (#2464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello team! I've added these changes to the code to expose some new metrics via Prometheus client to create the deployment dashboard in Grafana, this new dashboard will include info related to the chains, such as branches/tags, number of nodes, URLs, etc... *I'm using the values generated by the `ldflags`* The dashboard will look like this: Screenshot 2023-07-12 at 13 07 12 *Pending adding the consensus info, it will contain more info* Please, check the code and let me know if you find something to improve, I'll gladly do it 😊 Thanks in advance! 🚀 Jose Ramon Mañes closes: https://github.com/celestiaorg/devops/issues/401 --- nodebuilder/node/metrics.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/nodebuilder/node/metrics.go b/nodebuilder/node/metrics.go index 7d722524e8..07c9a5fc0f 100644 --- a/nodebuilder/node/metrics.go +++ b/nodebuilder/node/metrics.go @@ -5,6 +5,7 @@ import ( "time" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) @@ -33,6 +34,14 @@ func WithMetrics() error { return err } + buildInfoGauge, err := meter.Float64ObservableGauge( + "build_info", + metric.WithDescription("Celestia Node build information"), + ) + if err != nil { + return err + } + callback := func(ctx context.Context, observer metric.Observer) error { if !nodeStarted { // Observe node start timestamp @@ -42,9 +51,22 @@ func WithMetrics() error { } observer.ObserveFloat64(totalNodeRunTime, time.Since(timeStarted).Seconds()) + + // Observe build info with labels + labels := metric.WithAttributes( + attribute.String("build_time", buildTime), + attribute.String("last_commit", lastCommit), + attribute.String("semantic_version", semanticVersion), + attribute.String("system_version", systemVersion), + attribute.String("golang_version", golangVersion), + ) + + observer.ObserveFloat64(buildInfoGauge, 1, labels) + return nil } - _, err = meter.RegisterCallback(callback, nodeStartTS, totalNodeRunTime) + _, err = meter.RegisterCallback(callback, nodeStartTS, totalNodeRunTime, buildInfoGauge) + return err } From 7d981d58a5055d0130b832e3a8a0e889635f7c7d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 26 Jul 2023 21:26:18 +0800 Subject: [PATCH 0711/1008] test(share/utils): fix no deadline test for ctxWithSplitTimeout (#2502) Quick fix for one of the ctxWithSplitTimeout test cases. --- share/getters/utils_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/share/getters/utils_test.go b/share/getters/utils_test.go index db8cd0f8cc..5ffad228f9 100644 --- a/share/getters/utils_test.go +++ b/share/getters/utils_test.go @@ -186,7 +186,7 @@ func Test_ctxWithSplitTimeout(t *testing.T) { splitFactor: []int{-1, 0, 1, 2}, minTimeout: time.Minute, }, - want: 0, + want: time.Minute, }, { name: "no context timeout, minTimeout = 0", @@ -201,12 +201,18 @@ func Test_ctxWithSplitTimeout(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { for _, sf := range tt.args.splitFactor { - ctx, cancel := context.WithTimeout(context.Background(), tt.args.ctxTimeout) + ctx, cancel := context.WithCancel(context.Background()) + // add timeout if original context should have it + if tt.args.ctxTimeout > 0 { + ctx, cancel = context.WithTimeout(ctx, tt.args.ctxTimeout) + } t.Cleanup(cancel) got, _ := ctxWithSplitTimeout(ctx, sf, tt.args.minTimeout) dl, ok := got.Deadline() - if !ok { - require.Equal(t, tt.want, 0) + // in case no deadline is found in ctx or not expected to be found, check both cases apply at the same time + if !ok || tt.want == 0 { + require.False(t, ok) + require.Equal(t, tt.want, time.Duration(0)) continue } d := time.Until(dl) From e354bb5414b5bde5b7b950f6b29a0419ca44214b Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 26 Jul 2023 21:44:59 +0800 Subject: [PATCH 0712/1008] fix(share/availability): update availability ErrNotAvailable handling logic (#2445) ## Overview `ipldFormat.IsNotFound` could no longer reach Availability layer. It will get converted on getter layer to `share.ErrNotFound`. Update outdated error handling. --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/availability/full/availability.go | 3 +-- share/availability/full/availability_test.go | 26 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 4b19183be1..fb8dead839 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -4,7 +4,6 @@ import ( "context" "errors" - ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/share" @@ -77,7 +76,7 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro } log.Errorw("availability validation failed", "root", root.String(), "err", err.Error()) var byzantineErr *byzantine.ErrByzantine - if ipldFormat.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { + if errors.Is(err, share.ErrNotFound) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { return share.ErrNotAvailable } } diff --git a/share/availability/full/availability_test.go b/share/availability/full/availability_test.go index 48ae0534aa..b6d217cd6b 100644 --- a/share/availability/full/availability_test.go +++ b/share/availability/full/availability_test.go @@ -4,9 +4,16 @@ import ( "context" "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" + + "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/mocks" ) func TestShareAvailableOverMocknet_Full(t *testing.T) { @@ -32,3 +39,22 @@ func TestSharesAvailable_Full(t *testing.T) { err := avail.SharesAvailable(ctx, dah) assert.NoError(t, err) } + +func TestSharesAvailable_Full_ErrNotAvailable(t *testing.T) { + ctrl := gomock.NewController(t) + getter := mocks.NewMockGetter(ctrl) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + eds := edstest.RandEDS(t, 4) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) + avail := TestAvailability(getter) + + errors := []error{share.ErrNotFound, context.DeadlineExceeded} + for _, getterErr := range errors { + getter.EXPECT().GetEDS(gomock.Any(), gomock.Any()).Return(nil, getterErr) + err := avail.SharesAvailable(ctx, &dah) + require.ErrorIs(t, err, share.ErrNotAvailable) + } +} From 26d97c33fc566b740d9e19a994e13ab58d0fd848 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:32:46 +0000 Subject: [PATCH 0713/1008] chore(deps): bump cosmossdk.io/errors from 1.0.0-beta.7 to 1.0.0 (#2486) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 4275047ede..76baac83cc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch require ( - cosmossdk.io/errors v1.0.0-beta.7 + cosmossdk.io/errors v1.0.0 cosmossdk.io/math v1.0.0-rc.0 github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 @@ -74,11 +74,11 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.9.0 + golang.org/x/crypto v0.11.0 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/sync v0.2.0 - golang.org/x/text v0.9.0 - google.golang.org/grpc v1.56.1 + golang.org/x/text v0.11.0 + google.golang.org/grpc v1.56.2 google.golang.org/protobuf v1.31.0 ) @@ -311,17 +311,17 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect golang.org/x/tools v0.9.1 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index a6d7f9e346..3a44e1f25b 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuW cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= -cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= +cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= +cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= cosmossdk.io/math v1.0.0-rc.0 h1:ml46ukocrAAoBpYKMidF0R2tQJ1Uxfns0yH8wqgMAFc= cosmossdk.io/math v1.0.0-rc.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= @@ -2159,8 +2159,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2293,8 +2293,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2470,14 +2470,14 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2488,8 +2488,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2763,12 +2763,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= +google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs= +google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2815,8 +2815,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 329ab208e6d1b5a09320831cc6db55fbab457af3 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Mon, 31 Jul 2023 07:53:48 -0400 Subject: [PATCH 0714/1008] fix: check for namespace version 0 (#2514) Closes https://github.com/celestiaorg/celestia-node/issues/2513 Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- share/namespace.go | 3 +++ share/namespace_test.go | 53 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/share/namespace.go b/share/namespace.go index 433529a57f..276575a309 100644 --- a/share/namespace.go +++ b/share/namespace.go @@ -113,6 +113,9 @@ func (n Namespace) ValidateForData() error { if n.Equals(ParitySharesNamespace) || n.Equals(TailPaddingNamespace) { return fmt.Errorf("invalid data namespace(%s): parity and tail padding namespace are forbidden", n) } + if n.Version() != appns.NamespaceVersionZero { + return fmt.Errorf("invalid data namespace(%s): only version 0 is supported", n) + } return nil } diff --git a/share/namespace_test.go b/share/namespace_test.go index 62746e4b4e..7c18d233c8 100644 --- a/share/namespace_test.go +++ b/share/namespace_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" appns "github.com/celestiaorg/celestia-app/pkg/namespace" ) @@ -135,3 +136,55 @@ func TestFrom(t *testing.T) { }) } } + +func TestValidateForBlob(t *testing.T) { + type testCase struct { + name string + ns Namespace + wantErr bool + } + + validNamespace, err := NewBlobNamespaceV0(bytes.Repeat([]byte{0x1}, appns.NamespaceVersionZeroIDSize)) + require.NoError(t, err) + + testCases := []testCase{ + { + name: "valid blob namespace", + ns: validNamespace, + wantErr: false, + }, + { + name: "invalid blob namespace: parity shares namespace", + ns: ParitySharesNamespace, + wantErr: true, + }, + { + name: "invalid blob namespace: tail padding namespace", + ns: TailPaddingNamespace, + wantErr: true, + }, + { + name: "invalid blob namespace: tx namespace", + ns: TxNamespace, + wantErr: true, + }, + { + name: "invalid blob namespace: namespace version max", + ns: append([]byte{appns.NamespaceVersionMax}, bytes.Repeat([]byte{0x0}, appns.NamespaceIDSize)...), + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.ns.ValidateForBlob() + + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + }) + } + +} From 954f748ee63ebbdd95ec706b5b82a01ef4f5b1bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:02:42 +0000 Subject: [PATCH 0715/1008] chore(deps): bump github.com/spf13/cobra from 1.6.1 to 1.7.0 (#2487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.1 to 1.7.0.
    Release notes

    Sourced from github.com/spf13/cobra's releases.

    v1.7.0

    ✨ Features

    🐛 Bug fixes

    🧪 Testing & CI/CD

    🔧 Maintenance

    ✏️ Documentation


    This release contains several long running fixes, improvements to powershell completions, and further optimizations for completions.

    Thank you everyone who contributed to this release and all your hard work! Cobra and this community would never be possible without all of you! 🐍

    Full changelog: https://github.com/spf13/cobra/compare/v1.6.1...v1.7.0

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/spf13/cobra&package-manager=go_modules&previous-version=1.6.1&new-version=1.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: John Adler --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 76baac83cc..875f6557e7 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/prometheus/client_golang v1.14.0 github.com/pyroscope-io/client v0.7.1 github.com/pyroscope-io/otel-profiling-go v0.4.0 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 @@ -193,7 +193,7 @@ require ( github.com/huin/goupnp v1.2.0 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/influxdb-client-go/v2 v2.12.2 // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/ipfs/bbloom v0.0.4 // indirect diff --git a/go.sum b/go.sum index 3a44e1f25b..b6a8f996e0 100644 --- a/go.sum +++ b/go.sum @@ -942,8 +942,8 @@ github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+h github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= @@ -1900,8 +1900,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= From 794360f2f83c7c797ea5c0be7d8c61aaed8cd0db Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 31 Jul 2023 21:21:49 +0800 Subject: [PATCH 0716/1008] test(share/p2p): fix namespace add operations in tests (#2422) ## Overview Fixes the test with unreliable add/sub operations. Adds AddToNamespace for tests --- share/getters/shrex_test.go | 119 ++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 11 deletions(-) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 0dbe7e44db..d2dcfeca5e 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -2,6 +2,8 @@ package getters import ( "context" + "encoding/binary" + "errors" "testing" "time" @@ -100,18 +102,16 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - // corrupt NID - nID := make([]byte, share.NamespaceSize) - copy(nID, maxNamespace) - nID[share.NamespaceSize-1]-- + namespace, err := addToNamespace(maxNamespace, -1) + require.NoError(t, err) // check for namespace to be between max and min namespace in root - require.Len(t, filterRootsByNamespace(&dah, maxNamespace), 1) + require.Len(t, filterRootsByNamespace(&dah, namespace), 1) - emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, nID) + emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, namespace) require.NoError(t, err) // no shares should be returned require.Empty(t, emptyShares.Flatten()) - require.Nil(t, emptyShares.Verify(&dah, nID)) + require.Nil(t, emptyShares.Verify(&dah, namespace)) }) t.Run("ND_namespace_not_in_dah", func(t *testing.T) { @@ -126,10 +126,8 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - // corrupt namespace - namespace := make([]byte, share.NamespaceSize) - copy(namespace, maxNamesapce) - namespace[share.NamespaceSize-1]++ + namespace, err := addToNamespace(maxNamesapce, 1) + require.NoError(t, err) // check for namespace to be not in root require.Len(t, filterRootsByNamespace(&dah, namespace), 0) @@ -271,3 +269,102 @@ func newEDSClientServer( require.NoError(t, err) return client, server } + +// addToNamespace adds arbitrary int value to namespace, treating namespace as big-endian implementation of int +func addToNamespace(namespace share.Namespace, val int) (share.Namespace, error) { + if val == 0 { + return namespace, nil + } + // Convert the input integer to a byte slice and add it to result slice + result := make([]byte, len(namespace)) + if val > 0 { + binary.BigEndian.PutUint64(result[len(namespace)-8:], uint64(val)) + } else { + binary.BigEndian.PutUint64(result[len(namespace)-8:], uint64(-val)) + } + + // Perform addition byte by byte + var carry int + for i := len(namespace) - 1; i >= 0; i-- { + sum := 0 + if val > 0 { + sum = int(namespace[i]) + int(result[i]) + carry + } else { + sum = int(namespace[i]) - int(result[i]) + carry + } + + switch { + case sum > 255: + carry = 1 + sum -= 256 + case sum < 0: + carry = -1 + sum += 256 + default: + carry = 0 + } + + result[i] = uint8(sum) + } + + // Handle any remaining carry + if carry != 0 { + return nil, errors.New("namespace overflow") + } + + return result, nil +} + +func TestAddToNamespace(t *testing.T) { + testCases := []struct { + name string + value int + input share.Namespace + expected share.Namespace + expectedError error + }{ + { + name: "Positive value addition", + value: 42, + input: share.Namespace{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, + expected: share.Namespace{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2b}, + expectedError: nil, + }, + { + name: "Negative value addition", + value: -42, + input: share.Namespace{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, + expected: share.Namespace{0x1, 0x1, 0x1, 0x1, 0x1, 0x01, 0x1, 0x1, 0x1, 0x0, 0xd7}, + expectedError: nil, + }, + { + name: "Overflow error", + value: 1, + input: share.Namespace{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + expected: nil, + expectedError: errors.New("namespace overflow"), + }, + { + name: "Overflow error negative", + value: -1, + input: share.Namespace{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + expected: nil, + expectedError: errors.New("namespace overflow"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := addToNamespace(tc.input, tc.value) + if tc.expectedError == nil { + require.NoError(t, err) + require.Equal(t, tc.expected, result) + return + } + require.Error(t, err) + if err.Error() != tc.expectedError.Error() { + t.Errorf("Unexpected error message. Expected: %v, Got: %v", tc.expectedError, err) + } + }) + } +} From 208bb86598b21c6b368f86e1212cf882b8568b77 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 31 Jul 2023 22:17:09 +0800 Subject: [PATCH 0717/1008] fix(share/eds): remove Has() and Sync() badger calls in inverted index (#2518) Remove unnecessary `Has()` and `Sync()` badger calls in inverted index. Removal of calls greatly improve `Put()` performance on eds.Store --- share/eds/inverted_index.go | 25 +++++++------------------ share/eds/inverted_index_test.go | 4 ++-- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index f917619676..0e3665d973 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -40,35 +40,24 @@ func (s *simpleInvertedIndex) AddMultihashesForShard( return fmt.Errorf("failed to create ds batch: %w", err) } - if err := mhIter.ForEach(func(mh multihash.Multihash) error { + err = mhIter.ForEach(func(mh multihash.Multihash) error { key := ds.NewKey(string(mh)) - ok, err := s.ds.Has(ctx, key) + bz, err := json.Marshal(sk) if err != nil { - return fmt.Errorf("failed to check if value for multihash exists %s, err: %w", mh, err) + return fmt.Errorf("failed to marshal shard key to bytes: %w", err) } - - if !ok { - bz, err := json.Marshal(sk) - if err != nil { - return fmt.Errorf("failed to marshal shard key to bytes: %w", err) - } - if err := batch.Put(ctx, key, bz); err != nil { - return fmt.Errorf("failed to put mh=%s, err=%w", mh, err) - } + if err := batch.Put(ctx, key, bz); err != nil { + return fmt.Errorf("failed to put mh=%s, err=%w", mh, err) } - return nil - }); err != nil { + }) + if err != nil { return fmt.Errorf("failed to add index entry: %w", err) } if err := batch.Commit(ctx); err != nil { return fmt.Errorf("failed to commit batch: %w", err) } - - if err := s.ds.Sync(ctx, ds.Key{}); err != nil { - return fmt.Errorf("failed to sync puts: %w", err) - } return nil } diff --git a/share/eds/inverted_index_test.go b/share/eds/inverted_index_test.go index f228aa0d92..8fb037bb92 100644 --- a/share/eds/inverted_index_test.go +++ b/share/eds/inverted_index_test.go @@ -47,10 +47,10 @@ func TestMultihashesForShard(t *testing.T) { require.NoError(t, err) require.Equal(t, []shard.Key{shard.KeyFromString("shard1")}, shardKeys) - // 2. Add mh1 to shard2, and ensure that mh1 still points to shard1 + // 2. Add mh1 to shard2, and ensure that mh1 no longer points to shard1 err = invertedIndex.AddMultihashesForShard(ctx, &mockIterator{mhs: mhs[:1]}, shard.KeyFromString("shard2")) require.NoError(t, err) shardKeys, err = invertedIndex.GetShardsForMultihash(ctx, mhs[0]) require.NoError(t, err) - require.Equal(t, []shard.Key{shard.KeyFromString("shard1")}, shardKeys) + require.Equal(t, []shard.Key{shard.KeyFromString("shard2")}, shardKeys) } From 13c2daba63c9d999b9de8c017749187035ad34d3 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 1 Aug 2023 20:21:43 +0800 Subject: [PATCH 0718/1008] fix(share/eds): turn off eds store dagstore gc by default (#2529) Dagstore GC performs garbage collection by reclaiming transient files of shards that are currently available but inactive, or errored. We don't use transient files right now, so GC is turned off by default. --- share/eds/store.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/share/eds/store.go b/share/eds/store.go index 0683df0d99..9bc539590b 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -34,7 +34,10 @@ const ( indexPath = "/index/" transientsPath = "/transients/" - defaultGCInterval = time.Hour + // GC performs DAG store garbage collection by reclaiming transient files of + // shards that are currently available but inactive, or errored. + // We don't use transient files right now, so GC is turned off by default. + defaultGCInterval = 0 ) var ErrNotFound = errors.New("eds not found in store") @@ -117,13 +120,16 @@ func (s *Store) Start(ctx context.Context) error { return err } // start Store only if DagStore succeeds - ctx, cancel := context.WithCancel(context.Background()) + runCtx, cancel := context.WithCancel(context.Background()) s.cancel = cancel // initialize empty gc result to avoid panic on access s.lastGCResult.Store(&dagstore.GCResult{ Shards: make(map[shard.Key]error), }) - go s.gc(ctx) + + if s.gcInterval != 0 { + go s.gc(runCtx) + } return nil } From 08ffb69f0c14776f6b2acd7cdf774d92524533fd Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 1 Aug 2023 20:39:28 +0800 Subject: [PATCH 0719/1008] feat(share/shrex/nd): add swamp test for shrex nd (#2227) ## Overview Closes https://github.com/celestiaorg/celestia-node/issues/2119 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- nodebuilder/tests/nd_test.go | 199 +++++++++++++++++++++++++++++++++++ share/p2p/shrexnd/server.go | 24 +++-- 2 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 nodebuilder/tests/nd_test.go diff --git a/nodebuilder/tests/nd_test.go b/nodebuilder/tests/nd_test.go new file mode 100644 index 0000000000..955cff2ffe --- /dev/null +++ b/nodebuilder/tests/nd_test.go @@ -0,0 +1,199 @@ +package tests + +import ( + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/stretchr/testify/require" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" +) + +func TestShrexNDFromLights(t *testing.T) { + const ( + blocks = 10 + btime = time.Millisecond * 300 + bsize = 16 + ) + + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) + + bridge := sw.NewBridgeNode() + sw.SetBootstrapper(t, bridge) + + cfg := nodebuilder.DefaultConfig(node.Light) + cfg.Share.Discovery.PeersLimit = 1 + light := sw.NewNodeWithConfig(node.Light, cfg) + + err := bridge.Start(ctx) + require.NoError(t, err) + err = light.Start(ctx) + require.NoError(t, err) + + // wait for chain to be filled + require.NoError(t, <-fillDn) + + // first 2 blocks are not filled with data + for i := 3; i < blocks; i++ { + h, err := bridge.HeaderServ.GetByHeight(ctx, uint64(i)) + require.NoError(t, err) + + reqCtx, cancel := context.WithTimeout(ctx, time.Second*5) + + // ensure to fetch random namespace (not the reserved namespace) + namespace := h.DAH.RowRoots[1][:share.NamespaceSize] + + expected, err := bridge.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + require.NoError(t, err) + got, err := light.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + require.NoError(t, err) + + require.True(t, len(got[0].Shares) > 0) + require.Equal(t, expected, got) + + cancel() + } +} + +func TestShrexNDFromLightsWithBadFulls(t *testing.T) { + const ( + blocks = 10 + btime = time.Millisecond * 300 + bsize = 16 + amountOfFulls = 5 + testTimeout = time.Second * 10 + ) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) + + bridge := sw.NewBridgeNode() + sw.SetBootstrapper(t, bridge) + + // create full nodes with basic stream.reset handler + ndHandler := func(stream network.Stream) { + _ = stream.Reset() + } + fulls := make([]*nodebuilder.Node, 0, amountOfFulls) + for i := 0; i < amountOfFulls; i++ { + cfg := nodebuilder.DefaultConfig(node.Full) + setTimeInterval(cfg, testTimeout) + full := sw.NewNodeWithConfig(node.Full, cfg, replaceNDServer(cfg, ndHandler), replaceShareGetter()) + fulls = append(fulls, full) + } + + lnConfig := nodebuilder.DefaultConfig(node.Light) + lnConfig.Share.Discovery.PeersLimit = uint(amountOfFulls) + light := sw.NewNodeWithConfig(node.Light, lnConfig) + + // start all nodes + require.NoError(t, bridge.Start(ctx)) + require.NoError(t, startFullNodes(ctx, fulls...)) + require.NoError(t, light.Start(ctx)) + + // wait for chain to fill up + require.NoError(t, <-fillDn) + + // first 2 blocks are not filled with data + for i := 3; i < blocks; i++ { + h, err := bridge.HeaderServ.GetByHeight(ctx, uint64(i)) + require.NoError(t, err) + + if len(h.DAH.RowRoots) != bsize*2 { + // fill blocks does not always fill every block to the given block + // size - this check prevents trying to fetch shares for the parity + // namespace. + continue + } + + reqCtx, cancel := context.WithTimeout(ctx, time.Second*5) + + // ensure to fetch random namespace (not the reserved namespace) + namespace := h.DAH.RowRoots[1][:share.NamespaceSize] + + expected, err := bridge.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + require.NoError(t, err) + require.True(t, len(expected[0].Shares) > 0) + + // choose a random full to test + gotFull, err := fulls[len(fulls)/2].ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + require.NoError(t, err) + require.True(t, len(gotFull[0].Shares) > 0) + + gotLight, err := light.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + require.NoError(t, err) + require.True(t, len(gotLight[0].Shares) > 0) + + require.Equal(t, expected, gotFull) + require.Equal(t, expected, gotLight) + + cancel() + } +} + +func startFullNodes(ctx context.Context, fulls ...*nodebuilder.Node) error { + for _, full := range fulls { + err := full.Start(ctx) + if err != nil { + return err + } + } + return nil +} + +func replaceNDServer(cfg *nodebuilder.Config, handler network.StreamHandler) fx.Option { + return fx.Decorate(fx.Annotate( + func( + host host.Host, + store *eds.Store, + getter *getters.StoreGetter, + network p2p.Network, + ) (*shrexnd.Server, error) { + cfg.Share.ShrExNDParams.WithNetworkID(network.String()) + return shrexnd.NewServer(cfg.Share.ShrExNDParams, host, store, getter) + }, + fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { + // replace handler for server + server.SetHandler(handler) + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *shrexnd.Server) error { + return server.Start(ctx) + }), + )) +} + +func replaceShareGetter() fx.Option { + return fx.Decorate(fx.Annotate( + func( + host host.Host, + store *eds.Store, + storeGetter *getters.StoreGetter, + shrexGetter *getters.ShrexGetter, + network p2p.Network, + ) share.Getter { + cascade := make([]share.Getter, 0, 2) + cascade = append(cascade, storeGetter) + cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + return getters.NewCascadeGetter(cascade) + }, + )) +} diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index d61e6b2e68..830a853946 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -29,8 +29,9 @@ type Server struct { host host.Host protocolID protocol.ID - getter share.Getter - store *eds.Store + handler network.StreamHandler + getter share.Getter + store *eds.Store params *Parameters middleware *p2p.Middleware @@ -52,18 +53,20 @@ func NewServer(params *Parameters, host host.Host, store *eds.Store, getter shar middleware: p2p.NewMiddleware(params.ConcurrencyLimit), } - return srv, nil -} - -// Start starts the server -func (srv *Server) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) srv.cancel = cancel handler := func(s network.Stream) { srv.handleNamespacedData(ctx, s) } - srv.host.SetStreamHandler(srv.protocolID, srv.middleware.RateLimitHandler(handler)) + srv.handler = srv.middleware.RateLimitHandler(handler) + + return srv, nil +} + +// Start starts the server +func (srv *Server) Start(context.Context) error { + srv.host.SetStreamHandler(srv.protocolID, srv.handler) return nil } @@ -74,6 +77,11 @@ func (srv *Server) Stop(context.Context) error { return nil } +// SetHandler sets server handler +func (srv *Server) SetHandler(handler network.StreamHandler) { + srv.handler = handler +} + func (srv *Server) observeRateLimitedRequests() { numRateLimited := srv.middleware.DrainCounter() if numRateLimited > 0 { From d0e271ebaad6af7680cba889c49c39768d50524c Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 2 Aug 2023 09:30:25 +0200 Subject: [PATCH 0720/1008] feat(share/eds): introduce List method for the Store (#2494) --- share/eds/store.go | 15 +++++++++++++++ share/eds/store_test.go | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/share/eds/store.go b/share/eds/store.go index 9bc539590b..bffbed9f64 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "encoding/hex" "errors" "fmt" "io" @@ -409,6 +410,20 @@ func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { } } +// List lists all the registered EDSes. +func (s *Store) List() ([]share.DataHash, error) { + shards := s.dgstr.AllShardsInfo() + hashes := make([]share.DataHash, 0, len(shards)) + for shrd := range shards { + hash, err := hex.DecodeString(shrd.String()) + if err != nil { + return nil, err + } + hashes = append(hashes, hash) + } + return hashes, nil +} + func setupPath(basepath string) error { err := os.MkdirAll(basepath+blocksPath, os.ModePerm) if err != nil { diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 491b63c48a..4b263e7062 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -154,6 +154,23 @@ func TestEDSStore(t *testing.T) { _, err = edsStore.cache.Get(shardKey) assert.NoError(t, err, errCacheMiss) }) + + t.Run("List", func(t *testing.T) { + const amount = 10 + hashes := make([]share.DataHash, 0, amount) + for range make([]byte, amount) { + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + hashes = append(hashes, dah.Hash()) + } + + hashesOut, err := edsStore.List() + require.NoError(t, err) + for _, hash := range hashes { + assert.Contains(t, hashesOut, hash) + } + }) } // TestEDSStore_GC verifies that unused transient shards are collected by the GC periodically. From 165f81ae9f848b383bea054c55232f2a3aeb2685 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 2 Aug 2023 18:41:46 +0800 Subject: [PATCH 0721/1008] fix(share/eds): print error for stuck register shard (#2516) Sometimes register shard can take longer, that provided context timeout. This PR logs errors if register shard is stuck. --- share/eds/store.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/share/eds/store.go b/share/eds/store.go index bffbed9f64..cd85380ba1 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -203,6 +203,7 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext select { case <-ctx.Done(): + go logLateResult(ch, time.Minute*5) return ctx.Err() case result := <-ch: if result.Error != nil { @@ -212,6 +213,31 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext } } +// waitForResult waits for a result from the res channel for a maximum duration specified by +// maxWait. If the result is not received within the specified duration, it logs an error +// indicating that the parent context has expired and the shard registration is stuck. If a result +// is received, it checks for any error and logs appropriate messages. +func logLateResult(res <-chan dagstore.ShardResult, maxWait time.Duration) { + tnow := time.Now() + select { + case <-time.After(maxWait): + log.Errorf("parent context is expired, while register shard is stuck for more than %v sec", time.Since(tnow)) + return + case result := <-res: + // don't log if result was received right after launch of the func + if time.Since(tnow) < time.Second { + return + } + if result.Error != nil { + log.Errorf("failed to register shard after context expired: %v ago, err: %w", time.Since(tnow), result.Error) + return + } + log.Warnf("parent context expired, but register shard finished with no error,"+ + " after context expired: %v ago", time.Since(tnow)) + return + } +} + // GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as a // CARv1 file. // The Reader strictly reads the CAR header and first quadrant (1/4) of the EDS, omitting all the From 110a41cf70e88a165f68c50b23e4f4789eecfcaa Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Wed, 2 Aug 2023 15:56:17 +0200 Subject: [PATCH 0722/1008] chore(deps): bump celestia-app to rc10 (#2508) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 875f6557e7..ef1d8cc01a 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc9 + github.com/celestiaorg/celestia-app v1.0.0-rc10 github.com/celestiaorg/go-fraud v0.1.2 github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 @@ -332,7 +332,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app diff --git a/go.sum b/go.sum index b6a8f996e0..82e18196ba 100644 --- a/go.sum +++ b/go.sum @@ -344,12 +344,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc9 h1:6xDYE+OziXO/rLeYy/MutnJpE8M2sIPryZ/ifSWUmdc= -github.com/celestiaorg/celestia-app v1.0.0-rc9/go.mod h1:aGFnIIdA30DtFzznYbcfMdNnXiUebfEUkkrQu8imC3I= +github.com/celestiaorg/celestia-app v1.0.0-rc10 h1:wglTplF5qVVOtSYCGtNHz8qA37azD+fgcxCKAphYL6c= +github.com/celestiaorg/celestia-app v1.0.0-rc10/go.mod h1:A17xfzurB2RHpplh6+nGMeC9hCquu0aMYnanOeifDO0= github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHVxcTzhzQW44KgTcooR3OxnK4= github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= -github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13 h1:N1PrCWcYkaODeIQyyVBmDKDTwiQWZ31bgtTEYIGeby8= -github.com/celestiaorg/cosmos-sdk v1.16.0-sdk-v0.46.13/go.mod h1:xpBZc/OYZ736hp0IZlBGNUhEgCD9C+bKs8yNLZibyv0= +github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13 h1:CxEQDQEQR1ypB+VUmCISIqFVmHfb+mx8x+zh7rHbyU8= +github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13/go.mod h1:xpBZc/OYZ736hp0IZlBGNUhEgCD9C+bKs8yNLZibyv0= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= From fb8702d5814f258926eea16e0e30c32f8b877930 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 2 Aug 2023 19:16:50 +0200 Subject: [PATCH 0723/1008] chore: bump app v1.0.0 rc11 (#2527) --- core/client_test.go | 4 +- core/exchange_test.go | 2 +- core/fetcher_test.go | 4 +- core/testing.go | 86 ++++----------------------- go.mod | 2 +- go.sum | 4 +- nodebuilder/tests/blob_test.go | 4 +- nodebuilder/tests/reconstruct_test.go | 10 ++++ nodebuilder/tests/swamp/config.go | 25 ++++---- nodebuilder/tests/swamp/swamp.go | 6 +- 10 files changed, 46 insertions(+), 101 deletions(-) diff --git a/core/client_test.go b/core/client_test.go index 7467b3c3a3..8ad9060555 100644 --- a/core/client_test.go +++ b/core/client_test.go @@ -10,7 +10,7 @@ import ( ) func TestRemoteClient_Status(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) client := StartTestNode(t).Client @@ -20,7 +20,7 @@ func TestRemoteClient_Status(t *testing.T) { } func TestRemoteClient_StartBlockSubscription_And_GetBlock(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) client := StartTestNode(t).Client diff --git a/core/exchange_test.go b/core/exchange_test.go index f6302b5742..579c69fba6 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -31,7 +31,7 @@ func TestCoreExchange_RequestHeaders(t *testing.T) { assert.Equal(t, 10, len(headers)) } -func createCoreFetcher(t *testing.T, cfg *TestConfig) (*BlockFetcher, testnode.Context) { +func createCoreFetcher(t *testing.T, cfg *testnode.Config) (*BlockFetcher, testnode.Context) { cctx := StartTestNodeWithConfig(t, cfg) // wait for height 2 in order to be able to start submitting txs (this prevents // flakiness with accessing account state) diff --git a/core/fetcher_test.go b/core/fetcher_test.go index de7d1ba05e..3380dbb402 100644 --- a/core/fetcher_test.go +++ b/core/fetcher_test.go @@ -11,7 +11,7 @@ import ( ) func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) client := StartTestNode(t).Client @@ -42,7 +42,7 @@ func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { // TestBlockFetcherHeaderValues tests that both the Commit and ValidatorSet // endpoints are working as intended. func TestBlockFetcherHeaderValues(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) client := StartTestNode(t).Client diff --git a/core/testing.go b/core/testing.go index 393ec62c09..8d29ce9bbc 100644 --- a/core/testing.go +++ b/core/testing.go @@ -1,30 +1,18 @@ package core import ( - "fmt" "net" "net/url" "testing" + "time" - appconfig "github.com/cosmos/cosmos-sdk/server/config" "github.com/stretchr/testify/require" tmconfig "github.com/tendermint/tendermint/config" tmrand "github.com/tendermint/tendermint/libs/rand" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/celestiaorg/celestia-app/test/util/testnode" ) -// TestConfig encompasses all the configs required to run test Tendermint + Celestia App tandem. -type TestConfig struct { - ConsensusParams *tmproto.ConsensusParams - Tendermint *tmconfig.Config - App *appconfig.Config - - Accounts []string - SuppressLogs bool -} - // DefaultTestConfig returns the default testing configuration for Tendermint + Celestia App tandem. // // It fetches free ports from OS and sets them into configs, s.t. @@ -32,17 +20,8 @@ type TestConfig struct { // multiple tests nodes in parallel. // // Additionally, it instructs Tendermint + Celestia App tandem to setup 10 funded accounts. -func DefaultTestConfig() *TestConfig { - conCfg := testnode.DefaultParams() - - tnCfg := testnode.DefaultTendermintConfig() - tnCfg.RPC.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) - tnCfg.RPC.GRPCListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) - tnCfg.P2P.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) - - appCfg := testnode.DefaultAppConfig() - appCfg.GRPC.Address = fmt.Sprintf("127.0.0.1:%d", getFreePort()) - appCfg.API.Address = fmt.Sprintf("tcp://127.0.0.1:%d", getFreePort()) +func DefaultTestConfig() *testnode.Config { + cfg := testnode.DefaultConfig() // instructs creating funded accounts // 10 usually is enough for testing @@ -51,13 +30,13 @@ func DefaultTestConfig() *TestConfig { accounts[i] = tmrand.Str(9) } - return &TestConfig{ - ConsensusParams: conCfg, - Tendermint: tnCfg, - App: appCfg, - Accounts: accounts, - SuppressLogs: true, - } + cfg.TmConfig.Consensus.TimeoutCommit = time.Millisecond * 200 + + cfg = cfg. + WithAccounts(accounts). + WithSupressLogs(true) + + return cfg } // StartTestNode simply starts Tendermint and Celestia App tandem with default testing @@ -67,40 +46,13 @@ func StartTestNode(t *testing.T) testnode.Context { } // StartTestNodeWithConfig starts Tendermint and Celestia App tandem with custom configuration. -func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { - state, kr, err := testnode.DefaultGenesisState(cfg.Accounts...) - require.NoError(t, err) - - tmNode, app, cctx, err := testnode.New( - t, - cfg.ConsensusParams, - cfg.Tendermint, - cfg.SuppressLogs, - state, - kr, - "private", - ) - require.NoError(t, err) - - cctx, cleanupCoreNode, err := testnode.StartNode(tmNode, cctx) - require.NoError(t, err) - t.Cleanup(func() { - err := cleanupCoreNode() - require.NoError(t, err) - }) - - cctx, cleanupGRPCServer, err := StartGRPCServer(app, cfg.App, cctx) - require.NoError(t, err) - t.Cleanup(func() { - err := cleanupGRPCServer() - require.NoError(t, err) - }) - +func StartTestNodeWithConfig(t *testing.T, cfg *testnode.Config) testnode.Context { + cctx, _, _ := testnode.NewNetwork(t, cfg) // we want to test over remote http client, // so we are as close to the real environment as possible // however, it might be useful to use local tendermint client // if you need to debug something inside of it - ip, port, err := getEndpoint(cfg.Tendermint) + ip, port, err := getEndpoint(cfg.TmConfig) require.NoError(t, err) client, err := NewRemote(ip, port) require.NoError(t, err) @@ -116,18 +68,6 @@ func StartTestNodeWithConfig(t *testing.T, cfg *TestConfig) testnode.Context { return cctx } -func getFreePort() int { - a, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err == nil { - var l *net.TCPListener - if l, err = net.ListenTCP("tcp", a); err == nil { - defer l.Close() - return l.Addr().(*net.TCPAddr).Port - } - } - panic("while getting free port: " + err.Error()) -} - func getEndpoint(cfg *tmconfig.Config) (string, string, error) { url, err := url.Parse(cfg.RPC.ListenAddress) if err != nil { diff --git a/go.mod b/go.mod index ef1d8cc01a..c89d7d77d3 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc10 + github.com/celestiaorg/celestia-app v1.0.0-rc11 github.com/celestiaorg/go-fraud v0.1.2 github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 diff --git a/go.sum b/go.sum index 82e18196ba..f8cedecfd1 100644 --- a/go.sum +++ b/go.sum @@ -344,8 +344,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc10 h1:wglTplF5qVVOtSYCGtNHz8qA37azD+fgcxCKAphYL6c= -github.com/celestiaorg/celestia-app v1.0.0-rc10/go.mod h1:A17xfzurB2RHpplh6+nGMeC9hCquu0aMYnanOeifDO0= +github.com/celestiaorg/celestia-app v1.0.0-rc11 h1:u94drhPdvgwNvx0F2xGib+fyOyP+fIvp3aL6Avd/HT0= +github.com/celestiaorg/celestia-app v1.0.0-rc11/go.mod h1:A17xfzurB2RHpplh6+nGMeC9hCquu0aMYnanOeifDO0= github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHVxcTzhzQW44KgTcooR3OxnK4= github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13 h1:CxEQDQEQR1ypB+VUmCISIqFVmHfb+mx8x+zh7rHbyU8= diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index 6a7db50c7c..a9e3a0464b 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -19,9 +19,9 @@ import ( ) func TestBlobModule(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) t.Cleanup(cancel) - sw := swamp.NewSwamp(t) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second*1)) appBlobs0, err := blobtest.GenerateV0Blobs([]int{8, 4}, true) require.NoError(t, err) diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index a4e3296c2f..8b1490272c 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -53,6 +53,11 @@ func TestFullReconstructFromBridge(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) + // TODO: This is required to avoid flakes coming from unfinished retry + // mechanism for the same peer in go-header + _, err = bridge.HeaderServ.WaitForHeight(ctx, uint64(blocks)) + require.NoError(t, err) + cfg := nodebuilder.DefaultConfig(node.Full) cfg.Share.UseShareExchange = false cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, getMultiAddr(t, bridge.Host)) @@ -125,6 +130,11 @@ func TestFullReconstructFromLights(t *testing.T) { require.NoError(t, bridge.Start(ctx)) bootstrapperAddr := host.InfoFromHost(bootstrapper.Host) + // TODO: This is required to avoid flakes coming from unfinished retry + // mechanism for the same peer in go-header + _, err = bridge.HeaderServ.WaitForHeight(ctx, uint64(blocks)) + require.NoError(t, err) + cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) cfg.Share.UseShareExchange = false diff --git a/nodebuilder/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go index e6c06b40cc..047baa9f59 100644 --- a/nodebuilder/tests/swamp/config.go +++ b/nodebuilder/tests/swamp/config.go @@ -3,35 +3,30 @@ package swamp import ( "time" + "github.com/celestiaorg/celestia-app/test/util/testnode" + "github.com/celestiaorg/celestia-node/core" ) -// Config struct represents a set of pre-requisite attributes from the test scenario -type Config struct { - *core.TestConfig -} - // DefaultConfig creates a celestia-app instance with a block time of around // 100ms -func DefaultConfig() *Config { +func DefaultConfig() *testnode.Config { cfg := core.DefaultTestConfig() // timeout commit lower than this tend to be flakier - cfg.Tendermint.Consensus.TimeoutCommit = 200 * time.Millisecond - return &Config{ - cfg, - } + cfg.TmConfig.Consensus.TimeoutCommit = 200 * time.Millisecond + return cfg } // Option for modifying Swamp's Config. -type Option func(*Config) +type Option func(*testnode.Config) // WithBlockTime sets a custom interval for block creation. func WithBlockTime(t time.Duration) Option { - return func(c *Config) { + return func(c *testnode.Config) { // for empty block - c.Tendermint.Consensus.CreateEmptyBlocksInterval = t + c.TmConfig.Consensus.CreateEmptyBlocksInterval = t // for filled block - c.Tendermint.Consensus.TimeoutCommit = t - c.Tendermint.Consensus.SkipTimeoutCommit = false + c.TmConfig.Consensus.TimeoutCommit = t + c.TmConfig.Consensus.SkipTimeoutCommit = false } } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 3532635883..58584912be 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -48,7 +48,7 @@ const DefaultTestTimeout = time.Minute * 5 // - trustedHash taken from the CoreClient and shared between nodes type Swamp struct { t *testing.T - cfg *Config + cfg *testnode.Config Network mocknet.Mocknet Bootstrappers []ma.Multiaddr @@ -76,7 +76,7 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { // Now, we are making an assumption that consensus mechanism is already tested out // so, we are not creating bridge nodes with each one containing its own core client // instead we are assigning all created BNs to 1 Core from the swamp - cctx := core.StartTestNodeWithConfig(t, ic.TestConfig) + cctx := core.StartTestNodeWithConfig(t, ic) swp := &Swamp{ t: t, cfg: ic, @@ -188,7 +188,7 @@ func (s *Swamp) setupGenesis() { func (s *Swamp) DefaultTestConfig(tp node.Type) *nodebuilder.Config { cfg := nodebuilder.DefaultConfig(tp) - ip, port, err := net.SplitHostPort(s.cfg.App.GRPC.Address) + ip, port, err := net.SplitHostPort(s.cfg.AppConfig.GRPC.Address) require.NoError(s.t, err) cfg.Core.IP = ip From 135977825d57b574759331f9a71a375fec9d6c5d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 3 Aug 2023 23:48:16 +0800 Subject: [PATCH 0724/1008] fix(share/p2p): fix race in shrexeds test (#2534) Quick fix for race in test. Found in CI run --- share/p2p/shrexeds/exchange_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index b1d2e0ad18..14e220a8f9 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -47,21 +47,27 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is unavailable initially, but is found after multiple requests t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { - storageDelay := time.Second eds := edstest.RandEDS(t, 4) dah, err := da.NewDataAvailabilityHeader(eds) require.NoError(t, err) + + lock := make(chan struct{}) go func() { - time.Sleep(storageDelay) + <-lock err = store.Put(ctx, dah.Hash(), eds) - // require.NoError(t, err) + require.NoError(t, err) + lock <- struct{}{} }() requestedEDS, err := client.RequestEDS(ctx, dah.Hash(), server.host.ID()) assert.ErrorIs(t, err, p2p.ErrNotFound) assert.Nil(t, requestedEDS) - time.Sleep(storageDelay * 2) + // unlock write + lock <- struct{}{} + // wait for write to finish + <-lock + requestedEDS, err = client.RequestEDS(ctx, dah.Hash(), server.host.ID()) assert.NoError(t, err) assert.Equal(t, eds.Flattened(), requestedEDS.Flattened()) From f7da123759c474826f8bd6574fc2f41859bf5bcc Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 2 Aug 2023 18:31:44 +0800 Subject: [PATCH 0725/1008] feat(share/eds)!: upgrade badger to v4 (#2520) Upgrades badger to latest version (v4). Also update badger config to the one shown no hangups. --- go.mod | 8 +++++--- go.sum | 16 +++++++++------- nodebuilder/store.go | 25 ++++--------------------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index c89d7d77d3..50d2ddc9a9 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc11 + github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.1.2 github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 @@ -19,7 +20,6 @@ require ( github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 - github.com/dgraph-io/badger/v2 v2.2007.4 github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/dagstore v0.5.6 github.com/filecoin-project/go-jsonrpc v0.3.1 @@ -33,7 +33,6 @@ require ( github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 - github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 github.com/ipfs/go-ipfs-exchange-interface v0.2.0 github.com/ipfs/go-ipfs-exchange-offline v0.3.0 @@ -131,7 +130,9 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect - github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/badger/v4 v4.1.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect @@ -164,6 +165,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/flatbuffers v1.12.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect diff --git a/go.sum b/go.sum index f8cedecfd1..2593760535 100644 --- a/go.sum +++ b/go.sum @@ -228,7 +228,6 @@ github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRr github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -352,6 +351,8 @@ github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13 h1:CxEQDQEQR1ypB+VUmCISIq github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13/go.mod h1:xpBZc/OYZ736hp0IZlBGNUhEgCD9C+bKs8yNLZibyv0= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= +github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= +github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= github.com/celestiaorg/go-fraud v0.1.2/go.mod h1:kHZXQY+6gd1kYkoWRFFKgWyrLPWRgDN3vd1Ll9gE/oo= github.com/celestiaorg/go-header v0.2.12 h1:3H9nir20+MTY1vXbLxOUOV05ZspotR6JOiZGKxACHCQ= @@ -510,14 +511,15 @@ github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhY github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= -github.com/dgraph-io/badger/v2 v2.2007.3/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4APOQ= +github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= -github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -740,7 +742,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= @@ -751,6 +752,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -999,8 +1002,6 @@ github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaH github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= -github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= -github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= @@ -2467,6 +2468,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/nodebuilder/store.go b/nodebuilder/store.go index f0957a16b6..6d313893b1 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -5,13 +5,14 @@ import ( "fmt" "path/filepath" "sync" + "time" "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/dgraph-io/badger/v2/options" "github.com/ipfs/go-datastore" - dsbadger "github.com/ipfs/go-ds-badger2" "github.com/mitchellh/go-homedir" + dsbadger "github.com/celestiaorg/go-ds-badger4" + "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/keystore" ) @@ -118,25 +119,7 @@ func (f *fsStore) Datastore() (datastore.Batching, error) { } opts := dsbadger.DefaultOptions // this should be copied - - // Badger sets ValueThreshold to 1K by default and this makes shares being stored in LSM tree - // instead of the value log, so we change the value to be lower than share size, - // so shares are store in value log. For value log and LSM definitions - opts.ValueThreshold = 128 - // We always write unique values to Badger transaction so there is no need to detect conflicts. - opts.DetectConflicts = false - // Use MemoryMap for better performance - opts.ValueLogLoadingMode = options.MemoryMap - opts.TableLoadingMode = options.MemoryMap - // Truncate set to true will truncate corrupted data on start if there is any. - // If we don't truncate, the node will refuse to start and will beg for recovering, etc. - // If we truncate, the node will start with any uncorrupted data and reliably sync again what was - // corrupted in most cases. - opts.Truncate = true - // MaxTableSize defines in memory and on disk size of LSM tree - // Bigger values constantly takes more RAM - // TODO(@Wondertan): Make configurable with more conservative defaults for Light Node - opts.MaxTableSize = 64 << 20 + opts.GcInterval = time.Minute * 10 ds, err := dsbadger.NewDatastore(dataPath(f.path), &opts) if err != nil { From 3c257589d6070941c94201bec97034b34a48ca34 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:08:50 +0800 Subject: [PATCH 0726/1008] fix(share/eds)!: use separate badger for inverted index (#2517) Having same badger instance for dagstore shards and inverted index has proven to have hangup on Put operation for 1-10min. During tests it has shown, that splitting badger into 2 instances solves the hangup issue. Also once #2479 is implemented and inverted index will needs to be removed. Separate badger will be much easier for users migration. --- share/eds/inverted_index.go | 26 ++++++++++++++++++++++---- share/eds/inverted_index_test.go | 9 ++++----- share/eds/store.go | 28 +++++++++++++++++----------- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index 0e3665d973..d302932369 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -8,10 +8,13 @@ import ( "github.com/filecoin-project/dagstore/index" "github.com/filecoin-project/dagstore/shard" ds "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" "github.com/multiformats/go-multihash" + + dsbadger "github.com/celestiaorg/go-ds-badger4" ) +const invertedIndexPath = "/inverted_index/" + // simpleInvertedIndex is an inverted index that only stores a single shard key per multihash. Its // implementation is modified from the default upstream implementation in dagstore/index. type simpleInvertedIndex struct { @@ -21,10 +24,21 @@ type simpleInvertedIndex struct { // newSimpleInvertedIndex returns a new inverted index that only stores a single shard key per // multihash. This is because we use badger as a storage backend, so updates are expensive, and we // don't care which shard is used to serve a cid. -func newSimpleInvertedIndex(dts ds.Batching) *simpleInvertedIndex { - return &simpleInvertedIndex{ - ds: namespace.Wrap(dts, ds.NewKey("/inverted/index")), +func newSimpleInvertedIndex(storePath string) (*simpleInvertedIndex, error) { + opts := dsbadger.DefaultOptions // this should be copied + // turn off value log GC + opts.GcInterval = 0 + // 20 compactors show to have no hangups on put operation up to 40k blocks with eds size 128. + opts.NumCompactors = 20 + // use minimum amount of NumLevelZeroTables to trigger L0 compaction faster + opts.NumLevelZeroTables = 1 + + ds, err := dsbadger.NewDatastore(storePath+invertedIndexPath, &opts) + if err != nil { + return nil, fmt.Errorf("can't open Badger Datastore: %w", err) } + + return &simpleInvertedIndex{ds: ds}, nil } func (s *simpleInvertedIndex) AddMultihashesForShard( @@ -75,3 +89,7 @@ func (s *simpleInvertedIndex) GetShardsForMultihash(ctx context.Context, mh mult return []shard.Key{shardKey}, nil } + +func (s *simpleInvertedIndex) close() error { + return s.ds.Close() +} diff --git a/share/eds/inverted_index_test.go b/share/eds/inverted_index_test.go index 8fb037bb92..e83c2be267 100644 --- a/share/eds/inverted_index_test.go +++ b/share/eds/inverted_index_test.go @@ -5,8 +5,6 @@ import ( "testing" "github.com/filecoin-project/dagstore/shard" - "github.com/ipfs/go-datastore" - ds_sync "github.com/ipfs/go-datastore/sync" "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) @@ -37,11 +35,12 @@ func TestMultihashesForShard(t *testing.T) { } mi := &mockIterator{mhs: mhs} - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - invertedIndex := newSimpleInvertedIndex(ds) + path := t.TempDir() + invertedIndex, err := newSimpleInvertedIndex(path) + require.NoError(t, err) // 1. Add all 3 multihashes to shard1 - err := invertedIndex.AddMultihashesForShard(ctx, mi, shard.KeyFromString("shard1")) + err = invertedIndex.AddMultihashesForShard(ctx, mi, shard.KeyFromString("shard1")) require.NoError(t, err) shardKeys, err := invertedIndex.GetShardsForMultihash(ctx, mhs[0]) require.NoError(t, err) diff --git a/share/eds/store.go b/share/eds/store.go index cd85380ba1..2218e13954 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -56,8 +56,8 @@ type Store struct { cache *blockstoreCache bs bstore.Blockstore - topIdx index.Inverted - carIdx index.FullIndexRepo + carIdx index.FullIndexRepo + invertedIdx *simpleInvertedIndex basepath string gcInterval time.Duration @@ -83,14 +83,17 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to create index repository: %w", err) } - invertedRepo := newSimpleInvertedIndex(ds) + invertedIdx, err := newSimpleInvertedIndex(basepath) + if err != nil { + return nil, fmt.Errorf("failed to create index: %w", err) + } dagStore, err := dagstore.NewDAGStore( dagstore.Config{ TransientsDir: basepath + transientsPath, IndexRepo: fsRepo, Datastore: ds, MountRegistry: r, - TopLevelIndex: invertedRepo, + TopLevelIndex: invertedIdx, }, ) if err != nil { @@ -103,13 +106,13 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } store := &Store{ - basepath: basepath, - dgstr: dagStore, - topIdx: invertedRepo, - carIdx: fsRepo, - gcInterval: defaultGCInterval, - mounts: r, - cache: cache, + basepath: basepath, + dgstr: dagStore, + carIdx: fsRepo, + invertedIdx: invertedIdx, + gcInterval: defaultGCInterval, + mounts: r, + cache: cache, } store.bs = newBlockstore(store, cache) return store, nil @@ -137,6 +140,9 @@ func (s *Store) Start(ctx context.Context) error { // Stop stops the underlying DAGStore. func (s *Store) Stop(context.Context) error { defer s.cancel() + if err := s.invertedIdx.close(); err != nil { + return err + } return s.dgstr.Close() } From 9ec085baf103397c94e6405957044928c3b0dbc1 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 2 Aug 2023 17:23:33 +0300 Subject: [PATCH 0727/1008] feat(share/proof)!: use nmt definition of Proof (#2510) Resolves #2512 --- Makefile | 3 +- share/eds/byzantine/bad_encoding.go | 4 +- share/eds/byzantine/pb/share.pb.go | 353 +++------------------------ share/eds/byzantine/pb/share.proto | 10 +- share/eds/byzantine/share_proof.go | 21 +- share/p2p/shrexnd/client.go | 7 +- share/p2p/shrexnd/params.go | 2 +- share/p2p/shrexnd/pb/share.pb.go | 356 +++------------------------- share/p2p/shrexnd/pb/share.proto | 10 +- share/p2p/shrexnd/server.go | 12 +- 10 files changed, 90 insertions(+), 688 deletions(-) diff --git a/Makefile b/Makefile index 2567b225d3..1044d75d3b 100644 --- a/Makefile +++ b/Makefile @@ -137,13 +137,14 @@ PB_PKGS=$(shell find . -name 'pb' -type d) PB_CORE=$(shell go list -f {{.Dir}} -m github.com/tendermint/tendermint) PB_GOGO=$(shell go list -f {{.Dir}} -m github.com/gogo/protobuf) PB_CELESTIA_APP=$(shell go list -f {{.Dir}} -m github.com/celestiaorg/celestia-app) +PB_NMT=$(shell go list -f {{.Dir}} -m github.com/celestiaorg/nmt) ## pb-gen: Generate protobuf code for all /pb/*.proto files in the project. pb-gen: @echo '--> Generating protobuf' @for dir in $(PB_PKGS); \ do for file in `find $$dir -type f -name "*.proto"`; \ - do protoc -I=. -I=${PB_CORE}/proto/ -I=${PB_GOGO} -I=${PB_CELESTIA_APP}/proto --gogofaster_out=paths=source_relative:. $$file; \ + do protoc -I=. -I=${PB_CORE}/proto/ -I=${PB_GOGO} -I=${PB_CELESTIA_APP}/proto -I=${PB_NMT} --gogofaster_out=paths=source_relative:. $$file; \ echo '-->' $$file; \ done; \ done; diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 95f2a7fd03..3c5bc6951b 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -17,7 +17,9 @@ import ( ) const ( - BadEncoding fraud.ProofType = "badencoding" + version = "v0.1" + + BadEncoding fraud.ProofType = "badencoding" + version ) func init() { diff --git a/share/eds/byzantine/pb/share.pb.go b/share/eds/byzantine/pb/share.pb.go index 33b9cdd1ab..4186eabc64 100644 --- a/share/eds/byzantine/pb/share.pb.go +++ b/share/eds/byzantine/pb/share.pb.go @@ -5,6 +5,7 @@ package share_eds_byzantine_pb import ( fmt "fmt" + pb "github.com/celestiaorg/nmt/pb" proto "github.com/gogo/protobuf/proto" io "io" math "math" @@ -47,84 +48,16 @@ func (Axis) EnumDescriptor() ([]byte, []int) { return fileDescriptor_d28ce8f160a920d1, []int{0} } -type MerkleProof struct { - Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` - End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` - Nodes [][]byte `protobuf:"bytes,3,rep,name=nodes,proto3" json:"nodes,omitempty"` - LeafHash []byte `protobuf:"bytes,4,opt,name=leaf_hash,json=leafHash,proto3" json:"leaf_hash,omitempty"` -} - -func (m *MerkleProof) Reset() { *m = MerkleProof{} } -func (m *MerkleProof) String() string { return proto.CompactTextString(m) } -func (*MerkleProof) ProtoMessage() {} -func (*MerkleProof) Descriptor() ([]byte, []int) { - return fileDescriptor_d28ce8f160a920d1, []int{0} -} -func (m *MerkleProof) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *MerkleProof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MerkleProof.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *MerkleProof) XXX_Merge(src proto.Message) { - xxx_messageInfo_MerkleProof.Merge(m, src) -} -func (m *MerkleProof) XXX_Size() int { - return m.Size() -} -func (m *MerkleProof) XXX_DiscardUnknown() { - xxx_messageInfo_MerkleProof.DiscardUnknown(m) -} - -var xxx_messageInfo_MerkleProof proto.InternalMessageInfo - -func (m *MerkleProof) GetStart() int64 { - if m != nil { - return m.Start - } - return 0 -} - -func (m *MerkleProof) GetEnd() int64 { - if m != nil { - return m.End - } - return 0 -} - -func (m *MerkleProof) GetNodes() [][]byte { - if m != nil { - return m.Nodes - } - return nil -} - -func (m *MerkleProof) GetLeafHash() []byte { - if m != nil { - return m.LeafHash - } - return nil -} - type Share struct { - Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"` - Proof *MerkleProof `protobuf:"bytes,2,opt,name=Proof,proto3" json:"Proof,omitempty"` + Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"` + Proof *pb.Proof `protobuf:"bytes,2,opt,name=Proof,proto3" json:"Proof,omitempty"` } func (m *Share) Reset() { *m = Share{} } func (m *Share) String() string { return proto.CompactTextString(m) } func (*Share) ProtoMessage() {} func (*Share) Descriptor() ([]byte, []int) { - return fileDescriptor_d28ce8f160a920d1, []int{1} + return fileDescriptor_d28ce8f160a920d1, []int{0} } func (m *Share) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -160,7 +93,7 @@ func (m *Share) GetData() []byte { return nil } -func (m *Share) GetProof() *MerkleProof { +func (m *Share) GetProof() *pb.Proof { if m != nil { return m.Proof } @@ -179,7 +112,7 @@ func (m *BadEncoding) Reset() { *m = BadEncoding{} } func (m *BadEncoding) String() string { return proto.CompactTextString(m) } func (*BadEncoding) ProtoMessage() {} func (*BadEncoding) Descriptor() ([]byte, []int) { - return fileDescriptor_d28ce8f160a920d1, []int{2} + return fileDescriptor_d28ce8f160a920d1, []int{1} } func (m *BadEncoding) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -245,7 +178,6 @@ func (m *BadEncoding) GetAxis() Axis { func init() { proto.RegisterEnum("share.eds.byzantine.pb.Axis", Axis_name, Axis_value) - proto.RegisterType((*MerkleProof)(nil), "share.eds.byzantine.pb.MerkleProof") proto.RegisterType((*Share)(nil), "share.eds.byzantine.pb.Share") proto.RegisterType((*BadEncoding)(nil), "share.eds.byzantine.pb.BadEncoding") } @@ -255,78 +187,26 @@ func init() { } var fileDescriptor_d28ce8f160a920d1 = []byte{ - // 347 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x41, 0x4b, 0xf3, 0x30, - 0x1c, 0xc6, 0x9b, 0xb7, 0xed, 0xde, 0xf7, 0xfd, 0x77, 0xca, 0x08, 0x32, 0x02, 0x6a, 0x28, 0xf5, - 0x52, 0x3c, 0xb4, 0x32, 0xf1, 0xe0, 0xd1, 0xa9, 0x30, 0x41, 0x99, 0x44, 0xd0, 0xa3, 0xa4, 0x26, - 0x5b, 0x8b, 0x23, 0x1d, 0x4d, 0x0f, 0xd3, 0x4f, 0xe1, 0x87, 0xf2, 0xe0, 0x71, 0x47, 0x8f, 0xb2, - 0x7d, 0x11, 0x49, 0x3a, 0x64, 0x07, 0x77, 0xfb, 0x3f, 0x0f, 0x4f, 0xf2, 0xfc, 0xfe, 0x09, 0x44, - 0x3a, 0xe7, 0x95, 0x4c, 0xa5, 0xd0, 0x69, 0xf6, 0xf2, 0xca, 0x55, 0x5d, 0x28, 0x99, 0x4e, 0xb3, - 0xd4, 0xda, 0xc9, 0xb4, 0x2a, 0xeb, 0x12, 0x77, 0x1b, 0x21, 0x85, 0x4e, 0x7e, 0x32, 0xc9, 0x34, - 0x8b, 0x72, 0x08, 0x6e, 0x64, 0xf5, 0x3c, 0x91, 0xb7, 0x55, 0x59, 0x8e, 0xf0, 0x0e, 0xf8, 0xba, - 0xe6, 0x55, 0x4d, 0x50, 0x88, 0x62, 0x97, 0x35, 0x02, 0x77, 0xc0, 0x95, 0x4a, 0x90, 0x3f, 0xd6, - 0x33, 0xa3, 0xc9, 0xa9, 0x52, 0x48, 0x4d, 0xdc, 0xd0, 0x8d, 0xdb, 0xac, 0x11, 0x78, 0x17, 0xfe, - 0x4f, 0x24, 0x1f, 0x3d, 0xe6, 0x5c, 0xe7, 0xc4, 0x0b, 0x51, 0xdc, 0x66, 0xff, 0x8c, 0x31, 0xe0, - 0x3a, 0x8f, 0xee, 0xc1, 0xbf, 0x33, 0x0c, 0x18, 0x83, 0x77, 0xc1, 0x6b, 0x6e, 0x2b, 0xda, 0xcc, - 0xce, 0xf8, 0x14, 0x7c, 0x0b, 0x60, 0x3b, 0x82, 0xde, 0x41, 0xf2, 0x3b, 0x6e, 0xb2, 0xc6, 0xca, - 0x9a, 0x13, 0xd1, 0x3b, 0x82, 0xa0, 0xcf, 0xc5, 0xa5, 0x7a, 0x2a, 0x45, 0xa1, 0xc6, 0x98, 0x02, - 0x0c, 0x24, 0x17, 0xb2, 0x32, 0xad, 0xab, 0x92, 0x35, 0x07, 0x77, 0xa1, 0x35, 0x90, 0xc5, 0x38, - 0xaf, 0x6d, 0x97, 0xc7, 0x56, 0x0a, 0x9f, 0x40, 0xcb, 0xf2, 0x35, 0x3b, 0x05, 0xbd, 0xfd, 0x4d, - 0x0c, 0x36, 0xc5, 0x56, 0x61, 0xf3, 0x12, 0x57, 0x4a, 0xc8, 0x99, 0xdd, 0x77, 0x8b, 0x35, 0x02, - 0x1f, 0x81, 0x77, 0x36, 0x2b, 0x34, 0xf1, 0x43, 0x14, 0x6f, 0xf7, 0xf6, 0x36, 0x5d, 0xc5, 0x67, - 0x85, 0x66, 0x36, 0x79, 0x48, 0xc0, 0x33, 0x0a, 0xff, 0x05, 0x97, 0x0d, 0x1f, 0x3a, 0x8e, 0x19, - 0xce, 0x87, 0xd7, 0x1d, 0xd4, 0x27, 0x1f, 0x0b, 0x8a, 0xe6, 0x0b, 0x8a, 0xbe, 0x16, 0x14, 0xbd, - 0x2d, 0xa9, 0x33, 0x5f, 0x52, 0xe7, 0x73, 0x49, 0x9d, 0xac, 0x65, 0xff, 0xf6, 0xf8, 0x3b, 0x00, - 0x00, 0xff, 0xff, 0x0f, 0xa3, 0x91, 0xb4, 0x01, 0x02, 0x00, 0x00, -} - -func (m *MerkleProof) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MerkleProof) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MerkleProof) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.LeafHash) > 0 { - i -= len(m.LeafHash) - copy(dAtA[i:], m.LeafHash) - i = encodeVarintShare(dAtA, i, uint64(len(m.LeafHash))) - i-- - dAtA[i] = 0x22 - } - if len(m.Nodes) > 0 { - for iNdEx := len(m.Nodes) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Nodes[iNdEx]) - copy(dAtA[i:], m.Nodes[iNdEx]) - i = encodeVarintShare(dAtA, i, uint64(len(m.Nodes[iNdEx]))) - i-- - dAtA[i] = 0x1a - } - } - if m.End != 0 { - i = encodeVarintShare(dAtA, i, uint64(m.End)) - i-- - dAtA[i] = 0x10 - } - if m.Start != 0 { - i = encodeVarintShare(dAtA, i, uint64(m.Start)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil + // 297 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xc1, 0x4a, 0xec, 0x30, + 0x14, 0x86, 0x9b, 0x3b, 0xed, 0x5c, 0x38, 0xa3, 0xe3, 0x10, 0x64, 0x08, 0xa2, 0xa1, 0x0c, 0x08, + 0xc5, 0x45, 0x2a, 0x15, 0x1f, 0xc0, 0xaa, 0x50, 0x41, 0x18, 0x89, 0x0b, 0xd7, 0x89, 0x89, 0xd3, + 0x6e, 0xda, 0xd2, 0x74, 0x51, 0x7d, 0x0a, 0x1f, 0xca, 0x85, 0xcb, 0x59, 0xba, 0x94, 0xf6, 0x45, + 0xa4, 0x69, 0x11, 0x17, 0xba, 0x3b, 0xdf, 0x9f, 0x2f, 0x3f, 0xc9, 0x81, 0x95, 0x49, 0x45, 0xa5, + 0x43, 0xad, 0x4c, 0x28, 0x9f, 0x5f, 0x44, 0x5e, 0x67, 0xb9, 0x0e, 0x4b, 0x19, 0xda, 0x98, 0x95, + 0x55, 0x51, 0x17, 0x78, 0x39, 0x80, 0x56, 0x86, 0x7d, 0x3b, 0xac, 0x94, 0x07, 0xf3, 0x52, 0x86, + 0x65, 0x55, 0x14, 0x4f, 0x83, 0xb7, 0x8a, 0xc1, 0xbb, 0xef, 0x4d, 0x8c, 0xc1, 0xbd, 0x12, 0xb5, + 0x20, 0xc8, 0x47, 0xc1, 0x0e, 0xb7, 0x33, 0x3e, 0x06, 0xef, 0xae, 0x77, 0xc9, 0x3f, 0x1f, 0x05, + 0xb3, 0x68, 0x8f, 0x8d, 0x37, 0x25, 0xb3, 0x31, 0x1f, 0x4e, 0x57, 0x6f, 0x08, 0x66, 0xb1, 0x50, + 0xd7, 0xf9, 0x63, 0xa1, 0xb2, 0x7c, 0x83, 0x29, 0x40, 0xa2, 0x85, 0xd2, 0x55, 0x22, 0x4c, 0x3a, + 0x16, 0xfe, 0x48, 0xf0, 0x12, 0xa6, 0x89, 0xce, 0x36, 0x69, 0x6d, 0x7b, 0x5d, 0x3e, 0x12, 0x3e, + 0x87, 0xa9, 0x7d, 0x8b, 0x21, 0x13, 0x7f, 0x12, 0xcc, 0xa2, 0x23, 0xf6, 0xfb, 0x27, 0x98, 0xb5, + 0xf8, 0x28, 0xe3, 0x7d, 0xf0, 0x6e, 0x72, 0xa5, 0x1b, 0xe2, 0xfa, 0x28, 0xd8, 0xe5, 0x03, 0xe0, + 0x53, 0x70, 0x2f, 0x9a, 0xcc, 0x10, 0xcf, 0x47, 0xc1, 0x3c, 0x3a, 0xfc, 0xab, 0x4a, 0x34, 0x99, + 0xe1, 0xd6, 0x3c, 0x21, 0xe0, 0xf6, 0x84, 0xff, 0xc3, 0x84, 0xaf, 0x1f, 0x16, 0x4e, 0x3f, 0x5c, + 0xae, 0x6f, 0x17, 0x28, 0x26, 0xef, 0x2d, 0x45, 0xdb, 0x96, 0xa2, 0xcf, 0x96, 0xa2, 0xd7, 0x8e, + 0x3a, 0xdb, 0x8e, 0x3a, 0x1f, 0x1d, 0x75, 0xe4, 0xd4, 0x6e, 0xf1, 0xec, 0x2b, 0x00, 0x00, 0xff, + 0xff, 0xb1, 0x96, 0xb9, 0xbe, 0x93, 0x01, 0x00, 0x00, } func (m *Share) Marshal() (dAtA []byte, err error) { @@ -441,31 +321,6 @@ func encodeVarintShare(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *MerkleProof) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Start != 0 { - n += 1 + sovShare(uint64(m.Start)) - } - if m.End != 0 { - n += 1 + sovShare(uint64(m.End)) - } - if len(m.Nodes) > 0 { - for _, b := range m.Nodes { - l = len(b) - n += 1 + l + sovShare(uint64(l)) - } - } - l = len(m.LeafHash) - if l > 0 { - n += 1 + l + sovShare(uint64(l)) - } - return n -} - func (m *Share) Size() (n int) { if m == nil { return 0 @@ -517,160 +372,6 @@ func sovShare(x uint64) (n int) { func sozShare(x uint64) (n int) { return sovShare(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *MerkleProof) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MerkleProof: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MerkleProof: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Start", wireType) - } - m.Start = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Start |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field End", wireType) - } - m.End = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.End |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Nodes", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthShare - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthShare - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Nodes = append(m.Nodes, make([]byte, postIndex-iNdEx)) - copy(m.Nodes[len(m.Nodes)-1], dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LeafHash", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthShare - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthShare - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.LeafHash = append(m.LeafHash[:0], dAtA[iNdEx:postIndex]...) - if m.LeafHash == nil { - m.LeafHash = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipShare(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthShare - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *Share) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -764,7 +465,7 @@ func (m *Share) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.Proof == nil { - m.Proof = &MerkleProof{} + m.Proof = &pb.Proof{} } if err := m.Proof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err diff --git a/share/eds/byzantine/pb/share.proto b/share/eds/byzantine/pb/share.proto index e08dffae45..33e3dae2c2 100644 --- a/share/eds/byzantine/pb/share.proto +++ b/share/eds/byzantine/pb/share.proto @@ -1,17 +1,11 @@ syntax = "proto3"; package share.eds.byzantine.pb; - -message MerkleProof { - int64 start = 1; - int64 end = 2; - repeated bytes nodes = 3; - bytes leaf_hash = 4; -} +import "pb/proof.proto"; message Share { bytes Data = 1; - MerkleProof Proof = 2; + proof.pb.Proof Proof = 2; } enum axis { diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index d6aa9dad51..0a79ebfe33 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -9,6 +9,7 @@ import ( "github.com/minio/sha256-simd" "github.com/celestiaorg/nmt" + nmt_pb "github.com/celestiaorg/nmt/pb" "github.com/celestiaorg/celestia-node/share" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" @@ -58,11 +59,12 @@ func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { return &pb.Share{ Data: s.Share, - Proof: &pb.MerkleProof{ - Start: int64(s.Proof.Start()), - End: int64(s.Proof.End()), - Nodes: s.Proof.Nodes(), - LeafHash: s.Proof.LeafHash(), + Proof: &nmt_pb.Proof{ + Start: int64(s.Proof.Start()), + End: int64(s.Proof.End()), + Nodes: s.Proof.Nodes(), + LeafHash: s.Proof.LeafHash(), + IsMaxNamespaceIgnored: s.Proof.IsMaxNamespaceIDIgnored(), }, } } @@ -122,6 +124,11 @@ func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { return shares } -func ProtoToProof(protoProof *pb.MerkleProof) nmt.Proof { - return nmt.NewInclusionProof(int(protoProof.Start), int(protoProof.End), protoProof.Nodes, ipld.NMTIgnoreMaxNamespace) +func ProtoToProof(protoProof *nmt_pb.Proof) nmt.Proof { + return nmt.NewInclusionProof( + int(protoProof.Start), + int(protoProof.End), + protoProof.Nodes, + protoProof.IsMaxNamespaceIgnored, + ) } diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index 74162f5981..bbe34ddc38 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -17,7 +17,6 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexnd/pb" ) @@ -135,7 +134,7 @@ func convertToNamespacedShares(rows []*pb.Row) share.NamespacedShares { int(row.Proof.Start), int(row.Proof.End), row.Proof.Nodes, - ipld.NMTIgnoreMaxNamespace, + row.Proof.IsMaxNamespaceIgnored, ) proof = &tmpProof } @@ -155,8 +154,8 @@ func convertToNonInclusionProofs(rows []*pb.Row) share.NamespacedShares { int(row.Proof.Start), int(row.Proof.End), row.Proof.Nodes, - row.Proof.Hashleaf, - ipld.NMTIgnoreMaxNamespace, + row.Proof.LeafHash, + row.Proof.IsMaxNamespaceIgnored, ) shares = append(shares, share.NamespacedRow{ Proof: &proof, diff --git a/share/p2p/shrexnd/params.go b/share/p2p/shrexnd/params.go index a645267962..8489627a07 100644 --- a/share/p2p/shrexnd/params.go +++ b/share/p2p/shrexnd/params.go @@ -8,7 +8,7 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p" ) -const protocolString = "/shrex/nd/v0.0.2" +const protocolString = "/shrex/nd/v0.0.3" var log = logging.Logger("shrex/nd") diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go index e4c7656b4e..933f5cbe54 100644 --- a/share/p2p/shrexnd/pb/share.pb.go +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -5,6 +5,7 @@ package share_p2p_shrex_nd import ( fmt "fmt" + pb "github.com/celestiaorg/nmt/pb" proto "github.com/gogo/protobuf/proto" io "io" math "math" @@ -161,8 +162,8 @@ func (m *GetSharesByNamespaceResponse) GetRows() []*Row { } type Row struct { - Shares [][]byte `protobuf:"bytes,1,rep,name=shares,proto3" json:"shares,omitempty"` - Proof *Proof `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + Shares [][]byte `protobuf:"bytes,1,rep,name=shares,proto3" json:"shares,omitempty"` + Proof *pb.Proof `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` } func (m *Row) Reset() { *m = Row{} } @@ -205,118 +206,47 @@ func (m *Row) GetShares() [][]byte { return nil } -func (m *Row) GetProof() *Proof { +func (m *Row) GetProof() *pb.Proof { if m != nil { return m.Proof } return nil } -type Proof struct { - Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` - End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` - Nodes [][]byte `protobuf:"bytes,3,rep,name=nodes,proto3" json:"nodes,omitempty"` - Hashleaf []byte `protobuf:"bytes,4,opt,name=hashleaf,proto3" json:"hashleaf,omitempty"` -} - -func (m *Proof) Reset() { *m = Proof{} } -func (m *Proof) String() string { return proto.CompactTextString(m) } -func (*Proof) ProtoMessage() {} -func (*Proof) Descriptor() ([]byte, []int) { - return fileDescriptor_ed9f13149b0de397, []int{3} -} -func (m *Proof) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *Proof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Proof.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *Proof) XXX_Merge(src proto.Message) { - xxx_messageInfo_Proof.Merge(m, src) -} -func (m *Proof) XXX_Size() int { - return m.Size() -} -func (m *Proof) XXX_DiscardUnknown() { - xxx_messageInfo_Proof.DiscardUnknown(m) -} - -var xxx_messageInfo_Proof proto.InternalMessageInfo - -func (m *Proof) GetStart() int64 { - if m != nil { - return m.Start - } - return 0 -} - -func (m *Proof) GetEnd() int64 { - if m != nil { - return m.End - } - return 0 -} - -func (m *Proof) GetNodes() [][]byte { - if m != nil { - return m.Nodes - } - return nil -} - -func (m *Proof) GetHashleaf() []byte { - if m != nil { - return m.Hashleaf - } - return nil -} - func init() { proto.RegisterEnum("share.p2p.shrex.nd.StatusCode", StatusCode_name, StatusCode_value) proto.RegisterType((*GetSharesByNamespaceRequest)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceRequest") proto.RegisterType((*GetSharesByNamespaceResponse)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceResponse") proto.RegisterType((*Row)(nil), "share.p2p.shrex.nd.Row") - proto.RegisterType((*Proof)(nil), "share.p2p.shrex.nd.Proof") } func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } var fileDescriptor_ed9f13149b0de397 = []byte{ - // 396 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x6d, 0x6f, 0x62, 0x92, 0x49, 0x40, 0xd6, 0x80, 0xa8, 0xa1, 0x95, 0x15, 0xf9, 0x14, - 0x81, 0x64, 0x4b, 0x46, 0xe2, 0xee, 0xb6, 0x01, 0x22, 0xca, 0xa6, 0xda, 0x94, 0x3f, 0xb7, 0x68, - 0x8b, 0xb7, 0xf2, 0x01, 0xbc, 0x8b, 0x77, 0xa3, 0xc0, 0x99, 0x17, 0xe0, 0xb1, 0x38, 0xe6, 0xc8, - 0x11, 0x25, 0x2f, 0x82, 0xbc, 0x4e, 0x94, 0x03, 0xb9, 0xf9, 0x9b, 0xf9, 0xcd, 0x37, 0x9f, 0x47, - 0x0b, 0x23, 0x5d, 0xf2, 0x5a, 0xa4, 0x2a, 0x53, 0xa9, 0x2e, 0x6b, 0xf1, 0xbd, 0x2a, 0x52, 0x75, - 0x9b, 0xda, 0x62, 0xa2, 0x6a, 0x69, 0x24, 0xe2, 0x4e, 0x64, 0x2a, 0xb1, 0x44, 0x52, 0x15, 0xf1, - 0x27, 0x38, 0x7d, 0x2d, 0xcc, 0xbc, 0x69, 0xe8, 0xf3, 0x1f, 0x94, 0x7f, 0x15, 0x5a, 0xf1, 0xcf, - 0x82, 0x89, 0x6f, 0x4b, 0xa1, 0x0d, 0x9e, 0x42, 0xbf, 0x96, 0xd2, 0x2c, 0x4a, 0xae, 0xcb, 0xd0, - 0x1d, 0xb9, 0xe3, 0x21, 0xeb, 0x35, 0x85, 0x37, 0x5c, 0x97, 0x78, 0x06, 0xfd, 0x6a, 0x3f, 0x10, - 0x7a, 0xb6, 0x79, 0x28, 0xc4, 0x3f, 0x5d, 0x38, 0x3b, 0x6e, 0xad, 0x95, 0xac, 0xb4, 0xc0, 0x97, - 0xe0, 0x6b, 0xc3, 0xcd, 0x52, 0x5b, 0xe3, 0x07, 0x59, 0x94, 0xfc, 0x9f, 0x2f, 0x99, 0x5b, 0xe2, - 0x42, 0x16, 0x82, 0xed, 0x68, 0x7c, 0x0e, 0x9d, 0x5a, 0xae, 0x74, 0xe8, 0x8d, 0xc8, 0x78, 0x90, - 0x9d, 0x1c, 0x9b, 0x62, 0x72, 0xc5, 0x2c, 0x14, 0x53, 0x20, 0x4c, 0xae, 0xf0, 0x31, 0xf8, 0x16, - 0x6b, 0x76, 0x91, 0xf1, 0x90, 0xed, 0x14, 0xa6, 0xd0, 0x55, 0xb5, 0x94, 0x77, 0x36, 0xfe, 0x20, - 0x7b, 0x72, 0xcc, 0xec, 0xba, 0x01, 0x58, 0xcb, 0xc5, 0x1c, 0xba, 0x56, 0xe3, 0x23, 0xe8, 0x6a, - 0xc3, 0x6b, 0x63, 0xc3, 0x13, 0xd6, 0x0a, 0x0c, 0x80, 0x88, 0xaa, 0xb0, 0x6e, 0x84, 0x35, 0x9f, - 0x0d, 0x57, 0xc9, 0x42, 0xe8, 0x90, 0xd8, 0xc5, 0xad, 0xc0, 0xa7, 0xd0, 0x6b, 0x4e, 0xfa, 0x45, - 0xf0, 0xbb, 0xb0, 0xd3, 0x9e, 0x75, 0xaf, 0x9f, 0x7d, 0x04, 0x38, 0xfc, 0x35, 0x0e, 0xe0, 0xde, - 0x94, 0x7e, 0xc8, 0xaf, 0xa6, 0x97, 0x81, 0x83, 0x3e, 0x78, 0xb3, 0xb7, 0x81, 0x8b, 0xf7, 0xa1, - 0x4f, 0x67, 0x37, 0x8b, 0x57, 0xb3, 0xf7, 0xf4, 0x32, 0xf0, 0x70, 0x08, 0xbd, 0x29, 0xbd, 0x99, - 0x30, 0x9a, 0x5f, 0x05, 0x04, 0x4f, 0xe0, 0x21, 0xcd, 0xdf, 0x4d, 0xe6, 0xd7, 0xf9, 0xc5, 0x64, - 0x71, 0xc0, 0x3a, 0xe7, 0xe1, 0xef, 0x4d, 0xe4, 0xae, 0x37, 0x91, 0xfb, 0x77, 0x13, 0xb9, 0xbf, - 0xb6, 0x91, 0xb3, 0xde, 0x46, 0xce, 0x9f, 0x6d, 0xe4, 0xdc, 0xfa, 0xf6, 0x81, 0xbc, 0xf8, 0x17, - 0x00, 0x00, 0xff, 0xff, 0x0b, 0x93, 0xfd, 0x1b, 0x44, 0x02, 0x00, 0x00, + // 357 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x4d, 0x6b, 0xea, 0x40, + 0x14, 0x86, 0xf3, 0xe1, 0xcd, 0xd5, 0xa3, 0xd7, 0x1b, 0xe6, 0xc2, 0x35, 0x54, 0x09, 0x22, 0x14, + 0xa4, 0x85, 0x04, 0x52, 0xe8, 0x3e, 0x7e, 0xb4, 0x95, 0xda, 0x51, 0x46, 0xfb, 0xb1, 0x93, 0xa4, + 0x4e, 0xc9, 0xa6, 0x99, 0x69, 0x26, 0x62, 0xbb, 0xee, 0x1f, 0xe8, 0xcf, 0xea, 0xd2, 0x65, 0x97, + 0x45, 0xff, 0x48, 0xc9, 0x98, 0xe2, 0xa2, 0xee, 0xe6, 0xbc, 0xe7, 0x39, 0x67, 0x5e, 0xde, 0x03, + 0x4d, 0x11, 0x05, 0x09, 0x75, 0xb9, 0xc7, 0x5d, 0x11, 0x25, 0xf4, 0x39, 0x9e, 0xbb, 0x3c, 0x74, + 0xa5, 0xe8, 0xf0, 0x84, 0xa5, 0x0c, 0xa1, 0xbc, 0xf0, 0xb8, 0x23, 0x09, 0x27, 0x9e, 0x1f, 0x54, + 0x79, 0xe8, 0xf2, 0x84, 0xb1, 0x87, 0x2d, 0xd3, 0xba, 0x83, 0xfa, 0x39, 0x4d, 0x27, 0x19, 0x28, + 0x3a, 0x2f, 0x38, 0x78, 0xa4, 0x82, 0x07, 0xf7, 0x94, 0xd0, 0xa7, 0x05, 0x15, 0x29, 0xaa, 0x43, + 0x29, 0x61, 0x2c, 0x9d, 0x45, 0x81, 0x88, 0x2c, 0xb5, 0xa9, 0xb6, 0x2b, 0xa4, 0x98, 0x09, 0x17, + 0x81, 0x88, 0x50, 0x03, 0x4a, 0xf1, 0xf7, 0x80, 0xa5, 0xc9, 0xe6, 0x4e, 0x68, 0xbd, 0xaa, 0xd0, + 0xd8, 0xbf, 0x5a, 0x70, 0x16, 0x0b, 0x8a, 0x4e, 0xc1, 0x10, 0x69, 0x90, 0x2e, 0x84, 0x5c, 0x5c, + 0xf5, 0x6c, 0xe7, 0xa7, 0x5f, 0x67, 0x22, 0x89, 0x2e, 0x9b, 0x53, 0x92, 0xd3, 0xe8, 0x18, 0x0a, + 0x09, 0x5b, 0x0a, 0x4b, 0x6b, 0xea, 0xed, 0xb2, 0x57, 0xdb, 0x37, 0x45, 0xd8, 0x92, 0x48, 0xa8, + 0xd5, 0x03, 0x9d, 0xb0, 0x25, 0xfa, 0x0f, 0x86, 0xc4, 0xb2, 0xbf, 0xf4, 0x76, 0x85, 0xe4, 0x15, + 0x3a, 0x84, 0x5f, 0x32, 0x0d, 0x69, 0xbf, 0xec, 0xfd, 0x75, 0xf2, 0x6c, 0x42, 0x67, 0x9c, 0x3d, + 0xc8, 0xb6, 0x7b, 0x74, 0x0b, 0xb0, 0x33, 0x82, 0xca, 0xf0, 0x7b, 0x80, 0x6f, 0xfc, 0xe1, 0xa0, + 0x67, 0x2a, 0xc8, 0x00, 0x6d, 0x74, 0x69, 0xaa, 0xe8, 0x0f, 0x94, 0xf0, 0x68, 0x3a, 0x3b, 0x1b, + 0x5d, 0xe3, 0x9e, 0xa9, 0xa1, 0x0a, 0x14, 0x07, 0x78, 0xda, 0x27, 0xd8, 0x1f, 0x9a, 0x3a, 0xaa, + 0xc1, 0x3f, 0xec, 0x5f, 0xf5, 0x27, 0x63, 0xbf, 0xdb, 0x9f, 0xed, 0xb0, 0x42, 0xc7, 0x7a, 0x5f, + 0xdb, 0xea, 0x6a, 0x6d, 0xab, 0x9f, 0x6b, 0x5b, 0x7d, 0xdb, 0xd8, 0xca, 0x6a, 0x63, 0x2b, 0x1f, + 0x1b, 0x5b, 0x09, 0x0d, 0x79, 0x9f, 0x93, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x8b, 0x14, + 0xc5, 0xe7, 0x01, 0x00, 0x00, } func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { @@ -442,55 +372,6 @@ func (m *Row) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *Proof) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Proof) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Proof) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Hashleaf) > 0 { - i -= len(m.Hashleaf) - copy(dAtA[i:], m.Hashleaf) - i = encodeVarintShare(dAtA, i, uint64(len(m.Hashleaf))) - i-- - dAtA[i] = 0x22 - } - if len(m.Nodes) > 0 { - for iNdEx := len(m.Nodes) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Nodes[iNdEx]) - copy(dAtA[i:], m.Nodes[iNdEx]) - i = encodeVarintShare(dAtA, i, uint64(len(m.Nodes[iNdEx]))) - i-- - dAtA[i] = 0x1a - } - } - if m.End != 0 { - i = encodeVarintShare(dAtA, i, uint64(m.End)) - i-- - dAtA[i] = 0x10 - } - if m.Start != 0 { - i = encodeVarintShare(dAtA, i, uint64(m.Start)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - func encodeVarintShare(dAtA []byte, offset int, v uint64) int { offset -= sovShare(v) base := offset @@ -556,31 +437,6 @@ func (m *Row) Size() (n int) { return n } -func (m *Proof) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Start != 0 { - n += 1 + sovShare(uint64(m.Start)) - } - if m.End != 0 { - n += 1 + sovShare(uint64(m.End)) - } - if len(m.Nodes) > 0 { - for _, b := range m.Nodes { - l = len(b) - n += 1 + l + sovShare(uint64(l)) - } - } - l = len(m.Hashleaf) - if l > 0 { - n += 1 + l + sovShare(uint64(l)) - } - return n -} - func sovShare(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -899,7 +755,7 @@ func (m *Row) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.Proof == nil { - m.Proof = &Proof{} + m.Proof = &pb.Proof{} } if err := m.Proof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -926,160 +782,6 @@ func (m *Row) Unmarshal(dAtA []byte) error { } return nil } -func (m *Proof) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Proof: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Proof: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Start", wireType) - } - m.Start = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Start |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field End", wireType) - } - m.End = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.End |= int64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Nodes", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthShare - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthShare - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Nodes = append(m.Nodes, make([]byte, postIndex-iNdEx)) - copy(m.Nodes[len(m.Nodes)-1], dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hashleaf", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthShare - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthShare - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hashleaf = append(m.Hashleaf[:0], dAtA[iNdEx:postIndex]...) - if m.Hashleaf == nil { - m.Hashleaf = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipShare(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthShare - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipShare(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto index 306865d17f..931b25faa9 100644 --- a/share/p2p/shrexnd/pb/share.proto +++ b/share/p2p/shrexnd/pb/share.proto @@ -1,6 +1,7 @@ syntax = "proto3"; package share.p2p.shrex.nd; +import "pb/proof.proto"; message GetSharesByNamespaceRequest{ bytes root_hash = 1; @@ -22,12 +23,5 @@ enum StatusCode { message Row { repeated bytes shares = 1; - Proof proof = 2; -} - -message Proof { - int64 start = 1; - int64 end = 2; - repeated bytes nodes = 3; - bytes hashleaf = 4; + proof.pb.Proof proof = 2; } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 830a853946..42810b1f48 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -14,6 +14,7 @@ import ( "go.uber.org/zap" "github.com/celestiaorg/go-libp2p-messenger/serde" + nmt_pb "github.com/celestiaorg/nmt/pb" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" @@ -188,11 +189,12 @@ func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNa for _, row := range shares { row := &pb.Row{ Shares: row.Shares, - Proof: &pb.Proof{ - Start: int64(row.Proof.Start()), - End: int64(row.Proof.End()), - Nodes: row.Proof.Nodes(), - Hashleaf: row.Proof.LeafHash(), + Proof: &nmt_pb.Proof{ + Start: int64(row.Proof.Start()), + End: int64(row.Proof.End()), + Nodes: row.Proof.Nodes(), + LeafHash: row.Proof.LeafHash(), + IsMaxNamespaceIgnored: row.Proof.IsMaxNamespaceIDIgnored(), }, } From a9cff49083f866c03aac34004cd47a654eb3f36d Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:30:19 +0800 Subject: [PATCH 0728/1008] feat(share/eds)!: Don't use json.Marshal for inverted index (#2410) ## Overview Inverted index stores shard keys as json objects. `json.Marshal` and `json.Unmarshal` are memory heavy operations and slows down inverted index building. Shard keys are strings and could be stored and retrieved as is, without marshalling/unmarshallng. Benchmarks shows 15% performance gain for `store.Put` operation. --- share/eds/inverted_index.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index d302932369..bb4092fa52 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -2,7 +2,6 @@ package eds import ( "context" - "encoding/json" "fmt" "github.com/filecoin-project/dagstore/index" @@ -56,11 +55,7 @@ func (s *simpleInvertedIndex) AddMultihashesForShard( err = mhIter.ForEach(func(mh multihash.Multihash) error { key := ds.NewKey(string(mh)) - bz, err := json.Marshal(sk) - if err != nil { - return fmt.Errorf("failed to marshal shard key to bytes: %w", err) - } - if err := batch.Put(ctx, key, bz); err != nil { + if err := batch.Put(ctx, key, []byte(sk.String())); err != nil { return fmt.Errorf("failed to put mh=%s, err=%w", mh, err) } return nil @@ -82,12 +77,7 @@ func (s *simpleInvertedIndex) GetShardsForMultihash(ctx context.Context, mh mult return nil, fmt.Errorf("failed to lookup index for mh %s, err: %w", mh, err) } - var shardKey shard.Key - if err := json.Unmarshal(sbz, &shardKey); err != nil { - return nil, fmt.Errorf("failed to unmarshal shard key for mh=%s, err=%w", mh, err) - } - - return []shard.Key{shardKey}, nil + return []shard.Key{shard.KeyFromString(string(sbz))}, nil } func (s *simpleInvertedIndex) close() error { From 2ed901c3c55f86cdaa8f1d386ac0d58889824467 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:30:51 +0800 Subject: [PATCH 0729/1008] feat(share/store): store eds in memory for index builder (#2409) ## Overview Previously generated eds was first store to disk and then was read again while building car index for dagstore. This PR contains 2 changes: - writes eds into buffer to write it as 1 chunk instead of many small writes to disk to reduce disk i/o pressure. - introduces in-memory cache that will be used to read eds for building car index. Combined changes brings 3x performance improvement for eds.Put operation --- share/eds/store.go | 58 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/share/eds/store.go b/share/eds/store.go index 2218e13954..bf244a41d8 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -73,7 +73,10 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } r := mount.NewRegistry() - err = r.Register("fs", &mount.FileMount{Path: basepath + blocksPath}) + err = r.Register("fs", &inMemoryOnceMount{Mount: &mount.FileMount{}}) + if err != nil { + return nil, fmt.Errorf("failed to register memory mount on the registry: %w", err) + } if err != nil { return nil, fmt.Errorf("failed to register FS mount on the registry: %w", err) } @@ -194,15 +197,24 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext } defer f.Close() - err = WriteEDS(ctx, square, f) + // save encoded eds into buffer + mount := &inMemoryOnceMount{ + // TODO: buffer could be pre-allocated with capacity calculated based on eds size. + buf: bytes.NewBuffer(nil), + Mount: &mount.FileMount{Path: s.basepath + blocksPath + key}, + } + err = WriteEDS(ctx, square, mount) if err != nil { return fmt.Errorf("failed to write EDS to file: %w", err) } + // write whole buffered mount data in one go to optimize i/o + if _, err = mount.WriteTo(f); err != nil { + return fmt.Errorf("failed to write EDS to file: %w", err) + } + ch := make(chan dagstore.ShardResult, 1) - err = s.dgstr.RegisterShard(ctx, shard.KeyFromString(key), &mount.FileMount{ - Path: s.basepath + blocksPath + key, - }, ch, dagstore.RegisterOpts{}) + err = s.dgstr.RegisterShard(ctx, shard.KeyFromString(key), mount, ch, dagstore.RegisterOpts{}) if err != nil { return fmt.Errorf("failed to initiate shard registration: %w", err) } @@ -471,3 +483,39 @@ func setupPath(basepath string) error { } return nil } + +// inMemoryOnceMount is used to allow reading once from buffer before using main mount.Reader +type inMemoryOnceMount struct { + buf *bytes.Buffer + + readOnce atomic.Bool + mount.Mount +} + +func (m *inMemoryOnceMount) Fetch(ctx context.Context) (mount.Reader, error) { + if !m.readOnce.Swap(true) { + reader := &inMemoryReader{Reader: bytes.NewReader(m.buf.Bytes())} + // release memory for gc, otherwise buffer will stick forever + m.buf = nil + return reader, nil + } + return m.Mount.Fetch(ctx) +} + +func (m *inMemoryOnceMount) Write(b []byte) (int, error) { + return m.buf.Write(b) +} + +func (m *inMemoryOnceMount) WriteTo(w io.Writer) (int64, error) { + return io.Copy(w, bytes.NewReader(m.buf.Bytes())) +} + +// inMemoryReader extends bytes.Reader to implement mount.Reader interface +type inMemoryReader struct { + *bytes.Reader +} + +// Close allows inMemoryReader to satisfy mount.Reader interface +func (r *inMemoryReader) Close() error { + return nil +} From d24e18cbd4b58ef34a73f00d31f2dc86302028b6 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:09:44 +0800 Subject: [PATCH 0730/1008] feat(share/store): add eds store metrics (#2536) --- nodebuilder/settings.go | 2 + nodebuilder/share/opts.go | 5 + share/eds/metrics.go | 267 ++++++++++++++++++++++++++++++++++++++ share/eds/store.go | 132 +++++++++++++++---- 4 files changed, 378 insertions(+), 28 deletions(-) create mode 100644 share/eds/metrics.go diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 9a9f7fcf47..c30996d24e 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -90,6 +90,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti case node.Full: opts = fx.Options( baseComponents, + fx.Invoke(share.WithStoreMetrics), fx.Invoke(share.WithShrexServerMetrics), samplingMetrics, ) @@ -101,6 +102,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti case node.Bridge: opts = fx.Options( baseComponents, + fx.Invoke(share.WithStoreMetrics), fx.Invoke(share.WithShrexServerMetrics), ) default: diff --git a/nodebuilder/share/opts.go b/nodebuilder/share/opts.go index 20ba0ce58c..e236847f41 100644 --- a/nodebuilder/share/opts.go +++ b/nodebuilder/share/opts.go @@ -1,6 +1,7 @@ package share import ( + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" disc "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" @@ -41,3 +42,7 @@ func WithShrexServerMetrics(edsServer *shrexeds.Server, ndServer *shrexnd.Server func WithShrexGetterMetrics(sg *getters.ShrexGetter) error { return sg.WithMetrics() } + +func WithStoreMetrics(s *eds.Store) error { + return s.WithMetrics() +} diff --git a/share/eds/metrics.go b/share/eds/metrics.go new file mode 100644 index 0000000000..82adb246f1 --- /dev/null +++ b/share/eds/metrics.go @@ -0,0 +1,267 @@ +package eds + +import ( + "context" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +const ( + failedKey = "failed" + sizeKey = "eds_size" + cachedKey = "cached" + + putResultKey = "result" + putOK putResult = "ok" + putExists putResult = "exists" + putFailed putResult = "failed" + + opNameKey = "op" + longOpResultKey = "result" + longOpUnresolved longOpResult = "unresolved" + longOpOK longOpResult = "ok" + longOpFailed longOpResult = "failed" +) + +var ( + meter = otel.Meter("eds_store") +) + +type putResult string + +type longOpResult string + +type metrics struct { + putTime metric.Float64Histogram + getCARTime metric.Float64Histogram + getCARBlockstoreTime metric.Float64Histogram + getDAHTime metric.Float64Histogram + removeTime metric.Float64Histogram + getTime metric.Float64Histogram + hasTime metric.Float64Histogram + listTime metric.Float64Histogram + getAccessorTime metric.Float64Histogram + + longOpTime metric.Float64Histogram + gcTime metric.Float64Histogram +} + +func (s *Store) WithMetrics() error { + putTime, err := meter.Float64Histogram("eds_store_put_time_histogram", + metric.WithDescription("eds store put time histogram(s)")) + if err != nil { + return err + } + + getCARTime, err := meter.Float64Histogram("eds_store_get_car_time_histogram", + metric.WithDescription("eds store get car time histogram(s)")) + if err != nil { + return err + } + + getCARBlockstoreTime, err := meter.Float64Histogram("eds_store_get_car_blockstore_time_histogram", + metric.WithDescription("eds store get car blockstore time histogram(s)")) + if err != nil { + return err + } + + getDAHTime, err := meter.Float64Histogram("eds_store_get_dah_time_histogram", + metric.WithDescription("eds store get dah time histogram(s)")) + if err != nil { + return err + } + + removeTime, err := meter.Float64Histogram("eds_store_remove_time_histogram", + metric.WithDescription("eds store remove time histogram(s)")) + if err != nil { + return err + } + + getTime, err := meter.Float64Histogram("eds_store_get_time_histogram", + metric.WithDescription("eds store get time histogram(s)")) + if err != nil { + return err + } + + hasTime, err := meter.Float64Histogram("eds_store_has_time_histogram", + metric.WithDescription("eds store has time histogram(s)")) + if err != nil { + return err + } + + listTime, err := meter.Float64Histogram("eds_store_list_time_histogram", + metric.WithDescription("eds store list time histogram(s)")) + if err != nil { + return err + } + + getAccessorTime, err := meter.Float64Histogram("eds_store_get_accessor_time_histogram", + metric.WithDescription("eds store get accessor time histogram(s)")) + if err != nil { + return err + } + + longOpTime, err := meter.Float64Histogram("eds_store_long_operation_time_histogram", + metric.WithDescription("eds store long operation time histogram(s)")) + if err != nil { + return err + } + + gcTime, err := meter.Float64Histogram("eds_store_gc_time", + metric.WithDescription("dagstore gc time histogram(s)")) + if err != nil { + return err + } + + s.metrics = &metrics{ + putTime: putTime, + getCARTime: getCARTime, + getCARBlockstoreTime: getCARBlockstoreTime, + getDAHTime: getDAHTime, + removeTime: removeTime, + getTime: getTime, + hasTime: hasTime, + listTime: listTime, + getAccessorTime: getAccessorTime, + longOpTime: longOpTime, + gcTime: gcTime, + } + return nil +} + +func (m *metrics) observeGCtime(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + m.gcTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observePut(ctx context.Context, dur time.Duration, result putResult, size uint) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.putTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.String(putResultKey, string(result)), + attribute.Int(sizeKey, int(size)))) +} + +func (m *metrics) observeLongOp(ctx context.Context, opName string, dur time.Duration, result longOpResult) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.longOpTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.String(opNameKey, opName), + attribute.String(longOpResultKey, string(result)))) +} + +func (m *metrics) observeGetCAR(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.getCARTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeCARBlockstore(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.getCARBlockstoreTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeGetDAH(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.getDAHTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeRemove(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.removeTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeGet(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.getTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeHas(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.hasTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeList(ctx context.Context, dur time.Duration, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.listTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeGetAccessor(ctx context.Context, dur time.Duration, cached, failed bool) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.getAccessorTime.Record(ctx, dur.Seconds(), metric.WithAttributes( + attribute.Bool(cachedKey, cached), + attribute.Bool(failedKey, failed))) +} diff --git a/share/eds/store.go b/share/eds/store.go index cd85380ba1..2b2c37b26d 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -63,6 +63,8 @@ type Store struct { gcInterval time.Duration // lastGCResult is only stored on the store for testing purposes. lastGCResult atomic.Pointer[dagstore.GCResult] + + metrics *metrics } // NewStore creates a new EDS Store under the given basepath and datastore. @@ -148,7 +150,9 @@ func (s *Store) gc(ctx context.Context) { case <-ctx.Done(): return case <-ticker.C: + tnow := time.Now() res, err := s.dgstr.GC(ctx) + s.metrics.observeGCtime(ctx, time.Since(tnow), err != nil) if err != nil { log.Errorf("garbage collecting dagstore: %v", err) return @@ -164,22 +168,30 @@ func (s *Store) gc(ctx context.Context) { // The square is verified on the Exchange level, and Put only stores the square, trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. // Additionally, the file gets indexed s.t. store.Blockstore can access them. -func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) (err error) { - // if root already exists, short-circuit - has, err := s.Has(ctx, root) - if err != nil { - return fmt.Errorf("failed to check if root already exists in index: %w", err) - } - if has { - return dagstore.ErrShardExists - } - +func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) error { ctx, span := tracer.Start(ctx, "store/put", trace.WithAttributes( attribute.Int("width", int(square.Width())), )) - defer func() { - utils.SetStatusAndEnd(span, err) - }() + + tnow := time.Now() + err := s.put(ctx, root, square) + result := putOK + switch { + case errors.Is(err, dagstore.ErrShardExists): + result = putExists + case err != nil: + result = putFailed + } + utils.SetStatusAndEnd(span, err) + s.metrics.observePut(ctx, time.Since(tnow), result, square.Width()) + return err +} + +func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) (err error) { + // if root already exists, short-circuit + if has, _ := s.Has(ctx, root); has { + return dagstore.ErrShardExists + } key := root.String() f, err := os.OpenFile(s.basepath+blocksPath+key, os.O_CREATE|os.O_WRONLY, 0600) @@ -203,7 +215,8 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext select { case <-ctx.Done(): - go logLateResult(ch, time.Minute*5) + // if context finished before result was received, track result in separate goroutine + go trackLateResult("put", ch, s.metrics, time.Minute*5) return ctx.Err() case result := <-ch: if result.Error != nil { @@ -217,21 +230,24 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext // maxWait. If the result is not received within the specified duration, it logs an error // indicating that the parent context has expired and the shard registration is stuck. If a result // is received, it checks for any error and logs appropriate messages. -func logLateResult(res <-chan dagstore.ShardResult, maxWait time.Duration) { +func trackLateResult(opName string, res <-chan dagstore.ShardResult, metrics *metrics, maxWait time.Duration) { tnow := time.Now() select { case <-time.After(maxWait): + metrics.observeLongOp(context.Background(), opName, time.Since(tnow), longOpUnresolved) log.Errorf("parent context is expired, while register shard is stuck for more than %v sec", time.Since(tnow)) return case result := <-res: - // don't log if result was received right after launch of the func + // don't observe if result was received right after launch of the func if time.Since(tnow) < time.Second { return } if result.Error != nil { + metrics.observeLongOp(context.Background(), opName, time.Since(tnow), longOpFailed) log.Errorf("failed to register shard after context expired: %v ago, err: %w", time.Since(tnow), result.Error) return } + metrics.observeLongOp(context.Background(), opName, time.Since(tnow), longOpOK) log.Warnf("parent context expired, but register shard finished with no error,"+ " after context expired: %v ago", time.Since(tnow)) return @@ -247,8 +263,14 @@ func logLateResult(res <-chan dagstore.ShardResult, maxWait time.Duration) { // same reader. The cache is responsible for closing the underlying reader. func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { ctx, span := tracer.Start(ctx, "store/get-car") - defer span.End() + tnow := time.Now() + r, err := s.getCAR(ctx, root) + s.metrics.observeGetCAR(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return r, err +} +func (s *Store) getCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { key := root.String() accessor, err := s.getCachedAccessor(ctx, shard.KeyFromString(key)) if err != nil { @@ -272,6 +294,18 @@ func (s *Store) Blockstore() bstore.Blockstore { func (s *Store) CARBlockstore( ctx context.Context, root share.DataHash, +) (dagstore.ReadBlockstore, error) { + ctx, span := tracer.Start(ctx, "store/car-blockstore") + tnow := time.Now() + r, err := s.carBlockstore(ctx, root) + s.metrics.observeCARBlockstore(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return r, err +} + +func (s *Store) carBlockstore( + ctx context.Context, + root share.DataHash, ) (dagstore.ReadBlockstore, error) { key := shard.KeyFromString(root.String()) accessor, err := s.getCachedAccessor(ctx, key) @@ -283,9 +317,15 @@ func (s *Store) CARBlockstore( // GetDAH returns the DataAvailabilityHeader for the EDS identified by DataHash. func (s *Store) GetDAH(ctx context.Context, root share.DataHash) (*share.Root, error) { - ctx, span := tracer.Start(ctx, "store/get-dah") - defer span.End() + ctx, span := tracer.Start(ctx, "store/car-dah") + tnow := time.Now() + r, err := s.getDAH(ctx, root) + s.metrics.observeGetDAH(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return r, err +} +func (s *Store) getDAH(ctx context.Context, root share.DataHash) (*share.Root, error) { key := shard.KeyFromString(root.String()) accessor, err := s.getCachedAccessor(ctx, key) if err != nil { @@ -334,6 +374,7 @@ func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*dagstore.Shard } return res.Accessor, nil case <-ctx.Done(): + go trackLateResult("get_shard", ch, s.metrics, time.Minute) return nil, ctx.Err() } } @@ -343,30 +384,40 @@ func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessor lk.Lock() defer lk.Unlock() + tnow := time.Now() accessor, err := s.cache.unsafeGet(key) if err != nil && err != errCacheMiss { log.Errorf("unexpected error while reading key from bs cache %s: %s", key, err) } if accessor != nil { + s.metrics.observeGetAccessor(ctx, time.Since(tnow), true, false) return accessor, nil } // wasn't found in cache, so acquire it and add to cache shardAccessor, err := s.getAccessor(ctx, key) if err != nil { + s.metrics.observeGetAccessor(ctx, time.Since(tnow), false, err != nil) return nil, err } - return s.cache.unsafeAdd(key, shardAccessor) + + a, err := s.cache.unsafeAdd(key, shardAccessor) + s.metrics.observeGetAccessor(ctx, time.Since(tnow), false, err != nil) + return a, err } // Remove removes EDS from Store by the given share.Root hash and cleans up all // the indexing. -func (s *Store) Remove(ctx context.Context, root share.DataHash) (err error) { +func (s *Store) Remove(ctx context.Context, root share.DataHash) error { ctx, span := tracer.Start(ctx, "store/remove") - defer func() { - utils.SetStatusAndEnd(span, err) - }() + tnow := time.Now() + err := s.remove(ctx, root) + s.metrics.observeRemove(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return err +} +func (s *Store) remove(ctx context.Context, root share.DataHash) (err error) { key := root.String() ch := make(chan dagstore.ShardResult, 1) err = s.dgstr.DestroyShard(ctx, shard.KeyFromString(key), ch, dagstore.DestroyOpts{}) @@ -380,6 +431,7 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) (err error) { return fmt.Errorf("failed to destroy shard: %w", result.Error) } case <-ctx.Done(): + go trackLateResult("remove", ch, s.metrics, time.Minute) return ctx.Err() } @@ -402,7 +454,16 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) (err error) { // // It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by // recomputing it. -func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.ExtendedDataSquare, err error) { +func (s *Store) Get(ctx context.Context, root share.DataHash) (*rsmt2d.ExtendedDataSquare, error) { + ctx, span := tracer.Start(ctx, "store/get") + tnow := time.Now() + eds, err := s.get(ctx, root) + s.metrics.observeGet(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return eds, err +} + +func (s *Store) get(ctx context.Context, root share.DataHash) (eds *rsmt2d.ExtendedDataSquare, err error) { ctx, span := tracer.Start(ctx, "store/get") defer func() { utils.SetStatusAndEnd(span, err) @@ -420,10 +481,16 @@ func (s *Store) Get(ctx context.Context, root share.DataHash) (eds *rsmt2d.Exten } // Has checks if EDS exists by the given share.Root hash. -func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { - _, span := tracer.Start(ctx, "store/has") - defer span.End() +func (s *Store) Has(ctx context.Context, root share.DataHash) (has bool, err error) { + ctx, span := tracer.Start(ctx, "store/has") + tnow := time.Now() + eds, err := s.has(ctx, root) + s.metrics.observeHas(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return eds, err +} +func (s *Store) has(_ context.Context, root share.DataHash) (bool, error) { key := root.String() info, err := s.dgstr.GetShardInfo(shard.KeyFromString(key)) switch err { @@ -438,6 +505,15 @@ func (s *Store) Has(ctx context.Context, root share.DataHash) (bool, error) { // List lists all the registered EDSes. func (s *Store) List() ([]share.DataHash, error) { + ctx, span := tracer.Start(context.Background(), "store/list") + tnow := time.Now() + hashes, err := s.list() + s.metrics.observeList(ctx, time.Since(tnow), err != nil) + utils.SetStatusAndEnd(span, err) + return hashes, err +} + +func (s *Store) list() ([]share.DataHash, error) { shards := s.dgstr.AllShardsInfo() hashes := make([]share.DataHash, 0, len(shards)) for shrd := range shards { From cf1b117386c245e80a4015f20f69fb5d761b74c8 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:09:59 +0800 Subject: [PATCH 0731/1008] !feat(share/p2p/shrex-nd): rework shrex-nd to serve data in multiple messages (#2444) ## Overview shrex-nd protocol is limited to send messages with max size of 1mb. PR modifies shrex-nd protocol to stream namespace data row by row. Next time we will have msg size problem after ods size > 2048. Resolves https://github.com/celestiaorg/celestia-node/issues/2419 --- share/eds/edstest/testing.go | 14 ++ share/getters/shrex_test.go | 34 ++++- share/p2p/errors.go | 2 + share/p2p/metrics.go | 4 + share/p2p/shrexnd/client.go | 105 +++++++------- share/p2p/shrexnd/exchange_test.go | 2 +- share/p2p/shrexnd/pb/share.pb.go | 221 ++++++++++------------------- share/p2p/shrexnd/pb/share.proto | 6 +- share/p2p/shrexnd/server.go | 218 +++++++++++++++------------- share/sharetest/testing.go | 20 +++ 10 files changed, 325 insertions(+), 301 deletions(-) diff --git a/share/eds/edstest/testing.go b/share/eds/edstest/testing.go index 2b6d9ef78d..e66b42a566 100644 --- a/share/eds/edstest/testing.go +++ b/share/eds/edstest/testing.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" @@ -29,3 +30,16 @@ func RandEDS(t require.TestingT, size int) *rsmt2d.ExtendedDataSquare { require.NoError(t, err, "failure to recompute the extended data square") return eds } + +func RandEDSWithNamespace( + t require.TestingT, + namespace share.Namespace, + size int, +) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader) { + shares := sharetest.RandSharesWithNamespace(t, namespace, size*size) + eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) + require.NoError(t, err, "failure to recompute the extended data square") + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) + return eds, dah +} diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index d2dcfeca5e..ef07cf6797 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -1,9 +1,12 @@ package getters import ( + "bytes" "context" "encoding/binary" "errors" + "math/rand" + "sort" "testing" "time" @@ -17,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -31,6 +35,7 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestShrexGetter(t *testing.T) { @@ -58,12 +63,13 @@ func TestShrexGetter(t *testing.T) { getter := NewShrexGetter(edsClient, ndClient, peerManager) require.NoError(t, getter.Start(ctx)) - t.Run("ND_Available", func(t *testing.T) { - ctx, cancel := context.WithTimeout(ctx, time.Second) + t.Run("ND_Available, total data size > 1mb", func(t *testing.T) { + ctx, cancel := context.WithTimeout(ctx, time.Second*10) t.Cleanup(cancel) // generate test data - randEDS, dah, namespace := generateTestEDS(t) + namespace := sharetest.RandV0Namespace() + randEDS, dah := singleNamespaceEds(t, namespace, 64) require.NoError(t, edsStore.Put(ctx, dah.Hash(), randEDS)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), @@ -368,3 +374,25 @@ func TestAddToNamespace(t *testing.T) { }) } } + +func singleNamespaceEds( + t require.TestingT, + namespace share.Namespace, + size int, +) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader) { + shares := make([]share.Share, size*size) + rnd := rand.New(rand.NewSource(time.Now().Unix())) + for i := range shares { + shr := make([]byte, share.Size) + copy(share.GetNamespace(shr), namespace) + _, err := rnd.Read(share.GetData(shr)) + require.NoError(t, err) + shares[i] = shr + } + sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) + eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) + require.NoError(t, err, "failure to recompute the extended data square") + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) + return eds, dah +} diff --git a/share/p2p/errors.go b/share/p2p/errors.go index 77f23c554e..cb7b596f47 100644 --- a/share/p2p/errors.go +++ b/share/p2p/errors.go @@ -9,6 +9,8 @@ import ( // available at the moment. The request may be retried later, but it's unlikely to succeed. var ErrNotFound = errors.New("the requested data or resource could not be found") +var ErrRateLimited = errors.New("server is overloaded and rate limited the request") + // ErrInvalidResponse is returned when a peer returns an invalid response or caused an internal // error. It is used to signal that the peer couldn't serve the data successfully, and should not be // retried. diff --git a/share/p2p/metrics.go b/share/p2p/metrics.go index 1942be5d6b..fee3b12413 100644 --- a/share/p2p/metrics.go +++ b/share/p2p/metrics.go @@ -14,6 +14,10 @@ var meter = otel.Meter("shrex/eds") type status string const ( + StatusBadRequest status = "bad_request" + StatusSendRespErr status = "send_resp_err" + StatusSendReqErr status = "send_req_err" + StatusReadRespErr status = "read_resp_err" StatusInternalErr status = "internal_err" StatusNotFound status = "not_found" StatusTimeout status = "timeout" diff --git a/share/p2p/shrexnd/client.go b/share/p2p/shrexnd/client.go index bbe34ddc38..86c5150095 100644 --- a/share/p2p/shrexnd/client.go +++ b/share/p2p/shrexnd/client.go @@ -45,7 +45,7 @@ func NewClient(params *Parameters, host host.Host) (*Client, error) { } // RequestND requests namespaced data from the given peer. -// Returns shares with unverified inclusion proofs against the share.Root. +// Returns NamespacedShares with unverified inclusion proofs against the share.Root. func (c *Client) RequestND( ctx context.Context, root *share.Root, @@ -73,7 +73,7 @@ func (c *Client) RequestND( return nil, context.DeadlineExceeded } } - if err != p2p.ErrNotFound { + if err != p2p.ErrNotFound && err != p2p.ErrRateLimited { log.Warnw("client-nd: peer returned err", "err", err) } return nil, err @@ -100,6 +100,7 @@ func (c *Client) doRequest( _, err = serde.Write(stream, req) if err != nil { + c.metrics.ObserveRequests(ctx, 1, p2p.StatusSendReqErr) stream.Reset() //nolint:errcheck return nil, fmt.Errorf("client-nd: writing request: %w", err) } @@ -109,59 +110,70 @@ func (c *Client) doRequest( log.Debugw("client-nd: closing write side of the stream", "err", err) } - var resp pb.GetSharesByNamespaceResponse - _, err = serde.Read(stream, &resp) + if err := c.readStatus(ctx, stream); err != nil { + return nil, err + } + return c.readNamespacedShares(ctx, stream) +} + +func (c *Client) readStatus(ctx context.Context, stream network.Stream) error { + var resp pb.GetSharesByNamespaceStatusResponse + _, err := serde.Read(stream, &resp) if err != nil { // server is overloaded and closed the stream if errors.Is(err, io.EOF) { c.metrics.ObserveRequests(ctx, 1, p2p.StatusRateLimited) - return nil, p2p.ErrNotFound + return p2p.ErrRateLimited } + c.metrics.ObserveRequests(ctx, 1, p2p.StatusReadRespErr) stream.Reset() //nolint:errcheck - return nil, fmt.Errorf("client-nd: reading response: %w", err) + return fmt.Errorf("client-nd: reading status response: %w", err) } - return c.convertResponse(ctx, resp) + return c.convertStatusToErr(ctx, resp.Status) } -// convertToNamespacedShares converts proto Rows to share.NamespacedShares -func convertToNamespacedShares(rows []*pb.Row) share.NamespacedShares { - shares := make([]share.NamespacedRow, 0, len(rows)) - for _, row := range rows { - var proof *nmt.Proof +// readNamespacedShares converts proto Rows to share.NamespacedShares +func (c *Client) readNamespacedShares( + ctx context.Context, + stream network.Stream, +) (share.NamespacedShares, error) { + var shares share.NamespacedShares + for { + var row pb.NamespaceRowResponse + _, err := serde.Read(stream, &row) + if err != nil { + if errors.Is(err, io.EOF) { + // all data is received and steam is closed by server + return shares, nil + } + c.metrics.ObserveRequests(ctx, 1, p2p.StatusReadRespErr) + return nil, err + } + var proof nmt.Proof if row.Proof != nil { - tmpProof := nmt.NewInclusionProof( - int(row.Proof.Start), - int(row.Proof.End), - row.Proof.Nodes, - row.Proof.IsMaxNamespaceIgnored, - ) - proof = &tmpProof + if len(row.Shares) != 0 { + proof = nmt.NewInclusionProof( + int(row.Proof.Start), + int(row.Proof.End), + row.Proof.Nodes, + row.Proof.IsMaxNamespaceIgnored, + ) + } else { + proof = nmt.NewAbsenceProof( + int(row.Proof.Start), + int(row.Proof.End), + row.Proof.Nodes, + row.Proof.LeafHash, + row.Proof.IsMaxNamespaceIgnored, + ) + } } - shares = append(shares, share.NamespacedRow{ Shares: row.Shares, - Proof: proof, - }) - } - return shares -} - -func convertToNonInclusionProofs(rows []*pb.Row) share.NamespacedShares { - shares := make([]share.NamespacedRow, 0, len(rows)) - for _, row := range rows { - proof := nmt.NewAbsenceProof( - int(row.Proof.Start), - int(row.Proof.End), - row.Proof.Nodes, - row.Proof.LeafHash, - row.Proof.IsMaxNamespaceIgnored, - ) - shares = append(shares, share.NamespacedRow{ - Proof: &proof, + Proof: &proof, }) } - return shares } func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) { @@ -192,23 +204,20 @@ func (c *Client) setStreamDeadlines(ctx context.Context, stream network.Stream) } } -func (c *Client) convertResponse( - ctx context.Context, resp pb.GetSharesByNamespaceResponse) (share.NamespacedShares, error) { - switch resp.Status { +func (c *Client) convertStatusToErr(ctx context.Context, status pb.StatusCode) error { + switch status { case pb.StatusCode_OK: c.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) - return convertToNamespacedShares(resp.Rows), nil - case pb.StatusCode_NAMESPACE_NOT_FOUND: - return convertToNonInclusionProofs(resp.Rows), nil + return nil case pb.StatusCode_NOT_FOUND: c.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) - return nil, p2p.ErrNotFound + return p2p.ErrNotFound case pb.StatusCode_INVALID: - log.Debug("client-nd: invalid request") + log.Warn("client-nd: invalid request") fallthrough case pb.StatusCode_INTERNAL: fallthrough default: - return nil, p2p.ErrInvalidResponse + return p2p.ErrInvalidResponse } } diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 8d1dad17c0..1ca0736cd6 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -100,7 +100,7 @@ func TestExchange_RequestND(t *testing.T) { // wait until all server slots are taken wg.Wait() _, err = client.RequestND(ctx, nil, sharetest.RandV0Namespace(), server.host.ID()) - require.ErrorIs(t, err, p2p.ErrNotFound) + require.ErrorIs(t, err, p2p.ErrRateLimited) }) } diff --git a/share/p2p/shrexnd/pb/share.pb.go b/share/p2p/shrexnd/pb/share.pb.go index 933f5cbe54..7e3c11416f 100644 --- a/share/p2p/shrexnd/pb/share.pb.go +++ b/share/p2p/shrexnd/pb/share.pb.go @@ -26,11 +26,10 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type StatusCode int32 const ( - StatusCode_INVALID StatusCode = 0 - StatusCode_OK StatusCode = 1 - StatusCode_NOT_FOUND StatusCode = 2 - StatusCode_INTERNAL StatusCode = 3 - StatusCode_NAMESPACE_NOT_FOUND StatusCode = 4 + StatusCode_INVALID StatusCode = 0 + StatusCode_OK StatusCode = 1 + StatusCode_NOT_FOUND StatusCode = 2 + StatusCode_INTERNAL StatusCode = 3 ) var StatusCode_name = map[int32]string{ @@ -38,15 +37,13 @@ var StatusCode_name = map[int32]string{ 1: "OK", 2: "NOT_FOUND", 3: "INTERNAL", - 4: "NAMESPACE_NOT_FOUND", } var StatusCode_value = map[string]int32{ - "INVALID": 0, - "OK": 1, - "NOT_FOUND": 2, - "INTERNAL": 3, - "NAMESPACE_NOT_FOUND": 4, + "INVALID": 0, + "OK": 1, + "NOT_FOUND": 2, + "INTERNAL": 3, } func (x StatusCode) String() string { @@ -109,23 +106,22 @@ func (m *GetSharesByNamespaceRequest) GetNamespace() []byte { return nil } -type GetSharesByNamespaceResponse struct { +type GetSharesByNamespaceStatusResponse struct { Status StatusCode `protobuf:"varint,1,opt,name=status,proto3,enum=share.p2p.shrex.nd.StatusCode" json:"status,omitempty"` - Rows []*Row `protobuf:"bytes,2,rep,name=rows,proto3" json:"rows,omitempty"` } -func (m *GetSharesByNamespaceResponse) Reset() { *m = GetSharesByNamespaceResponse{} } -func (m *GetSharesByNamespaceResponse) String() string { return proto.CompactTextString(m) } -func (*GetSharesByNamespaceResponse) ProtoMessage() {} -func (*GetSharesByNamespaceResponse) Descriptor() ([]byte, []int) { +func (m *GetSharesByNamespaceStatusResponse) Reset() { *m = GetSharesByNamespaceStatusResponse{} } +func (m *GetSharesByNamespaceStatusResponse) String() string { return proto.CompactTextString(m) } +func (*GetSharesByNamespaceStatusResponse) ProtoMessage() {} +func (*GetSharesByNamespaceStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor_ed9f13149b0de397, []int{1} } -func (m *GetSharesByNamespaceResponse) XXX_Unmarshal(b []byte) error { +func (m *GetSharesByNamespaceStatusResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *GetSharesByNamespaceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *GetSharesByNamespaceStatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_GetSharesByNamespaceResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_GetSharesByNamespaceStatusResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -135,49 +131,42 @@ func (m *GetSharesByNamespaceResponse) XXX_Marshal(b []byte, deterministic bool) return b[:n], nil } } -func (m *GetSharesByNamespaceResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetSharesByNamespaceResponse.Merge(m, src) +func (m *GetSharesByNamespaceStatusResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSharesByNamespaceStatusResponse.Merge(m, src) } -func (m *GetSharesByNamespaceResponse) XXX_Size() int { +func (m *GetSharesByNamespaceStatusResponse) XXX_Size() int { return m.Size() } -func (m *GetSharesByNamespaceResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetSharesByNamespaceResponse.DiscardUnknown(m) +func (m *GetSharesByNamespaceStatusResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetSharesByNamespaceStatusResponse.DiscardUnknown(m) } -var xxx_messageInfo_GetSharesByNamespaceResponse proto.InternalMessageInfo +var xxx_messageInfo_GetSharesByNamespaceStatusResponse proto.InternalMessageInfo -func (m *GetSharesByNamespaceResponse) GetStatus() StatusCode { +func (m *GetSharesByNamespaceStatusResponse) GetStatus() StatusCode { if m != nil { return m.Status } return StatusCode_INVALID } -func (m *GetSharesByNamespaceResponse) GetRows() []*Row { - if m != nil { - return m.Rows - } - return nil -} - -type Row struct { +type NamespaceRowResponse struct { Shares [][]byte `protobuf:"bytes,1,rep,name=shares,proto3" json:"shares,omitempty"` Proof *pb.Proof `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` } -func (m *Row) Reset() { *m = Row{} } -func (m *Row) String() string { return proto.CompactTextString(m) } -func (*Row) ProtoMessage() {} -func (*Row) Descriptor() ([]byte, []int) { +func (m *NamespaceRowResponse) Reset() { *m = NamespaceRowResponse{} } +func (m *NamespaceRowResponse) String() string { return proto.CompactTextString(m) } +func (*NamespaceRowResponse) ProtoMessage() {} +func (*NamespaceRowResponse) Descriptor() ([]byte, []int) { return fileDescriptor_ed9f13149b0de397, []int{2} } -func (m *Row) XXX_Unmarshal(b []byte) error { +func (m *NamespaceRowResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *Row) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *NamespaceRowResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_Row.Marshal(b, m, deterministic) + return xxx_messageInfo_NamespaceRowResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -187,26 +176,26 @@ func (m *Row) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *Row) XXX_Merge(src proto.Message) { - xxx_messageInfo_Row.Merge(m, src) +func (m *NamespaceRowResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_NamespaceRowResponse.Merge(m, src) } -func (m *Row) XXX_Size() int { +func (m *NamespaceRowResponse) XXX_Size() int { return m.Size() } -func (m *Row) XXX_DiscardUnknown() { - xxx_messageInfo_Row.DiscardUnknown(m) +func (m *NamespaceRowResponse) XXX_DiscardUnknown() { + xxx_messageInfo_NamespaceRowResponse.DiscardUnknown(m) } -var xxx_messageInfo_Row proto.InternalMessageInfo +var xxx_messageInfo_NamespaceRowResponse proto.InternalMessageInfo -func (m *Row) GetShares() [][]byte { +func (m *NamespaceRowResponse) GetShares() [][]byte { if m != nil { return m.Shares } return nil } -func (m *Row) GetProof() *pb.Proof { +func (m *NamespaceRowResponse) GetProof() *pb.Proof { if m != nil { return m.Proof } @@ -216,37 +205,35 @@ func (m *Row) GetProof() *pb.Proof { func init() { proto.RegisterEnum("share.p2p.shrex.nd.StatusCode", StatusCode_name, StatusCode_value) proto.RegisterType((*GetSharesByNamespaceRequest)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceRequest") - proto.RegisterType((*GetSharesByNamespaceResponse)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceResponse") - proto.RegisterType((*Row)(nil), "share.p2p.shrex.nd.Row") + proto.RegisterType((*GetSharesByNamespaceStatusResponse)(nil), "share.p2p.shrex.nd.GetSharesByNamespaceStatusResponse") + proto.RegisterType((*NamespaceRowResponse)(nil), "share.p2p.shrex.nd.NamespaceRowResponse") } func init() { proto.RegisterFile("share/p2p/shrexnd/pb/share.proto", fileDescriptor_ed9f13149b0de397) } var fileDescriptor_ed9f13149b0de397 = []byte{ - // 357 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x4d, 0x6b, 0xea, 0x40, - 0x14, 0x86, 0xf3, 0xe1, 0xcd, 0xd5, 0xa3, 0xd7, 0x1b, 0xe6, 0xc2, 0x35, 0x54, 0x09, 0x22, 0x14, - 0xa4, 0x85, 0x04, 0x52, 0xe8, 0x3e, 0x7e, 0xb4, 0x95, 0xda, 0x51, 0x46, 0xfb, 0xb1, 0x93, 0xa4, - 0x4e, 0xc9, 0xa6, 0x99, 0x69, 0x26, 0x62, 0xbb, 0xee, 0x1f, 0xe8, 0xcf, 0xea, 0xd2, 0x65, 0x97, - 0x45, 0xff, 0x48, 0xc9, 0x98, 0xe2, 0xa2, 0xee, 0xe6, 0xbc, 0xe7, 0x39, 0x67, 0x5e, 0xde, 0x03, - 0x4d, 0x11, 0x05, 0x09, 0x75, 0xb9, 0xc7, 0x5d, 0x11, 0x25, 0xf4, 0x39, 0x9e, 0xbb, 0x3c, 0x74, - 0xa5, 0xe8, 0xf0, 0x84, 0xa5, 0x0c, 0xa1, 0xbc, 0xf0, 0xb8, 0x23, 0x09, 0x27, 0x9e, 0x1f, 0x54, - 0x79, 0xe8, 0xf2, 0x84, 0xb1, 0x87, 0x2d, 0xd3, 0xba, 0x83, 0xfa, 0x39, 0x4d, 0x27, 0x19, 0x28, - 0x3a, 0x2f, 0x38, 0x78, 0xa4, 0x82, 0x07, 0xf7, 0x94, 0xd0, 0xa7, 0x05, 0x15, 0x29, 0xaa, 0x43, - 0x29, 0x61, 0x2c, 0x9d, 0x45, 0x81, 0x88, 0x2c, 0xb5, 0xa9, 0xb6, 0x2b, 0xa4, 0x98, 0x09, 0x17, - 0x81, 0x88, 0x50, 0x03, 0x4a, 0xf1, 0xf7, 0x80, 0xa5, 0xc9, 0xe6, 0x4e, 0x68, 0xbd, 0xaa, 0xd0, - 0xd8, 0xbf, 0x5a, 0x70, 0x16, 0x0b, 0x8a, 0x4e, 0xc1, 0x10, 0x69, 0x90, 0x2e, 0x84, 0x5c, 0x5c, - 0xf5, 0x6c, 0xe7, 0xa7, 0x5f, 0x67, 0x22, 0x89, 0x2e, 0x9b, 0x53, 0x92, 0xd3, 0xe8, 0x18, 0x0a, - 0x09, 0x5b, 0x0a, 0x4b, 0x6b, 0xea, 0xed, 0xb2, 0x57, 0xdb, 0x37, 0x45, 0xd8, 0x92, 0x48, 0xa8, - 0xd5, 0x03, 0x9d, 0xb0, 0x25, 0xfa, 0x0f, 0x86, 0xc4, 0xb2, 0xbf, 0xf4, 0x76, 0x85, 0xe4, 0x15, - 0x3a, 0x84, 0x5f, 0x32, 0x0d, 0x69, 0xbf, 0xec, 0xfd, 0x75, 0xf2, 0x6c, 0x42, 0x67, 0x9c, 0x3d, - 0xc8, 0xb6, 0x7b, 0x74, 0x0b, 0xb0, 0x33, 0x82, 0xca, 0xf0, 0x7b, 0x80, 0x6f, 0xfc, 0xe1, 0xa0, - 0x67, 0x2a, 0xc8, 0x00, 0x6d, 0x74, 0x69, 0xaa, 0xe8, 0x0f, 0x94, 0xf0, 0x68, 0x3a, 0x3b, 0x1b, - 0x5d, 0xe3, 0x9e, 0xa9, 0xa1, 0x0a, 0x14, 0x07, 0x78, 0xda, 0x27, 0xd8, 0x1f, 0x9a, 0x3a, 0xaa, - 0xc1, 0x3f, 0xec, 0x5f, 0xf5, 0x27, 0x63, 0xbf, 0xdb, 0x9f, 0xed, 0xb0, 0x42, 0xc7, 0x7a, 0x5f, - 0xdb, 0xea, 0x6a, 0x6d, 0xab, 0x9f, 0x6b, 0x5b, 0x7d, 0xdb, 0xd8, 0xca, 0x6a, 0x63, 0x2b, 0x1f, - 0x1b, 0x5b, 0x09, 0x0d, 0x79, 0x9f, 0x93, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x8b, 0x14, - 0xc5, 0xe7, 0x01, 0x00, 0x00, + // 326 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x4f, 0x4b, 0xf3, 0x40, + 0x10, 0xc6, 0x93, 0x96, 0x37, 0x6f, 0x3b, 0xad, 0x35, 0x2c, 0x22, 0xc5, 0xca, 0x52, 0x02, 0x42, + 0xf1, 0xb0, 0x81, 0x08, 0x1e, 0x85, 0xd6, 0xfa, 0xa7, 0x58, 0x52, 0xd9, 0xb6, 0xe2, 0x41, 0x28, + 0x1b, 0xbb, 0x92, 0x8b, 0xd9, 0x35, 0xbb, 0x45, 0xfd, 0x16, 0x7e, 0x2c, 0x8f, 0x3d, 0x7a, 0x94, + 0xf6, 0x8b, 0x48, 0xb6, 0xd1, 0x1c, 0xf4, 0xb6, 0xf3, 0xcc, 0x33, 0xbf, 0x7d, 0x66, 0xa0, 0xad, + 0x62, 0x96, 0x72, 0x5f, 0x06, 0xd2, 0x57, 0x71, 0xca, 0x5f, 0x92, 0xb9, 0x2f, 0x23, 0xdf, 0x88, + 0x44, 0xa6, 0x42, 0x0b, 0x84, 0xf2, 0x22, 0x90, 0xc4, 0x38, 0x48, 0x32, 0xdf, 0x6b, 0xc8, 0xc8, + 0x97, 0xa9, 0x10, 0x0f, 0x1b, 0x8f, 0x77, 0x0b, 0xad, 0x0b, 0xae, 0xc7, 0x99, 0x51, 0xf5, 0x5e, + 0x43, 0xf6, 0xc8, 0x95, 0x64, 0xf7, 0x9c, 0xf2, 0xa7, 0x05, 0x57, 0x1a, 0xb5, 0xa0, 0x9a, 0x0a, + 0xa1, 0x67, 0x31, 0x53, 0x71, 0xd3, 0x6e, 0xdb, 0x9d, 0x3a, 0xad, 0x64, 0xc2, 0x25, 0x53, 0x31, + 0xda, 0x87, 0x6a, 0xf2, 0x3d, 0xd0, 0x2c, 0x99, 0x66, 0x21, 0x78, 0x77, 0xe0, 0xfd, 0x45, 0x1e, + 0x6b, 0xa6, 0x17, 0x8a, 0x72, 0x25, 0x45, 0xa2, 0x38, 0x3a, 0x06, 0x47, 0x19, 0xc5, 0xd0, 0x1b, + 0x01, 0x26, 0xbf, 0x43, 0x93, 0xcd, 0xcc, 0xa9, 0x98, 0x73, 0x9a, 0xbb, 0xbd, 0x29, 0xec, 0x14, + 0x61, 0xc5, 0xf3, 0x0f, 0x6f, 0x17, 0x1c, 0x03, 0xc8, 0x78, 0xe5, 0x4e, 0x9d, 0xe6, 0x15, 0x3a, + 0x80, 0x7f, 0x66, 0x6d, 0x93, 0xb3, 0x16, 0x6c, 0x93, 0xfc, 0x08, 0x11, 0xb9, 0xce, 0x1e, 0x74, + 0xd3, 0x3d, 0x3c, 0x01, 0x28, 0x3e, 0x43, 0x35, 0xf8, 0x3f, 0x08, 0x6f, 0xba, 0xc3, 0x41, 0xdf, + 0xb5, 0x90, 0x03, 0xa5, 0xd1, 0x95, 0x6b, 0xa3, 0x2d, 0xa8, 0x86, 0xa3, 0xc9, 0xec, 0x7c, 0x34, + 0x0d, 0xfb, 0x6e, 0x09, 0xd5, 0xa1, 0x32, 0x08, 0x27, 0x67, 0x34, 0xec, 0x0e, 0xdd, 0x72, 0xaf, + 0xf9, 0xbe, 0xc2, 0xf6, 0x72, 0x85, 0xed, 0xcf, 0x15, 0xb6, 0xdf, 0xd6, 0xd8, 0x5a, 0xae, 0xb1, + 0xf5, 0xb1, 0xc6, 0x56, 0xe4, 0x98, 0x7b, 0x1f, 0x7d, 0x05, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x53, + 0xb4, 0x86, 0xb7, 0x01, 0x00, 0x00, } func (m *GetSharesByNamespaceRequest) Marshal() (dAtA []byte, err error) { @@ -286,7 +273,7 @@ func (m *GetSharesByNamespaceRequest) MarshalToSizedBuffer(dAtA []byte) (int, er return len(dAtA) - i, nil } -func (m *GetSharesByNamespaceResponse) Marshal() (dAtA []byte, err error) { +func (m *GetSharesByNamespaceStatusResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -296,30 +283,16 @@ func (m *GetSharesByNamespaceResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *GetSharesByNamespaceResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *GetSharesByNamespaceStatusResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *GetSharesByNamespaceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *GetSharesByNamespaceStatusResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Rows) > 0 { - for iNdEx := len(m.Rows) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Rows[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintShare(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } if m.Status != 0 { i = encodeVarintShare(dAtA, i, uint64(m.Status)) i-- @@ -328,7 +301,7 @@ func (m *GetSharesByNamespaceResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } -func (m *Row) Marshal() (dAtA []byte, err error) { +func (m *NamespaceRowResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -338,12 +311,12 @@ func (m *Row) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Row) MarshalTo(dAtA []byte) (int, error) { +func (m *NamespaceRowResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *Row) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *NamespaceRowResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -400,7 +373,7 @@ func (m *GetSharesByNamespaceRequest) Size() (n int) { return n } -func (m *GetSharesByNamespaceResponse) Size() (n int) { +func (m *GetSharesByNamespaceStatusResponse) Size() (n int) { if m == nil { return 0 } @@ -409,16 +382,10 @@ func (m *GetSharesByNamespaceResponse) Size() (n int) { if m.Status != 0 { n += 1 + sovShare(uint64(m.Status)) } - if len(m.Rows) > 0 { - for _, e := range m.Rows { - l = e.Size() - n += 1 + l + sovShare(uint64(l)) - } - } return n } -func (m *Row) Size() (n int) { +func (m *NamespaceRowResponse) Size() (n int) { if m == nil { return 0 } @@ -561,7 +528,7 @@ func (m *GetSharesByNamespaceRequest) Unmarshal(dAtA []byte) error { } return nil } -func (m *GetSharesByNamespaceResponse) Unmarshal(dAtA []byte) error { +func (m *GetSharesByNamespaceStatusResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -584,10 +551,10 @@ func (m *GetSharesByNamespaceResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: GetSharesByNamespaceResponse: wiretype end group for non-group") + return fmt.Errorf("proto: GetSharesByNamespaceStatusResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: GetSharesByNamespaceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: GetSharesByNamespaceStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -609,40 +576,6 @@ func (m *GetSharesByNamespaceResponse) Unmarshal(dAtA []byte) error { break } } - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Rows", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowShare - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthShare - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthShare - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Rows = append(m.Rows, &Row{}) - if err := m.Rows[len(m.Rows)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipShare(dAtA[iNdEx:]) @@ -664,7 +597,7 @@ func (m *GetSharesByNamespaceResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *Row) Unmarshal(dAtA []byte) error { +func (m *NamespaceRowResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -687,10 +620,10 @@ func (m *Row) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Row: wiretype end group for non-group") + return fmt.Errorf("proto: NamespaceRowResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Row: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: NamespaceRowResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: diff --git a/share/p2p/shrexnd/pb/share.proto b/share/p2p/shrexnd/pb/share.proto index 931b25faa9..a5bdbfa071 100644 --- a/share/p2p/shrexnd/pb/share.proto +++ b/share/p2p/shrexnd/pb/share.proto @@ -8,9 +8,8 @@ message GetSharesByNamespaceRequest{ bytes namespace = 2; } -message GetSharesByNamespaceResponse{ +message GetSharesByNamespaceStatusResponse{ StatusCode status = 1; - repeated Row rows = 2; } enum StatusCode { @@ -18,10 +17,9 @@ enum StatusCode { OK = 1; NOT_FOUND = 2; INTERNAL = 3; - NAMESPACE_NOT_FOUND = 4; }; -message Row { +message NamespaceRowResponse { repeated bytes shares = 1; proof.pb.Proof proof = 2; } diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 42810b1f48..b391a3b2ce 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -2,7 +2,6 @@ package shrexnd import ( "context" - "encoding/hex" "errors" "fmt" "time" @@ -57,11 +56,7 @@ func NewServer(params *Parameters, host host.Host, store *eds.Store, getter shar ctx, cancel := context.WithCancel(context.Background()) srv.cancel = cancel - handler := func(s network.Stream) { - srv.handleNamespacedData(ctx, s) - } - srv.handler = srv.middleware.RateLimitHandler(handler) - + srv.handler = srv.middleware.RateLimitHandler(srv.streamHandler(ctx)) return srv, nil } @@ -78,6 +73,19 @@ func (srv *Server) Stop(context.Context) error { return nil } +func (srv *Server) streamHandler(ctx context.Context) network.StreamHandler { + return func(s network.Stream) { + err := srv.handleNamespacedData(ctx, s) + if err != nil { + s.Reset() //nolint:errcheck + return + } + if err = s.Close(); err != nil { + log.Debugw("server: closing stream", "err", err) + } + } +} + // SetHandler sets server handler func (srv *Server) SetHandler(handler network.StreamHandler) { srv.handler = handler @@ -90,104 +98,124 @@ func (srv *Server) observeRateLimitedRequests() { } } -func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) { - logger := log.With("peer", stream.Conn().RemotePeer().String()) - logger.Debug("server: handling nd request") +func (srv *Server) handleNamespacedData(ctx context.Context, stream network.Stream) error { + logger := log.With("source", "server", "peer", stream.Conn().RemotePeer().String()) + logger.Debug("handling nd request") srv.observeRateLimitedRequests() + req, err := srv.readRequest(logger, stream) + if err != nil { + logger.Warnw("read request", "err", err) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusBadRequest) + return err + } + + logger = logger.With("namespace", share.Namespace(req.Namespace).String(), + "hash", share.DataHash(req.RootHash).String()) + + ctx, cancel := context.WithTimeout(ctx, srv.params.HandleRequestTimeout) + defer cancel() + + shares, status, err := srv.getNamespaceData(ctx, req.RootHash, req.Namespace) + if err != nil { + // server should respond with status regardless if there was an error getting data + sendErr := srv.respondStatus(ctx, logger, stream, status) + if sendErr != nil { + logger.Errorw("sending response", "err", sendErr) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusSendRespErr) + } + logger.Errorw("handling request", "err", err) + return errors.Join(err, sendErr) + } + + err = srv.respondStatus(ctx, logger, stream, status) + if err != nil { + logger.Errorw("sending response", "err", err) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusSendRespErr) + return err + } + + err = srv.sendNamespacedShares(shares, stream) + if err != nil { + logger.Errorw("send nd data", "err", err) + srv.metrics.ObserveRequests(ctx, 1, p2p.StatusSendRespErr) + return err + } + return nil +} +func (srv *Server) readRequest( + logger *zap.SugaredLogger, + stream network.Stream, +) (*pb.GetSharesByNamespaceRequest, error) { err := stream.SetReadDeadline(time.Now().Add(srv.params.ServerReadTimeout)) if err != nil { - logger.Debugw("server: setting read deadline", "err", err) + logger.Debugw("setting read deadline", "err", err) } var req pb.GetSharesByNamespaceRequest _, err = serde.Read(stream, &req) if err != nil { - logger.Warnw("server: reading request", "err", err) - stream.Reset() //nolint:errcheck - return + return nil, fmt.Errorf("reading request: %w", err) + } - logger = logger.With("namespace", hex.EncodeToString(req.Namespace), "hash", share.DataHash(req.RootHash).String()) - logger.Debugw("server: new request") + logger.Debugw("new request") err = stream.CloseRead() if err != nil { - logger.Debugw("server: closing read side of the stream", "err", err) + logger.Debugw("closing read side of the stream", "err", err) } err = validateRequest(req) if err != nil { - logger.Warnw("server: invalid request", "err", err) - stream.Reset() //nolint:errcheck - return + return nil, fmt.Errorf("invalid request: %w", err) } + return &req, nil +} - ctx, cancel := context.WithTimeout(ctx, srv.params.HandleRequestTimeout) - defer cancel() - - dah, err := srv.store.GetDAH(ctx, req.RootHash) +func (srv *Server) getNamespaceData(ctx context.Context, + hash share.DataHash, namespace share.Namespace) (share.NamespacedShares, pb.StatusCode, error) { + dah, err := srv.store.GetDAH(ctx, hash) if err != nil { if errors.Is(err, eds.ErrNotFound) { - logger.Warn("server: DAH not found") - srv.respondNotFoundError(ctx, logger, stream) - return + return nil, pb.StatusCode_NOT_FOUND, nil } - logger.Errorw("server: retrieving DAH", "err", err) - srv.respondInternalError(ctx, logger, stream) - return + return nil, pb.StatusCode_INTERNAL, fmt.Errorf("retrieving DAH: %w", err) } - shares, err := srv.getter.GetSharesByNamespace(ctx, dah, req.Namespace) - switch { - case errors.Is(err, share.ErrNotFound): - logger.Warn("server: nd not found") - srv.respondNotFoundError(ctx, logger, stream) - return - case err != nil: - logger.Errorw("server: retrieving shares", "err", err) - srv.respondInternalError(ctx, logger, stream) - return - } - - resp := namespacedSharesToResponse(shares) - srv.respond(ctx, logger, stream, resp) -} - -// validateRequest checks correctness of the request -func validateRequest(req pb.GetSharesByNamespaceRequest) error { - if err := share.Namespace(req.Namespace).ValidateForData(); err != nil { - return err - } - if len(req.RootHash) != sha256.Size { - return fmt.Errorf("incorrect root hash length: %v", len(req.RootHash)) + shares, err := srv.getter.GetSharesByNamespace(ctx, dah, namespace) + if err != nil { + return nil, pb.StatusCode_INTERNAL, fmt.Errorf("retrieving shares: %w", err) } - return nil + + return shares, pb.StatusCode_OK, nil } -// respondNotFoundError sends a not found response to client -func (srv *Server) respondNotFoundError(ctx context.Context, - logger *zap.SugaredLogger, stream network.Stream) { - resp := &pb.GetSharesByNamespaceResponse{ - Status: pb.StatusCode_NOT_FOUND, +func (srv *Server) respondStatus( + ctx context.Context, + logger *zap.SugaredLogger, + stream network.Stream, + status pb.StatusCode, +) error { + srv.observeStatus(ctx, status) + + err := stream.SetWriteDeadline(time.Now().Add(srv.params.ServerWriteTimeout)) + if err != nil { + logger.Debugw("setting write deadline", "err", err) } - srv.respond(ctx, logger, stream, resp) -} -// respondInternalError sends internal error response to client -func (srv *Server) respondInternalError(ctx context.Context, - logger *zap.SugaredLogger, stream network.Stream) { - resp := &pb.GetSharesByNamespaceResponse{ - Status: pb.StatusCode_INTERNAL, + _, err = serde.Write(stream, &pb.GetSharesByNamespaceStatusResponse{Status: status}) + if err != nil { + return fmt.Errorf("writing response: %w", err) } - srv.respond(ctx, logger, stream, resp) + + return nil } -// namespacedSharesToResponse encodes shares into proto and sends it to client with OK status code -func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNamespaceResponse { - rows := make([]*pb.Row, 0, len(shares)) +// sendNamespacedShares encodes shares into proto messages and sends it to client +func (srv *Server) sendNamespacedShares(shares share.NamespacedShares, stream network.Stream) error { for _, row := range shares { - row := &pb.Row{ + row := &pb.NamespaceRowResponse{ Shares: row.Shares, Proof: &nmt_pb.Proof{ Start: int64(row.Proof.Start()), @@ -197,44 +225,32 @@ func namespacedSharesToResponse(shares share.NamespacedShares) *pb.GetSharesByNa IsMaxNamespaceIgnored: row.Proof.IsMaxNamespaceIDIgnored(), }, } - - rows = append(rows, row) - } - - status := pb.StatusCode_OK - if len(shares) == 0 || (len(shares) == 1 && len(shares[0].Shares) == 0) { - status = pb.StatusCode_NAMESPACE_NOT_FOUND - } - - return &pb.GetSharesByNamespaceResponse{ - Status: status, - Rows: rows, + _, err := serde.Write(stream, row) + if err != nil { + return fmt.Errorf("writing nd data to stream: %w", err) + } } + return nil } -func (srv *Server) respond(ctx context.Context, - logger *zap.SugaredLogger, stream network.Stream, resp *pb.GetSharesByNamespaceResponse) { - err := stream.SetWriteDeadline(time.Now().Add(srv.params.ServerWriteTimeout)) - if err != nil { - logger.Debugw("server: setting write deadline", "err", err) - } - - _, err = serde.Write(stream, resp) - if err != nil { - logger.Warnw("server: writing response", "err", err) - stream.Reset() //nolint:errcheck - return - } - +func (srv *Server) observeStatus(ctx context.Context, status pb.StatusCode) { switch { - case resp.Status == pb.StatusCode_OK: + case status == pb.StatusCode_OK: srv.metrics.ObserveRequests(ctx, 1, p2p.StatusSuccess) - case resp.Status == pb.StatusCode_NOT_FOUND: + case status == pb.StatusCode_NOT_FOUND: srv.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) - case resp.Status == pb.StatusCode_INTERNAL: + case status == pb.StatusCode_INTERNAL: srv.metrics.ObserveRequests(ctx, 1, p2p.StatusInternalErr) } - if err = stream.Close(); err != nil { - logger.Debugw("server: closing stream", "err", err) +} + +// validateRequest checks correctness of the request +func validateRequest(req pb.GetSharesByNamespaceRequest) error { + if err := share.Namespace(req.Namespace).ValidateForData(); err != nil { + return err } + if len(req.RootHash) != sha256.Size { + return fmt.Errorf("incorrect root hash length: %v", len(req.RootHash)) + } + return nil } diff --git a/share/sharetest/testing.go b/share/sharetest/testing.go index 50eb0eb003..3889260393 100644 --- a/share/sharetest/testing.go +++ b/share/sharetest/testing.go @@ -37,6 +37,26 @@ func RandShares(t require.TestingT, total int) []share.Share { return shares } +// RandSharesWithNamespace is same the as RandShares, but sets same namespace for all shares. +func RandSharesWithNamespace(t require.TestingT, namespace share.Namespace, total int) []share.Share { + if total&(total-1) != 0 { + t.Errorf("total must be power of 2: %d", total) + t.FailNow() + } + + shares := make([]share.Share, total) + rnd := rand.New(rand.NewSource(time.Now().Unix())) //nolint:gosec + for i := range shares { + shr := make([]byte, share.Size) + copy(share.GetNamespace(shr), namespace) + _, err := rnd.Read(share.GetData(shr)) + require.NoError(t, err) + shares[i] = shr + } + sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) + return shares +} + // RandV0Namespace generates random valid data namespace for testing purposes. func RandV0Namespace() share.Namespace { rb := make([]byte, namespace.NamespaceVersionZeroIDSize) From f00f8dad3e002484a0e96a860f69074deaff70c0 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 7 Aug 2023 14:15:53 +0200 Subject: [PATCH 0732/1008] chore: bump libp2p (#2533) --- go.mod | 28 ++++++++--------- go.sum | 58 ++++++++++++++++++------------------ nodebuilder/p2p/metrics.go | 3 -- nodebuilder/p2p/resources.go | 3 +- 4 files changed, 44 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index c89d7d77d3..0a02fe7c15 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.10.0 github.com/ipld/go-car v0.6.0 - github.com/libp2p/go-libp2p v0.28.1 + github.com/libp2p/go-libp2p v0.29.0 github.com/libp2p/go-libp2p-kad-dht v0.21.1 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 @@ -52,9 +52,9 @@ require ( github.com/minio/sha256-simd v1.0.1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.9.0 + github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multiaddr-dns v0.3.1 - github.com/multiformats/go-multihash v0.2.2 + github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/prometheus/client_golang v1.14.0 github.com/pyroscope-io/client v0.7.1 @@ -72,11 +72,11 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.39.0 go.opentelemetry.io/otel/trace v1.16.0 go.opentelemetry.io/proto/otlp v0.19.0 - go.uber.org/fx v1.19.3 + go.uber.org/fx v1.20.0 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.11.0 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/sync v0.2.0 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/sync v0.3.0 golang.org/x/text v0.11.0 google.golang.org/grpc v1.56.2 google.golang.org/protobuf v1.31.0 @@ -167,7 +167,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect + github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect @@ -218,7 +218,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/reedsolomon v1.11.1 // indirect github.com/koron/go-ssdp v0.0.4 // indirect @@ -232,7 +232,7 @@ require ( github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-reuseport v0.3.0 // indirect - github.com/libp2p/go-yamux/v4 v4.0.0 // indirect + github.com/libp2p/go-yamux/v4 v4.0.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/manifoldco/promptui v0.9.0 // indirect @@ -240,7 +240,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.54 // indirect + github.com/miekg/dns v1.1.55 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -255,7 +255,7 @@ require ( github.com/multiformats/go-multicodec v0.9.0 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.9.7 // indirect + github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -273,7 +273,7 @@ require ( github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect - github.com/quic-go/quic-go v0.33.0 // indirect + github.com/quic-go/quic-go v0.36.2 // indirect github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect @@ -310,12 +310,12 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.10.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/term v0.10.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index f8cedecfd1..813ec946d7 100644 --- a/go.sum +++ b/go.sum @@ -799,8 +799,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= -github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= +github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1148,8 +1148,8 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -1214,8 +1214,8 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= -github.com/libp2p/go-libp2p v0.28.1 h1:YurK+ZAI6cKfASLJBVFkpVBdl3wGhFi6fusOt725ii8= -github.com/libp2p/go-libp2p v0.28.1/go.mod h1:s3Xabc9LSwOcnv9UD4nORnXKTsWkPMkIMB/JIGXVnzk= +github.com/libp2p/go-libp2p v0.29.0 h1:QduJ2XQr/Crg4EnloueWDL0Jj86N3Ezhyyj7XH+XwHI= +github.com/libp2p/go-libp2p v0.29.0/go.mod h1:iNKL7mEnZ9wAss+03IjAwM9ZAQXfVUAPUUmOACQfQ/g= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= @@ -1422,8 +1422,8 @@ github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZj github.com/libp2p/go-yamux/v3 v3.0.1/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= github.com/libp2p/go-yamux/v3 v3.0.2/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= github.com/libp2p/go-yamux/v3 v3.1.1/go.mod h1:jeLEQgLXqE2YqX1ilAClIfCMDY+0uXQUKmmb/qp0gT4= -github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= -github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= +github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= +github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= @@ -1497,8 +1497,8 @@ github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= -github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1574,8 +1574,8 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= -github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU= +github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -1609,8 +1609,8 @@ github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= -github.com/multiformats/go-multihash v0.2.2 h1:Uu7LWs/PmWby1gkj1S1DXx3zyd3aVabA4FiMKn/2tAc= -github.com/multiformats/go-multihash v0.2.2/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= @@ -1662,8 +1662,8 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= -github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1671,7 +1671,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -1795,8 +1795,8 @@ github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc8 github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= +github.com/quic-go/quic-go v0.36.2 h1:ZX/UNQ4gvpCv2RmwdbA6lrRjF6EBm5yZ7TMoT4NQVrA= +github.com/quic-go/quic-go v0.36.2/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ= github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= @@ -2093,8 +2093,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= -go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= -go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= @@ -2178,8 +2178,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2211,8 +2211,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2339,8 +2339,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2569,8 +2569,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/nodebuilder/p2p/metrics.go b/nodebuilder/p2p/metrics.go index 3e6caf08ca..095c30d9b7 100644 --- a/nodebuilder/p2p/metrics.go +++ b/nodebuilder/p2p/metrics.go @@ -6,7 +6,6 @@ import ( "net/http" "time" - rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/fx" @@ -28,8 +27,6 @@ const ( // prometheusMetrics option sets up native libp2p metrics up func prometheusMetrics(lifecycle fx.Lifecycle, registerer prometheus.Registerer) error { - rcmgrObs.MustRegisterWith(registerer) - registry := registerer.(*prometheus.Registry) mux := http.NewServeMux() diff --git a/nodebuilder/p2p/resources.go b/nodebuilder/p2p/resources.go index 371747463a..6e24e1e542 100644 --- a/nodebuilder/p2p/resources.go +++ b/nodebuilder/p2p/resources.go @@ -6,7 +6,6 @@ import ( "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/network" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" - rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs" ma "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" "go.uber.org/fx" @@ -60,7 +59,7 @@ func allowList(ctx context.Context, cfg Config, bootstrappers Bootstrappers) (rc } func traceReporter() rcmgr.Option { - str, err := rcmgrObs.NewStatsTraceReporter() + str, err := rcmgr.NewStatsTraceReporter() if err != nil { panic(err) // err is always nil as per sources } From a86ddca1773f86e3427fa48669bb219b99c0d765 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 7 Aug 2023 16:15:35 +0300 Subject: [PATCH 0733/1008] feat(cmd): use rpc client instead of `http.Request` (#2521) --- cmd/celestia/blob.go | 229 +++++++++++++++++++++++++++++++++++++++++++ cmd/celestia/rpc.go | 155 ++++++++--------------------- 2 files changed, 270 insertions(+), 114 deletions(-) create mode 100644 cmd/celestia/blob.go diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go new file mode 100644 index 0000000000..9dcd72dcb7 --- /dev/null +++ b/cmd/celestia/blob.go @@ -0,0 +1,229 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "reflect" + "strconv" + + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/share" +) + +var plaintext bool + +func init() { + blobCmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd) + + getCmd.PersistentFlags().BoolVar( + &plaintext, + "plaintext", + false, + "printed blob's data as a plain text", + ) + getAllCmd.PersistentFlags().BoolVar( + &plaintext, + "plaintext", + false, + "printed blob's data as a plain text", + ) +} + +var blobCmd = &cobra.Command{ + Use: "blob [command]", + Short: "Allows to interact with the Blob Service via JSON-RPC", + Args: cobra.NoArgs, +} + +var getCmd = &cobra.Command{ + Use: "get [height, namespace, commitment]", + Args: cobra.ExactArgs(3), + Short: "Returns the blob for the given namespace by commitment at a particular height.", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a height:%v", err) + } + + namespace, err := parseV0Namespace(args[1]) + if err != nil { + return fmt.Errorf("error parsing a namespace:%v", err) + } + + commitment, err := base64.StdEncoding.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("error parsing a commitment:%v", err) + } + + blob, err := client.Blob.Get(cmd.Context(), height, namespace, commitment) + + printOutput(blob, err) + return nil + }, +} + +var getAllCmd = &cobra.Command{ + Use: "get-all [height, namespace]", + Args: cobra.ExactArgs(2), + Short: "Returns all blobs for the given namespace at a particular height.", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a height:%v", err) + } + + namespace, err := parseV0Namespace(args[1]) + if err != nil { + return fmt.Errorf("error parsing a namespace:%v", err) + } + + blobs, err := client.Blob.GetAll(cmd.Context(), height, []share.Namespace{namespace}) + + printOutput(blobs, err) + return nil + }, +} + +var submitCmd = &cobra.Command{ + Use: "submit [namespace, blobData]", + Args: cobra.ExactArgs(2), + Short: "Submit the blob at the given namespace. Note: only one blob is allowed to submit through the RPC.", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + namespace, err := parseV0Namespace(args[0]) + if err != nil { + return fmt.Errorf("error parsing a namespace:%v", err) + } + + parsedBlob, err := blob.NewBlobV0(namespace, []byte(args[1])) + if err != nil { + return fmt.Errorf("error creating a blob:%v", err) + } + + height, err := client.Blob.Submit(cmd.Context(), []*blob.Blob{parsedBlob}) + + response := struct { + Height uint64 `json:"uint64"` + Commitment blob.Commitment `json:"commitment"` + }{ + Height: height, + Commitment: parsedBlob.Commitment, + } + + printOutput(response, err) + return nil + }, +} + +var getProofCmd = &cobra.Command{ + Use: "get-proof [height, namespace, commitment]", + Args: cobra.ExactArgs(3), + Short: "Retrieves the blob in the given namespaces at the given height by commitment and returns its Proof.", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a height:%v", err) + } + + namespace, err := parseV0Namespace(args[1]) + if err != nil { + return fmt.Errorf("error parsing a namespace:%v", err) + } + + commitment, err := base64.StdEncoding.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("error parsing a commitment:%v", err) + } + + proof, err := client.Blob.GetProof(cmd.Context(), height, namespace, commitment) + + printOutput(proof, err) + return nil + }, +} + +func printOutput(data interface{}, err error) { + if err != nil { + data = err + } + + if plaintext && err == nil { + data = formatData(data) + } + + resp := struct { + Result interface{} `json:"result"` + }{ + Result: data, + } + + bytes, err := json.MarshalIndent(resp, "", " ") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Fprintln(os.Stdout, string(bytes)) +} + +func formatData(data interface{}) interface{} { + type tempBlob struct { + Namespace []byte `json:"namespace"` + Data string `json:"data"` + ShareVersion uint32 `json:"share_version"` + Commitment []byte `json:"commitment"` + } + + if reflect.TypeOf(data).Kind() == reflect.Slice { + blobs, ok := data.([]*blob.Blob) + if !ok { + fmt.Fprintln(os.Stderr, "could not cast to []blob.Blob") + os.Exit(1) + } + + result := make([]tempBlob, len(blobs)) + for i, b := range blobs { + result[i] = tempBlob{ + Namespace: b.Namespace(), + Data: string(b.Data), + ShareVersion: b.ShareVersion, + Commitment: b.Commitment, + } + } + return result + } + + b, ok := data.(*blob.Blob) + if !ok { + fmt.Fprintln(os.Stderr, "could not cast to blob.Blob") + os.Exit(1) + } + return tempBlob{ + Namespace: b.Namespace(), + Data: string(b.Data), + ShareVersion: b.ShareVersion, + Commitment: b.Commitment, + } +} diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 64b21aaf86..05439ad7b4 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -2,9 +2,11 @@ package main import ( "bytes" + "context" "encoding/base64" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "log" @@ -17,14 +19,11 @@ import ( "github.com/spf13/cobra" "github.com/celestiaorg/celestia-node/api/rpc/client" - "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/state" ) -const ( - authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" -) +const authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" var requestURL string var authTokenFlag string @@ -61,6 +60,7 @@ func init() { false, "Print JSON-RPC request along with the response", ) + rpcCmd.AddCommand(blobCmd) rootCmd.AddCommand(rpcCmd) } @@ -68,6 +68,25 @@ var rpcCmd = &cobra.Command{ Use: "rpc [namespace] [method] [params...]", Short: "Send JSON-RPC request", Args: cobra.MinimumNArgs(2), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + rpcClient, err := newRPCClient(cmd.Context()) + if err != nil { + return err + } + + ctx := context.WithValue(cmd.Context(), rpcClientKey{}, rpcClient) + cmd.SetContext(ctx) + return nil + }, + PersistentPostRunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + client.Close() + return nil + }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { modules := client.Modules if len(args) == 0 { @@ -115,116 +134,6 @@ func parseParams(method string, params []string) []interface{} { panic(fmt.Sprintf("Error parsing namespace: %v", err)) } parsedParams[1] = namespace - case "Submit": - // 1. Namespace - var err error - namespace, err := parseV0Namespace(params[0]) - if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) - } - // 2. Blob data - var blobData []byte - switch { - case strings.HasPrefix(params[1], "0x"): - decoded, err := hex.DecodeString(params[1][2:]) - if err != nil { - panic("Error decoding blob: hex string could not be decoded.") - } - blobData = decoded - case strings.HasPrefix(params[1], "\""): - // user input an utf string that needs to be encoded to base64 - src := []byte(params[1]) - blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src))) - base64.StdEncoding.Encode(blobData, []byte(params[1])) - default: - // otherwise, we assume the user has already encoded their input to base64 - blobData, err = base64.StdEncoding.DecodeString(params[1]) - if err != nil { - panic("Error decoding blob data: base64 string could not be decoded.") - } - } - parsedBlob, err := blob.NewBlobV0(namespace, blobData) - if err != nil { - panic(fmt.Sprintf("Error creating blob: %v", err)) - } - parsedParams[0] = []*blob.Blob{parsedBlob} - // param count doesn't match input length, so cut off nil values - return parsedParams[:1] - case "SubmitPayForBlob": - // 1. Fee (state.Int is a string) - parsedParams[0] = params[0] - // 2. GasLimit (uint64) - num, err := strconv.ParseUint(params[1], 10, 64) - if err != nil { - panic("Error parsing gas limit: uint64 could not be parsed.") - } - parsedParams[1] = num - // 3. Namespace - namespace, err := parseV0Namespace(params[2]) - if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) - } - // 4. Blob data - var blobData []byte - switch { - case strings.HasPrefix(params[3], "0x"): - decoded, err := hex.DecodeString(params[3][2:]) - if err != nil { - panic("Error decoding blob: hex string could not be decoded.") - } - blobData = decoded - case strings.HasPrefix(params[3], "\""): - // user input an utf string that needs to be encoded to base64 - src := []byte(params[1]) - blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src))) - base64.StdEncoding.Encode(blobData, []byte(params[3])) - default: - // otherwise, we assume the user has already encoded their input to base64 - blobData, err = base64.StdEncoding.DecodeString(params[3]) - if err != nil { - panic("Error decoding blob: base64 string could not be decoded.") - } - } - parsedBlob, err := blob.NewBlobV0(namespace, blobData) - if err != nil { - panic(fmt.Sprintf("Error creating blob: %v", err)) - } - parsedParams[2] = []*blob.Blob{parsedBlob} - return parsedParams[:3] - case "Get": - // 1. Height - num, err := strconv.ParseUint(params[0], 10, 64) - if err != nil { - panic("Error parsing height: uint64 could not be parsed.") - } - parsedParams[0] = num - // 2. NamespaceID - namespace, err := parseV0Namespace(params[1]) - if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) - } - parsedParams[1] = namespace - // 3: Commitment - commitment, err := base64.StdEncoding.DecodeString(params[2]) - if err != nil { - panic("Error decoding commitment: base64 string could not be decoded.") - } - parsedParams[2] = commitment - return parsedParams - case "GetAll": // NOTE: Over the cli, you can only pass one namespace - // 1. Height - num, err := strconv.ParseUint(params[0], 10, 64) - if err != nil { - panic("Error parsing height: uint64 could not be parsed.") - } - parsedParams[0] = num - // 2. Namespace - namespace, err := parseV0Namespace(params[1]) - if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) - } - parsedParams[1] = []share.Namespace{namespace} - return parsedParams case "QueryDelegation", "QueryUnbonding", "BalanceForAddress": var err error parsedParams[0], err = parseAddressFromString(params[0]) @@ -445,8 +354,26 @@ func decodeToBytes(param string) ([]byte, error) { } return decoded, nil } + func parseJSON(param string) (json.RawMessage, error) { var raw json.RawMessage err := json.Unmarshal([]byte(param), &raw) return raw, err } + +func newRPCClient(ctx context.Context) (*client.Client, error) { + if authTokenFlag == "" { + authTokenFlag = os.Getenv(authEnvKey) + } + return client.NewClient(ctx, requestURL, authTokenFlag) +} + +type rpcClientKey struct{} + +func rpcClient(ctx context.Context) (*client.Client, error) { + client, ok := ctx.Value(rpcClientKey{}).(*client.Client) + if !ok { + return nil, errors.New("rpc client was not set") + } + return client, nil +} From 65462525ea6afb819d8189c6d21cec0146ae55c5 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 7 Aug 2023 21:25:12 +0800 Subject: [PATCH 0734/1008] feat(eds/store): cache proofs after first eds recompute on sampling (#2429) ## Overview Before this change eds was recomputed twice on sampling: 1. inside sampling client to verify data integrity 2. On storing EDS to storage to calculate merkle tree proof nodes. This PR adds `proofsAdder` cache, that will store proof nodes for later use with key features: - cache could be optionally added to context and will be respected by ipld/shrex getter to populate on eds recompute. - cache is safe from second recompute, since it will only allow single recompute write and all consequent recomputes over same cache will be noop. Also this PR reworks writeEDS to use use `proofsAdder` instead of creating new blockstore and iterating over all keys in it. This approach speeds up writeEDS even without pre collected cache. Since there are no more need for blockstore being created, I have simplified writeEDS and removed a lot of redundant code including whole struct of `writeSession`. The PR also adds benchmark that uses disk for badger store (existing benchmark uses in-memory datastore instead of badger). This PR brings significant performance increase. On disk store badger results are: - before PR store.Put operation: **~1.5s** - after PR store.Put with cached proofs: **800ms** - after PR store.Put without cached proofs: **900ms** --- core/eds.go | 29 ++++++- core/exchange.go | 12 ++- core/listener.go | 6 +- nodebuilder/store_test.go | 83 +++++++++++++++++++ share/eds/eds.go | 170 ++++++++++++++------------------------ share/eds/eds_test.go | 29 ------- share/eds/retriever.go | 10 ++- share/getters/tee.go | 2 + share/ipld/nmt_adder.go | 101 +++++++++++++++++----- 9 files changed, 280 insertions(+), 162 deletions(-) diff --git a/core/eds.go b/core/eds.go index c87d947e35..eb93c249ba 100644 --- a/core/eds.go +++ b/core/eds.go @@ -3,11 +3,17 @@ package core import ( "context" "errors" + "fmt" "github.com/filecoin-project/dagstore" "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/shares" + "github.com/celestiaorg/celestia-app/pkg/square" + "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -17,12 +23,31 @@ import ( // extendBlock extends the given block data, returning the resulting // ExtendedDataSquare (EDS). If there are no transactions in the block, // nil is returned in place of the eds. -func extendBlock(data types.Data, appVersion uint64) (*rsmt2d.ExtendedDataSquare, error) { +func extendBlock(data types.Data, appVersion uint64, options ...nmt.Option) (*rsmt2d.ExtendedDataSquare, error) { if app.IsEmptyBlock(data, appVersion) { return nil, nil } - return app.ExtendBlock(data, appVersion) + // Construct the data square from the block's transactions + dataSquare, err := square.Construct(data.Txs.ToSliceOfBytes(), appVersion, appconsts.SquareSizeUpperBound(appVersion)) + if err != nil { + return nil, err + } + return extendShares(shares.ToBytes(dataSquare), options...) +} + +func extendShares(s [][]byte, options ...nmt.Option) (*rsmt2d.ExtendedDataSquare, error) { + // Check that the length of the square is a power of 2. + if !shares.IsPowerOfTwo(len(s)) { + return nil, fmt.Errorf("number of shares is not a power of 2: got %d", len(s)) + } + // here we construct a tree + // Note: uses the nmt wrapper to construct the tree. + squareSize := square.Size(len(s)) + return rsmt2d.ComputeExtendedDataSquare(s, + appconsts.DefaultCodec(), + wrapper.NewConstructor(uint64(squareSize), + options...)) } // storeEDS will only store extended block if it is not empty and doesn't already exist. diff --git a/core/exchange.go b/core/exchange.go index a3e98c0307..e041ff980f 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -9,9 +9,11 @@ import ( "golang.org/x/sync/errgroup" libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" ) const concurrencyLimit = 4 @@ -105,7 +107,8 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende } // extend block data - eds, err := extendBlock(block.Data, block.Header.Version.App) + adder := ipld.NewProofsAdder(int(block.Data.SquareSize)) + eds, err := extendBlock(block.Data, block.Header.Version.App, nmt.NodeVisitor(adder.VisitFn())) if err != nil { return nil, fmt.Errorf("extending block data for height %d: %w", &block.Height, err) } @@ -119,6 +122,8 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende return nil, fmt.Errorf("incorrect hash in header at height %d: expected %x, got %x", &block.Height, hash, eh.Hash()) } + + ctx = ipld.CtxWithProofsAdder(ctx, adder) err = storeEDS(ctx, eh.DAH.Hash(), eds, ce.store) if err != nil { return nil, fmt.Errorf("storing EDS to eds.Store for height %d: %w", &block.Height, err) @@ -142,7 +147,8 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 log.Debugw("fetched signed block from core", "height", b.Header.Height) // extend block data - eds, err := extendBlock(b.Data, b.Header.Version.App) + adder := ipld.NewProofsAdder(int(b.Data.SquareSize)) + eds, err := extendBlock(b.Data, b.Header.Version.App, nmt.NodeVisitor(adder.VisitFn())) if err != nil { return nil, fmt.Errorf("extending block data for height %d: %w", b.Header.Height, err) } @@ -151,6 +157,8 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 if err != nil { return nil, fmt.Errorf("constructing extended header for height %d: %w", b.Header.Height, err) } + + ctx = ipld.CtxWithProofsAdder(ctx, adder) err = storeEDS(ctx, eh.DAH.Hash(), eds, ce.store) if err != nil { return nil, fmt.Errorf("storing EDS to eds.Store for block height %d: %w", b.Header.Height, err) diff --git a/core/listener.go b/core/listener.go index 7ca408e4fb..334aa9293a 100644 --- a/core/listener.go +++ b/core/listener.go @@ -12,9 +12,11 @@ import ( "go.opentelemetry.io/otel/attribute" libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -150,7 +152,8 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS attribute.Int64("height", b.Header.Height), ) // extend block data - eds, err := extendBlock(b.Data, b.Header.Version.App) + adder := ipld.NewProofsAdder(int(b.Data.SquareSize)) + eds, err := extendBlock(b.Data, b.Header.Version.App, nmt.NodeVisitor(adder.VisitFn())) if err != nil { return fmt.Errorf("extending block data: %w", err) } @@ -161,6 +164,7 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS } // attempt to store block data if not empty + ctx = ipld.CtxWithProofsAdder(ctx, adder) err = storeEDS(ctx, b.Header.DataHash.Bytes(), eds, cl.store) if err != nil { return fmt.Errorf("storing EDS: %w", err) diff --git a/nodebuilder/store_test.go b/nodebuilder/store_test.go index 512d45bb70..5f2d1bed83 100644 --- a/nodebuilder/store_test.go +++ b/nodebuilder/store_test.go @@ -1,13 +1,26 @@ package nodebuilder import ( + "context" "strconv" "testing" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" + "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestRepo(t *testing.T) { @@ -50,3 +63,73 @@ func TestRepo(t *testing.T) { }) } } + +func BenchmarkStore(b *testing.B) { + ctx, cancel := context.WithCancel(context.Background()) + b.Cleanup(cancel) + + tmpDir := b.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := eds.NewStore(tmpDir, ds) + require.NoError(b, err) + err = edsStore.Start(ctx) + require.NoError(b, err) + + // BenchmarkStore/bench_read_128-10 14 78970661 ns/op (~70ms) + b.Run("bench put 128", func(b *testing.B) { + b.ResetTimer() + dir := b.TempDir() + + err := Init(*DefaultConfig(node.Full), dir, node.Full) + require.NoError(b, err) + + store, err := OpenStore(dir, nil) + require.NoError(b, err) + ds, err := store.Datastore() + require.NoError(b, err) + edsStore, err := eds.NewStore(dir, ds) + require.NoError(b, err) + err = edsStore.Start(ctx) + require.NoError(b, err) + + size := 128 + b.Run("enabled eds proof caching", func(b *testing.B) { + b.StopTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { + adder := ipld.NewProofsAdder(size * 2) + shares := sharetest.RandShares(b, size*size) + eds, err := rsmt2d.ComputeExtendedDataSquare( + shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(size), + nmt.NodeVisitor(adder.VisitFn())), + ) + require.NoError(b, err) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) + ctx := ipld.CtxWithProofsAdder(ctx, adder) + + b.StartTimer() + err = edsStore.Put(ctx, dah.Hash(), eds) + b.StopTimer() + require.NoError(b, err) + } + }) + + b.Run("disabled eds proof caching", func(b *testing.B) { + b.ResetTimer() + b.StopTimer() + for i := 0; i < b.N; i++ { + eds := edstest.RandEDS(b, size) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) + + b.StartTimer() + err = edsStore.Put(ctx, dah.Hash(), eds) + b.StopTimer() + require.NoError(b, err) + } + }) + }) +} diff --git a/share/eds/eds.go b/share/eds/eds.go index cc775491c9..f1efd3b433 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -8,12 +8,7 @@ import ( "io" "math" - "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - ds "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - bstore "github.com/ipfs/go-ipfs-blockstore" - format "github.com/ipfs/go-ipld-format" "github.com/ipld/go-car" "github.com/ipld/go-car/util" "github.com/minio/sha256-simd" @@ -30,15 +25,6 @@ import ( var ErrEmptySquare = errors.New("share: importing empty data") -// writingSession contains the components needed to write an EDS to a CARv1 file with our custom -// node order. -type writingSession struct { - eds *rsmt2d.ExtendedDataSquare - store bstore.Blockstore // caches inner nodes (proofs) while we walk the nmt tree. - hasher *nmt.NmtHasher - w io.Writer -} - // WriteEDS writes the entire EDS into the given io.Writer as CARv1 file. // This includes all shares in quadrant order, followed by all inner nodes of the NMT tree. // Order: [ Carv1Header | Q1 | Q2 | Q3 | Q4 | inner nodes ] @@ -49,80 +35,28 @@ func WriteEDS(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) utils.SetStatusAndEnd(span, err) }() - // 1. Reimport EDS. This is needed to traverse the NMT tree and cache the inner nodes (proofs) - writer, err := initializeWriter(ctx, eds, w) - if err != nil { - return fmt.Errorf("share: creating eds writer: %w", err) - } - - // 2. Creates and writes Carv1Header - // - Roots are the eds Row + Col roots - err = writer.writeHeader() + // Creates and writes Carv1Header. Roots are the eds Row + Col roots + err = writeHeader(eds, w) if err != nil { return fmt.Errorf("share: writing carv1 header: %w", err) } - - // 3. Iterates over shares in quadrant order via eds.GetCell - err = writer.writeQuadrants() + // Iterates over shares in quadrant order via eds.GetCell + err = writeQuadrants(eds, w) if err != nil { return fmt.Errorf("share: writing shares: %w", err) } - // 4. Iterates over in-memory blockstore and writes proofs to the CAR - err = writer.writeProofs(ctx) + // Iterates over proofs and writes them to the CAR + err = writeProofs(ctx, eds, w) if err != nil { return fmt.Errorf("share: writing proofs: %w", err) } return nil } -// initializeWriter reimports the EDS into an in-memory blockstore in order to cache the proofs. -func initializeWriter(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) (*writingSession, error) { - // we use an in-memory blockstore and an offline exchange - store := bstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) - bs := blockservice.New(store, nil) - // shares are extracted from the eds so that we can reimport them to traverse - shares := share.ExtractEDS(eds) - shareCount := len(shares) - if shareCount == 0 { - return nil, ErrEmptySquare - } - odsWidth := int(math.Sqrt(float64(shareCount)) / 2) - // (shareCount*2) - (odsWidth*4) is the amount of inner nodes visited - batchAdder := ipld.NewNmtNodeAdder(ctx, bs, format.MaxSizeBatchOption(innerNodeBatchSize(shareCount, odsWidth))) - // this adder ignores leaves, so that they are not added to the store we iterate through in - // writeProofs - eds, err := rsmt2d.ImportExtendedDataSquare( - shares, - share.DefaultRSMT2DCodec(), - wrapper.NewConstructor(uint64(odsWidth), - nmt.NodeVisitor(batchAdder.VisitInnerNodes)), - ) - if err != nil { - return nil, fmt.Errorf("recomputing data square: %w", err) - } - // compute roots - _, err = eds.RowRoots() - if err != nil { - return nil, fmt.Errorf("computing row roots: %w", err) - } - // commit the batch to DAG - err = batchAdder.Commit() - if err != nil { - return nil, fmt.Errorf("committing inner nodes to the dag: %w", err) - } - - return &writingSession{ - eds: eds, - store: store, - hasher: nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, ipld.NMTIgnoreMaxNamespace), - w: w, - }, nil -} - // writeHeader creates a CarV1 header using the EDS's Row and Column roots as the list of DAG roots. -func (w *writingSession) writeHeader() error { - rootCids, err := rootsToCids(w.eds) +func writeHeader(eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { + rootCids, err := rootsToCids(eds) if err != nil { return fmt.Errorf("getting root cids: %w", err) } @@ -130,14 +64,15 @@ func (w *writingSession) writeHeader() error { return car.WriteHeader(&car.CarHeader{ Roots: rootCids, Version: 1, - }, w.w) + }, w) } // writeQuadrants reorders the shares to quadrant order and writes them to the CARv1 file. -func (w *writingSession) writeQuadrants() error { - shares := quadrantOrder(w.eds) +func writeQuadrants(eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { + hasher := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, ipld.NMTIgnoreMaxNamespace) + shares := quadrantOrder(eds) for _, share := range shares { - leaf, err := w.hasher.HashLeaf(share) + leaf, err := hasher.HashLeaf(share) if err != nil { return fmt.Errorf("hashing share: %w", err) } @@ -145,7 +80,7 @@ func (w *writingSession) writeQuadrants() error { if err != nil { return fmt.Errorf("getting cid from share: %w", err) } - err = util.LdWrite(w.w, cid.Bytes(), share) + err = util.LdWrite(w, cid.Bytes(), share) if err != nil { return fmt.Errorf("writing share to file: %w", err) } @@ -155,30 +90,15 @@ func (w *writingSession) writeQuadrants() error { // writeProofs iterates over the in-memory blockstore's keys and writes all inner nodes to the // CARv1 file. -func (w *writingSession) writeProofs(ctx context.Context) error { - // we only stored proofs to the store, so we can just iterate over them here without getting any - // leaves - proofs, err := w.store.AllKeysChan(ctx) +func writeProofs(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { + // check if proofs are collected by ipld.ProofsAdder in previous reconstructions of eds + proofs, err := getProofs(ctx, eds) if err != nil { - return fmt.Errorf("getting all keys from the blockstore: %w", err) + return fmt.Errorf("recomputing proofs: %w", err) } - for proofCid := range proofs { - block, err := w.store.Get(ctx, proofCid) - if err != nil { - return fmt.Errorf("getting proof from the blockstore: %w", err) - } - node := block.RawData() - left, right := node[:ipld.NmtHashSize], node[ipld.NmtHashSize:] - hash, err := w.hasher.HashNode(left, right) - if err != nil { - return fmt.Errorf("hashing node: %w", err) - } - cid, err := ipld.CidFromNamespacedSha256(hash) - if err != nil { - return fmt.Errorf("getting cid: %w", err) - } - err = util.LdWrite(w.w, cid.Bytes(), node) + for id, proof := range proofs { + err := util.LdWrite(w, id.Bytes(), proof) if err != nil { return fmt.Errorf("writing proof to the car: %w", err) } @@ -186,6 +106,41 @@ func (w *writingSession) writeProofs(ctx context.Context) error { return nil } +func getProofs(ctx context.Context, eds *rsmt2d.ExtendedDataSquare) (map[cid.Cid][]byte, error) { + // check if there are proofs collected by ipld.ProofsAdder in previous reconstruction of eds + proofs := ipld.ProofsAdderFromCtx(ctx).Proofs() + if proofs != nil { + return proofs, nil + } + + // recompute proofs from eds + shares := share.ExtractEDS(eds) + shareCount := len(shares) + if shareCount == 0 { + return nil, ErrEmptySquare + } + odsWidth := int(math.Sqrt(float64(shareCount)) / 2) + + // this adder ignores leaves, so that they are not added to the store we iterate through in + // writeProofs + adder := ipld.NewProofsAdder(odsWidth * 2) + eds, err := rsmt2d.ImportExtendedDataSquare( + shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(odsWidth), + nmt.NodeVisitor(adder.VisitFn())), + ) + if err != nil { + return nil, fmt.Errorf("recomputing data square: %w", err) + } + // compute roots + if _, err = eds.RowRoots(); err != nil { + return nil, fmt.Errorf("computing row roots: %w", err) + } + + return adder.Proofs(), nil +} + // quadrantOrder reorders the shares in the EDS to quadrant row-by-row order, prepending the // respective namespace to the shares. // e.g. [ Q1 R1 | Q1 R2 | Q1 R3 | Q1 R4 | Q2 R1 | Q2 R2 .... ] @@ -287,10 +242,17 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (eds *rsmt2d shares[i] = share.GetData(block.RawData()) } + // use proofs adder if provided, to cache collected proofs while recomputing the eds + var opts []nmt.Option + visitor := ipld.ProofsAdderFromCtx(ctx).VisitFn() + if visitor != nil { + opts = append(opts, nmt.NodeVisitor(visitor)) + } + eds, err = rsmt2d.ComputeExtendedDataSquare( shares, share.DefaultRSMT2DCodec(), - wrapper.NewConstructor(uint64(odsWidth)), + wrapper.NewConstructor(uint64(odsWidth), opts...), ) if err != nil { return nil, fmt.Errorf("share: computing eds: %w", err) @@ -309,9 +271,3 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (eds *rsmt2d } return eds, nil } - -// innerNodeBatchSize calculates the total number of inner nodes in an EDS, -// to be flushed to the dagstore in a single write. -func innerNodeBatchSize(shareCount int, odsWidth int) int { - return (shareCount * 2) - (odsWidth * 4) -} diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index af870f60b3..70ce7a0e8e 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -22,7 +22,6 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" - "github.com/celestiaorg/celestia-node/share/ipld" ) //go:embed "testdata/example-root.json" @@ -137,34 +136,6 @@ func TestWriteEDSInQuadrantOrder(t *testing.T) { } } -// TestInnerNodeBatchSize verifies that the number of unique inner nodes is equal to ipld.BatchSize -// - shareCount. -func TestInnerNodeBatchSize(t *testing.T) { - tests := []struct { - name string - origWidth int - }{ - {"2", 2}, - {"4", 4}, - {"8", 8}, - {"16", 16}, - {"32", 32}, - // {"64", 64}, // test case too large for CI with race detector - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - extendedWidth := tt.origWidth * 2 - shareCount := extendedWidth * extendedWidth - assert.Equalf( - t, - innerNodeBatchSize(shareCount, tt.origWidth), - ipld.BatchSize(extendedWidth)-shareCount, - "batchSize(%v)", extendedWidth, - ) - }) - } -} - func TestReadWriteRoundtrip(t *testing.T) { eds := writeRandomEDS(t) dah, err := da.NewDataAvailabilityHeader(eds) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index 0f52a903bc..a870e07e22 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -17,6 +17,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -122,7 +123,14 @@ type retrievalSession struct { func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHeader) (*retrievalSession, error) { size := len(dah.RowRoots) treeFn := func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { - tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index) + // use proofs adder if provided, to cache collected proofs while recomputing the eds + var opts []nmt.Option + visitor := ipld.ProofsAdderFromCtx(ctx).VisitFn() + if visitor != nil { + opts = append(opts, nmt.NodeVisitor(visitor)) + } + + tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(size)/2, index, opts...) return &tree } diff --git a/share/getters/tee.go b/share/getters/tee.go index fe43c971b7..8ee1e91390 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -14,6 +14,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/ipld" ) var _ share.Getter = (*TeeGetter)(nil) @@ -51,6 +52,7 @@ func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d. utils.SetStatusAndEnd(span, err) }() + ctx = ipld.CtxWithProofsAdder(ctx, ipld.NewProofsAdder(len(root.RowRoots))) eds, err = tg.getter.GetEDS(ctx, root) if err != nil { return nil, err diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index 386a0083a3..0b4c521a80 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -9,6 +9,14 @@ import ( "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" + + "github.com/celestiaorg/nmt" +) + +type ctxKey int + +const ( + proofsAdderKey ctxKey = iota ) // NmtNodeAdder adds ipld.Nodes to the underlying ipld.Batch if it is inserted @@ -55,26 +63,6 @@ func (n *NmtNodeAdder) Visit(hash []byte, children ...[]byte) { } } -// VisitInnerNodes is a NodeVisitor that does not store leaf nodes to the blockservice. -func (n *NmtNodeAdder) VisitInnerNodes(hash []byte, children ...[]byte) { - n.lock.Lock() - defer n.lock.Unlock() - - if n.err != nil { - return // protect from further visits if there is an error - } - - id := MustCidFromNamespacedSha256(hash) - switch len(children) { - case 1: - break - case 2: - n.err = n.add.Add(n.ctx, newNMTNode(id, append(children[0], children[1]...))) - default: - panic("expected a binary tree") - } -} - // Commit checks for errors happened during Visit and if absent commits data to inner Batch. func (n *NmtNodeAdder) Commit() error { n.lock.Lock() @@ -112,3 +100,76 @@ func BatchSize(squareSize int) int { // and for the last two layers as well: return (squareSize*2-1)*squareSize*2 - (squareSize * squareSize) } + +type ProofsAdder struct { + lock sync.RWMutex + proofs map[cid.Cid][]byte +} + +func NewProofsAdder(squareSize int) *ProofsAdder { + return &ProofsAdder{ + // preallocate map to fit all inner nodes for given square size + proofs: make(map[cid.Cid][]byte, innerNodesAmount(squareSize)), + } +} + +func CtxWithProofsAdder(ctx context.Context, adder *ProofsAdder) context.Context { + return context.WithValue(ctx, proofsAdderKey, adder) +} + +func ProofsAdderFromCtx(ctx context.Context) *ProofsAdder { + val := ctx.Value(proofsAdderKey) + adder, ok := val.(*ProofsAdder) + if !ok || adder == nil { + return nil + } + return adder +} + +func (a *ProofsAdder) Proofs() map[cid.Cid][]byte { + if a == nil { + return nil + } + + a.lock.RLock() + defer a.lock.RUnlock() + return a.proofs +} + +func (a *ProofsAdder) VisitFn() nmt.NodeVisitorFn { + if a == nil { + return nil + } + + a.lock.RLock() + defer a.lock.RUnlock() + + // proofs are already collected, don't collect second time + if len(a.proofs) > 0 { + return nil + } + return a.visitInnerNodes +} + +func (a *ProofsAdder) visitInnerNodes(hash []byte, children ...[]byte) { + switch len(children) { + case 1: + break + case 2: + id := MustCidFromNamespacedSha256(hash) + a.addProof(id, append(children[0], children[1]...)) + default: + panic("expected a binary tree") + } +} + +func (a *ProofsAdder) addProof(id cid.Cid, proof []byte) { + a.lock.Lock() + defer a.lock.Unlock() + a.proofs[id] = proof +} + +// innerNodesAmount return amount of inner nodes in eds with given size +func innerNodesAmount(squareSize int) int { + return 2 * (squareSize - 1) * squareSize +} From ec36e7ee6eeb2ff7beb7137c5d8b20a102b610a8 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 8 Aug 2023 18:24:00 +0800 Subject: [PATCH 0735/1008] misc(metrics)!: split global attributes into two (#2546) ## Overview Currently there is only one global attribute, that build by concatenating both exported attributes: ``` "job" = "Bridge/robusta-11rc9/12D3KooWAEQUSJh7aJFq7Cw1mP4VWnqfHtHwwwnsXHFhpQ2DMn7j" ``` `Breaking`, because this PR is breaking metrics compatibility. --- nodebuilder/settings.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index c30996d24e..44508ba5e1 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -171,8 +171,13 @@ func initializeMetrics( sdk.WithReader(sdk.NewPeriodicReader(exp, sdk.WithTimeout(2*time.Second))), sdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNamespaceKey.String(nodeType.String()), - semconv.ServiceNameKey.String(fmt.Sprintf("%s/%s", network.String(), peerID.String()))))) + // ServiceNamespaceKey and ServiceNameKey will be concatenated into single attribute with key: + // "job" and value: "%service.namespace%/%service.name%" + semconv.ServiceNamespaceKey.String(network.String()), + semconv.ServiceNameKey.String(nodeType.String()), + // ServiceInstanceIDKey will be exported with key: "instance" + semconv.ServiceInstanceIDKey.String(peerID.String()), + ))) lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { return provider.Shutdown(ctx) From 3a3473df66796bb92fe17e6925eb9e1bfba4ac96 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 9 Aug 2023 11:02:34 +0300 Subject: [PATCH 0736/1008] fix(blob): ensure that the share sequence is not empty (#2547) --- blob/helper.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/blob/helper.go b/blob/helper.go index 35f0abc540..e87055ae07 100644 --- a/blob/helper.go +++ b/blob/helper.go @@ -32,6 +32,11 @@ func SharesToBlobs(rawShares []share.Share) ([]*Blob, error) { return nil, err } + // ensure that sequence length is not 0 + if len(shareSequences) == 0 { + return nil, ErrBlobNotFound + } + blobs := make([]*Blob, len(shareSequences)) for i, sequence := range shareSequences { data, err := sequence.RawData() From 94a6b63ea3e28b00e53537406ac409881f681a7f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 9 Aug 2023 11:03:24 +0300 Subject: [PATCH 0737/1008] fix(rpc): fix commands handling (#2515) --- cmd/celestia/blob.go | 16 ++++++++-------- cmd/celestia/rpc.go | 26 ++++++++++++++++++++++++-- share/getters/cascade.go | 8 +++++++- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go index 9dcd72dcb7..0b94fdcfdc 100644 --- a/cmd/celestia/blob.go +++ b/cmd/celestia/blob.go @@ -14,22 +14,22 @@ import ( "github.com/celestiaorg/celestia-node/share" ) -var plaintext bool +var base64Flag bool func init() { blobCmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd) getCmd.PersistentFlags().BoolVar( - &plaintext, - "plaintext", + &base64Flag, + "base64", false, - "printed blob's data as a plain text", + "printed blob's data a base64 string", ) getAllCmd.PersistentFlags().BoolVar( - &plaintext, - "plaintext", + &base64Flag, + "base64", false, - "printed blob's data as a plain text", + "printed blob's data as a base64 string", ) } @@ -170,7 +170,7 @@ func printOutput(data interface{}, err error) { data = err } - if plaintext && err == nil { + if !base64Flag && err == nil { data = formatData(data) } diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 05439ad7b4..dd50f07fc2 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -119,9 +119,17 @@ var rpcCmd = &cobra.Command{ func parseParams(method string, params []string) []interface{} { parsedParams := make([]interface{}, len(params)) - + validateParamsFn := func(has, want int) error { + if has != want { + return fmt.Errorf("rpc: invalid amount of params. has=%d, want=%d", has, want) + } + return nil + } switch method { case "GetSharesByNamespace": + if err := validateParamsFn(len(params), 2); err != nil { + panic(err) + } // 1. Share Root root, err := parseJSON(params[0]) if err != nil { @@ -134,8 +142,12 @@ func parseParams(method string, params []string) []interface{} { panic(fmt.Sprintf("Error parsing namespace: %v", err)) } parsedParams[1] = namespace + return parsedParams case "QueryDelegation", "QueryUnbonding", "BalanceForAddress": var err error + if err = validateParamsFn(len(params), 2); err != nil { + panic(err) + } parsedParams[0], err = parseAddressFromString(params[0]) if err != nil { panic(fmt.Errorf("error parsing address: %w", err)) @@ -155,6 +167,9 @@ func parseParams(method string, params []string) []interface{} { case "Transfer", "Delegate", "Undelegate": // 1. Address var err error + if err = validateParamsFn(len(params), 4); err != nil { + panic(err) + } parsedParams[0], err = parseAddressFromString(params[0]) if err != nil { panic(fmt.Errorf("error parsing address: %w", err)) @@ -172,6 +187,9 @@ func parseParams(method string, params []string) []interface{} { case "CancelUnbondingDelegation": // 1. Validator Address var err error + if err = validateParamsFn(len(params), 5); err != nil { + panic(err) + } parsedParams[0], err = parseAddressFromString(params[0]) if err != nil { panic(fmt.Errorf("error parsing address: %w", err)) @@ -186,9 +204,13 @@ func parseParams(method string, params []string) []interface{} { panic("Error parsing gas limit: uint64 could not be parsed.") } parsedParams[4] = num + return parsedParams case "BeginRedelegate": // 1. Source Validator Address var err error + if err = validateParamsFn(len(params), 5); err != nil { + panic(err) + } parsedParams[0], err = parseAddressFromString(params[0]) if err != nil { panic(fmt.Errorf("error parsing address: %w", err)) @@ -207,6 +229,7 @@ func parseParams(method string, params []string) []interface{} { panic("Error parsing gas limit: uint64 could not be parsed.") } parsedParams[4] = num + return parsedParams default: } @@ -228,7 +251,6 @@ func parseParams(method string, params []string) []interface{} { parsedParams[i] = param } } - return parsedParams } diff --git a/share/getters/cascade.go b/share/getters/cascade.go index ef59946666..98f29c87ca 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -3,6 +3,7 @@ package getters import ( "context" "errors" + "fmt" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -37,7 +38,12 @@ func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, co attribute.Int("col", col), )) defer span.End() - + if row >= len(root.RowRoots) || col >= len(root.ColumnRoots) { + err := fmt.Errorf("cascade/get-share: invalid indexes were provided:rowIndex=%d, colIndex=%d."+ + "squarewidth=%d", row, col, len(root.RowRoots)) + span.RecordError(err) + return nil, err + } get := func(ctx context.Context, get share.Getter) (share.Share, error) { return get.GetShare(ctx, root, row, col) } From 426c337368b9ed18b41586469ac24c6b52da15c3 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 10 Aug 2023 11:14:38 +0300 Subject: [PATCH 0738/1008] fix(cmd): fix formatting during submit (#2551) --- cmd/celestia/blob.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go index 0b94fdcfdc..28d05b3b42 100644 --- a/cmd/celestia/blob.go +++ b/cmd/celestia/blob.go @@ -199,8 +199,7 @@ func formatData(data interface{}) interface{} { if reflect.TypeOf(data).Kind() == reflect.Slice { blobs, ok := data.([]*blob.Blob) if !ok { - fmt.Fprintln(os.Stderr, "could not cast to []blob.Blob") - os.Exit(1) + return data } result := make([]tempBlob, len(blobs)) @@ -217,8 +216,7 @@ func formatData(data interface{}) interface{} { b, ok := data.(*blob.Blob) if !ok { - fmt.Fprintln(os.Stderr, "could not cast to blob.Blob") - os.Exit(1) + return data } return tempBlob{ Namespace: b.Namespace(), From 32834eb6722ec34450e7253cd164b705a81eb6d5 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 10 Aug 2023 20:24:52 +0800 Subject: [PATCH 0739/1008] fix(share/eds): don't use in-memory buffer for mount after shard recover (#2554) When shard recovered in dagstore, it was read from in-memory mount, that could have been read 0 times and had no in-memory buffer. --- share/eds/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/eds/store.go b/share/eds/store.go index 12993e8069..69e3c6b4a4 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -569,7 +569,7 @@ type inMemoryOnceMount struct { } func (m *inMemoryOnceMount) Fetch(ctx context.Context) (mount.Reader, error) { - if !m.readOnce.Swap(true) { + if m.buf != nil && !m.readOnce.Swap(true) { reader := &inMemoryReader{Reader: bytes.NewReader(m.buf.Bytes())} // release memory for gc, otherwise buffer will stick forever m.buf = nil From 87e9500120c27450e264ab4ffd59c87e92186194 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:09:53 +0200 Subject: [PATCH 0740/1008] =?UTF-8?q?fix(header):=20Only=20bridge=20node?= =?UTF-8?q?=20should=20panic=20on=20data=20root=20mismatch=20in=20e?= =?UTF-8?q?=E2=80=A6=20(#2558)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR fixes a DoS first discovered by @Wondertan and then secondarily by @vgonkivs 🤠 Only bridge nodes should panic on receiving a header where the computed data root does not match the DataHash in the RawHeader on ExtendedHeader validation. Resolves #2555 --- core/exchange.go | 4 ++-- core/header_test.go | 8 +++----- core/listener.go | 2 +- header/header.go | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/core/exchange.go b/core/exchange.go index e041ff980f..a9b3a532d5 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -115,7 +115,7 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende // construct extended header eh, err := ce.construct(ctx, &block.Header, comm, vals, eds) if err != nil { - return nil, fmt.Errorf("constructing extended header for height %d: %w", &block.Height, err) + panic(fmt.Errorf("constructing extended header for height %d: %w", &block.Height, err)) } // verify hashes match if !bytes.Equal(hash, eh.Hash()) { @@ -155,7 +155,7 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 // create extended header eh, err := ce.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { - return nil, fmt.Errorf("constructing extended header for height %d: %w", b.Header.Height, err) + panic(fmt.Errorf("constructing extended header for height %d: %w", b.Header.Height, err)) } ctx = ipld.CtxWithProofsAdder(ctx, adder) diff --git a/core/header_test.go b/core/header_test.go index 6315fbc143..1c89db9d6b 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -41,11 +41,9 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { func TestMismatchedDataHash_ComputedRoot(t *testing.T) { header := headertest.RandExtendedHeader(t) - header.DataHash = rand.Bytes(32) - panicFn := func() { - header.Validate() //nolint:errcheck - } - assert.Panics(t, panicFn) + err := header.Validate() + assert.Contains(t, err.Error(), "mismatch between data hash commitment from"+ + " core header and computed data root") } diff --git a/core/listener.go b/core/listener.go index 334aa9293a..17be12a780 100644 --- a/core/listener.go +++ b/core/listener.go @@ -160,7 +160,7 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS // generate extended header eh, err := cl.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { - return fmt.Errorf("making extended header: %w", err) + panic(fmt.Errorf("making extended header: %w", err)) } // attempt to store block data if not empty diff --git a/header/header.go b/header/header.go index 0e386be8b5..d69b11d998 100644 --- a/header/header.go +++ b/header/header.go @@ -146,8 +146,8 @@ func (eh *ExtendedHeader) Validate() error { // ensure data root from raw header matches computed root if !bytes.Equal(eh.DAH.Hash(), eh.DataHash) { - panic(fmt.Sprintf("mismatch between data hash commitment from core header and computed data root "+ - "at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash())) + return fmt.Errorf("mismatch between data hash commitment from core header and computed data root "+ + "at height %d: data hash: %X, computed root: %X", eh.Height(), eh.DataHash, eh.DAH.Hash()) } // Make sure the header is consistent with the commit. From 003c2c4ab4e75406216982b01af7418182753340 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 14 Aug 2023 20:32:19 +0700 Subject: [PATCH 0741/1008] fix(share/eds): dagstore shard restore reflection workaround (#2559) Turns out `dagstore` has bug in its restore shard code. For each shard it attempts to create deep copy of registered type, but copies only basic struct fields of the mount type. In out case it means, that it copies a pointer. That causes all shards to point to the same mount (same pointer). Before `dagstore` is fixed to take into account possibility of pointers/interface (and any other reference type) in mount type struct fields, we will use direct copy of `mount.FileMount` instead of `mount.Mount` interface. --- nodebuilder/store_test.go | 92 +++++++++++++++++++++++++++++---------- share/eds/store.go | 10 ++--- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/nodebuilder/store_test.go b/nodebuilder/store_test.go index 5f2d1bed83..8a39849060 100644 --- a/nodebuilder/store_test.go +++ b/nodebuilder/store_test.go @@ -4,9 +4,8 @@ import ( "context" "strconv" "testing" + "time" - "github.com/ipfs/go-datastore" - ds_sync "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -68,30 +67,13 @@ func BenchmarkStore(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) b.Cleanup(cancel) - tmpDir := b.TempDir() - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(tmpDir, ds) - require.NoError(b, err) - err = edsStore.Start(ctx) - require.NoError(b, err) - // BenchmarkStore/bench_read_128-10 14 78970661 ns/op (~70ms) b.Run("bench put 128", func(b *testing.B) { - b.ResetTimer() dir := b.TempDir() - err := Init(*DefaultConfig(node.Full), dir, node.Full) require.NoError(b, err) - store, err := OpenStore(dir, nil) - require.NoError(b, err) - ds, err := store.Datastore() - require.NoError(b, err) - edsStore, err := eds.NewStore(dir, ds) - require.NoError(b, err) - err = edsStore.Start(ctx) - require.NoError(b, err) - + store := newStore(ctx, b, dir) size := 128 b.Run("enabled eds proof caching", func(b *testing.B) { b.StopTimer() @@ -111,7 +93,7 @@ func BenchmarkStore(b *testing.B) { ctx := ipld.CtxWithProofsAdder(ctx, adder) b.StartTimer() - err = edsStore.Put(ctx, dah.Hash(), eds) + err = store.edsStore.Put(ctx, dah.Hash(), eds) b.StopTimer() require.NoError(b, err) } @@ -126,10 +108,76 @@ func BenchmarkStore(b *testing.B) { require.NoError(b, err) b.StartTimer() - err = edsStore.Put(ctx, dah.Hash(), eds) + err = store.edsStore.Put(ctx, dah.Hash(), eds) b.StopTimer() require.NoError(b, err) } }) }) } + +func TestStoreRestart(t *testing.T) { + const ( + blocks = 5 + size = 32 + ) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + dir := t.TempDir() + err := Init(*DefaultConfig(node.Full), dir, node.Full) + require.NoError(t, err) + + store := newStore(ctx, t, dir) + + hashes := make([][]byte, blocks) + for i := range hashes { + edss := edstest.RandEDS(t, size) + require.NoError(t, err) + dah, err := da.NewDataAvailabilityHeader(edss) + require.NoError(t, err) + err = store.edsStore.Put(ctx, dah.Hash(), edss) + require.NoError(t, err) + + // store hashes for read loop later + hashes[i] = dah.Hash() + } + + // restart store + store.stop(ctx, t) + store = newStore(ctx, t, dir) + + for _, h := range hashes { + edsReader, err := store.edsStore.GetCAR(ctx, h) + require.NoError(t, err) + odsReader, err := eds.ODSReader(edsReader) + require.NoError(t, err) + _, err = eds.ReadEDS(ctx, odsReader, h) + require.NoError(t, err) + } +} + +type store struct { + s Store + edsStore *eds.Store +} + +func newStore(ctx context.Context, t require.TestingT, dir string) store { + s, err := OpenStore(dir, nil) + require.NoError(t, err) + ds, err := s.Datastore() + require.NoError(t, err) + edsStore, err := eds.NewStore(dir, ds) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + return store{ + s: s, + edsStore: edsStore, + } +} + +func (s *store) stop(ctx context.Context, t *testing.T) { + require.NoError(t, s.edsStore.Stop(ctx)) + require.NoError(t, s.s.Close()) +} diff --git a/share/eds/store.go b/share/eds/store.go index 69e3c6b4a4..5a85b9ab55 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -75,7 +75,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } r := mount.NewRegistry() - err = r.Register("fs", &inMemoryOnceMount{Mount: &mount.FileMount{}}) + err = r.Register("fs", &inMemoryOnceMount{}) if err != nil { return nil, fmt.Errorf("failed to register memory mount on the registry: %w", err) } @@ -212,8 +212,8 @@ func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext // save encoded eds into buffer mount := &inMemoryOnceMount{ // TODO: buffer could be pre-allocated with capacity calculated based on eds size. - buf: bytes.NewBuffer(nil), - Mount: &mount.FileMount{Path: s.basepath + blocksPath + key}, + buf: bytes.NewBuffer(nil), + FileMount: mount.FileMount{Path: s.basepath + blocksPath + key}, } err = WriteEDS(ctx, square, mount) if err != nil { @@ -565,7 +565,7 @@ type inMemoryOnceMount struct { buf *bytes.Buffer readOnce atomic.Bool - mount.Mount + mount.FileMount } func (m *inMemoryOnceMount) Fetch(ctx context.Context) (mount.Reader, error) { @@ -575,7 +575,7 @@ func (m *inMemoryOnceMount) Fetch(ctx context.Context) (mount.Reader, error) { m.buf = nil return reader, nil } - return m.Mount.Fetch(ctx) + return m.FileMount.Fetch(ctx) } func (m *inMemoryOnceMount) Write(b []byte) (int, error) { From 79a195cf615dac4f93e4a05c8fdbd467355700ae Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:27:05 +0700 Subject: [PATCH 0742/1008] metrics(runtime): add runtime metrics (#2570) Resolves https://github.com/celestiaorg/celestia-node/issues/2465 --- go.mod | 1 + go.sum | 2 ++ nodebuilder/settings.go | 35 +++++++++++++++++++++++++---------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 01a7325793..873c5fc2b7 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 + go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 diff --git a/go.sum b/go.sum index ed814e6ede..e3e90e746b 100644 --- a/go.sum +++ b/go.sum @@ -2054,6 +2054,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEasIt2kmh/WmXUznUksChApTgBGc= +go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 44508ba5e1..b2a29723db 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -8,6 +8,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/pyroscope-io/client/pyroscope" otelpyroscope "github.com/pyroscope-io/otel-profiling-go" + "go.opentelemetry.io/contrib/instrumentation/runtime" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" @@ -29,6 +30,8 @@ import ( "github.com/celestiaorg/celestia-node/state" ) +const defaultMetricsCollectInterval = 10 * time.Second + // WithNetwork specifies the Network to which the Node should connect to. // WARNING: Use this option with caution and never run the Node with different networks over the // same persisted Store. @@ -168,16 +171,28 @@ func initializeMetrics( } provider := sdk.NewMeterProvider( - sdk.WithReader(sdk.NewPeriodicReader(exp, sdk.WithTimeout(2*time.Second))), - sdk.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - // ServiceNamespaceKey and ServiceNameKey will be concatenated into single attribute with key: - // "job" and value: "%service.namespace%/%service.name%" - semconv.ServiceNamespaceKey.String(network.String()), - semconv.ServiceNameKey.String(nodeType.String()), - // ServiceInstanceIDKey will be exported with key: "instance" - semconv.ServiceInstanceIDKey.String(peerID.String()), - ))) + sdk.WithReader( + sdk.NewPeriodicReader(exp, + sdk.WithTimeout(defaultMetricsCollectInterval), + sdk.WithInterval(defaultMetricsCollectInterval))), + sdk.WithResource( + resource.NewWithAttributes( + semconv.SchemaURL, + // ServiceNamespaceKey and ServiceNameKey will be concatenated into single attribute with key: + // "job" and value: "%service.namespace%/%service.name%" + semconv.ServiceNamespaceKey.String(network.String()), + semconv.ServiceNameKey.String(nodeType.String()), + // ServiceInstanceIDKey will be exported with key: "instance" + semconv.ServiceInstanceIDKey.String(peerID.String()), + ))) + + err = runtime.Start( + runtime.WithMinimumReadMemStatsInterval(defaultMetricsCollectInterval), + runtime.WithMeterProvider(provider)) + if err != nil { + return fmt.Errorf("start runtime metrics: %w", err) + } + lc.Append(fx.Hook{ OnStop: func(ctx context.Context) error { return provider.Shutdown(ctx) From e6347514c107b1518499c80eecf71b36ba9cbd93 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 17 Aug 2023 21:41:35 +0800 Subject: [PATCH 0743/1008] fix(nodebuilder): set default log level for bitswap-client (#2576) `bitswap-client` logger generates 10k logs per sec in INFO level. This PR sets default log level to WARN --- logs/logs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/logs/logs.go b/logs/logs.go index 15c26888c5..23d0683996 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -11,6 +11,7 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("dht", "ERROR") _ = logging.SetLogLevel("swarm2", "WARN") _ = logging.SetLogLevel("bitswap", "WARN") + _ = logging.SetLogLevel("bitswap-client", "WARN") _ = logging.SetLogLevel("connmgr", "WARN") _ = logging.SetLogLevel("nat", "INFO") _ = logging.SetLogLevel("dht/RtRefreshManager", "FATAL") From 736763e6e689f9d604f3b12a2d941328f5819f89 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 17 Aug 2023 15:43:05 +0200 Subject: [PATCH 0744/1008] fix: TestShareAvailable_DisconnectedFullNodes (#2560) TestShareAvailable_DisconnectedFullNodes had multiple bugs which caused it to be a false positive. The following issues were fixed: 1. The parameters used now don't allow for reconstruction from a single subnetwork, as they did before. This has been verified with a simulation - for `s = 20`, and `k = 16`, `c` set to 32 gives a 99.5% chance of reconstruction, and 16 (single subnetwork) gives a 0.5% chance. Before, both values of `c` gave a near 100% chance for reconstruction. 2. Increased the timeout for the first reconstruction. There was a check that verified that reconstruction was not possible from a single subnetwork, but it only failed to reconstruct because the timeout was too short. Increasing the timeout with the original parameters verified this. 3. Shape the network topology before the first reconstruction (for example, separating the LNs into subnets) 4. Calling final reconstruction in an error group. The full nodes are codependent on each other for either one to reconstruct. Calling them sequentially fails, because the first full node needs the second full node to have sampled in order to get shares from it. This was another signal that the full nodes could reconstruct from a single subnetwork, and it has now been fixed by allowing both FNs to sample concurrently. --- .../availability/full/reconstruction_test.go | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/share/availability/full/reconstruction_test.go b/share/availability/full/reconstruction_test.go index 03b998d49e..f3b6ce91bd 100644 --- a/share/availability/full/reconstruction_test.go +++ b/share/availability/full/reconstruction_test.go @@ -181,35 +181,15 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { light.DefaultSampleAmount = 20 // s const ( origSquareSize = 16 // k - lightNodes = 60 // c - total number of nodes on two subnetworks + lightNodes = 32 // c - total number of nodes on two subnetworks ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) defer cancel() net := availability_test.NewTestDAGNet(ctx, t) source, root := RandNode(net, origSquareSize) - // create two full nodes and ensure they are disconnected - full1 := Node(net) - full2 := Node(net) - net.Disconnect(full1.ID(), full2.ID()) - - // ensure fulls and source are not connected - // so that fulls take data from light nodes only - net.Disconnect(full1.ID(), source.ID()) - net.Disconnect(full2.ID(), source.ID()) - - // start reconstruction for fulls that should fail - ctxErr, cancelErr := context.WithTimeout(ctx, eds.RetrieveQuadrantTimeout*8) - errg, errCtx := errgroup.WithContext(ctxErr) - errg.Go(func() error { - return full1.SharesAvailable(errCtx, root) - }) - errg.Go(func() error { - return full2.SharesAvailable(errCtx, root) - }) - // create light nodes and start sampling for them immediately lights1, lights2 := make( []*availability_test.TestNode, lightNodes/2), @@ -237,6 +217,16 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { }(i) } + // create two full nodes and ensure they are disconnected + full1 := Node(net) + full2 := Node(net) + net.Disconnect(full1.ID(), full2.ID()) + + // ensure fulls and source are not connected + // so that fulls take data from light nodes only + net.Disconnect(full1.ID(), source.ID()) + net.Disconnect(full2.ID(), source.ID()) + // shape topology for i := 0; i < len(lights1); i++ { // ensure lights1 are only connected to source and full1 @@ -249,6 +239,16 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { net.Disconnect(lights2[i].ID(), full1.ID()) } + // start reconstruction for fulls that should fail + ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*5) + errg, errCtx := errgroup.WithContext(ctxErr) + errg.Go(func() error { + return full1.SharesAvailable(errCtx, root) + }) + errg.Go(func() error { + return full2.SharesAvailable(errCtx, root) + }) + // check that any of the fulls cannot reconstruct on their own err := errg.Wait() require.ErrorIs(t, err, share.ErrNotAvailable) @@ -262,10 +262,14 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { full2.ClearStorage() // they both should be able to reconstruct the block - err = full1.SharesAvailable(ctx, root) - require.NoError(t, err, share.ErrNotAvailable) - err = full2.SharesAvailable(ctx, root) - require.NoError(t, err, share.ErrNotAvailable) + errg, bctx := errgroup.WithContext(ctx) + errg.Go(func() error { + return full1.SharesAvailable(bctx, root) + }) + errg.Go(func() error { + return full2.SharesAvailable(bctx, root) + }) + require.NoError(t, errg.Wait()) // wait for all routines to finish before exit, in case there are any errors to log wg.Wait() } From 8eef855a1eaefd9d6eb8e10dcfebb48265b225c9 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:44:01 +0800 Subject: [PATCH 0745/1008] chore(deps): bump ipfs networking deps (#2478) ## Overview Main change is update of github.com/ipfs/go-libipfs , that is now located in github.com/ipfs/boxo Updates libs and their dependancies: - github.com/libp2p/go-libp2p-kad-dht - github.com/libp2p/go-libp2p-routing-helpers - github.com/ipfs/go-block-format - github.com/ipfs/go-blockservice - github.com/ipfs/go-ipld-format - github.com/ipfs/go-merkledag - github.com/ipld/go-car - github.com/multiformats/go-multihash Supersedes: https://github.com/celestiaorg/celestia-node/pull/2268 https://github.com/celestiaorg/celestia-node/pull/2378 https://github.com/celestiaorg/celestia-node/pull/2379 https://github.com/celestiaorg/celestia-node/pull/1957 --------- Co-authored-by: Ryan Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- go.mod | 41 ++++++----- go.sum | 93 ++++++++++++------------- nodebuilder/node.go | 2 +- nodebuilder/p2p/bitswap.go | 12 ++-- nodebuilder/p2p/ipld.go | 4 +- share/availability/test/corrupt_data.go | 4 +- share/availability/test/testing.go | 6 +- share/eds/adapters.go | 2 +- share/eds/adapters_test.go | 2 +- share/eds/blockstore.go | 4 +- share/eds/eds_test.go | 2 +- share/eds/store.go | 2 +- share/ipld/get_shares_test.go | 2 +- share/ipld/nmt.go | 2 +- 14 files changed, 88 insertions(+), 90 deletions(-) diff --git a/go.mod b/go.mod index 873c5fc2b7..032a9a2a58 100644 --- a/go.mod +++ b/go.mod @@ -30,24 +30,23 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/imdario/mergo v0.3.16 - github.com/ipfs/go-blockservice v0.5.0 + github.com/ipfs/boxo v0.11.0 + github.com/ipfs/go-block-format v0.1.2 + github.com/ipfs/go-blockservice v0.5.1 // down 1 version, 0.5.2 is marked as deprecated and raises alerts github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 - github.com/ipfs/go-ipfs-blockstore v1.2.0 - github.com/ipfs/go-ipfs-exchange-interface v0.2.0 github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-ipfs-routing v0.3.0 github.com/ipfs/go-ipld-cbor v0.0.6 - github.com/ipfs/go-ipld-format v0.4.0 - github.com/ipfs/go-libipfs v0.6.0 + github.com/ipfs/go-ipld-format v0.5.0 github.com/ipfs/go-log/v2 v2.5.1 - github.com/ipfs/go-merkledag v0.10.0 - github.com/ipld/go-car v0.6.0 + github.com/ipfs/go-merkledag v0.11.0 + github.com/ipld/go-car v0.6.2 github.com/libp2p/go-libp2p v0.29.0 - github.com/libp2p/go-libp2p-kad-dht v0.21.1 + github.com/libp2p/go-libp2p-kad-dht v0.24.2 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 - github.com/libp2p/go-libp2p-routing-helpers v0.6.1 + github.com/libp2p/go-libp2p-routing-helpers v0.7.0 github.com/minio/sha256-simd v1.0.1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 @@ -74,10 +73,10 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 go.uber.org/fx v1.20.0 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.12.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 golang.org/x/sync v0.3.0 - golang.org/x/text v0.11.0 + golang.org/x/text v0.12.0 google.golang.org/grpc v1.56.2 google.golang.org/protobuf v1.31.0 ) @@ -200,19 +199,18 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.12.2 // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/go-bitswap v0.12.0 // indirect - github.com/ipfs/go-block-format v0.1.1 // indirect + github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect + github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect github.com/ipfs/go-ipfs-pq v0.0.3 // indirect - github.com/ipfs/go-ipfs-util v0.0.2 // indirect - github.com/ipfs/go-ipld-legacy v0.1.1 // indirect - github.com/ipfs/go-ipns v0.3.0 // indirect + github.com/ipfs/go-ipfs-util v0.0.3 // indirect + github.com/ipfs/go-ipld-legacy v0.2.1 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-peertaskqueue v0.8.1 // indirect github.com/ipfs/go-verifcid v0.0.2 // indirect - github.com/ipld/go-car/v2 v2.5.1 // indirect + github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 // indirect github.com/ipld/go-codec-dagpb v1.6.0 // indirect github.com/ipld/go-ipld-prime v0.20.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect @@ -230,7 +228,7 @@ require ( github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect - github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect + github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect @@ -282,7 +280,7 @@ require ( github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect - github.com/rivo/uniseg v0.4.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.29.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect @@ -316,10 +314,11 @@ require ( golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/term v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect golang.org/x/tools v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect diff --git a/go.sum b/go.sum index e3e90e746b..75924752f5 100644 --- a/go.sum +++ b/go.sum @@ -831,8 +831,8 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -964,17 +964,18 @@ github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mq github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/go-bitfield v1.0.0 h1:y/XHm2GEmD9wKngheWNNCNL0pzrWXZwCdQGv1ikXknQ= +github.com/ipfs/boxo v0.11.0 h1:urMxhZ3xoF4HssJVD3+0ssGT9pptEfHfbL8DYdoWFlg= +github.com/ipfs/boxo v0.11.0/go.mod h1:8IfDmp+FzFGcF4zjAgHMVPpwYw4AjN9ePEzDfkaYJ1w= +github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= -github.com/ipfs/go-bitswap v0.12.0 h1:ClbLaufwv8SRQK0sBhl4wDVqJoZGAGMVxdjQy5CTt6c= -github.com/ipfs/go-bitswap v0.12.0/go.mod h1:Iwjkd6+vaDjVIa6b6ogmZgs+b5U3EkIFEX79kQ4DjnI= +github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= -github.com/ipfs/go-block-format v0.1.1 h1:129vSO3zwbsYADcyQWcOYiuCpAqt462SFfqFHdFJhhI= -github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= +github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= +github.com/ipfs/go-block-format v0.1.2/go.mod h1:mACVcrxarQKstUU3Yf/RdwbC4DzPV6++rO2a3d+a/KE= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= -github.com/ipfs/go-blockservice v0.5.0 h1:B2mwhhhVQl2ntW2EIpaWPwSCxSuqr5fFA93Ms4bYLEY= -github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= +github.com/ipfs/go-blockservice v0.5.1 h1:9pAtkyKAz/skdHTh0kH8VulzWp+qmSDD0aI17TYP/s0= +github.com/ipfs/go-blockservice v0.5.1/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -1009,11 +1010,11 @@ github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUN github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= github.com/ipfs/go-ipfs-blockstore v1.1.2/go.mod h1:w51tNR9y5+QXB0wkNcHt4O2aSZjTdqaEWaQdSxEyUOY= -github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= -github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= +github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= +github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= -github.com/ipfs/go-ipfs-chunker v0.0.1 h1:cHUUxKFQ99pozdahi+uSC/3Y6HeRpi9oTeUHbE27SEw= +github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= @@ -1021,8 +1022,8 @@ github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzO github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= -github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= -github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= +github.com/ipfs/go-ipfs-exchange-interface v0.2.1 h1:jMzo2VhLKSHbVe+mHNzYgs95n0+t0Q69GQ5WhRDZV/s= +github.com/ipfs/go-ipfs-exchange-interface v0.2.1/go.mod h1:MUsYn6rKbG6CTtsDp+lKJPmVt3ZrCViNyH3rfPGsZ2E= github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= @@ -1033,23 +1034,19 @@ github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbi github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= -github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= +github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= -github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= -github.com/ipfs/go-ipld-format v0.4.0 h1:yqJSaJftjmjc9jEOFYlpkwOLVKv68OD27jFLlSghBlQ= -github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= +github.com/ipfs/go-ipld-format v0.5.0 h1:WyEle9K96MSrvr47zZHKKcDxJ/vlpET6PSiQsAFO+Ds= +github.com/ipfs/go-ipld-format v0.5.0/go.mod h1:ImdZqJQaEouMjCvqCe0ORUS+uoBmf7Hf+EO/jh+nk3M= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= -github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2cdcc= -github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= -github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A= -github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= -github.com/ipfs/go-libipfs v0.6.0 h1:3FuckAJEm+zdHbHbf6lAyk0QUzc45LsFcGw102oBCZM= -github.com/ipfs/go-libipfs v0.6.0/go.mod h1:UjjDIuehp2GzlNP0HEr5I9GfFT7zWgst+YfpUEIThtw= +github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= +github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= @@ -1066,19 +1063,19 @@ github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOL github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= -github.com/ipfs/go-merkledag v0.10.0 h1:IUQhj/kzTZfam4e+LnaEpoiZ9vZF6ldimVlby+6OXL4= -github.com/ipfs/go-merkledag v0.10.0/go.mod h1:zkVav8KiYlmbzUzNM6kENzkdP5+qR7+2mCwxkQ6GIj8= +github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY= +github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= -github.com/ipfs/go-unixfsnode v1.5.1 h1:JcR3t5C2nM1V7PMzhJ/Qmo19NkoFIKweDSZyDx+CjkI= -github.com/ipld/go-car v0.6.0 h1:d5QrGLnHAxiNLHor+DKGrLdqnM0dQJh2whfSXRDq6J0= -github.com/ipld/go-car v0.6.0/go.mod h1:tBrW1XZ3L2XipLxA69RnTVGW3rve6VX4TbaTYkq8aEA= +github.com/ipfs/go-unixfsnode v1.7.1 h1:RRxO2b6CSr5UQ/kxnGzaChTjp5LWTdf3Y4n8ANZgB/s= +github.com/ipld/go-car v0.6.2 h1:Hlnl3Awgnq8icK+ze3iRghk805lu8YNq3wlREDTF2qc= +github.com/ipld/go-car v0.6.2/go.mod h1:oEGXdwp6bmxJCZ+rARSkDliTeYnVzv3++eXajZ+Bmr8= github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= -github.com/ipld/go-car/v2 v2.5.1 h1:U2ux9JS23upEgrJScW8VQuxmE94560kYxj9CQUpcfmk= -github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E= +github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 h1:0OZwzSYWIuiKEOXd/2vm5cMcEmmGLFn+1h6lHELCm3s= +github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= @@ -1087,8 +1084,8 @@ github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHt github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= -github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= +github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -1264,10 +1261,10 @@ github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxn github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.21.1 h1:xpfp8/t9+X2ip1l8Umap1/UGNnJ3RHJgKGAEsnRAlTo= -github.com/libp2p/go-libp2p-kad-dht v0.21.1/go.mod h1:Oy8wvbdjpB70eS5AaFaI68tOtrdo3KylTvXDjikxqFo= -github.com/libp2p/go-libp2p-kbucket v0.5.0 h1:g/7tVm8ACHDxH29BGrpsQlnNeu+6OF1A9bno/4/U1oA= -github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= +github.com/libp2p/go-libp2p-kad-dht v0.24.2 h1:zd7myKBKCmtZBhI3I0zm8xBkb28v3gmSEtQfBdAdFwc= +github.com/libp2p/go-libp2p-kad-dht v0.24.2/go.mod h1:BShPzRbK6+fN3hk8a0WGAYKpb8m4k+DtchkqouGTrSg= +github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= +github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= @@ -1303,8 +1300,8 @@ github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7 github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-resource-manager v0.2.1/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= -github.com/libp2p/go-libp2p-routing-helpers v0.6.1 h1:tI3rHOf/FDQsxC2pHBaOZiqPJ0MZYyzGAf4V45xla4U= -github.com/libp2p/go-libp2p-routing-helpers v0.6.1/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= +github.com/libp2p/go-libp2p-routing-helpers v0.7.0 h1:sirOYVD0wGWjkDwHZvinunIpaqPLBXkcnXApVHwZFGA= +github.com/libp2p/go-libp2p-routing-helpers v0.7.0/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= @@ -1814,8 +1811,8 @@ github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNw github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= -github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -2162,8 +2159,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2474,14 +2471,14 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2492,8 +2489,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2587,6 +2584,8 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNq gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 33132dc5e4..6d83e6c4c3 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -6,8 +6,8 @@ import ( "fmt" "strings" + "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-blockservice" - exchange "github.com/ipfs/go-ipfs-exchange-interface" logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index 3a99dd2c7d..19f98609fc 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -4,11 +4,11 @@ import ( "context" "fmt" + "github.com/ipfs/boxo/bitswap" + "github.com/ipfs/boxo/bitswap/network" + "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-datastore" - blockstore "github.com/ipfs/go-ipfs-blockstore" - exchange "github.com/ipfs/go-ipfs-exchange-interface" - "github.com/ipfs/go-libipfs/bitswap" - "github.com/ipfs/go-libipfs/bitswap/network" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" hst "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/protocol" @@ -48,7 +48,7 @@ func blockstoreFromDatastore(ctx context.Context, ds datastore.Batching) (blocks blockstore.CacheOpts{ HasBloomFilterSize: defaultBloomFilterSize, HasBloomFilterHashes: defaultBloomFilterHashes, - HasARCCacheSize: defaultARCCacheSize, + HasTwoQueueCacheSize: defaultARCCacheSize, }, ) } @@ -58,7 +58,7 @@ func blockstoreFromEDSStore(ctx context.Context, store *eds.Store) (blockstore.B ctx, store.Blockstore(), blockstore.CacheOpts{ - HasARCCacheSize: defaultARCCacheSize, + HasTwoQueueCacheSize: defaultARCCacheSize, }, ) } diff --git a/nodebuilder/p2p/ipld.go b/nodebuilder/p2p/ipld.go index 6278538825..ad32cc39f5 100644 --- a/nodebuilder/p2p/ipld.go +++ b/nodebuilder/p2p/ipld.go @@ -1,9 +1,9 @@ package p2p import ( + "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-blockservice" - blockstore "github.com/ipfs/go-ipfs-blockstore" - exchange "github.com/ipfs/go-ipfs-exchange-interface" ) // blockService constructs IPFS's BlockService for fetching arbitrary Merkle structures. diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index 1bb8bd243a..f0bd8fbbc5 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -7,11 +7,11 @@ import ( mrand "math/rand" "testing" + "github.com/ipfs/boxo/blockstore" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - blockstore "github.com/ipfs/go-ipfs-blockstore" - blocks "github.com/ipfs/go-libipfs/blocks" ) var _ blockstore.Blockstore = (*FraudulentBlockstore)(nil) diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 27d6669061..7427699f95 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -4,13 +4,13 @@ import ( "context" "testing" + "github.com/ipfs/boxo/bitswap" + "github.com/ipfs/boxo/bitswap/network" + "github.com/ipfs/boxo/blockstore" "github.com/ipfs/go-blockservice" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - blockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/ipfs/go-ipfs-routing/offline" - "github.com/ipfs/go-libipfs/bitswap" - "github.com/ipfs/go-libipfs/bitswap/network" record "github.com/libp2p/go-libp2p-record" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" diff --git a/share/eds/adapters.go b/share/eds/adapters.go index 8d98f092d7..fe498400b3 100644 --- a/share/eds/adapters.go +++ b/share/eds/adapters.go @@ -5,9 +5,9 @@ import ( "sync" "github.com/filecoin-project/dagstore" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - "github.com/ipfs/go-libipfs/blocks" ) var _ blockservice.BlockGetter = (*BlockGetter)(nil) diff --git a/share/eds/adapters_test.go b/share/eds/adapters_test.go index 6ccd29e99d..70165b81c8 100644 --- a/share/eds/adapters_test.go +++ b/share/eds/adapters_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/ipfs/go-libipfs/blocks" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/share/ipld" diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 3c63fa3945..9fc9d7f2f8 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -6,11 +6,11 @@ import ( "fmt" "github.com/filecoin-project/dagstore" + bstore "github.com/ipfs/boxo/blockstore" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" - bstore "github.com/ipfs/go-ipfs-blockstore" ipld "github.com/ipfs/go-ipld-format" - blocks "github.com/ipfs/go-libipfs/blocks" ) var _ bstore.Blockstore = (*blockstore)(nil) diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 70ce7a0e8e..6ebca8b779 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -9,8 +9,8 @@ import ( "os" "testing" + bstore "github.com/ipfs/boxo/blockstore" ds "github.com/ipfs/go-datastore" - bstore "github.com/ipfs/go-ipfs-blockstore" carv1 "github.com/ipld/go-car" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/share/eds/store.go b/share/eds/store.go index 5a85b9ab55..fd679e2591 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -16,8 +16,8 @@ import ( "github.com/filecoin-project/dagstore/index" "github.com/filecoin-project/dagstore/mount" "github.com/filecoin-project/dagstore/shard" + bstore "github.com/ipfs/boxo/blockstore" "github.com/ipfs/go-datastore" - bstore "github.com/ipfs/go-ipfs-blockstore" carv1 "github.com/ipld/go-car" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" diff --git a/share/ipld/get_shares_test.go b/share/ipld/get_shares_test.go index 7ee7704fc0..3a79fea238 100644 --- a/share/ipld/get_shares_test.go +++ b/share/ipld/get_shares_test.go @@ -10,11 +10,11 @@ import ( "testing" "time" + "github.com/ipfs/boxo/blockstore" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - blockstore "github.com/ipfs/go-ipfs-blockstore" offline "github.com/ipfs/go-ipfs-exchange-offline" mdutils "github.com/ipfs/go-merkledag/test" "github.com/minio/sha256-simd" diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index 485642dde4..e7d6b4b513 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -8,10 +8,10 @@ import ( "hash" "math/rand" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - "github.com/ipfs/go-libipfs/blocks" logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" mhcore "github.com/multiformats/go-multihash/core" From c2c0827e48dd578fc4e13e9dc1650d853ea0a778 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 18 Aug 2023 18:00:18 +0800 Subject: [PATCH 0746/1008] fix(share/discovery): discovery should try to connect to empty hosts peers (#2575) libp2p routed host will attempt to find address, if none are provided. First in `Peerstore`, then by calling `FindPeer`: ``` // if we were given some addresses, keep + use them. if len(pi.Addrs) > 0 { rh.Peerstore().AddAddrs(pi.ID, pi.Addrs, peerstore.TempAddrTTL) } // Check if we have some addresses in our recent memory. addrs := rh.Peerstore().Addrs(pi.ID) if len(addrs) < 1 { // no addrs? find some with the routing system. var err error addrs, err = rh.findPeerAddrs(ctx, pi.ID) if err != nil { return err } } ``` It is worth to try to connect in such case. --- share/p2p/discovery/discovery.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index 9779d72195..af6fef1f89 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -325,10 +325,6 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo d.metrics.observeHandlePeer(ctx, handlePeerSkipSelf) logger.Debug("skip handle: self discovery") return false - case len(peer.Addrs) == 0: - d.metrics.observeHandlePeer(ctx, handlePeerEmptyAddrs) - logger.Debug("skip handle: empty address list") - return false case d.set.Size() >= d.set.Limit(): d.metrics.observeHandlePeer(ctx, handlePeerEnoughPeers) logger.Debug("skip handle: enough peers found") From 323f603b622438cabd626caeffd24b6c9017d523 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Fri, 18 Aug 2023 19:05:54 +0800 Subject: [PATCH 0747/1008] feat(eds/store): add eds blockstore cache metrics (#2567) Add metrics allowing to see blockstore cache size and eviction pressure --- share/eds/accessor_cache.go | 63 +++++++++++++++++++++++++++++++++---- share/eds/metrics.go | 3 ++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/share/eds/accessor_cache.go b/share/eds/accessor_cache.go index eac79b946f..cd0f0537fa 100644 --- a/share/eds/accessor_cache.go +++ b/share/eds/accessor_cache.go @@ -1,6 +1,7 @@ package eds import ( + "context" "errors" "fmt" "reflect" @@ -9,6 +10,8 @@ import ( "github.com/filecoin-project/dagstore" "github.com/filecoin-project/dagstore/shard" lru "github.com/hashicorp/golang-lru" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" ) var ( @@ -32,11 +35,23 @@ type blockstoreCache struct { // caches the blockstore for a given shard for shard read affinity i.e. // further reads will likely be from the same shard. Maps (shard key -> blockstore). cache *lru.Cache + + metrics *cacheMetrics } func newBlockstoreCache(cacheSize int) (*blockstoreCache, error) { + bc := &blockstoreCache{} // instantiate the blockstore cache - bslru, err := lru.NewWithEvict(cacheSize, func(_ interface{}, val interface{}) { + bslru, err := lru.NewWithEvict(cacheSize, bc.evictFn()) + if err != nil { + return nil, fmt.Errorf("failed to instantiate blockstore cache: %w", err) + } + bc.cache = bslru + return bc, nil +} + +func (bc *blockstoreCache) evictFn() func(_ interface{}, val interface{}) { + return func(_ interface{}, val interface{}) { // ensure we close the blockstore for a shard when it's evicted so dagstore can gc it. abs, ok := val.(*accessorWithBlockstore) if !ok { @@ -46,14 +61,12 @@ func newBlockstoreCache(cacheSize int) (*blockstoreCache, error) { )) } - if err := abs.sa.Close(); err != nil { + err := abs.sa.Close() + if err != nil { log.Errorf("couldn't close accessor after cache eviction: %s", err) } - }) - if err != nil { - return nil, fmt.Errorf("failed to instantiate blockstore cache: %w", err) + bc.metrics.observeEvicted(err != nil) } - return &blockstoreCache{cache: bslru}, nil } // Get retrieves the blockstore for a given shard key from the cache. If the blockstore is not in @@ -117,3 +130,41 @@ func (bc *blockstoreCache) unsafeAdd( func shardKeyToStriped(sk shard.Key) byte { return sk.String()[len(sk.String())-1] } + +type cacheMetrics struct { + evictedCounter metric.Int64Counter +} + +func (bc *blockstoreCache) withMetrics() error { + evictedCounter, err := meter.Int64Counter("eds_blockstore_cache_evicted_counter", + metric.WithDescription("eds blockstore cache evicted event counter")) + if err != nil { + return err + } + + cacheSize, err := meter.Int64ObservableGauge("eds_blockstore_cache_size", + metric.WithDescription("total amount of items in blockstore cache"), + ) + if err != nil { + return err + } + + callback := func(ctx context.Context, observer metric.Observer) error { + observer.ObserveInt64(cacheSize, int64(bc.cache.Len())) + return nil + } + _, err = meter.RegisterCallback(callback, cacheSize) + if err != nil { + return err + } + bc.metrics = &cacheMetrics{evictedCounter: evictedCounter} + return nil +} + +func (m *cacheMetrics) observeEvicted(failed bool) { + if m == nil { + return + } + m.evictedCounter.Add(context.Background(), 1, metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} diff --git a/share/eds/metrics.go b/share/eds/metrics.go index 82adb246f1..6547e239cd 100644 --- a/share/eds/metrics.go +++ b/share/eds/metrics.go @@ -116,6 +116,9 @@ func (s *Store) WithMetrics() error { return err } + if err = s.cache.withMetrics(); err != nil { + return err + } s.metrics = &metrics{ putTime: putTime, getCARTime: getCARTime, From 1cc1da6b353d67f27ac9be98ee614f9076a1a787 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Fri, 18 Aug 2023 10:06:01 -0400 Subject: [PATCH 0748/1008] chore: bump to rsmt2d v0.11.0 (#2580) --- go.mod | 4 +-- go.sum | 8 ++--- share/eds/eds.go | 2 +- share/eds/edstest/testing.go | 2 +- share/eds/retriever_test.go | 4 +-- share/getters/getter_test.go | 12 ++++---- share/helpers.go | 58 ----------------------------------- share/ipld/add.go | 2 +- share/ipld/get_shares_test.go | 8 ++--- 9 files changed, 21 insertions(+), 79 deletions(-) delete mode 100644 share/helpers.go diff --git a/go.mod b/go.mod index 032a9a2a58..8310e845f2 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.18.1 - github.com/celestiaorg/rsmt2d v0.10.0 + github.com/celestiaorg/rsmt2d v0.11.0 github.com/cosmos/cosmos-sdk v0.46.13 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 @@ -221,7 +221,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/klauspost/reedsolomon v1.11.1 // indirect + github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect diff --git a/go.sum b/go.sum index 75924752f5..68837e301b 100644 --- a/go.sum +++ b/go.sum @@ -367,8 +367,8 @@ github.com/celestiaorg/nmt v0.18.1 h1:zU3apzW4y0fs0ilQA74XnEYW8FvRv0CUK2LXK66L3r github.com/celestiaorg/nmt v0.18.1/go.mod h1:0l8q6UYRju1xNrxtvV6NwPdW3lfsN6KuZ0htRnModdc= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= -github.com/celestiaorg/rsmt2d v0.10.0 h1:8dprr6CW5mCk5YPnbiLdirojw9YsJOE+XB+GORb8sT0= -github.com/celestiaorg/rsmt2d v0.10.0/go.mod h1:BiCZkCJfhDHUEOJKXUeu+CudjluecKvRTqHcuxKvodc= +github.com/celestiaorg/rsmt2d v0.11.0 h1:lcto/637WyTEZR3dLRoNvyuExfnUbxvdvKi3qz/2V4k= +github.com/celestiaorg/rsmt2d v0.11.0/go.mod h1:6Y580I3gVr0+OVFfW6m2JTwnCCmvW3WfbwSLfuT+HCA= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -1157,8 +1157,8 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/klauspost/reedsolomon v1.11.1 h1:0gCWQXOB8pVe1Y5SGozDA5t2qoVxX3prsV+qHgI/Fik= -github.com/klauspost/reedsolomon v1.11.1/go.mod h1:FXLZzlJIdfqEnQLdUKWNRuMZg747hZ4oYp2Ml60Lb/k= +github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= +github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= diff --git a/share/eds/eds.go b/share/eds/eds.go index f1efd3b433..bc35f59b56 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -114,7 +114,7 @@ func getProofs(ctx context.Context, eds *rsmt2d.ExtendedDataSquare) (map[cid.Cid } // recompute proofs from eds - shares := share.ExtractEDS(eds) + shares := eds.Flattened() shareCount := len(shares) if shareCount == 0 { return nil, ErrEmptySquare diff --git a/share/eds/edstest/testing.go b/share/eds/edstest/testing.go index e66b42a566..ddca285f0c 100644 --- a/share/eds/edstest/testing.go +++ b/share/eds/edstest/testing.go @@ -15,7 +15,7 @@ import ( func RandByzantineEDS(t *testing.T, size int) *rsmt2d.ExtendedDataSquare { eds := RandEDS(t, size) - shares := share.ExtractEDS(eds) + shares := eds.Flattened() copy(share.GetData(shares[0]), share.GetData(shares[1])) // corrupting eds eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) require.NoError(t, err, "failure to recompute the extended data square") diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 2277f894a1..ebccf0e384 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -63,7 +63,7 @@ func TestRetriever_Retrieve(t *testing.T) { require.NoError(t, err) out, err := r.Retrieve(ctx, &dah) require.NoError(t, err) - assert.True(t, share.EqualEDS(in, out)) + assert.True(t, in.Equals(out)) }) } } @@ -74,7 +74,7 @@ func TestRetriever_ByzantineError(t *testing.T) { defer cancel() bserv := mdutils.Bserv() - shares := share.ExtractEDS(edstest.RandEDS(t, width)) + shares := edstest.RandEDS(t, width).Flattened() _, err := ipld.ImportShares(ctx, shares, bserv) require.NoError(t, err) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 571129f029..b86b3b98df 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -52,7 +52,7 @@ func TestTeeGetter(t *testing.T) { retrievedEDS, err := tg.GetEDS(ctx, &dah) require.NoError(t, err) - require.True(t, share.EqualEDS(randEds, retrievedEDS)) + require.True(t, randEds.Equals(retrievedEDS)) // eds store now has the EDS and it can be retrieved ok, err = edsStore.Has(ctx, dah.Hash()) @@ -60,7 +60,7 @@ func TestTeeGetter(t *testing.T) { assert.NoError(t, err) finalEDS, err := edsStore.Get(ctx, dah.Hash()) assert.NoError(t, err) - require.True(t, share.EqualEDS(randEds, finalEDS)) + require.True(t, randEds.Equals(finalEDS)) }) t.Run("ShardAlreadyExistsDoesntError", func(t *testing.T) { @@ -70,12 +70,12 @@ func TestTeeGetter(t *testing.T) { retrievedEDS, err := tg.GetEDS(ctx, &dah) require.NoError(t, err) - require.True(t, share.EqualEDS(randEds, retrievedEDS)) + require.True(t, randEds.Equals(retrievedEDS)) // no error should be returned, even though the EDS identified by the DAH already exists retrievedEDS, err = tg.GetEDS(ctx, &dah) require.NoError(t, err) - require.True(t, share.EqualEDS(randEds, retrievedEDS)) + require.True(t, randEds.Equals(retrievedEDS)) }) } @@ -120,7 +120,7 @@ func TestStoreGetter(t *testing.T) { retrievedEDS, err := sg.GetEDS(ctx, &dah) require.NoError(t, err) - assert.True(t, share.EqualEDS(randEds, retrievedEDS)) + assert.True(t, randEds.Equals(retrievedEDS)) // root not found root := share.Root{} @@ -199,7 +199,7 @@ func TestIPLDGetter(t *testing.T) { retrievedEDS, err := sg.GetEDS(ctx, &dah) require.NoError(t, err) - assert.True(t, share.EqualEDS(randEds, retrievedEDS)) + assert.True(t, randEds.Equals(retrievedEDS)) }) t.Run("GetSharesByNamespace", func(t *testing.T) { diff --git a/share/helpers.go b/share/helpers.go deleted file mode 100644 index 0d729fd9b2..0000000000 --- a/share/helpers.go +++ /dev/null @@ -1,58 +0,0 @@ -package share - -import ( - "bytes" - - "github.com/celestiaorg/rsmt2d" -) - -// TODO(Wondertan): All these helpers should be methods on rsmt2d.EDS - -// ExtractODS returns the original shares of the given ExtendedDataSquare. This -// is a helper function for circumstances where AddShares must be used after the EDS has already -// been generated. -func ExtractODS(eds *rsmt2d.ExtendedDataSquare) []Share { - origWidth := eds.Width() / 2 - origShares := make([][]byte, origWidth*origWidth) - for i := uint(0); i < origWidth; i++ { - row := eds.Row(i) - for j := uint(0); j < origWidth; j++ { - origShares[(i*origWidth)+j] = row[j] - } - } - return origShares -} - -// ExtractEDS takes an EDS and extracts all shares from it in a flattened slice(row by row). -func ExtractEDS(eds *rsmt2d.ExtendedDataSquare) []Share { - flattenedEDSSize := eds.Width() * eds.Width() - out := make([][]byte, flattenedEDSSize) - count := 0 - for i := uint(0); i < eds.Width(); i++ { - for _, share := range eds.Row(i) { - out[count] = share - count++ - } - } - return out -} - -// EqualEDS check whether two given EDSes are equal. -// TODO(Wondertan): Propose use of int by default instead of uint for the sake convenience and -// Golang practices -func EqualEDS(a *rsmt2d.ExtendedDataSquare, b *rsmt2d.ExtendedDataSquare) bool { - if a.Width() != b.Width() { - return false - } - - for i := uint(0); i < a.Width(); i++ { - ar, br := a.Row(i), b.Row(i) - for j := 0; j < len(ar); j++ { - if !bytes.Equal(ar[j], br[j]) { - return false - } - } - } - - return true -} diff --git a/share/ipld/add.go b/share/ipld/add.go index 5807a320fd..7e5909669d 100644 --- a/share/ipld/add.go +++ b/share/ipld/add.go @@ -79,7 +79,7 @@ func ImportShares( } func ImportEDS(ctx context.Context, square *rsmt2d.ExtendedDataSquare, adder blockservice.BlockService) error { - shares := share.ExtractEDS(square) + shares := square.Flattened() _, err := ImportShares(ctx, shares, adder) return err } diff --git a/share/ipld/get_shares_test.go b/share/ipld/get_shares_test.go index 3a79fea238..11639cbec7 100644 --- a/share/ipld/get_shares_test.go +++ b/share/ipld/get_shares_test.go @@ -94,7 +94,7 @@ func TestBlockRecovery(t *testing.T) { colRoots, err := testEds.ColRoots() require.NoError(t, err) - flat := share.ExtractEDS(testEds) + flat := testEds.Flattened() // recover a partially complete square rdata := removeRandShares(flat, tc.d) @@ -116,7 +116,7 @@ func TestBlockRecovery(t *testing.T) { reds, err := rsmt2d.ImportExtendedDataSquare(rdata, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(squareSize)) require.NoError(t, err) // check that the squares are equal - assert.Equal(t, share.ExtractEDS(testEds), share.ExtractEDS(reds)) + assert.Equal(t, testEds.Flattened(), reds.Flattened()) }) } } @@ -133,7 +133,7 @@ func Test_ConvertEDStoShares(t *testing.T) { ) require.NoError(t, err) - resshares := share.ExtractODS(testEds) + resshares := testEds.FlattenedODS() require.Equal(t, shares, resshares) } @@ -434,7 +434,7 @@ func TestBatchSize(t *testing.T) { bs := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) randEds := edstest.RandEDS(t, tt.origWidth) - _, err := AddShares(ctx, share.ExtractODS(randEds), blockservice.New(bs, offline.Exchange(bs))) + _, err := AddShares(ctx, randEds.FlattenedODS(), blockservice.New(bs, offline.Exchange(bs))) require.NoError(t, err) out, err := bs.AllKeysChan(ctx) From 04e29282b8f861d693ae2fdb91f9e445002e3063 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Sat, 19 Aug 2023 03:08:16 +0800 Subject: [PATCH 0749/1008] fix(share/ipld): Remove proofs from leaked ctx (#2574) --- core/exchange.go | 4 ++++ core/listener.go | 2 ++ share/eds/eds.go | 8 +++++--- share/getters/tee.go | 5 ++++- share/ipld/nmt_adder.go | 20 ++++++++++++++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/core/exchange.go b/core/exchange.go index a9b3a532d5..bed2404195 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -108,6 +108,8 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende // extend block data adder := ipld.NewProofsAdder(int(block.Data.SquareSize)) + defer adder.Purge() + eds, err := extendBlock(block.Data, block.Header.Version.App, nmt.NodeVisitor(adder.VisitFn())) if err != nil { return nil, fmt.Errorf("extending block data for height %d: %w", &block.Height, err) @@ -148,6 +150,8 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 // extend block data adder := ipld.NewProofsAdder(int(b.Data.SquareSize)) + defer adder.Purge() + eds, err := extendBlock(b.Data, b.Header.Version.App, nmt.NodeVisitor(adder.VisitFn())) if err != nil { return nil, fmt.Errorf("extending block data for height %d: %w", b.Header.Height, err) diff --git a/core/listener.go b/core/listener.go index 17be12a780..565fc62032 100644 --- a/core/listener.go +++ b/core/listener.go @@ -153,6 +153,8 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS ) // extend block data adder := ipld.NewProofsAdder(int(b.Data.SquareSize)) + defer adder.Purge() + eds, err := extendBlock(b.Data, b.Header.Version.App, nmt.NodeVisitor(adder.VisitFn())) if err != nil { return fmt.Errorf("extending block data: %w", err) diff --git a/share/eds/eds.go b/share/eds/eds.go index bc35f59b56..cf38a14cbc 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -108,9 +108,9 @@ func writeProofs(ctx context.Context, eds *rsmt2d.ExtendedDataSquare, w io.Write func getProofs(ctx context.Context, eds *rsmt2d.ExtendedDataSquare) (map[cid.Cid][]byte, error) { // check if there are proofs collected by ipld.ProofsAdder in previous reconstruction of eds - proofs := ipld.ProofsAdderFromCtx(ctx).Proofs() - if proofs != nil { - return proofs, nil + if adder := ipld.ProofsAdderFromCtx(ctx); adder != nil { + defer adder.Purge() + return adder.Proofs(), nil } // recompute proofs from eds @@ -124,6 +124,8 @@ func getProofs(ctx context.Context, eds *rsmt2d.ExtendedDataSquare) (map[cid.Cid // this adder ignores leaves, so that they are not added to the store we iterate through in // writeProofs adder := ipld.NewProofsAdder(odsWidth * 2) + defer adder.Purge() + eds, err := rsmt2d.ImportExtendedDataSquare( shares, share.DefaultRSMT2DCodec(), diff --git a/share/getters/tee.go b/share/getters/tee.go index 8ee1e91390..9c89b2dec5 100644 --- a/share/getters/tee.go +++ b/share/getters/tee.go @@ -52,7 +52,10 @@ func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d. utils.SetStatusAndEnd(span, err) }() - ctx = ipld.CtxWithProofsAdder(ctx, ipld.NewProofsAdder(len(root.RowRoots))) + adder := ipld.NewProofsAdder(len(root.RowRoots)) + ctx = ipld.CtxWithProofsAdder(ctx, adder) + defer adder.Purge() + eds, err = tg.getter.GetEDS(ctx, root) if err != nil { return nil, err diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index 0b4c521a80..d090c679d9 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -101,11 +101,13 @@ func BatchSize(squareSize int) int { return (squareSize*2-1)*squareSize*2 - (squareSize * squareSize) } +// ProofsAdder is used to collect proof nodes, while traversing merkle tree type ProofsAdder struct { lock sync.RWMutex proofs map[cid.Cid][]byte } +// NewProofsAdder creates new instance of ProofsAdder. func NewProofsAdder(squareSize int) *ProofsAdder { return &ProofsAdder{ // preallocate map to fit all inner nodes for given square size @@ -113,10 +115,14 @@ func NewProofsAdder(squareSize int) *ProofsAdder { } } +// CtxWithProofsAdder creates context, that will contain ProofsAdder. If context is leaked to +// another go-routine, proofs will be not collected by gc. To prevent it, use Purge after Proofs +// are collected from adder, to preemptively release memory allocated for proofs. func CtxWithProofsAdder(ctx context.Context, adder *ProofsAdder) context.Context { return context.WithValue(ctx, proofsAdderKey, adder) } +// ProofsAdderFromCtx extracts ProofsAdder from context func ProofsAdderFromCtx(ctx context.Context) *ProofsAdder { val := ctx.Value(proofsAdderKey) adder, ok := val.(*ProofsAdder) @@ -126,6 +132,7 @@ func ProofsAdderFromCtx(ctx context.Context) *ProofsAdder { return adder } +// Proofs returns proofs collected by ProofsAdder func (a *ProofsAdder) Proofs() map[cid.Cid][]byte { if a == nil { return nil @@ -136,6 +143,7 @@ func (a *ProofsAdder) Proofs() map[cid.Cid][]byte { return a.proofs } +// VisitFn returns NodeVisitorFn, that will collect proof nodes while traversing merkle tree. func (a *ProofsAdder) VisitFn() nmt.NodeVisitorFn { if a == nil { return nil @@ -151,6 +159,18 @@ func (a *ProofsAdder) VisitFn() nmt.NodeVisitorFn { return a.visitInnerNodes } +// Purge removed proofs from ProofsAdder allowing GC to collect the memory +func (a *ProofsAdder) Purge() { + if a == nil { + return + } + + a.lock.Lock() + defer a.lock.Unlock() + + a.proofs = nil +} + func (a *ProofsAdder) visitInnerNodes(hash []byte, children ...[]byte) { switch len(children) { case 1: From 13e9b1f99aa5c8a528e4419f82fbddc37ef688b6 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 21 Aug 2023 17:26:11 +0200 Subject: [PATCH 0750/1008] feat(nodebuilder/state): Provide stubbed state module if a core endpoint not provided (#2577) This is a pre-requisite for #2511 It makes a check in CoreAccessor constructor to see if a core endpoint was provided, and returns nil CoreAccessor if not. A stubbed state module will then be provided if no core endpoint was provided so that errors are more readable. Previously, node start logic relied on the fact that the grpc Dial inside CoreAccessor.Start was non-blocking so it could silently fail under the hood and any calls made on state Module would return errors from the inability to reach the address that is the default for the core config (which was confusing). --- nodebuilder/core/config.go | 16 ++++- nodebuilder/fraud/lifecycle.go | 8 +++ nodebuilder/module.go | 2 +- nodebuilder/node_light_test.go | 10 +++ nodebuilder/node_test.go | 12 ---- nodebuilder/settings.go | 7 +- nodebuilder/state/core.go | 4 +- nodebuilder/state/module.go | 11 ++-- nodebuilder/state/stub.go | 116 +++++++++++++++++++++++++++++++++ state/core_access.go | 6 +- 10 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 nodebuilder/state/stub.go diff --git a/nodebuilder/core/config.go b/nodebuilder/core/config.go index c0261c7c86..d0455be86c 100644 --- a/nodebuilder/core/config.go +++ b/nodebuilder/core/config.go @@ -18,14 +18,18 @@ type Config struct { // node's connection to a Celestia-Core endpoint. func DefaultConfig() Config { return Config{ - IP: "0.0.0.0", - RPCPort: "0", - GRPCPort: "0", + IP: "", + RPCPort: "", + GRPCPort: "", } } // Validate performs basic validation of the config. func (cfg *Config) Validate() error { + if !cfg.IsEndpointConfigured() { + return nil + } + ip, err := utils.ValidateAddr(cfg.IP) if err != nil { return err @@ -41,3 +45,9 @@ func (cfg *Config) Validate() error { } return nil } + +// IsEndpointConfigured returns whether a core endpoint has been set +// on the config (true if set). +func (cfg *Config) IsEndpointConfigured() bool { + return cfg.IP != "" +} diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go index 24ed402f5d..cffa4d0f56 100644 --- a/nodebuilder/fraud/lifecycle.go +++ b/nodebuilder/fraud/lifecycle.go @@ -31,6 +31,10 @@ type ServiceBreaker[S service] struct { // Start starts the inner service if there are no fraud proofs stored. // Subscribes for fraud and stops the service whenever necessary. func (breaker *ServiceBreaker[S]) Start(ctx context.Context) error { + if breaker == nil { + return nil + } + proofs, err := breaker.FraudServ.Get(ctx, breaker.FraudType) switch err { default: @@ -57,6 +61,10 @@ func (breaker *ServiceBreaker[S]) Start(ctx context.Context) error { // Stop stops the service and cancels subscription. func (breaker *ServiceBreaker[S]) Stop(ctx context.Context) error { + if breaker == nil { + return nil + } + if breaker.ctx.Err() != nil { // short circuit if the service was already stopped return nil diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 51edc26c72..719705e35c 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -45,7 +45,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store fx.Supply(signer), // modules provided by the node p2p.ConstructModule(tp, &cfg.P2P), - state.ConstructModule(tp, &cfg.State), + state.ConstructModule(tp, &cfg.State, &cfg.Core), header.ConstructModule(tp, &cfg.Header), share.ConstructModule(tp, &cfg.Share), rpc.ConstructModule(tp, &cfg.RPC), diff --git a/nodebuilder/node_light_test.go b/nodebuilder/node_light_test.go index a7a70d0622..7138a23c9e 100644 --- a/nodebuilder/node_light_test.go +++ b/nodebuilder/node_light_test.go @@ -1,6 +1,7 @@ package nodebuilder import ( + "context" "crypto/rand" "testing" @@ -11,6 +12,7 @@ import ( nodebuilder "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/state" ) func TestNewLightWithP2PKey(t *testing.T) { @@ -44,3 +46,11 @@ func TestLight_WithNetwork(t *testing.T) { require.NotNil(t, node) assert.Equal(t, p2p.Private, node.Network) } + +// TestLight_WithStubbedCoreAccessor ensures that a node started without +// a core connection will return a stubbed StateModule. +func TestLight_WithStubbedCoreAccessor(t *testing.T) { + node := TestNode(t, nodebuilder.Light) + _, err := node.StateServ.Balance(context.Background()) + assert.ErrorIs(t, state.ErrNoStateAccess, err) +} diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index f481162b5c..3fc3f4f02a 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -43,14 +43,8 @@ func TestLifecycle(t *testing.T) { err := node.Start(ctx) require.NoError(t, err) - // ensure the state service is running - require.False(t, node.StateServ.IsStopped(ctx)) - err = node.Stop(ctx) require.NoError(t, err) - - // ensure the state service is stopped - require.True(t, node.StateServ.IsStopped(ctx)) }) } } @@ -96,14 +90,8 @@ func TestLifecycle_WithMetrics(t *testing.T) { err := node.Start(ctx) require.NoError(t, err) - // ensure the state service is running - require.False(t, node.StateServ.IsStopped(ctx)) - err = node.Stop(ctx) require.NoError(t, err) - - // ensure the state service is stopped - require.True(t, node.StateServ.IsStopped(ctx)) }) } } diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index b2a29723db..97440fa7dc 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -74,7 +74,12 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti baseComponents := fx.Options( fx.Supply(metricOpts), fx.Invoke(initializeMetrics), - fx.Invoke(state.WithMetrics), + fx.Invoke(func(ca *state.CoreAccessor) { + if ca == nil { + return + } + state.WithMetrics(ca) + }), fx.Invoke(fraud.WithMetrics), fx.Invoke(node.WithMetrics), fx.Invoke(modheader.WithMetrics), diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index a3eb7f7b6d..4636e0f099 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -19,10 +19,10 @@ func coreAccessor( signer *apptypes.KeyringSigner, sync *sync.Syncer[*header.ExtendedHeader], fraudServ libfraud.Service, -) (*state.CoreAccessor, *modfraud.ServiceBreaker[*state.CoreAccessor]) { +) (*state.CoreAccessor, Module, *modfraud.ServiceBreaker[*state.CoreAccessor]) { ca := state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) - return ca, &modfraud.ServiceBreaker[*state.CoreAccessor]{ + return ca, ca, &modfraud.ServiceBreaker[*state.CoreAccessor]{ Service: ca, FraudType: byzantine.BadEncoding, FraudServ: fraudServ, diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index 24305dabe1..fe90d023eb 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -6,6 +6,8 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/nodebuilder/core" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/state" @@ -15,14 +17,14 @@ var log = logging.Logger("module/state") // ConstructModule provides all components necessary to construct the // state service. -func ConstructModule(tp node.Type, cfg *Config) fx.Option { +func ConstructModule(tp node.Type, cfg *Config, coreCfg *core.Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() baseComponents := fx.Options( fx.Supply(*cfg), fx.Error(cfgErr), - fx.Provide(fx.Annotate( + fxutil.ProvideIf(coreCfg.IsEndpointConfigured(), fx.Annotate( coreAccessor, fx.OnStart(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*state.CoreAccessor]) error { return breaker.Start(ctx) @@ -31,9 +33,8 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return breaker.Stop(ctx) }), )), - // the module is needed for the handler - fx.Provide(func(ca *state.CoreAccessor) Module { - return ca + fxutil.ProvideIf(!coreCfg.IsEndpointConfigured(), func() (*state.CoreAccessor, Module) { + return nil, &stubbedStateModule{} }), ) diff --git a/nodebuilder/state/stub.go b/nodebuilder/state/stub.go new file mode 100644 index 0000000000..94326fed5e --- /dev/null +++ b/nodebuilder/state/stub.go @@ -0,0 +1,116 @@ +package state + +import ( + "context" + "errors" + + "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/state" +) + +var ErrNoStateAccess = errors.New("node is running without state access") + +// stubbedStateModule provides a stub for the state module to return +// errors when state endpoints are accessed without a running connection +// to a core endpoint. +type stubbedStateModule struct{} + +func (s stubbedStateModule) IsStopped(context.Context) bool { + return true +} + +func (s stubbedStateModule) AccountAddress(context.Context) (state.Address, error) { + return state.Address{}, ErrNoStateAccess +} + +func (s stubbedStateModule) Balance(context.Context) (*state.Balance, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) BalanceForAddress( + context.Context, + state.Address, +) (*state.Balance, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) Transfer( + _ context.Context, + _ state.AccAddress, + _, _ state.Int, + _ uint64, +) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) SubmitTx(context.Context, state.Tx) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) SubmitPayForBlob( + context.Context, + state.Int, + uint64, + []*blob.Blob, +) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) CancelUnbondingDelegation( + _ context.Context, + _ state.ValAddress, + _, _, _ state.Int, + _ uint64, +) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) BeginRedelegate( + _ context.Context, + _, _ state.ValAddress, + _, _ state.Int, + _ uint64, +) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) Undelegate( + _ context.Context, + _ state.ValAddress, + _, _ state.Int, + _ uint64, +) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) Delegate( + _ context.Context, + _ state.ValAddress, + _, _ state.Int, + _ uint64, +) (*state.TxResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) QueryDelegation( + context.Context, + state.ValAddress, +) (*types.QueryDelegationResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) QueryUnbonding( + context.Context, + state.ValAddress, +) (*types.QueryUnbondingDelegationResponse, error) { + return nil, ErrNoStateAccess +} + +func (s stubbedStateModule) QueryRedelegations( + _ context.Context, + _, _ state.ValAddress, +) (*types.QueryRedelegationsResponse, error) { + return nil, ErrNoStateAccess +} diff --git a/state/core_access.go b/state/core_access.go index 3fefaa3ed9..1a50b15b72 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -90,7 +90,11 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { // dial given celestia-core endpoint endpoint := fmt.Sprintf("%s:%s", ca.coreIP, ca.grpcPort) - client, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) + client, err := grpc.DialContext( + ctx, + endpoint, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) if err != nil { return err } From f839ee3c444b79620e5fc4b393f98347044ca4f2 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Tue, 22 Aug 2023 04:45:52 -0500 Subject: [PATCH 0751/1008] chore!: update celestia-app to v1.0.0-rc12 (#2581) ## Overview bumps app to v1.0.0-rc12 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- blob/service_test.go | 9 +++++---- go.mod | 10 +++++----- go.sum | 16 ++++++++-------- nodebuilder/tests/nd_test.go | 8 ++++++-- share/namespace.go | 25 +++++++++++++++++-------- state/core_access.go | 1 + 6 files changed, 42 insertions(+), 27 deletions(-) diff --git a/blob/service_test.go b/blob/service_test.go index 51475178d2..1dcabe7129 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/go-header/store" @@ -309,9 +310,9 @@ func TestService_GetSingleBlobWithoutPadding(t *testing.T) { ns1, ns2 := blobs[0].Namespace().ToAppNamespace(), blobs[1].Namespace().ToAppNamespace() - padding0, err := shares.NamespacePaddingShare(ns1) + padding0, err := shares.NamespacePaddingShare(ns1, appconsts.ShareVersionZero) require.NoError(t, err) - padding1, err := shares.NamespacePaddingShare(ns2) + padding1, err := shares.NamespacePaddingShare(ns2, appconsts.ShareVersionZero) require.NoError(t, err) rawShares0, err := BlobsToShares(blobs[0]) require.NoError(t, err) @@ -354,9 +355,9 @@ func TestService_GetAllWithoutPadding(t *testing.T) { ns1, ns2 := blobs[0].Namespace().ToAppNamespace(), blobs[1].Namespace().ToAppNamespace() - padding0, err := shares.NamespacePaddingShare(ns1) + padding0, err := shares.NamespacePaddingShare(ns1, appconsts.ShareVersionZero) require.NoError(t, err) - padding1, err := shares.NamespacePaddingShare(ns2) + padding1, err := shares.NamespacePaddingShare(ns2, appconsts.ShareVersionZero) require.NoError(t, err) rawShares0, err := BlobsToShares(blobs[0]) require.NoError(t, err) diff --git a/go.mod b/go.mod index 8310e845f2..70b246143c 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc11 + github.com/celestiaorg/celestia-app v1.0.0-rc12 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.1.2 github.com/celestiaorg/go-header v0.2.12 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.18.1 github.com/celestiaorg/rsmt2d v0.11.0 - github.com/cosmos/cosmos-sdk v0.46.13 + github.com/cosmos/cosmos-sdk v0.46.14 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 github.com/etclabscore/go-openrpc-reflect v0.0.37 @@ -55,7 +55,7 @@ require ( github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/prometheus/client_golang v1.14.0 - github.com/pyroscope-io/client v0.7.1 + github.com/pyroscope-io/client v0.7.2 github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -270,7 +270,7 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/pyroscope-io/godeltaprof v0.1.0 // indirect + github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect @@ -334,7 +334,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app diff --git a/go.sum b/go.sum index 68837e301b..95422004c7 100644 --- a/go.sum +++ b/go.sum @@ -343,12 +343,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc11 h1:u94drhPdvgwNvx0F2xGib+fyOyP+fIvp3aL6Avd/HT0= -github.com/celestiaorg/celestia-app v1.0.0-rc11/go.mod h1:A17xfzurB2RHpplh6+nGMeC9hCquu0aMYnanOeifDO0= +github.com/celestiaorg/celestia-app v1.0.0-rc12 h1:ko9hPD4oz1UTS4ZqzikGVQ0wXi5+4kEhDb7decx5Ehs= +github.com/celestiaorg/celestia-app v1.0.0-rc12/go.mod h1:vXvKEudUpdJCvUr79qVKKJ0Xo7ofsuU80+Hs9aKGjvU= github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHVxcTzhzQW44KgTcooR3OxnK4= github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= -github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13 h1:CxEQDQEQR1ypB+VUmCISIqFVmHfb+mx8x+zh7rHbyU8= -github.com/celestiaorg/cosmos-sdk v1.16.1-sdk-v0.46.13/go.mod h1:xpBZc/OYZ736hp0IZlBGNUhEgCD9C+bKs8yNLZibyv0= +github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 h1:PckXGxLJjXv97VO3xS8NPHN5oO83X5nvJLbc/4s8jUM= +github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14/go.mod h1:70Go8qNy7YAb1PUcHCChRHNX2ke7c9jgUIEklUX+Mac= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= @@ -1781,10 +1781,10 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/pyroscope-io/client v0.7.1 h1:yFRhj3vbgjBxehvxQmedmUWJQ4CAfCHhn+itPsuWsHw= -github.com/pyroscope-io/client v0.7.1/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= -github.com/pyroscope-io/godeltaprof v0.1.0 h1:UBqtjt0yZi4jTxqZmLAs34XG6ycS3vUTlhEUSq4NHLE= -github.com/pyroscope-io/godeltaprof v0.1.0/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= +github.com/pyroscope-io/client v0.7.2 h1:OX2qdUQsS8RSkn/3C8isD7f/P0YiZQlRbAlecAaj/R8= +github.com/pyroscope-io/client v0.7.2/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8= +github.com/pyroscope-io/godeltaprof v0.1.2 h1:MdlEmYELd5w+lvIzmZvXGNMVzW2Qc9jDMuJaPOR75g4= +github.com/pyroscope-io/godeltaprof v0.1.2/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKAvs5drr0511Ki8ic= github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= diff --git a/nodebuilder/tests/nd_test.go b/nodebuilder/tests/nd_test.go index 955cff2ffe..cb02e2a178 100644 --- a/nodebuilder/tests/nd_test.go +++ b/nodebuilder/tests/nd_test.go @@ -48,8 +48,12 @@ func TestShrexNDFromLights(t *testing.T) { // wait for chain to be filled require.NoError(t, <-fillDn) - // first 2 blocks are not filled with data - for i := 3; i < blocks; i++ { + // first 15 blocks are not filled with data + // + // TODO: we need to stop guessing + // the block that actually has transactions. We can get this data from the + // response returned by FillBlock. + for i := 16; i < blocks; i++ { h, err := bridge.HeaderServ.GetByHeight(ctx, uint64(i)) require.NoError(t, err) diff --git a/share/namespace.go b/share/namespace.go index 276575a309..e7ed7dc0d6 100644 --- a/share/namespace.go +++ b/share/namespace.go @@ -14,13 +14,19 @@ const NamespaceSize = appns.NamespaceSize // Various reserved namespaces. var ( - MaxReservedNamespace = Namespace(appns.MaxReservedNamespace.Bytes()) - ParitySharesNamespace = Namespace(appns.ParitySharesNamespace.Bytes()) - TailPaddingNamespace = Namespace(appns.TailPaddingNamespace.Bytes()) - ReservedPaddingNamespace = Namespace(appns.ReservedPaddingNamespace.Bytes()) - TxNamespace = Namespace(appns.TxNamespace.Bytes()) - PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) - ISRNamespace = Namespace(appns.IntermediateStateRootsNamespace.Bytes()) + // MaxPrimaryReservedNamespace is the highest primary reserved namespace. + // Namespaces lower than this are reserved for protocol use. + MaxPrimaryReservedNamespace = Namespace(appns.MaxPrimaryReservedNamespace.Bytes()) + // MinSecondaryReservedNamespace is the lowest secondary reserved namespace + // reserved for protocol use. Namespaces higher than this are reserved for + // protocol use. + MinSecondaryReservedNamespace = Namespace(appns.MinSecondaryReservedNamespace.Bytes()) + ParitySharesNamespace = Namespace(appns.ParitySharesNamespace.Bytes()) + TailPaddingNamespace = Namespace(appns.TailPaddingNamespace.Bytes()) + ReservedPaddingNamespace = Namespace(appns.PrimaryReservedPaddingNamespace.Bytes()) + TxNamespace = Namespace(appns.TxNamespace.Bytes()) + PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) + ISRNamespace = Namespace(appns.IntermediateStateRootsNamespace.Bytes()) ) // Namespace represents namespace of a Share. @@ -124,7 +130,10 @@ func (n Namespace) ValidateForBlob() error { if err := n.ValidateForData(); err != nil { return err } - if bytes.Compare(n, MaxReservedNamespace) < 1 { + if bytes.Compare(n, MaxPrimaryReservedNamespace) < 1 { + return fmt.Errorf("invalid blob namespace(%s): reserved namespaces are forbidden", n) + } + if bytes.Compare(n, MinSecondaryReservedNamespace) > -1 { return fmt.Errorf("invalid blob namespace(%s): reserved namespaces are forbidden", n) } return nil diff --git a/state/core_access.go b/state/core_access.go index 1a50b15b72..73d525347f 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -182,6 +182,7 @@ func (ca *CoreAccessor) SubmitPayForBlob( ctx, ca.signer, ca.coreConn, + sdktx.BroadcastMode_BROADCAST_MODE_BLOCK, appblobs, apptypes.SetGasLimit(gasLim), withFee(fee), From 44a0b374ab82556e02f29f81bed7efb1932b3871 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 22 Aug 2023 12:23:10 +0200 Subject: [PATCH 0752/1008] chore: bump deps + go1.21 (#2589) Update to go1.21 was forced by `go mod tidy`. Theoretically, we could still live on go1.20 just supporting builds for people who run go1.21, but for some uninvestigated reason, we are forced to. Closes #2566 Closes #2247 --- .github/workflows/go-ci.yml | 2 +- Dockerfile | 2 +- Makefile | 6 +- README.md | 2 +- go.mod | 80 +++++------ go.sum | 212 +++++++++++++++++++---------- share/availability/test/testing.go | 2 +- share/eds/byzantine/share_proof.go | 2 +- share/eds/eds.go | 2 +- share/getter.go | 3 +- share/getters/getter_test.go | 2 +- share/getters/shrex_test.go | 3 +- share/getters/utils_test.go | 3 +- share/ipld/get_shares_test.go | 4 +- share/p2p/shrexnd/server.go | 2 +- 15 files changed, 196 insertions(+), 131 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index e6c4e5881b..7435b7912f 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -4,7 +4,7 @@ on: workflow_call: env: - GO_VERSION: '1.20' + GO_VERSION: '1.21' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/Dockerfile b/Dockerfile index 2d07babc94..c8e9bb2e1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.20-alpine3.18 as builder +FROM docker.io/golang:1.21-alpine3.18 as builder # hadolint ignore=DL3018 RUN apk update && apk add --no-cache \ diff --git a/Makefile b/Makefile index 1044d75d3b..feef6172aa 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ install-key: fmt: sort-imports @find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs gofmt -w -s @find . -name '*.go' -type f -not -path "*.git*" -not -name '*.pb.go' -not -name '*pb_test.go' | xargs goimports -w -local github.com/celestiaorg - @go mod tidy -compat=1.17 + @go mod tidy -compat=1.20 @cfmt -w -m=100 ./... @markdownlint --fix --quiet --config .markdownlint.yaml . .PHONY: fmt @@ -161,14 +161,14 @@ openrpc-gen: lint-imports: @echo "--> Running imports linter" @for file in `find . -type f -name '*.go'`; \ - do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout $$file \ + do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/"$(PROJECTNAME)"" -output stdout $$file \ || exit 1; \ done; .PHONY: lint-imports ## sort-imports: Sort Go imports. sort-imports: - @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... + @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/"$(PROJECTNAME)"" -output stdout . .PHONY: sort-imports ## adr-gen: Generate ADR from template. Must set NUM and TITLE parameters. diff --git a/README.md b/README.md index 76c722275d..0711c2d223 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-avai | Requirement | Notes | | ----------- |----------------| -| Go version | 1.20 or higher | +| Go version | 1.21 or higher | ## System Requirements diff --git a/go.mod b/go.mod index 70b246143c..91b55ad256 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,21 @@ module github.com/celestiaorg/celestia-node -go 1.20 +go 1.21 + +toolchain go1.21.0 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch require ( cosmossdk.io/errors v1.0.0 - cosmossdk.io/math v1.0.0-rc.0 + cosmossdk.io/math v1.1.1 github.com/BurntSushi/toml v1.3.2 - github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 + github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc12 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.1.2 - github.com/celestiaorg/go-header v0.2.12 + github.com/celestiaorg/go-header v0.2.13 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.18.1 github.com/celestiaorg/rsmt2d v0.11.0 @@ -27,34 +29,31 @@ require ( github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 - github.com/hashicorp/go-retryablehttp v0.7.2 - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/hashicorp/go-retryablehttp v0.7.4 + github.com/hashicorp/golang-lru v1.0.2 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.11.0 github.com/ipfs/go-block-format v0.1.2 github.com/ipfs/go-blockservice v0.5.1 // down 1 version, 0.5.2 is marked as deprecated and raises alerts github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 - github.com/ipfs/go-ipfs-exchange-offline v0.3.0 - github.com/ipfs/go-ipfs-routing v0.3.0 github.com/ipfs/go-ipld-cbor v0.0.6 github.com/ipfs/go-ipld-format v0.5.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.11.0 github.com/ipld/go-car v0.6.2 - github.com/libp2p/go-libp2p v0.29.0 - github.com/libp2p/go-libp2p-kad-dht v0.24.2 + github.com/libp2p/go-libp2p v0.30.0 + github.com/libp2p/go-libp2p-kad-dht v0.25.0 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 - github.com/libp2p/go-libp2p-routing-helpers v0.7.0 - github.com/minio/sha256-simd v1.0.1 + github.com/libp2p/go-libp2p-routing-helpers v0.7.1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.10.1 + github.com/multiformats/go-multiaddr v0.11.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.16.0 github.com/pyroscope-io/client v0.7.2 github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.7.0 @@ -70,27 +69,28 @@ require ( go.opentelemetry.io/otel/sdk v1.16.0 go.opentelemetry.io/otel/sdk/metric v0.39.0 go.opentelemetry.io/otel/trace v1.16.0 - go.opentelemetry.io/proto/otlp v0.19.0 + go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 - go.uber.org/zap v1.24.0 + go.uber.org/zap v1.25.0 golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 golang.org/x/text v0.12.0 - google.golang.org/grpc v1.56.2 + google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 ) require ( - cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.19.1 // indirect + cloud.google.com/go v0.110.6 // indirect + cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.13.0 // indirect - cloud.google.com/go/storage v1.28.1 // indirect + cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/storage v1.30.1 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect + github.com/Jorropo/jsync v1.0.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/StackExchange/wmi v1.2.1 // indirect @@ -116,7 +116,7 @@ require ( github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha8 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect - github.com/cosmos/gogoproto v1.4.10 // indirect + github.com/cosmos/gogoproto v1.4.11 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.6 // indirect github.com/cosmos/ibc-go/v6 v6.2.0 // indirect @@ -169,10 +169,11 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect + github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.1 // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -188,7 +189,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect @@ -203,6 +204,7 @@ require ( github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect + github.com/ipfs/go-ipfs-exchange-offline v0.3.0 // indirect github.com/ipfs/go-ipfs-pq v0.0.3 // indirect github.com/ipfs/go-ipfs-util v0.0.3 // indirect github.com/ipfs/go-ipld-legacy v0.2.1 // indirect @@ -232,10 +234,10 @@ require ( github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/libp2p/go-reuseport v0.3.0 // indirect + github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -246,6 +248,7 @@ require ( github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/highwayhash v1.0.2 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect @@ -257,7 +260,7 @@ require ( github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect - github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect + github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -269,12 +272,11 @@ require ( github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.2 // indirect - github.com/quic-go/qtls-go1-20 v0.2.2 // indirect - github.com/quic-go/quic-go v0.36.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.2 // indirect + github.com/quic-go/quic-go v0.37.6 // indirect github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect @@ -312,18 +314,18 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/term v0.11.0 // indirect - golang.org/x/tools v0.11.0 // indirect + golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/api v0.114.0 // indirect + google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 95422004c7..84e5eb5991 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q= +cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -75,8 +75,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -116,13 +116,12 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -180,8 +179,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -196,8 +195,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= -cosmossdk.io/math v1.0.0-rc.0 h1:ml46ukocrAAoBpYKMidF0R2tQJ1Uxfns0yH8wqgMAFc= -cosmossdk.io/math v1.0.0-rc.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k= +cosmossdk.io/math v1.1.1 h1:Eqx44E6fSvG055Z6VNiCLWA9fra0JSyP0kQX7VvNNfk= +cosmossdk.io/math v1.1.1/go.mod h1:uFRkSZDz38KjWjm6jN+/sI8tJWQxbGwxcjOTzapWSpE= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -220,6 +219,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3 github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -230,13 +230,18 @@ github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU= +github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -256,11 +261,13 @@ github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSa github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= +github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 h1:T3+cD5fYvuH36h7EZq+TDpm+d8a6FSD4pQsbmuGGQ8o= github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= +github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b h1:doCpXjVwui6HUN+xgNsNS3SZ0/jUZ68Eb+mJRNOZfog= +github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -323,6 +330,7 @@ github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= +github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= @@ -339,6 +347,7 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bufbuild/protocompile v0.1.0 h1:HjgJBI85hY/qmW5tw/66sNDZ7z0UDdVSi/5r40WHw4s= +github.com/bufbuild/protocompile v0.1.0/go.mod h1:ix/MMMdsT3fzxfw91dvbfzKW3fRRnuPCP47kpAm5m/4= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= @@ -355,8 +364,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= github.com/celestiaorg/go-fraud v0.1.2/go.mod h1:kHZXQY+6gd1kYkoWRFFKgWyrLPWRgDN3vd1Ll9gE/oo= -github.com/celestiaorg/go-header v0.2.12 h1:3H9nir20+MTY1vXbLxOUOV05ZspotR6JOiZGKxACHCQ= -github.com/celestiaorg/go-header v0.2.12/go.mod h1:NhiWq97NtAYyRBu8quzYOUghQULjgOzO2Ql0iVEFOf0= +github.com/celestiaorg/go-header v0.2.13 h1:sUJLXYs8ViPpxLXyIIaW3h4tPFgtVYMhzsLC4GHfS8I= +github.com/celestiaorg/go-header v0.2.13/go.mod h1:NhiWq97NtAYyRBu8quzYOUghQULjgOzO2Ql0iVEFOf0= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= @@ -414,9 +423,13 @@ github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b80 github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= +github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= @@ -435,6 +448,7 @@ github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJ github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -457,8 +471,8 @@ github.com/cosmos/cosmos-sdk/api v0.1.0/go.mod h1:CupqQBskAOiTXO1XDZ/wrtWzN/wTxU github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= -github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= -github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.6 h1:XY78yEeNPrEYyNCKlqr9chrwoeSDJ0bV2VjocTk//OU= @@ -497,6 +511,7 @@ github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6 github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= @@ -532,6 +547,7 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -570,8 +586,11 @@ github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -586,6 +605,7 @@ github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= @@ -593,6 +613,7 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -607,11 +628,13 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= @@ -668,6 +691,7 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -678,12 +702,15 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -691,6 +718,7 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= @@ -700,7 +728,6 @@ github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -776,6 +803,7 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= @@ -785,6 +813,7 @@ github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIG github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -802,9 +831,11 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= -github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -827,12 +858,13 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -857,7 +889,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= @@ -884,6 +915,7 @@ github.com/hashicorp/go-getter v1.7.0 h1:bzrYP+qu/gMrL1au7/aDvkoOVGUJpeKBgbqRHAC github.com/hashicorp/go-getter v1.7.0/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -892,8 +924,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= @@ -909,10 +941,11 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= -github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= @@ -967,8 +1000,10 @@ github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyq github.com/ipfs/boxo v0.11.0 h1:urMxhZ3xoF4HssJVD3+0ssGT9pptEfHfbL8DYdoWFlg= github.com/ipfs/boxo v0.11.0/go.mod h1:8IfDmp+FzFGcF4zjAgHMVPpwYw4AjN9ePEzDfkaYJ1w= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= +github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= +github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= @@ -1015,6 +1050,7 @@ github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHv github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= +github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= @@ -1071,6 +1107,7 @@ github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68 github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= github.com/ipfs/go-unixfsnode v1.7.1 h1:RRxO2b6CSr5UQ/kxnGzaChTjp5LWTdf3Y4n8ANZgB/s= +github.com/ipfs/go-unixfsnode v1.7.1/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk= github.com/ipld/go-car v0.6.2 h1:Hlnl3Awgnq8icK+ze3iRghk805lu8YNq3wlREDTF2qc= github.com/ipld/go-car v0.6.2/go.mod h1:oEGXdwp6bmxJCZ+rARSkDliTeYnVzv3++eXajZ+Bmr8= github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= @@ -1086,6 +1123,7 @@ github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3u github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= +github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= @@ -1104,6 +1142,7 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -1172,6 +1211,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -1212,8 +1252,8 @@ github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniV github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= -github.com/libp2p/go-libp2p v0.29.0 h1:QduJ2XQr/Crg4EnloueWDL0Jj86N3Ezhyyj7XH+XwHI= -github.com/libp2p/go-libp2p v0.29.0/go.mod h1:iNKL7mEnZ9wAss+03IjAwM9ZAQXfVUAPUUmOACQfQ/g= +github.com/libp2p/go-libp2p v0.30.0 h1:9EZwFtJPFBcs/yJTnP90TpN1hgrT/EsFfM+OZuwV87U= +github.com/libp2p/go-libp2p v0.30.0/go.mod h1:nr2g5V7lfftwgiJ78/HrID+pwvayLyqKCEirT2Y3Byg= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= @@ -1261,8 +1301,8 @@ github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxn github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.24.2 h1:zd7myKBKCmtZBhI3I0zm8xBkb28v3gmSEtQfBdAdFwc= -github.com/libp2p/go-libp2p-kad-dht v0.24.2/go.mod h1:BShPzRbK6+fN3hk8a0WGAYKpb8m4k+DtchkqouGTrSg= +github.com/libp2p/go-libp2p-kad-dht v0.25.0 h1:T2SXQ/VlXTQVLChWY/+OyOsmGMRJvB5kiR+eJt7jtvI= +github.com/libp2p/go-libp2p-kad-dht v0.25.0/go.mod h1:P6fz+J+u4tPigvS5J0kxQ1isksqAhmXiS/pNaEw/nFI= github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= @@ -1300,8 +1340,8 @@ github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7 github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-resource-manager v0.2.1/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= -github.com/libp2p/go-libp2p-routing-helpers v0.7.0 h1:sirOYVD0wGWjkDwHZvinunIpaqPLBXkcnXApVHwZFGA= -github.com/libp2p/go-libp2p-routing-helpers v0.7.0/go.mod h1:R289GUxUMzRXIbWGSuUUTPrlVJZ3Y/pPz495+qgXJX8= +github.com/libp2p/go-libp2p-routing-helpers v0.7.1 h1:kc0kWCZecbBPAiFEHhxfGJZPqjg1g9zV+X+ovR4Tmnc= +github.com/libp2p/go-libp2p-routing-helpers v0.7.1/go.mod h1:cHStPSRC/wgbfpb5jYdMP7zaSmc2wWcb1mkzNr6AR8o= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= @@ -1328,6 +1368,7 @@ github.com/libp2p/go-libp2p-testing v0.7.0/go.mod h1:OLbdn9DbgdMwv00v+tlp1l3oe2C github.com/libp2p/go-libp2p-testing v0.9.0/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= github.com/libp2p/go-libp2p-testing v0.9.2/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-tls v0.3.0/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5oFlg+wo2FxJDY= github.com/libp2p/go-libp2p-tls v0.4.1/go.mod h1:EKCixHEysLNDlLUoKxv+3f/Lp90O2EXNjTr0UQDnrIw= @@ -1385,8 +1426,8 @@ github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= -github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjPyvUw= -github.com/libp2p/go-reuseport v0.3.0/go.mod h1:laea40AimhtfEqysZ71UpYj4S+R9VpH8PgqLo7L+SwI= +github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= @@ -1440,8 +1481,9 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= @@ -1482,6 +1524,7 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1572,8 +1615,8 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU= -github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ= +github.com/multiformats/go-multiaddr v0.11.0 h1:XqGyJ8ufbCE0HmTDwx2kPdsrQ36AGPZNZX6s6xfJH10= +github.com/multiformats/go-multiaddr v0.11.0/go.mod h1:gWUm0QLR4thQ6+ZF6SXUw8YjtwQSPapICM+NmCkxHSM= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -1670,15 +1713,19 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -1692,6 +1739,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1743,8 +1791,8 @@ github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66Id github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1778,8 +1826,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.2 h1:OX2qdUQsS8RSkn/3C8isD7f/P0YiZQlRbAlecAaj/R8= github.com/pyroscope-io/client v0.7.2/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8= @@ -1789,12 +1837,10 @@ github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKA github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= -github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= -github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.36.2 h1:ZX/UNQ4gvpCv2RmwdbA6lrRjF6EBm5yZ7TMoT4NQVrA= -github.com/quic-go/quic-go v0.36.2/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ= +github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= +github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.37.6 h1:2IIUmQzT5YNxAiaPGjs++Z4hGOtIR0q79uS5qE9ccfY= +github.com/quic-go/quic-go v0.37.6/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= @@ -1819,6 +1865,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -1873,6 +1920,7 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= @@ -1913,6 +1961,7 @@ github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+eg github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1951,12 +2000,14 @@ github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJH github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= @@ -1972,6 +2023,7 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -1986,6 +2038,7 @@ github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= +github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -1995,6 +2048,7 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= +github.com/warpfork/go-testmark v0.11.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -2005,6 +2059,7 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:X github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= +github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= @@ -2018,6 +2073,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -2032,6 +2088,7 @@ github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN github.com/zondax/ledger-go v0.14.1/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc= +gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 h1:dizWJqTWjwyD8KGcMOwgrkqu1JIkofYgKkmDeNE7oAs= gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -2081,8 +2138,8 @@ go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwY go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -2099,8 +2156,9 @@ go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -2118,8 +2176,8 @@ go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -2158,6 +2216,7 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= @@ -2178,8 +2237,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2293,8 +2352,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2488,6 +2547,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= @@ -2498,6 +2558,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2570,8 +2631,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2641,8 +2702,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2766,12 +2827,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs= -google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2808,7 +2869,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -2818,8 +2878,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= -google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2854,6 +2914,7 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= @@ -2897,6 +2958,7 @@ nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0 nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= pgregory.net/rapid v0.5.3 h1:163N50IHFqr1phZens4FQOdPgfJscR7a562mjQqeo4M= +pgregory.net/rapid v0.5.3/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 7427699f95..8977678198 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -7,10 +7,10 @@ import ( "github.com/ipfs/boxo/bitswap" "github.com/ipfs/boxo/bitswap/network" "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/routing/offline" "github.com/ipfs/go-blockservice" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - "github.com/ipfs/go-ipfs-routing/offline" record "github.com/libp2p/go-libp2p-record" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 0a79ebfe33..411fd85818 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -2,11 +2,11 @@ package byzantine import ( "context" + "crypto/sha256" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "github.com/minio/sha256-simd" "github.com/celestiaorg/nmt" nmt_pb "github.com/celestiaorg/nmt/pb" diff --git a/share/eds/eds.go b/share/eds/eds.go index cf38a14cbc..e689aec31c 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -3,6 +3,7 @@ package eds import ( "bytes" "context" + "crypto/sha256" "errors" "fmt" "io" @@ -11,7 +12,6 @@ import ( "github.com/ipfs/go-cid" "github.com/ipld/go-car" "github.com/ipld/go-car/util" - "github.com/minio/sha256-simd" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" diff --git a/share/getter.go b/share/getter.go index 7caa954a24..a33ea7aa87 100644 --- a/share/getter.go +++ b/share/getter.go @@ -2,11 +2,10 @@ package share import ( "context" + "crypto/sha256" "errors" "fmt" - "github.com/minio/sha256-simd" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" ) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index b86b3b98df..5cc73aa7cb 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" + "github.com/ipfs/boxo/exchange/offline" bsrv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - offline "github.com/ipfs/go-ipfs-exchange-offline" mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index ef07cf6797..0ca807d0d4 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -276,7 +276,8 @@ func newEDSClientServer( return client, server } -// addToNamespace adds arbitrary int value to namespace, treating namespace as big-endian implementation of int +// addToNamespace adds arbitrary int value to namespace, treating namespace as big-endian +// implementation of int func addToNamespace(namespace share.Namespace, val int) (share.Namespace, error) { if val == 0 { return namespace, nil diff --git a/share/getters/utils_test.go b/share/getters/utils_test.go index 5ffad228f9..65de9d47f2 100644 --- a/share/getters/utils_test.go +++ b/share/getters/utils_test.go @@ -209,7 +209,8 @@ func Test_ctxWithSplitTimeout(t *testing.T) { t.Cleanup(cancel) got, _ := ctxWithSplitTimeout(ctx, sf, tt.args.minTimeout) dl, ok := got.Deadline() - // in case no deadline is found in ctx or not expected to be found, check both cases apply at the same time + // in case no deadline is found in ctx or not expected to be found, check both cases apply at the + // same time if !ok || tt.want == 0 { require.False(t, ok) require.Equal(t, tt.want, time.Duration(0)) diff --git a/share/ipld/get_shares_test.go b/share/ipld/get_shares_test.go index 11639cbec7..dd6c3a6636 100644 --- a/share/ipld/get_shares_test.go +++ b/share/ipld/get_shares_test.go @@ -3,6 +3,7 @@ package ipld import ( "bytes" "context" + "crypto/sha256" "errors" mrand "math/rand" "sort" @@ -11,13 +12,12 @@ import ( "time" "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/exchange/offline" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - offline "github.com/ipfs/go-ipfs-exchange-offline" mdutils "github.com/ipfs/go-merkledag/test" - "github.com/minio/sha256-simd" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index b391a3b2ce..153bbbb1ba 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -2,6 +2,7 @@ package shrexnd import ( "context" + "crypto/sha256" "errors" "fmt" "time" @@ -9,7 +10,6 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/protocol" - "github.com/minio/sha256-simd" "go.uber.org/zap" "github.com/celestiaorg/go-libp2p-messenger/serde" From 10351be4c26431f58bfef829b5e02065d20e515f Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 22 Aug 2023 22:54:21 +0800 Subject: [PATCH 0753/1008] fix(share/eds): scale up inverted_index badger levels (#2591) Current instance of badger uses default value (7) for amount of used levels, that allows badger to grow up to 1.1TiB. This limit could be reaching within 1 week of 128 ods size blocks, so we need to bump the value to at least 8 levels (11 TiB). --- share/eds/inverted_index.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index bb4092fa52..81df5d92ef 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -31,6 +31,8 @@ func newSimpleInvertedIndex(storePath string) (*simpleInvertedIndex, error) { opts.NumCompactors = 20 // use minimum amount of NumLevelZeroTables to trigger L0 compaction faster opts.NumLevelZeroTables = 1 + // MaxLevels = 8 will allow the db to grow to ~11.1 TiB + opts.MaxLevels = 8 ds, err := dsbadger.NewDatastore(storePath+invertedIndexPath, &opts) if err != nil { From bb9b4d45a9958f38b66ccb9b425c63fd38e30b20 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Tue, 22 Aug 2023 17:38:19 +0200 Subject: [PATCH 0754/1008] feat(blob): improve gas estimation and track min gas price (#2511) ## Overview This PR incorporates the more recent gas estimation method in celestia-app. It also makes use of a new gRPC endpoint for consensus nodes that reveals the nodes min gas price. This enables the blob module to submit transactions with the correct gas price. If the node changes that price, the blob module is able to parse the error and retry using the new gas price. Additionally, a stubbed CoreAccessor was introduced for when a core endpoint is not provided in order to return a better/more readable error, whereas previously, the CoreAccessor depended on the grpc dial to silently fail during start and any call to the state module would return a "cannot dial address" error. It now looks like this: ``` { "jsonrpc": "2.0", "id": 1, "error": { "code": 1, "message": "node is running without state access" } } ``` ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- blob/helper.go | 17 ----- blob/service.go | 11 +-- state/core_access.go | 146 +++++++++++++++++++++++++++++++++----- state/core_access_test.go | 97 ++++++++++++++++++++----- state/metrics.go | 4 +- 5 files changed, 214 insertions(+), 61 deletions(-) diff --git a/blob/helper.go b/blob/helper.go index e87055ae07..5627fac998 100644 --- a/blob/helper.go +++ b/blob/helper.go @@ -6,7 +6,6 @@ import ( "github.com/tendermint/tendermint/types" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-node/share" @@ -86,22 +85,6 @@ func BlobsToShares(blobs ...*Blob) ([]share.Share, error) { return shares.ToBytes(rawShares), nil } -const ( - perByteGasTolerance = 2 - pfbGasFixedCost = 80000 -) - -// estimateGas estimates the gas required to pay for a set of blobs in a PFB. -func estimateGas(blobs ...*Blob) uint64 { - totalByteCount := 0 - for _, blob := range blobs { - totalByteCount += len(blob.Data) + appconsts.NamespaceSize - } - variableGasAmount := (appconsts.DefaultGasPerBlobByte + perByteGasTolerance) * totalByteCount - - return uint64(variableGasAmount + pfbGasFixedCost) -} - // constructAndVerifyBlob reconstruct a Blob from the passed shares and compares commitments. func constructAndVerifyBlob(sh []share.Share, commitment Commitment) (*Blob, bool, error) { blob, err := SharesToBlobs(sh) diff --git a/blob/service.go b/blob/service.go index 960f8c6c01..da14fc06c7 100644 --- a/blob/service.go +++ b/blob/service.go @@ -10,7 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/types" logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-node/header" @@ -36,7 +35,7 @@ type Service struct { blobSumitter Submitter // shareGetter retrieves the EDS to fetch all shares from the requested header. shareGetter share.Getter - // headerGetter fetches header by the provided height + // headerGetter fetches header by the provided height headerGetter func(context.Context, uint64) (*header.ExtendedHeader, error) } @@ -55,15 +54,11 @@ func NewService( // Submit sends PFB transaction and reports the height in which it was included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. +// Handles gas estimation and fee calculation. func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { log.Debugw("submitting blobs", "amount", len(blobs)) - var ( - gasLimit = estimateGas(blobs...) - fee = int64(appconsts.DefaultMinGasPrice * float64(gasLimit)) - ) - - resp, err := s.blobSumitter.SubmitPayForBlob(ctx, types.NewInt(fee), gasLimit, blobs) + resp, err := s.blobSumitter.SubmitPayForBlob(ctx, types.OneInt().Neg(), 0, blobs) if err != nil { return 0, err } diff --git a/state/core_access.go b/state/core_access.go index 73d525347f..43f5313c5f 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -4,13 +4,17 @@ import ( "context" "errors" "fmt" + "math" + "sync" "time" sdkErrors "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/api/tendermint/abci" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdktypes "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" + auth "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" logging "github.com/ipfs/go-log/v2" @@ -21,6 +25,8 @@ import ( "google.golang.org/grpc/credentials/insecure" "github.com/celestiaorg/celestia-app/app" + apperrors "github.com/celestiaorg/celestia-app/app/errors" + "github.com/celestiaorg/celestia-app/pkg/appconsts" appblob "github.com/celestiaorg/celestia-app/x/blob" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" libhead "github.com/celestiaorg/go-header" @@ -34,6 +40,8 @@ var ( ErrInvalidAmount = errors.New("state: amount must be greater than zero") ) +const maxRetries = 5 + // CoreAccessor implements service over a gRPC connection // with a celestia-core node. type CoreAccessor struct { @@ -54,8 +62,15 @@ type CoreAccessor struct { rpcPort string grpcPort string + // these fields are mutatable and thus need to be protected by a mutex + lock sync.Mutex lastPayForBlob int64 payForBlobCount int64 + // minGasPrice is the minimum gas price that the node will accept. + // NOTE: just because the first node accepts the transaction, does not mean it + // will find a proposer that does accept the transaction. Better would be + // to set a global min gas price that correct processes conform to. + minGasPrice float64 } // NewCoreAccessor dials the given celestia-core endpoint and @@ -112,6 +127,11 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { } ca.rpcCli = cli + ca.minGasPrice, err = ca.queryMinimumGasPrice(ctx) + if err != nil { + return fmt.Errorf("querying minimum gas price: %w", err) + } + return nil } @@ -160,6 +180,9 @@ func (ca *CoreAccessor) constructSignedTx( return ca.signer.EncodeTx(tx) } +// SubmitPayForBlob builds, signs, and synchronously submits a MsgPayForBlob. It blocks until the transaction +// is committed and returns the TxReponse. If gasLim is set to 0, the method will automatically estimate the +// gas limit. If the fee is negative, the method will use the nodes min gas price multiplied by the gas limit. func (ca *CoreAccessor) SubmitPayForBlob( ctx context.Context, fee Int, @@ -178,25 +201,67 @@ func (ca *CoreAccessor) SubmitPayForBlob( appblobs[i] = &b.Blob } - response, err := appblob.SubmitPayForBlob( - ctx, - ca.signer, - ca.coreConn, - sdktx.BroadcastMode_BROADCAST_MODE_BLOCK, - appblobs, - apptypes.SetGasLimit(gasLim), - withFee(fee), - ) - // metrics should only be counted on a successful PFD tx - if err == nil && response.Code == 0 { - ca.lastPayForBlob = time.Now().UnixMilli() - ca.payForBlobCount++ - } + // we only estimate gas if the user wants us to (by setting the gasLim to 0). In the future we may want + // to make these arguments optional. + if gasLim == 0 { + blobSizes := make([]uint32, len(blobs)) + for i, blob := range blobs { + blobSizes[i] = uint32(len(blob.Data)) + } - if response != nil && response.Code != 0 { - err = errors.Join(err, sdkErrors.ABCIError(response.Codespace, response.Code, response.Logs.String())) + // TODO (@cmwaters): the default gas per byte and the default tx size cost per byte could be changed + // through governance. This section could be more robust by tracking these values and adjusting the + // gas limit accordingly (as is done for the gas price) + gasLim = apptypes.EstimateGas(blobSizes, appconsts.DefaultGasPerBlobByte, auth.DefaultTxSizeCostPerByte) + } + + minGasPrice := ca.getMinGasPrice() + + // set the fee for the user as the minimum gas price multiplied by the gas limit + estimatedFee := false + if fee.IsNegative() { + estimatedFee = true + fee = sdktypes.NewInt(int64(math.Ceil(minGasPrice * float64(gasLim)))) + } + + var lastErr error + for attempt := 0; attempt < maxRetries; attempt++ { + response, err := appblob.SubmitPayForBlob( + ctx, + ca.signer, + ca.coreConn, + sdktx.BroadcastMode_BROADCAST_MODE_BLOCK, + appblobs, + apptypes.SetGasLimit(gasLim), + withFee(fee), + ) + + // the node is capable of changing the min gas price at any time so we must be able to detect it and + // update our version accordingly + if apperrors.IsInsufficientMinGasPrice(err) && estimatedFee { + // The error message contains enough information to parse the new min gas price + minGasPrice, err = apperrors.ParseInsufficientMinGasPrice(err, minGasPrice, gasLim) + if err != nil { + return nil, fmt.Errorf("parsing insufficient min gas price error: %w", err) + } + ca.setMinGasPrice(minGasPrice) + lastErr = err + // update the fee to retry again + fee = sdktypes.NewInt(int64(math.Ceil(minGasPrice * float64(gasLim)))) + continue + } + + // metrics should only be counted on a successful PFD tx + if err == nil && response.Code == 0 { + ca.markSuccessfulPFB() + } + + if response != nil && response.Code != 0 { + err = errors.Join(err, sdkErrors.ABCIError(response.Codespace, response.Code, response.Logs.String())) + } + return response, err } - return response, err + return nil, fmt.Errorf("failed to submit blobs after %d attempts: %w", maxRetries, lastErr) } func (ca *CoreAccessor) AccountAddress(context.Context) (Address, error) { @@ -460,6 +525,53 @@ func (ca *CoreAccessor) QueryRedelegations( }) } +func (ca *CoreAccessor) LastPayForBlob() int64 { + ca.lock.Lock() + defer ca.lock.Unlock() + return ca.lastPayForBlob +} + +func (ca *CoreAccessor) PayForBlobCount() int64 { + ca.lock.Lock() + defer ca.lock.Unlock() + return ca.payForBlobCount +} + +func (ca *CoreAccessor) markSuccessfulPFB() { + ca.lock.Lock() + defer ca.lock.Unlock() + ca.lastPayForBlob = time.Now().UnixMilli() + ca.payForBlobCount++ +} + +func (ca *CoreAccessor) setMinGasPrice(minGasPrice float64) { + ca.lock.Lock() + defer ca.lock.Unlock() + ca.minGasPrice = minGasPrice +} + +func (ca *CoreAccessor) getMinGasPrice() float64 { + ca.lock.Lock() + defer ca.lock.Unlock() + return ca.minGasPrice +} + +// QueryMinimumGasPrice returns the minimum gas price required by the node. +func (ca *CoreAccessor) queryMinimumGasPrice( + ctx context.Context, +) (float64, error) { + rsp, err := nodeservice.NewServiceClient(ca.coreConn).Config(ctx, &nodeservice.ConfigRequest{}) + if err != nil { + return 0, err + } + + coins, err := sdktypes.ParseDecCoins(rsp.MinimumGasPrice) + if err != nil { + return 0, err + } + return coins.AmountOf(app.BondDenom).MustFloat64(), nil +} + func (ca *CoreAccessor) IsStopped(context.Context) bool { return ca.ctx.Err() != nil } diff --git a/state/core_access_test.go b/state/core_access_test.go index 5018cee4f2..69e9f251c0 100644 --- a/state/core_access_test.go +++ b/state/core_access_test.go @@ -2,32 +2,95 @@ package state import ( "context" + "errors" + "fmt" + "strings" "testing" + "time" + "cosmossdk.io/math" + sdktypes "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/test/util/testnode" + blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/share" ) -func TestLifecycle(t *testing.T) { - ca := NewCoreAccessor(nil, nil, "", "", "") +func TestSubmitPayForBlob(t *testing.T) { + accounts := []string{"jimy", "rob"} + tmCfg := testnode.DefaultTendermintConfig() + tmCfg.Consensus.TimeoutCommit = time.Millisecond * 1 + appConf := testnode.DefaultAppConfig() + appConf.API.Enable = true + appConf.MinGasPrices = fmt.Sprintf("0.1%s", app.BondDenom) + + config := testnode.DefaultConfig().WithTendermintConfig(tmCfg).WithAppConfig(appConf).WithAccounts(accounts) + cctx, rpcAddr, grpcAddr := testnode.NewNetwork(t, config) ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + signer := blobtypes.NewKeyringSigner(cctx.Keyring, accounts[0], cctx.ChainID) + ca := NewCoreAccessor(signer, nil, "127.0.0.1", extractPort(rpcAddr), extractPort(grpcAddr)) // start the accessor err := ca.Start(ctx) require.NoError(t, err) - // ensure accessor isn't stopped - require.False(t, ca.IsStopped(ctx)) - // cancel the top level context (this should not affect the lifecycle of the - // accessor as it should manage its own internal context) - cancel() - // ensure accessor was unaffected by top-level context cancellation - require.False(t, ca.IsStopped(ctx)) - // stop the accessor - stopCtx, stopCancel := context.WithCancel(context.Background()) - t.Cleanup(stopCancel) - err = ca.Stop(stopCtx) + t.Cleanup(func() { + _ = ca.Stop(ctx) + }) + + ns, err := share.NewBlobNamespaceV0([]byte("namespace")) + require.NoError(t, err) + blobbyTheBlob, err := blob.NewBlobV0(ns, []byte("data")) require.NoError(t, err) - // ensure accessor is stopped - require.True(t, ca.IsStopped(ctx)) - // ensure that stopping the accessor again does not return an error - err = ca.Stop(stopCtx) + + minGas, err := ca.queryMinimumGasPrice(ctx) require.NoError(t, err) + require.Equal(t, appconsts.DefaultMinGasPrice, minGas) + + testcases := []struct { + name string + blobs []*blob.Blob + fee math.Int + gasLim uint64 + expErr error + }{ + { + name: "empty blobs", + blobs: []*blob.Blob{}, + fee: sdktypes.ZeroInt(), + gasLim: 0, + expErr: errors.New("state: no blobs provided"), + }, + { + name: "good blob with user provided gas and fees", + blobs: []*blob.Blob{blobbyTheBlob}, + fee: sdktypes.NewInt(10_000), // roughly 0.12 utia per gas (should be good) + gasLim: blobtypes.DefaultEstimateGas([]uint32{uint32(len(blobbyTheBlob.Data))}), + expErr: nil, + }, + // TODO: add more test cases. The problem right now is that the celestia-app doesn't + // correctly construct the node (doesn't pass the min gas price) hence the price on + // everything is zero and we can't actually test the correct behavior + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + resp, err := ca.SubmitPayForBlob(ctx, tc.fee, tc.gasLim, tc.blobs) + require.Equal(t, tc.expErr, err) + if err == nil { + require.EqualValues(t, 0, resp.Code) + } + }) + } + +} + +func extractPort(addr string) string { + splitStr := strings.Split(addr, ":") + return splitStr[len(splitStr)-1] } diff --git a/state/metrics.go b/state/metrics.go index 169023b5f9..aa166e901d 100644 --- a/state/metrics.go +++ b/state/metrics.go @@ -20,8 +20,8 @@ func WithMetrics(ca *CoreAccessor) { ) callback := func(ctx context.Context, observer metric.Observer) error { - observer.ObserveInt64(pfbCounter, ca.payForBlobCount) - observer.ObserveInt64(lastPfbTimestamp, ca.lastPayForBlob) + observer.ObserveInt64(pfbCounter, ca.PayForBlobCount()) + observer.ObserveInt64(lastPfbTimestamp, ca.LastPayForBlob()) return nil } _, err := meter.RegisterCallback(callback, pfbCounter, lastPfbTimestamp) From 7682b003b2c462447a2680da9f55068d0b0327fc Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 23 Aug 2023 14:36:30 +0300 Subject: [PATCH 0755/1008] fix(cmd/blob): fix response (#2600) --- cmd/celestia/blob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go index 28d05b3b42..8ab130c24d 100644 --- a/cmd/celestia/blob.go +++ b/cmd/celestia/blob.go @@ -121,7 +121,7 @@ var submitCmd = &cobra.Command{ height, err := client.Blob.Submit(cmd.Context(), []*blob.Blob{parsedBlob}) response := struct { - Height uint64 `json:"uint64"` + Height uint64 `json:"height"` Commitment blob.Commitment `json:"commitment"` }{ Height: height, From d5b1e199615a790083acfcdfc7b5e31c5a99827e Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:12:53 +0800 Subject: [PATCH 0756/1008] deps: bump dagstore (#2598) Update dagstore to latest fork version --- go.mod | 8 +- go.sum | 322 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 318 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 91b55ad256..cf2329de01 100644 --- a/go.mod +++ b/go.mod @@ -171,7 +171,7 @@ require ( github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect @@ -214,7 +214,7 @@ require ( github.com/ipfs/go-verifcid v0.0.2 // indirect github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 // indirect github.com/ipld/go-codec-dagpb v1.6.0 // indirect - github.com/ipld/go-ipld-prime v0.20.0 // indirect + github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect @@ -302,7 +302,7 @@ require ( github.com/tklauser/numcpus v0.4.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect - github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect + github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.1 // indirect @@ -337,7 +337,7 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 - github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 + github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230823095320-1d66549bbeb3 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index 84e5eb5991..90c6e89ec9 100644 --- a/go.sum +++ b/go.sum @@ -253,6 +253,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= @@ -273,6 +274,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= @@ -319,6 +321,7 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= @@ -327,6 +330,7 @@ github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= @@ -358,8 +362,8 @@ github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHV github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 h1:PckXGxLJjXv97VO3xS8NPHN5oO83X5nvJLbc/4s8jUM= github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14/go.mod h1:70Go8qNy7YAb1PUcHCChRHNX2ke7c9jgUIEklUX+Mac= -github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6 h1:/yCwMCoOPcYCiG18u8/1pv5eXF04xczoQO3sR0bKsgM= -github.com/celestiaorg/dagstore v0.0.0-20230413141458-735ab09a15d6/go.mod h1:ta/DlqIH10bvhwqJIw51Nq3QU4XVMp6pz3f0Deve9fM= +github.com/celestiaorg/dagstore v0.0.0-20230823095320-1d66549bbeb3 h1:2/UcuBL0HQ8Y7QXhBjaNMI2LT8or1F67wArTJIs3Vos= +github.com/celestiaorg/dagstore v0.0.0-20230823095320-1d66549bbeb3/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= @@ -395,12 +399,15 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= @@ -445,6 +452,7 @@ github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1 github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= +github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= @@ -458,6 +466,7 @@ github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -487,6 +496,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= +github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -513,6 +524,7 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= @@ -601,6 +613,7 @@ github.com/filecoin-project/go-jsonrpc v0.3.1/go.mod h1:jBSvPTl8V1N7gSTuCR4bis8w github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -612,13 +625,18 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= @@ -660,6 +678,7 @@ github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNV github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -828,9 +847,11 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -840,8 +861,9 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -873,6 +895,7 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -944,6 +967,7 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -972,6 +996,7 @@ github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -995,20 +1020,33 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/boxo v0.11.0 h1:urMxhZ3xoF4HssJVD3+0ssGT9pptEfHfbL8DYdoWFlg= github.com/ipfs/boxo v0.11.0/go.mod h1:8IfDmp+FzFGcF4zjAgHMVPpwYw4AjN9ePEzDfkaYJ1w= +github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= +github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= +github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= +github.com/ipfs/go-bitswap v0.1.8/go.mod h1:TOWoxllhccevbWFUR2N7B1MTSVVge1s6XSMiCSA4MzM= +github.com/ipfs/go-bitswap v0.3.4/go.mod h1:4T7fvNv/LmOys+21tnLzGKncMeeXUYUd1nUiJ2teMvI= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= +github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= github.com/ipfs/go-bitswap v0.11.0/go.mod h1:05aE8H3XOU+LXpTedeAS0OZpcO1WFsj5niYQH9a1Tmk= +github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= +github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= github.com/ipfs/go-block-format v0.1.2/go.mod h1:mACVcrxarQKstUU3Yf/RdwbC4DzPV6++rO2a3d+a/KE= +github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= +github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= +github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= +github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= github.com/ipfs/go-blockservice v0.5.1 h1:9pAtkyKAz/skdHTh0kH8VulzWp+qmSDD0aI17TYP/s0= github.com/ipfs/go-blockservice v0.5.1/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -1019,10 +1057,18 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= +github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= +github.com/ipfs/go-cid v0.3.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= +github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-cidutil v0.1.0 h1:RW5hO7Vcf16dplUU60Hs0AKDkQAVPVplr7lk97CFL+Q= +github.com/ipfs/go-cidutil v0.1.0/go.mod h1:e7OEVBMIv9JaOxt9zaGEmAoSlXW9jdFZ5lP/0PwcfpA= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.0.5/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= +github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= +github.com/ipfs/go-datastore v0.3.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -1035,37 +1081,62 @@ github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= +github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= +github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.5.0 h1:s++MEBbD3ZKc9/8/njrn4flZLnCuY9I79v94gBUNumo= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= +github.com/ipfs/go-fetcher v1.5.0/go.mod h1:5pDZ0393oRF/fHiLmtFZtpMNBQfHOYNPtryWedVuSWE= +github.com/ipfs/go-fetcher v1.6.1/go.mod h1:27d/xMV8bodjVs9pugh/RCjjK2OZ68UgAMspMdingNo= +github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= +github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= +github.com/ipfs/go-ipfs-blockstore v0.1.4/go.mod h1:Jxm3XMVjh6R17WvxFEiyKBLUGr86HgIYJW/D/MwqeYQ= github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= github.com/ipfs/go-ipfs-blockstore v1.1.2/go.mod h1:w51tNR9y5+QXB0wkNcHt4O2aSZjTdqaEWaQdSxEyUOY= +github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= +github.com/ipfs/go-ipfs-blockstore v1.3.0/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= +github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8= github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= +github.com/ipfs/go-ipfs-ds-help v0.0.1/go.mod h1:gtP9xRaZXqIQRh1HRpp595KbBEdgqWFxefeVKOV8sxo= github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= +github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM= github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= +github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= github.com/ipfs/go-ipfs-exchange-interface v0.2.1 h1:jMzo2VhLKSHbVe+mHNzYgs95n0+t0Q69GQ5WhRDZV/s= github.com/ipfs/go-ipfs-exchange-interface v0.2.1/go.mod h1:MUsYn6rKbG6CTtsDp+lKJPmVt3ZrCViNyH3rfPGsZ2E= +github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAzpUws3x7UeEGkzQc3iNkM0= github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= +github.com/ipfs/go-ipfs-exchange-offline v0.2.0/go.mod h1:HjwBeW0dvZvfOMwDP0TSKXIHf2s+ksdP4E3MLDRtLKY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= +github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= +github.com/ipfs/go-ipfs-files v0.0.8/go.mod h1:wiN/jSG8FKyk7N0WyctKSvq3ljIa2NNTiZB55kpTdOs= +github.com/ipfs/go-ipfs-files v0.3.0 h1:fallckyc5PYjuMEitPNrjRfpwl7YFt69heCOUhsbGxQ= +github.com/ipfs/go-ipfs-files v0.3.0/go.mod h1:xAUtYMwB+iu/dtf6+muHNSFQCJG2dSiStR2P6sn9tIM= +github.com/ipfs/go-ipfs-keystore v0.1.0/go.mod h1:LvLw7Qhnb0RlMOfCzK6OmyWxICip6lQ06CCmdbee75U= +github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs= +github.com/ipfs/go-ipfs-posinfo v0.0.1/go.mod h1:SwyeVP+jCwiDu0C313l/8jg6ZxM0qqtlt2a0vILTc1A= +github.com/ipfs/go-ipfs-pq v0.0.1/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-pq v0.0.3 h1:YpoHVJB+jzK15mr/xsWC574tyDLkezVrDNeaalQBsTE= github.com/ipfs/go-ipfs-pq v0.0.3/go.mod h1:btNw5hsHBpRcSSgZtiNm/SLj5gYIZ18AKtv3kERkRb4= +github.com/ipfs/go-ipfs-redirects-file v0.1.1/go.mod h1:tAwRjCV0RjLTjH8DR/AU7VYvfQECg+lpUy2Mdzv7gyk= +github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= github.com/ipfs/go-ipfs-routing v0.3.0 h1:9W/W3N+g+y4ZDeffSgqhgo7BsBSJwPMcyssET9OWevc= github.com/ipfs/go-ipfs-routing v0.3.0/go.mod h1:dKqtTFIql7e1zYsEuWLyuOU+E0WJWW8JjbTPLParDWo= @@ -1073,16 +1144,30 @@ github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyB github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= +github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= +github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= +github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k= github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= +github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= +github.com/ipfs/go-ipld-format v0.3.1/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= +github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-format v0.5.0 h1:WyEle9K96MSrvr47zZHKKcDxJ/vlpET6PSiQsAFO+Ds= github.com/ipfs/go-ipld-format v0.5.0/go.mod h1:ImdZqJQaEouMjCvqCe0ORUS+uoBmf7Hf+EO/jh+nk3M= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= +github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= github.com/ipfs/go-ipld-legacy v0.2.1/go.mod h1:782MOUghNzMO2DER0FlBR94mllfdCJCkTtDtPM51otM= +github.com/ipfs/go-ipns v0.2.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= +github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= +github.com/ipfs/go-libipfs v0.1.0/go.mod h1:qX0d9h+wu53PFtCTXxdXVBakd6ZCvGDdkZUKmdLMLx0= +github.com/ipfs/go-libipfs v0.3.0/go.mod h1:pSUHZ5qPJTAidsxe9bAeHp3KIiw2ODEW2a2kM3v+iXI= +github.com/ipfs/go-libipfs v0.4.0/go.mod h1:XsU2cP9jBhDrXoJDe0WxikB8XcVmD3k2MEZvB3dbYu8= +github.com/ipfs/go-libipfs v0.6.0 h1:3FuckAJEm+zdHbHbf6lAyk0QUzc45LsFcGw102oBCZM= +github.com/ipfs/go-libipfs v0.6.0/go.mod h1:UjjDIuehp2GzlNP0HEr5I9GfFT7zWgst+YfpUEIThtw= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= @@ -1098,29 +1183,65 @@ github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72g github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= +github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= +github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= +github.com/ipfs/go-merkledag v0.6.0/go.mod h1:9HSEwRd5sV+lbykiYP+2NC/3o6MZbKNaa4hfNcH5iH0= +github.com/ipfs/go-merkledag v0.9.0/go.mod h1:bPHqkHt5OZ0p1n3iqPeDiw2jIBkjAytRjS3WSBwjq90= +github.com/ipfs/go-merkledag v0.10.0/go.mod h1:zkVav8KiYlmbzUzNM6kENzkdP5+qR7+2mCwxkQ6GIj8= github.com/ipfs/go-merkledag v0.11.0 h1:DgzwK5hprESOzS4O1t/wi6JDpyVQdvm9Bs59N/jqfBY= github.com/ipfs/go-merkledag v0.11.0/go.mod h1:Q4f/1ezvBiJV0YCIXvt51W/9/kqJGH4I1LsA7+djsM4= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= +github.com/ipfs/go-namesys v0.7.0/go.mod h1:KYSZBVZG3VJC34EfqqJPG7T48aWgxseoMPAPA5gLyyQ= +github.com/ipfs/go-path v0.1.1/go.mod h1:vC8q4AKOtrjJz2NnllIrmr2ZbGlF5fW2OKKyhV9ggb0= +github.com/ipfs/go-path v0.3.0/go.mod h1:NOScsVgxfC/eIw4nz6OiGwK42PjaSJ4Y/ZFPn1Xe07I= +github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= +github.com/ipfs/go-peertaskqueue v0.1.1/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= +github.com/ipfs/go-peertaskqueue v0.2.0/go.mod h1:5/eNrBEbtSKWCG+kQK8K8fGNixoYUnr+P7jivavs9lY= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= +github.com/ipfs/go-peertaskqueue v0.8.0/go.mod h1:cz8hEnnARq4Du5TGqiWKgMr/BOSQ5XOgMOh1K5YYKKM= github.com/ipfs/go-peertaskqueue v0.8.1 h1:YhxAs1+wxb5jk7RvS0LHdyiILpNmRIRnZVztekOF0pg= github.com/ipfs/go-peertaskqueue v0.8.1/go.mod h1:Oxxd3eaK279FxeydSPPVGHzbwVeHjatZ2GA8XD+KbPU= +github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= +github.com/ipfs/go-unixfs v0.3.1/go.mod h1:h4qfQYzghiIc8ZNFKiLMFWOTzrWIAtzYQ59W/pCFf1o= +github.com/ipfs/go-unixfs v0.4.3/go.mod h1:TSG7G1UuT+l4pNj91raXAPkX0BhJi3jST1FDTfQ5QyM= +github.com/ipfs/go-unixfs v0.4.4/go.mod h1:TSG7G1UuT+l4pNj91raXAPkX0BhJi3jST1FDTfQ5QyM= +github.com/ipfs/go-unixfs v0.4.5 h1:wj8JhxvV1G6CD7swACwSKYa+NgtdWC1RUit+gFnymDU= +github.com/ipfs/go-unixfs v0.4.5/go.mod h1:BIznJNvt/gEx/ooRMI4Us9K8+qeGO7vx1ohnbk8gjFg= +github.com/ipfs/go-unixfsnode v1.1.2/go.mod h1:5dcE2x03pyjHk4JjamXmunTMzz+VUtqvPwZjIEkfV6s= +github.com/ipfs/go-unixfsnode v1.4.0/go.mod h1:qc7YFFZ8tABc58p62HnIYbUMwj9chhUuFWmxSokfePo= +github.com/ipfs/go-unixfsnode v1.5.1/go.mod h1:ed79DaG9IEuZITJVQn4U6MZDftv6I3ygUBLPfhEbHvk= +github.com/ipfs/go-unixfsnode v1.5.2/go.mod h1:NlOebRwYx8lMCNMdhAhEspYPBD3obp7TE0LvBqHY+ks= github.com/ipfs/go-unixfsnode v1.7.1 h1:RRxO2b6CSr5UQ/kxnGzaChTjp5LWTdf3Y4n8ANZgB/s= github.com/ipfs/go-unixfsnode v1.7.1/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk= +github.com/ipfs/interface-go-ipfs-core v0.9.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0= +github.com/ipfs/interface-go-ipfs-core v0.10.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0= +github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= github.com/ipld/go-car v0.6.2 h1:Hlnl3Awgnq8icK+ze3iRghk805lu8YNq3wlREDTF2qc= github.com/ipld/go-car v0.6.2/go.mod h1:oEGXdwp6bmxJCZ+rARSkDliTeYnVzv3++eXajZ+Bmr8= github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZzeVNeFcI= +github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E= +github.com/ipld/go-car/v2 v2.8.0/go.mod h1:a+BnAxUqgr7wcWxW/lI6ctyEQ2v9gjBChPytwFMp2f4= +github.com/ipld/go-car/v2 v2.10.1/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg= github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 h1:0OZwzSYWIuiKEOXd/2vm5cMcEmmGLFn+1h6lHELCm3s= github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= +github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= +github.com/ipld/go-codec-dagpb v1.4.1/go.mod h1:XdXTO/TUD/ra9RcK/NfmwBfr1JpFxM2uRKaB9oe4LxE= +github.com/ipld/go-codec-dagpb v1.5.0/go.mod h1:0yRIutEFD8o1DGVqw4RSHh+BUTlJA9XWldxaaWR/o4g= github.com/ipld/go-codec-dagpb v1.6.0 h1:9nYazfyu9B1p3NAgfVdpRco3Fs2nFC72DqVsMj6rOcc= github.com/ipld/go-codec-dagpb v1.6.0/go.mod h1:ANzFhfP2uMJxRBr8CE+WQWs5UsNa0pYtmKZ+agnUw9s= +github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/ipld/go-ipld-prime v0.14.0/go.mod h1:9ASQLwUFLptCov6lIYc70GRB4V7UTyLD0IJtrDJe6ZM= -github.com/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= +github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= +github.com/ipld/go-ipld-prime v0.18.0/go.mod h1:735yXW548CKrLwVCYXzqx90p5deRJMVVxM9eJ4Qe+qE= +github.com/ipld/go-ipld-prime v0.19.0/go.mod h1:Q9j3BaVXwaA3o5JUDNvptDDr/x8+F7FG6XJ8WI3ILg4= github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= +github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= +github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73/go.mod h1:2PJ0JgxyB08t0b2WKrcuqI3di0V+5n6RS/LTUJhkoxY= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo= github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw= @@ -1139,6 +1260,7 @@ github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0 github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jellydator/ttlcache/v2 v2.11.1/go.mod h1:RtE5Snf0/57e+2cLWFYWCCsLas2Hy3c5Z4n14XmSvTI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -1184,7 +1306,9 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -1192,6 +1316,11 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -1200,8 +1329,10 @@ github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7v github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= +github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -1246,32 +1377,44 @@ github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZ github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= +github.com/libp2p/go-libp2p v0.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM= +github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= +github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.19.0/go.mod h1:Ki9jJXLO2YqrTIFxofV7Twyd3INWPT97+r8hGt7XPjI= +github.com/libp2p/go-libp2p v0.22.0/go.mod h1:UDolmweypBSjQb2f7xutPnwZ/fxioLbMBxSjRksxxU4= +github.com/libp2p/go-libp2p v0.23.4/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= +github.com/libp2p/go-libp2p v0.25.0/go.mod h1:vXHmFpcfl+xIGN4qW58Bw3a0/SKGAesr5/T4IuJHE3o= +github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= github.com/libp2p/go-libp2p v0.30.0 h1:9EZwFtJPFBcs/yJTnP90TpN1hgrT/EsFfM+OZuwV87U= github.com/libp2p/go-libp2p v0.30.0/go.mod h1:nr2g5V7lfftwgiJ78/HrID+pwvayLyqKCEirT2Y3Byg= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= +github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= +github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= +github.com/libp2p/go-libp2p-autonat v0.4.0/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= github.com/libp2p/go-libp2p-blankhost v0.3.0/go.mod h1:urPC+7U01nCGgJ3ZsV8jdwTp6Ji9ID0dMTvq+aJ+nZU= +github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-circuit v0.6.0/go.mod h1:kB8hY+zCpMeScyvFrKrGicRdid6vNXbunKE4rXATZ0M= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= +github.com/libp2p/go-libp2p-core v0.0.3/go.mod h1:j+YQMNz9WNSkNezXOsahp9kwZBKBvxLpKD316QWSJXE= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= @@ -1297,12 +1440,18 @@ github.com/libp2p/go-libp2p-core v0.11.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQR github.com/libp2p/go-libp2p-core v0.12.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= github.com/libp2p/go-libp2p-core v0.14.0/go.mod h1:tLasfcVdTXnixsLB0QYaT1syJOhsbrhG7q6pGrHtBg8= github.com/libp2p/go-libp2p-core v0.15.1/go.mod h1:agSaboYM4hzB1cWekgVReqV5M4g5M+2eNNejV+1EEhs= +github.com/libp2p/go-libp2p-core v0.19.0/go.mod h1:AkA+FUKQfYt1FLNef5fOPlo/naAWjKy/RCjkcPjqzYg= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= +github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= +github.com/libp2p/go-libp2p-kad-dht v0.19.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= +github.com/libp2p/go-libp2p-kad-dht v0.21.0/go.mod h1:Bhm9diAFmc6qcWAr084bHNL159srVZRKADdp96Qqd1I= github.com/libp2p/go-libp2p-kad-dht v0.25.0 h1:T2SXQ/VlXTQVLChWY/+OyOsmGMRJvB5kiR+eJt7jtvI= github.com/libp2p/go-libp2p-kad-dht v0.25.0/go.mod h1:P6fz+J+u4tPigvS5J0kxQ1isksqAhmXiS/pNaEw/nFI= +github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= +github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= @@ -1313,15 +1462,18 @@ github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxW github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5BcKwrrb2VmOYb3M= +github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-nat v0.1.0/go.mod h1:DQzAG+QbDYjN1/C3B6vXucLtz3u9rEonLVPtZVzQqks= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= +github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-noise v0.4.0/go.mod h1:BzzY5pyzCYSyJbQy9oD8z5oP2idsafjt4/X42h9DjZU= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= +github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= @@ -1340,6 +1492,7 @@ github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7 github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-resource-manager v0.2.1/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= +github.com/libp2p/go-libp2p-routing-helpers v0.4.0/go.mod h1:dYEAgkVhqho3/YKxfOEGdFMIcWfAFNlZX8iAIihYA2E= github.com/libp2p/go-libp2p-routing-helpers v0.7.1 h1:kc0kWCZecbBPAiFEHhxfGJZPqjg1g9zV+X+ovR4Tmnc= github.com/libp2p/go-libp2p-routing-helpers v0.7.1/go.mod h1:cHStPSRC/wgbfpb5jYdMP7zaSmc2wWcb1mkzNr6AR8o= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= @@ -1351,6 +1504,7 @@ github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaT github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= +github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-swarm v0.8.0/go.mod h1:sOMp6dPuqco0r0GHTzfVheVBh6UEL0L1lXUZ5ot2Fvc= github.com/libp2p/go-libp2p-swarm v0.10.0/go.mod h1:71ceMcV6Rg/0rIQ97rsZWMzto1l9LnNquef+efcRbmA= @@ -1367,6 +1521,7 @@ github.com/libp2p/go-libp2p-testing v0.5.0/go.mod h1:QBk8fqIL1XNcno/l3/hhaIEn4aL github.com/libp2p/go-libp2p-testing v0.7.0/go.mod h1:OLbdn9DbgdMwv00v+tlp1l3oe2Cl+FAjoWIA2pa0X6E= github.com/libp2p/go-libp2p-testing v0.9.0/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= github.com/libp2p/go-libp2p-testing v0.9.2/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= +github.com/libp2p/go-libp2p-testing v0.11.0/go.mod h1:qG4sF27dfKFoK9KlVzK2y52LQKhp0VEmLjV5aDqr1Hg= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= @@ -1375,17 +1530,21 @@ github.com/libp2p/go-libp2p-tls v0.4.1/go.mod h1:EKCixHEysLNDlLUoKxv+3f/Lp90O2EX github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= +github.com/libp2p/go-libp2p-transport-upgrader v0.4.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-transport-upgrader v0.5.0/go.mod h1:Rc+XODlB3yce7dvFV4q/RmyJGsFcCZRkeZMu/Zdg0mo= github.com/libp2p/go-libp2p-transport-upgrader v0.7.0/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= github.com/libp2p/go-libp2p-transport-upgrader v0.7.1/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= +github.com/libp2p/go-libp2p-xor v0.1.0/go.mod h1:LSTM5yRnjGZbWNTA/hRwq2gGFrvRIbQJscoIL/u6InY= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= +github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= +github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= github.com/libp2p/go-libp2p-yamux v0.8.0/go.mod h1:yTkPgN2ib8FHyU1ZcVD7aelzyAqXXwEPbyx+aSKm9h8= github.com/libp2p/go-libp2p-yamux v0.8.1/go.mod h1:rUozF8Jah2dL9LLGyBaBeTQeARdwhefMCTQVQt6QobE= @@ -1400,12 +1559,15 @@ github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3 github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= +github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= +github.com/libp2p/go-msgio v0.0.3/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= +github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= @@ -1423,9 +1585,11 @@ github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= +github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= @@ -1442,34 +1606,44 @@ github.com/libp2p/go-stream-muxer-multistream v0.4.0/go.mod h1:nb+dGViZleRP4XcyH github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= +github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-tcp-transport v0.4.0/go.mod h1:0y52Rwrn4076xdJYu/51/qJIdxz+EWDAOG2S45sV3VI= github.com/libp2p/go-tcp-transport v0.5.0/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-tcp-transport v0.5.1/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= +github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc= +github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= github.com/libp2p/go-ws-transport v0.6.0/go.mod h1:dXqtI9e2JV9FtF1NOtWVZSKXh5zXvnuwPXfj8GPBbYU= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= +github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/libp2p/go-yamux/v3 v3.0.1/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= github.com/libp2p/go-yamux/v3 v3.0.2/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= github.com/libp2p/go-yamux/v3 v3.1.1/go.mod h1:jeLEQgLXqE2YqX1ilAClIfCMDY+0uXQUKmmb/qp0gT4= +github.com/libp2p/go-yamux/v3 v3.1.2/go.mod h1:jeLEQgLXqE2YqX1ilAClIfCMDY+0uXQUKmmb/qp0gT4= +github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= +github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= github.com/lucas-clemente/quic-go v0.27.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= +github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= +github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1494,10 +1668,15 @@ github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZE github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= +github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= +github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= +github.com/marten-seemann/webtransport-go v0.1.1/go.mod h1:kBEh5+RSvOA4troP1vyOVBWK4MIMzDICXVrvCPrYcrM= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1517,8 +1696,10 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1535,9 +1716,11 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= @@ -1615,6 +1798,9 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= +github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= +github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= +github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr v0.11.0 h1:XqGyJ8ufbCE0HmTDwx2kPdsrQ36AGPZNZX6s6xfJH10= github.com/multiformats/go-multiaddr v0.11.0/go.mod h1:gWUm0QLR4thQ6+ZF6SXUw8YjtwQSPapICM+NmCkxHSM= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= @@ -1635,11 +1821,18 @@ github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.3.1-0.20210902112759-1539a079fd61/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= +github.com/multiformats/go-multicodec v0.3.1-0.20211210143421-a526f306ed2c/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= +github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= +github.com/multiformats/go-multicodec v0.6.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.8.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= +github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= @@ -1650,14 +1843,19 @@ github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUj github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= +github.com/multiformats/go-multihash v0.2.0/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= +github.com/multiformats/go-multistream v0.2.0/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= github.com/multiformats/go-multistream v0.3.0/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-multistream v0.3.1/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= +github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= +github.com/multiformats/go-multistream v0.4.0/go.mod h1:BS6ZSYcA4NwYEaIMeCtpJydp2Dc+fNRA6uJMSu/m8+4= github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -1703,6 +1901,13 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -1712,6 +1917,12 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -1765,6 +1976,7 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1775,6 +1987,7 @@ github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUI github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= +github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= @@ -1791,6 +2004,8 @@ github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66Id github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1799,6 +2014,7 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -1814,6 +2030,7 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1826,6 +2043,7 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -1837,10 +2055,15 @@ github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKA github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= +github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= github.com/quic-go/quic-go v0.37.6 h1:2IIUmQzT5YNxAiaPGjs++Z4hGOtIR0q79uS5qE9ccfY= github.com/quic-go/quic-go v0.37.6/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= +github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= @@ -1880,6 +2103,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -1922,9 +2146,11 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= @@ -1981,6 +2207,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= @@ -1994,6 +2221,7 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= +github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= @@ -2011,6 +2239,7 @@ github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= @@ -2024,6 +2253,7 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:s github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= +github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -2046,25 +2276,34 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= -github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= +github.com/warpfork/go-testmark v0.10.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-testmark v0.11.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= +github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= +github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= -github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= +github.com/whyrusleeping/cbor-gen v0.0.0-20221220214510-0333c149dec0/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 h1:yVYDLoN2gmB3OdBXFW8e1UwgVbmCvNlnAKhvHPaNARI= +github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= +github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= +github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= @@ -2112,6 +2351,8 @@ go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEa go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= @@ -2135,6 +2376,8 @@ go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh4 go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= +go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -2146,10 +2389,13 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -2165,6 +2411,7 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= @@ -2176,6 +2423,9 @@ go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= @@ -2192,6 +2442,7 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -2202,6 +2453,7 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -2217,7 +2469,14 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2236,7 +2495,12 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20210615023648-acb5c1269671/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= -golang.org/x/exp v0.0.0-20210714144626-1041f73d31d8/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -2268,8 +2532,12 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2292,7 +2560,9 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -2349,9 +2619,18 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2398,6 +2677,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2416,6 +2696,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2423,8 +2704,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190524122548-abf6ff778158/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2503,6 +2786,7 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2514,20 +2798,29 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= @@ -2536,6 +2829,10 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2549,6 +2846,9 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2622,6 +2922,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -2629,8 +2930,11 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2902,6 +3206,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -2936,6 +3241,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b6efbc91eaaa16041a5a32c8d306c52803c13bca Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 23 Aug 2023 14:28:58 +0200 Subject: [PATCH 0757/1008] nodebuilder/core: Set default ports for RPC and GRPC (#2596) Now that we short-circuit on the IP address being empty, it's fine to set defaults for RPC and GRPC ports This fixes swamp tests as well --- nodebuilder/core/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/core/config.go b/nodebuilder/core/config.go index d0455be86c..4affcd3087 100644 --- a/nodebuilder/core/config.go +++ b/nodebuilder/core/config.go @@ -19,8 +19,8 @@ type Config struct { func DefaultConfig() Config { return Config{ IP: "", - RPCPort: "", - GRPCPort: "", + RPCPort: "26657", + GRPCPort: "9090", } } From bbe970f8b2151ce1ad2a4bc3fefb5319fbf0ca58 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 23 Aug 2023 18:15:55 +0300 Subject: [PATCH 0758/1008] feat(cmd/node): add logs cmd to dynamically change the log level (#2601) ## Overview Added cmds that allow to change log level during runtime ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- cmd/celestia/logs.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ cmd/celestia/rpc.go | 1 + cmd/flags_misc.go | 16 +++++++-------- 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 cmd/celestia/logs.go diff --git a/cmd/celestia/logs.go b/cmd/celestia/logs.go new file mode 100644 index 0000000000..ac302ff6dd --- /dev/null +++ b/cmd/celestia/logs.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/cmd" +) + +var logCmd = &cobra.Command{ + Use: cmd.LogLevelFlag, + Args: cobra.ExactArgs(1), + Short: "Allows to set log level for all modules to " + + "`DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL and their lower-case forms`", + + RunE: func(c *cobra.Command, args []string) error { + client, err := rpcClient(c.Context()) + if err != nil { + return err + } + return client.Node.LogLevelSet(c.Context(), "*", args[0]) + }, +} + +var logModuleCmd = &cobra.Command{ + Use: cmd.LogLevelModuleFlag, + Args: cobra.MinimumNArgs(1), + Short: "Allows to set log level for a particular module in format :", + RunE: func(c *cobra.Command, args []string) error { + client, err := rpcClient(c.Context()) + if err != nil { + return err + } + for _, ll := range args { + params := strings.Split(ll, ":") + if len(params) != 2 { + return fmt.Errorf("cmd: %s arg must be in form :,"+ + "e.g. pubsub:debug", cmd.LogLevelModuleFlag) + } + if err = client.Node.LogLevelSet(c.Context(), params[0], params[1]); err != nil { + return err + } + } + return nil + }, +} diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index dd50f07fc2..b8724fa789 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -60,6 +60,7 @@ func init() { false, "Print JSON-RPC request along with the response", ) + rpcCmd.AddCommand(logCmd, logModuleCmd) rpcCmd.AddCommand(blobCmd) rootCmd.AddCommand(rpcCmd) } diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 4a11977b86..cd539bde4c 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -20,8 +20,8 @@ import ( ) var ( - logLevelFlag = "log.level" - logLevelModuleFlag = "log.level.module" + LogLevelFlag = "log.level" + LogLevelModuleFlag = "log.level.module" pprofFlag = "pprof" tracingFlag = "tracing" tracingEndpointFlag = "tracing.endpoint" @@ -40,14 +40,14 @@ func MiscFlags() *flag.FlagSet { flags := &flag.FlagSet{} flags.String( - logLevelFlag, + LogLevelFlag, "INFO", `DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL and their lower-case forms`, ) flags.StringSlice( - logLevelModuleFlag, + LogLevelModuleFlag, nil, ":, e.g. pubsub:debug", ) @@ -123,24 +123,24 @@ and their lower-case forms`, // ParseMiscFlags parses miscellaneous flags from the given cmd and applies values to Env. func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { - logLevel := cmd.Flag(logLevelFlag).Value.String() + logLevel := cmd.Flag(LogLevelFlag).Value.String() if logLevel != "" { level, err := logging.LevelFromString(logLevel) if err != nil { - return ctx, fmt.Errorf("cmd: while parsing '%s': %w", logLevelFlag, err) + return ctx, fmt.Errorf("cmd: while parsing '%s': %w", LogLevelFlag, err) } logs.SetAllLoggers(level) } - logModules, err := cmd.Flags().GetStringSlice(logLevelModuleFlag) + logModules, err := cmd.Flags().GetStringSlice(LogLevelModuleFlag) if err != nil { panic(err) } for _, ll := range logModules { params := strings.Split(ll, ":") if len(params) != 2 { - return ctx, fmt.Errorf("cmd: %s arg must be in form :, e.g. pubsub:debug", logLevelModuleFlag) + return ctx, fmt.Errorf("cmd: %s arg must be in form :, e.g. pubsub:debug", LogLevelModuleFlag) } err := logging.SetLogLevel(params[0], params[1]) From 440e232999331adf1d1dc034e5ceb8b14e9db5c7 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 24 Aug 2023 20:15:57 +0800 Subject: [PATCH 0759/1008] chore(deps): bump dagstore (#2602) Updating dagstore should fix the issue with panic of mmap on bitswap reads. --- go.mod | 4 ++-- go.sum | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index cf2329de01..0d26924835 100644 --- a/go.mod +++ b/go.mod @@ -212,7 +212,7 @@ require ( github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-peertaskqueue v0.8.1 // indirect github.com/ipfs/go-verifcid v0.0.2 // indirect - github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 // indirect + github.com/ipld/go-car/v2 v2.11.0 // indirect github.com/ipld/go-codec-dagpb v1.6.0 // indirect github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect @@ -337,7 +337,7 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 - github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230823095320-1d66549bbeb3 + github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 diff --git a/go.sum b/go.sum index 90c6e89ec9..6adeef7f7c 100644 --- a/go.sum +++ b/go.sum @@ -362,8 +362,8 @@ github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHV github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 h1:PckXGxLJjXv97VO3xS8NPHN5oO83X5nvJLbc/4s8jUM= github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14/go.mod h1:70Go8qNy7YAb1PUcHCChRHNX2ke7c9jgUIEklUX+Mac= -github.com/celestiaorg/dagstore v0.0.0-20230823095320-1d66549bbeb3 h1:2/UcuBL0HQ8Y7QXhBjaNMI2LT8or1F67wArTJIs3Vos= -github.com/celestiaorg/dagstore v0.0.0-20230823095320-1d66549bbeb3/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= +github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJx5/hgZ+IeOLEIoLsAveJN/7/ZtQQtPSVw= +github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= @@ -1213,8 +1213,9 @@ github.com/ipfs/go-unixfsnode v1.1.2/go.mod h1:5dcE2x03pyjHk4JjamXmunTMzz+VUtqvP github.com/ipfs/go-unixfsnode v1.4.0/go.mod h1:qc7YFFZ8tABc58p62HnIYbUMwj9chhUuFWmxSokfePo= github.com/ipfs/go-unixfsnode v1.5.1/go.mod h1:ed79DaG9IEuZITJVQn4U6MZDftv6I3ygUBLPfhEbHvk= github.com/ipfs/go-unixfsnode v1.5.2/go.mod h1:NlOebRwYx8lMCNMdhAhEspYPBD3obp7TE0LvBqHY+ks= -github.com/ipfs/go-unixfsnode v1.7.1 h1:RRxO2b6CSr5UQ/kxnGzaChTjp5LWTdf3Y4n8ANZgB/s= github.com/ipfs/go-unixfsnode v1.7.1/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk= +github.com/ipfs/go-unixfsnode v1.7.4 h1:iLvKyAVKUYOIAW2t4kDYqsT7VLGj31eXJE2aeqGfbwA= +github.com/ipfs/go-unixfsnode v1.7.4/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk= github.com/ipfs/interface-go-ipfs-core v0.9.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0= github.com/ipfs/interface-go-ipfs-core v0.10.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0= github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= @@ -1224,8 +1225,8 @@ github.com/ipld/go-car/v2 v2.1.1/go.mod h1:+2Yvf0Z3wzkv7NeI69i8tuZ+ft7jyjPYIWZze github.com/ipld/go-car/v2 v2.5.1/go.mod h1:jKjGOqoCj5zn6KjnabD6JbnCsMntqU2hLiU6baZVO3E= github.com/ipld/go-car/v2 v2.8.0/go.mod h1:a+BnAxUqgr7wcWxW/lI6ctyEQ2v9gjBChPytwFMp2f4= github.com/ipld/go-car/v2 v2.10.1/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg= -github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33 h1:0OZwzSYWIuiKEOXd/2vm5cMcEmmGLFn+1h6lHELCm3s= -github.com/ipld/go-car/v2 v2.10.2-0.20230622090957-499d0c909d33/go.mod h1:sQEkXVM3csejlb1kCCb+vQ/pWBKX9QtvsrysMQjOgOg= +github.com/ipld/go-car/v2 v2.11.0 h1:lkAPwbbTFqbdfawgm+bfmFc8PjGC7D12VcaLXPCLNfM= +github.com/ipld/go-car/v2 v2.11.0/go.mod h1:aDszqev0zjtU8l96g4lwXHaU9bzArj56Y7eEN0q/xqA= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= github.com/ipld/go-codec-dagpb v1.4.1/go.mod h1:XdXTO/TUD/ra9RcK/NfmwBfr1JpFxM2uRKaB9oe4LxE= From 12054379426ebe8b91b73604373ec1665477ebe8 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Thu, 24 Aug 2023 10:23:55 -0400 Subject: [PATCH 0760/1008] refactor: address namespace follow-ups from celestia-app v1.0.0-rc12 bump (#2597) Addresses two follow-ups from https://github.com/celestiaorg/celestia-node/pull/2581 Closes https://github.com/celestiaorg/celestia-node/issues/2561 --- share/namespace.go | 14 +++++++------- share/namespace_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/share/namespace.go b/share/namespace.go index e7ed7dc0d6..df4ad74058 100644 --- a/share/namespace.go +++ b/share/namespace.go @@ -20,13 +20,13 @@ var ( // MinSecondaryReservedNamespace is the lowest secondary reserved namespace // reserved for protocol use. Namespaces higher than this are reserved for // protocol use. - MinSecondaryReservedNamespace = Namespace(appns.MinSecondaryReservedNamespace.Bytes()) - ParitySharesNamespace = Namespace(appns.ParitySharesNamespace.Bytes()) - TailPaddingNamespace = Namespace(appns.TailPaddingNamespace.Bytes()) - ReservedPaddingNamespace = Namespace(appns.PrimaryReservedPaddingNamespace.Bytes()) - TxNamespace = Namespace(appns.TxNamespace.Bytes()) - PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) - ISRNamespace = Namespace(appns.IntermediateStateRootsNamespace.Bytes()) + MinSecondaryReservedNamespace = Namespace(appns.MinSecondaryReservedNamespace.Bytes()) + ParitySharesNamespace = Namespace(appns.ParitySharesNamespace.Bytes()) + TailPaddingNamespace = Namespace(appns.TailPaddingNamespace.Bytes()) + PrimaryReservedPaddingNamespace = Namespace(appns.PrimaryReservedPaddingNamespace.Bytes()) + TxNamespace = Namespace(appns.TxNamespace.Bytes()) + PayForBlobNamespace = Namespace(appns.PayForBlobNamespace.Bytes()) + ISRNamespace = Namespace(appns.IntermediateStateRootsNamespace.Bytes()) ) // Namespace represents namespace of a Share. diff --git a/share/namespace_test.go b/share/namespace_test.go index 7c18d233c8..786441b043 100644 --- a/share/namespace_test.go +++ b/share/namespace_test.go @@ -173,6 +173,16 @@ func TestValidateForBlob(t *testing.T) { ns: append([]byte{appns.NamespaceVersionMax}, bytes.Repeat([]byte{0x0}, appns.NamespaceIDSize)...), wantErr: true, }, + { + name: "invalid blob namespace: primary reserved namespace", + ns: primaryReservedNamespace(0x10), + wantErr: true, + }, + { + name: "invalid blob namespace: secondary reserved namespace", + ns: secondaryReservedNamespace(0x10), + wantErr: true, + }, } for _, tc := range testCases { @@ -186,5 +196,21 @@ func TestValidateForBlob(t *testing.T) { assert.NoError(t, err) }) } +} + +func primaryReservedNamespace(lastByte byte) Namespace { + result := make([]byte, NamespaceSize) + result = append(result, appns.NamespaceVersionZero) + result = append(result, appns.NamespaceVersionZeroPrefix...) + result = append(result, bytes.Repeat([]byte{0x0}, appns.NamespaceVersionZeroIDSize-1)...) + result = append(result, lastByte) + return result +} +func secondaryReservedNamespace(lastByte byte) Namespace { + result := make([]byte, NamespaceSize) + result = append(result, appns.NamespaceVersionMax) + result = append(result, bytes.Repeat([]byte{0xFF}, appns.NamespaceIDSize-1)...) + result = append(result, lastByte) + return result } From 628559bc92fd7e8caaac5f264efff0fce5df2215 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 25 Aug 2023 14:21:16 +0200 Subject: [PATCH 0761/1008] feat(edssser): introduce EDS Store Stresser and cel-shed utility (#2482) --- cmd/cel-shed/eds_store_stress.go | 165 +++++++++++++++++++++++++++++ cmd/cel-shed/main.go | 9 +- libs/edssser/edssser.go | 172 +++++++++++++++++++++++++++++++ nodebuilder/init.go | 9 +- 4 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 cmd/cel-shed/eds_store_stress.go create mode 100644 libs/edssser/edssser.go diff --git a/cmd/cel-shed/eds_store_stress.go b/cmd/cel-shed/eds_store_stress.go new file mode 100644 index 0000000000..62ea5cb772 --- /dev/null +++ b/cmd/cel-shed/eds_store_stress.go @@ -0,0 +1,165 @@ +package main + +import ( + "context" + "errors" + _ "expvar" + "fmt" + "math" + "net/http" + "os" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/mitchellh/go-homedir" + "github.com/pyroscope-io/client/pyroscope" + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/libs/edssser" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +const ( + edsStorePathFlag = "path" + edsWritesFlag = "writes" + edsSizeFlag = "size" + edsDisableLogFlag = "disable-log" + edsLogStatFreqFlag = "log-stat-freq" + edsCleanupFlag = "cleanup" + edsFreshStartFlag = "fresh" + + pyroscopeEndpointFlag = "pyroscope" + putTimeoutFlag = "timeout" + badgerLogLevelFlag = "badger-log-level" +) + +func init() { + edsStoreCmd.AddCommand(edsStoreStress) + + defaultPath := "~/.edssser" + path, err := homedir.Expand(defaultPath) + if err != nil { + panic(err) + } + + pathFlagUsage := fmt.Sprintf("Directory path to use for stress test. Uses %s by default.", defaultPath) + edsStoreStress.Flags().String(edsStorePathFlag, path, pathFlagUsage) + edsStoreStress.Flags().String(pyroscopeEndpointFlag, "", + "Pyroscope address. If no address provided, pyroscope will be disabled") + edsStoreStress.Flags().Int(edsWritesFlag, math.MaxInt, "Total EDS writes to make. MaxInt by default.") + edsStoreStress.Flags().Int(edsSizeFlag, 128, "Chooses EDS size. 128 by default.") + edsStoreStress.Flags().Bool(edsDisableLogFlag, false, "Disables logging. Enabled by default.") + edsStoreStress.Flags().Int(edsLogStatFreqFlag, 10, "Write statistic logging frequency. 10 by default.") + edsStoreStress.Flags().Bool(edsCleanupFlag, false, "Cleans up the store on stop. Disabled by default.") + edsStoreStress.Flags().Bool(edsFreshStartFlag, false, "Cleanup previous state on start. Disabled by default.") + edsStoreStress.Flags().Int(putTimeoutFlag, 30, "Sets put timeout in seconds. 30 sec by default.") + edsStoreStress.Flags().String(badgerLogLevelFlag, "INFO", "Badger log level, Defaults to INFO") + + // kill redundant print + nodebuilder.PrintKeyringInfo = false +} + +var edsStoreCmd = &cobra.Command{ + Use: "eds-store [subcommand]", + Short: "Collection of eds-store related utilities", +} + +var edsStoreStress = &cobra.Command{ + Use: "stress", + Short: `Runs eds.Store stress test over default node.Store Datastore backend (e.g. Badger).`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + // expose expvar vars over http + go http.ListenAndServe(":9999", http.DefaultServeMux) //nolint:errcheck,gosec + + endpoint, _ := cmd.Flags().GetString(pyroscopeEndpointFlag) + if endpoint != "" { + _, err = pyroscope.Start(pyroscope.Config{ + ApplicationName: "cel-shred.stresser", + ServerAddress: endpoint, + ProfileTypes: []pyroscope.ProfileType{ + pyroscope.ProfileCPU, + pyroscope.ProfileAllocObjects, + pyroscope.ProfileAllocSpace, + pyroscope.ProfileInuseObjects, + pyroscope.ProfileInuseSpace, + }, + }) + if err != nil { + fmt.Printf("failed to launch pyroscope with addr: %s err: %s\n", endpoint, err.Error()) + } else { + fmt.Println("connected pyroscope to:", endpoint) + } + } + + path, _ := cmd.Flags().GetString(edsStorePathFlag) + fmt.Printf("using %s\n", path) + + freshStart, _ := cmd.Flags().GetBool(edsFreshStartFlag) + if freshStart { + err = os.RemoveAll(path) + if err != nil { + return err + } + } + + cleanup, _ := cmd.Flags().GetBool(edsCleanupFlag) + if cleanup { + defer func() { + err = errors.Join(err, os.RemoveAll(path)) + }() + } + + loglevel, _ := cmd.Flags().GetString(badgerLogLevelFlag) + if err = logging.SetLogLevel("badger", loglevel); err != nil { + return err + } + + disableLog, _ := cmd.Flags().GetBool(edsDisableLogFlag) + logFreq, _ := cmd.Flags().GetInt(edsLogStatFreqFlag) + edsWrites, _ := cmd.Flags().GetInt(edsWritesFlag) + edsSize, _ := cmd.Flags().GetInt(edsSizeFlag) + putTimeout, _ := cmd.Flags().GetInt(putTimeoutFlag) + + cfg := edssser.Config{ + EDSSize: edsSize, + EDSWrites: edsWrites, + EnableLog: !disableLog, + LogFilePath: path, + StatLogFreq: logFreq, + OpTimeout: time.Duration(putTimeout) * time.Second, + } + + err = nodebuilder.Init(*nodebuilder.DefaultConfig(node.Full), path, node.Full) + if err != nil { + return err + } + + nodestore, err := nodebuilder.OpenStore(path, nil) + if err != nil { + return err + } + defer func() { + err = errors.Join(err, nodestore.Close()) + }() + + datastore, err := nodestore.Datastore() + if err != nil { + return err + } + + stresser, err := edssser.NewEDSsser(path, datastore, cfg) + if err != nil { + return err + } + + stats, err := stresser.Run(cmd.Context()) + if !errors.Is(err, context.Canceled) { + return err + } + + fmt.Printf("%s", stats.Finalize()) + return nil + }, +} diff --git a/cmd/cel-shed/main.go b/cmd/cel-shed/main.go index 7982cfc1be..872bbb48a9 100644 --- a/cmd/cel-shed/main.go +++ b/cmd/cel-shed/main.go @@ -3,12 +3,14 @@ package main import ( "context" "os" + "os/signal" + "syscall" "github.com/spf13/cobra" ) func init() { - rootCmd.AddCommand(p2pCmd, headerCmd) + rootCmd.AddCommand(p2pCmd, headerCmd, edsStoreCmd) } var rootCmd = &cobra.Command{ @@ -26,5 +28,8 @@ func main() { } func run() error { - return rootCmd.ExecuteContext(context.Background()) + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + return rootCmd.ExecuteContext(ctx) } diff --git a/libs/edssser/edssser.go b/libs/edssser/edssser.go new file mode 100644 index 0000000000..fd11b47fcf --- /dev/null +++ b/libs/edssser/edssser.go @@ -0,0 +1,172 @@ +package edssser + +import ( + "context" + "errors" + "fmt" + "os" + "sync" + "testing" + "time" + + "github.com/ipfs/go-datastore" + + "github.com/celestiaorg/celestia-app/pkg/da" + + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/edstest" +) + +type Config struct { + EDSSize int + EDSWrites int + EnableLog bool + LogFilePath string + StatLogFreq int + OpTimeout time.Duration +} + +// EDSsser stand for EDS Store Stresser. +type EDSsser struct { + config Config + datastore datastore.Batching + edsstoreMu sync.Mutex + edsstore *eds.Store + + statsFileMu sync.Mutex + statsFile *os.File +} + +func NewEDSsser(path string, datastore datastore.Batching, cfg Config) (*EDSsser, error) { + edsstore, err := eds.NewStore(path, datastore) + if err != nil { + return nil, err + } + + return &EDSsser{ + config: cfg, + datastore: datastore, + edsstore: edsstore, + }, nil +} + +func (ss *EDSsser) Run(ctx context.Context) (stats Stats, err error) { + ss.edsstoreMu.Lock() + defer ss.edsstoreMu.Unlock() + + err = ss.edsstore.Start(ctx) + if err != nil { + return stats, err + } + defer func() { + err = errors.Join(err, ss.edsstore.Stop(ctx)) + }() + + edsHashes, err := ss.edsstore.List() + if err != nil { + return stats, err + } + fmt.Printf("recovered %d EDSes\n\n", len(edsHashes)) + + t := &testing.T{} + for toWrite := ss.config.EDSWrites - len(edsHashes); ctx.Err() == nil && toWrite > 0; toWrite-- { + took, err := ss.put(ctx, t) + + stats.TotalWritten++ + stats.TotalTime += took + if took < stats.MinTime || stats.MinTime == 0 { + stats.MinTime = took + } else if took > stats.MaxTime { + stats.MaxTime = took + } + + if ss.config.EnableLog { + if stats.TotalWritten%ss.config.StatLogFreq == 0 { + stats := stats.Finalize() + fmt.Println(stats) + go func() { + err := ss.dumpStat(stats) + if err != nil { + fmt.Printf("error dumping stats: %s\n", err.Error()) + } + }() + } + if err != nil { + fmt.Printf("ERROR put: %s, took: %v, at: %v\n", err.Error(), took, time.Now()) + continue + } + if took > ss.config.OpTimeout/2 { + fmt.Println("long put", "size", ss.config.EDSSize, "took", took, "at", time.Now()) + continue + } + + fmt.Println("square written", "size", ss.config.EDSSize, "took", took, "at", time.Now()) + } + } + return stats, nil +} + +func (ss *EDSsser) dumpStat(stats Stats) (err error) { + ss.statsFileMu.Lock() + defer ss.statsFileMu.Unlock() + + ss.statsFile, err = os.Create(ss.config.LogFilePath + "/edssser_stats.txt") + if err != nil { + return err + } + + _, err = ss.statsFile.Write([]byte(stats.String())) + if err != nil { + return err + } + + return ss.statsFile.Close() +} + +type Stats struct { + TotalWritten int + TotalTime, MinTime, MaxTime, AvgTime time.Duration + // Deviation ? +} + +func (stats Stats) Finalize() Stats { + if stats.TotalTime != 0 { + stats.AvgTime = stats.TotalTime / time.Duration(stats.TotalWritten) + } + return stats +} + +func (stats Stats) String() string { + return fmt.Sprintf(` +TotalWritten %d +TotalWritingTime %v +MaxTime %s +MinTime %s +AvgTime %s +`, + stats.TotalWritten, + stats.TotalTime, + stats.MaxTime, + stats.MinTime, + stats.AvgTime, + ) +} + +func (ss *EDSsser) put(ctx context.Context, t *testing.T) (time.Duration, error) { + ctx, cancel := context.WithTimeout(ctx, ss.config.OpTimeout) + if ss.config.OpTimeout == 0 { + ctx, cancel = context.WithCancel(ctx) + } + defer cancel() + + // divide by 2 to get ODS size as expected by RandEDS + square := edstest.RandEDS(t, ss.config.EDSSize/2) + dah, err := da.NewDataAvailabilityHeader(square) + if err != nil { + return 0, err + } + + now := time.Now() + err = ss.edsstore.Put(ctx, dah.Hash(), square) + return time.Since(now), err +} diff --git a/nodebuilder/init.go b/nodebuilder/init.go index 2cabfc8abd..0593d88560 100644 --- a/nodebuilder/init.go +++ b/nodebuilder/init.go @@ -18,6 +18,9 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) +// PrintKeyringInfo whether to print keyring information during init. +var PrintKeyringInfo = true + // Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under // 'path'. func Init(cfg Config, path string, tp node.Type) error { @@ -213,8 +216,10 @@ func generateKeys(cfg Config, ksPath string) error { if err != nil { return err } - fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n", - keyInfo.Name, addr.String(), mn) + if PrintKeyringInfo { + fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n", + keyInfo.Name, addr.String(), mn) + } return nil } From 4b960224e735c7a7e2036f730b6ccc06f8688a2b Mon Sep 17 00:00:00 2001 From: "[NODERS]TEAM" <94483941+nodersteam@users.noreply.github.com> Date: Fri, 25 Aug 2023 19:34:06 +0700 Subject: [PATCH 0762/1008] feat(share/p2p/discovery): Warning for discovery loop if haven't found wanted peers in 5 minutes (#2573) --- share/p2p/discovery/discovery.go | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index af6fef1f89..c880b9b3c2 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -33,6 +33,10 @@ const ( // retryTimeout defines time interval between discovery and advertise attempts. retryTimeout = time.Second + + // logInterval defines the time interval at which a warning message will be logged + // if the desired number of nodes is not detected. + logInterval = 5 * time.Minute ) // discoveryRetryTimeout defines time interval between discovery attempts, needed for tests @@ -195,26 +199,33 @@ func (d *Discovery) Advertise(ctx context.Context) { } // discoveryLoop ensures we always have '~peerLimit' connected peers. -// It starts peer discovery per request and restarts the process until the soft limit reached. +// It initiates peer discovery upon request and restarts the process until the soft limit is reached. func (d *Discovery) discoveryLoop(ctx context.Context) { t := time.NewTicker(discoveryRetryTimeout) defer t.Stop() + + warnTicker := time.NewTicker(logInterval) + defer warnTicker.Stop() + for { - // drain all previous ticks from channel + // drain all previous ticks from the channel drainChannel(t.C) select { case <-t.C: - found := d.discover(ctx) - if !found { - // rerun discovery if amount of peers didn't reach the limit + if !d.discover(ctx) { + // rerun discovery if the number of peers hasn't reached the limit continue } - case <-ctx.Done(): - return - } - - select { - case <-d.triggerDisc: + case <-warnTicker.C: + if d.set.Size() < d.set.Limit() { + log.Warnf( + "Potentially degraded connectivity, unable to discover the desired amount of full node peers in %v. "+ + "Number of peers discovered: %d. Required: %d.", + logInterval, d.set.Size(), d.set.Limit(), + ) + } + // Do not break the loop; just continue + continue case <-ctx.Done(): return } From 196e849e08b95e363c0c33d2c9b00bca83206521 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 28 Aug 2023 14:37:01 +0200 Subject: [PATCH 0763/1008] fix(eds/blockstore): Puts on EDSStore Blockstore (#2532) Closes #2424 Alternative Design Discussions: - Using in-memory instead of in on disk: During an unexpected shutdown or in the case of AsyncGetter, cleanup would not occur and blocks would be stuck in the store indefinitely. Using an in-memory blockstore would fix this issue upon restarts - Using a local Blockgetter to pass to NewErrByzantine: Instead of putting the blocks into the EDS blockstore, the retrieval session could make an in-memory blockstore on the fly to hand to NewErrByzantine when needed. This would look cleaner in the code/make sense architecturally, but it would mean full nodes cannot share these shares with each other during reconstruction. --- go.mod | 2 +- nodebuilder/tests/reconstruct_test.go | 151 ++++++++++++++++++++++++++ share/eds/blockstore.go | 77 +++++++++---- share/eds/inverted_index.go | 6 +- share/eds/retriever.go | 2 + share/eds/store.go | 2 +- share/getters/getter_test.go | 9 +- 7 files changed, 221 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 0d26924835..63f6879041 100644 --- a/go.mod +++ b/go.mod @@ -202,7 +202,7 @@ require ( github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect - github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect + github.com/ipfs/go-ipfs-ds-help v1.1.0 github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect github.com/ipfs/go-ipfs-exchange-offline v0.3.0 // indirect github.com/ipfs/go-ipfs-pq v0.0.3 // indirect diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 8b1490272c..d8640c5249 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -14,6 +14,7 @@ import ( "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" + ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" @@ -81,6 +82,156 @@ func TestFullReconstructFromBridge(t *testing.T) { require.NoError(t, errg.Wait()) } +/* +Test-Case: Full Node reconstructs blocks from each other, after unsuccessfully syncing the complete +block from LN subnetworks. Analog to TestShareAvailable_DisconnectedFullNodes. +*/ +func TestFullReconstructFromFulls(t *testing.T) { + light.DefaultSampleAmount = 10 // s + const ( + blocks = 10 + btime = time.Millisecond * 300 + bsize = 8 // k + lnodes = 12 // c - total number of nodes on two subnetworks + ) + + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) + + const defaultTimeInterval = time.Second * 5 + bridge := sw.NewBridgeNode() + + sw.SetBootstrapper(t, bridge) + require.NoError(t, bridge.Start(ctx)) + + // TODO: This is required to avoid flakes coming from unfinished retry + // mechanism for the same peer in go-header + _, err := bridge.HeaderServ.WaitForHeight(ctx, uint64(blocks)) + require.NoError(t, err) + + lights1 := make([]*nodebuilder.Node, lnodes/2) + lights2 := make([]*nodebuilder.Node, lnodes/2) + subs := make([]event.Subscription, lnodes) + errg, errCtx := errgroup.WithContext(ctx) + for i := 0; i < lnodes/2; i++ { + i := i + errg.Go(func() error { + lnConfig := nodebuilder.DefaultConfig(node.Light) + setTimeInterval(lnConfig, defaultTimeInterval) + light := sw.NewNodeWithConfig(node.Light, lnConfig) + sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) + if err != nil { + return err + } + subs[i] = sub + lights1[i] = light + return light.Start(errCtx) + }) + errg.Go(func() error { + lnConfig := nodebuilder.DefaultConfig(node.Light) + setTimeInterval(lnConfig, defaultTimeInterval) + light := sw.NewNodeWithConfig(node.Light, lnConfig) + sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) + if err != nil { + return err + } + subs[(lnodes/2)+i] = sub + lights2[i] = light + return light.Start(errCtx) + }) + } + + require.NoError(t, errg.Wait()) + + for i := 0; i < lnodes; i++ { + select { + case <-ctx.Done(): + t.Fatal("peer was not found") + case <-subs[i].Out(): + require.NoError(t, subs[i].Close()) + continue + } + } + + // Remove bootstrappers to prevent FNs from connecting to bridge + sw.Bootstrappers = []ma.Multiaddr{} + // Use light nodes from respective subnetworks as bootstrappers to prevent connection to bridge + lnBootstrapper1, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(lights1[0].Host)) + require.NoError(t, err) + lnBootstrapper2, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(lights2[0].Host)) + require.NoError(t, err) + + cfg := nodebuilder.DefaultConfig(node.Full) + setTimeInterval(cfg, defaultTimeInterval) + cfg.Share.UseShareExchange = false + cfg.Share.Discovery.PeersLimit = 0 + cfg.Header.TrustedPeers = []string{lnBootstrapper1[0].String()} + full1 := sw.NewNodeWithConfig(node.Full, cfg) + cfg.Header.TrustedPeers = []string{lnBootstrapper2[0].String()} + full2 := sw.NewNodeWithConfig(node.Full, cfg) + require.NoError(t, full1.Start(ctx)) + require.NoError(t, full2.Start(ctx)) + + // Form topology + for i := 0; i < lnodes/2; i++ { + // Separate light nodes into two subnetworks + for j := 0; j < lnodes/2; j++ { + sw.Disconnect(t, lights1[i], lights2[j]) + if i != j { + sw.Connect(t, lights1[i], lights1[j]) + sw.Connect(t, lights2[i], lights2[j]) + } + } + + sw.Connect(t, full1, lights1[i]) + sw.Disconnect(t, full1, lights2[i]) + + sw.Connect(t, full2, lights2[i]) + sw.Disconnect(t, full2, lights1[i]) + } + + // Ensure the fulls are not connected to the bridge + sw.Disconnect(t, full1, full2) + sw.Disconnect(t, full1, bridge) + sw.Disconnect(t, full2, bridge) + + h, err := full1.HeaderServ.WaitForHeight(ctx, uint64(10+blocks-1)) + require.NoError(t, err) + + // Ensure that the full nodes cannot reconstruct before being connected to each other + ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*30) + errg, errCtx = errgroup.WithContext(ctxErr) + errg.Go(func() error { + return full1.ShareServ.SharesAvailable(errCtx, h.DAH) + }) + errg.Go(func() error { + return full2.ShareServ.SharesAvailable(errCtx, h.DAH) + }) + require.Error(t, errg.Wait()) + cancelErr() + + // Reconnect FNs + sw.Connect(t, full1, full2) + + errg, bctx := errgroup.WithContext(ctx) + for i := 10; i < blocks+11; i++ { + h, err := full1.HeaderServ.WaitForHeight(bctx, uint64(i)) + require.NoError(t, err) + errg.Go(func() error { + return full1.ShareServ.SharesAvailable(bctx, h.DAH) + }) + errg.Go(func() error { + return full2.ShareServ.SharesAvailable(bctx, h.DAH) + }) + } + + require.NoError(t, <-fillDn) + require.NoError(t, errg.Wait()) +} + /* Test-Case: Full Node reconstructs blocks only from Light Nodes Pre-Reqs: diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 9fc9d7f2f8..22ef28f821 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -10,12 +10,15 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + dshelp "github.com/ipfs/go-ipfs-ds-help" ipld "github.com/ipfs/go-ipld-format" ) var _ bstore.Blockstore = (*blockstore)(nil) var ( + blockstoreCacheKey = datastore.NewKey("bs-cache") errUnsupportedOperation = errors.New("unsupported operation") ) @@ -30,29 +33,42 @@ var ( type blockstore struct { store *Store cache *blockstoreCache + ds datastore.Batching } -func newBlockstore(store *Store, cache *blockstoreCache) *blockstore { +func newBlockstore(store *Store, cache *blockstoreCache, ds datastore.Batching) *blockstore { return &blockstore{ store: store, cache: cache, + ds: namespace.Wrap(ds, blockstoreCacheKey), } } func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) - if errors.Is(err, ErrNotFound) { - return false, nil + if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { + // key wasn't found in top level blockstore, but could be in datastore while being reconstructed + dsHas, dsErr := bs.ds.Has(ctx, dshelp.MultihashToDsKey(cid.Hash())) + if dsErr != nil { + return false, nil + } + return dsHas, nil } if err != nil { - return false, fmt.Errorf("failed to find shards containing multihash: %w", err) + return false, err } + return len(keys) > 0, nil } func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) - if errors.Is(err, ErrNotFound) { + if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { + k := dshelp.MultihashToDsKey(cid.Hash()) + blockData, err := bs.ds.Get(ctx, k) + if err == nil { + return blocks.NewBlockWithCid(blockData, cid) + } // nmt's GetNode expects an ipld.ErrNotFound when a cid is not found. return nil, ipld.ErrNotFound{Cid: cid} } @@ -65,7 +81,12 @@ func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) - if errors.Is(err, ErrNotFound) { + if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { + k := dshelp.MultihashToDsKey(cid.Hash()) + size, err := bs.ds.GetSize(ctx, k) + if err == nil { + return size, nil + } // nmt's GetSize expects an ipld.ErrNotFound when a cid is not found. return 0, ipld.ErrNotFound{Cid: cid} } @@ -75,27 +96,35 @@ func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { return blockstr.GetSize(ctx, cid) } -// DeleteBlock is a noop on the EDS blockstore that returns an errUnsupportedOperation when called. -func (bs *blockstore) DeleteBlock(context.Context, cid.Cid) error { - return errUnsupportedOperation +func (bs *blockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { + k := dshelp.MultihashToDsKey(cid.Hash()) + return bs.ds.Delete(ctx, k) } -// Put is a noop on the EDS blockstore, but it does not return an error because it is called by -// bitswap. For clarification, an implementation of Put does not make sense in this context because -// it is unclear which CAR file the block should be written to. -// -// TODO: throw errUnsupportedOperation after issue #1440 -func (bs *blockstore) Put(context.Context, blocks.Block) error { - return nil +func (bs *blockstore) Put(ctx context.Context, blk blocks.Block) error { + k := dshelp.MultihashToDsKey(blk.Cid().Hash()) + // note: we leave duplicate resolution to the underlying datastore + return bs.ds.Put(ctx, k, blk.RawData()) } -// PutMany is a noop on the EDS blockstore, but it does not return an error because it is called by -// bitswap. For clarification, an implementation of PutMany does not make sense in this context -// because it is unclear which CAR file the blocks should be written to. -// -// TODO: throw errUnsupportedOperation after issue #1440 -func (bs *blockstore) PutMany(context.Context, []blocks.Block) error { - return nil +func (bs *blockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { + if len(blocks) == 1 { + // performance fast-path + return bs.Put(ctx, blocks[0]) + } + + t, err := bs.ds.Batch(ctx) + if err != nil { + return err + } + for _, b := range blocks { + k := dshelp.MultihashToDsKey(b.Cid().Hash()) + err = t.Put(ctx, k, b.RawData()) + if err != nil { + return err + } + } + return t.Commit(ctx) } // AllKeysChan is a noop on the EDS blockstore because the keys are not stored in a single CAR file. @@ -112,7 +141,7 @@ func (bs *blockstore) HashOnRead(bool) { // getReadOnlyBlockstore finds the underlying blockstore of the shard that contains the given CID. func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (dagstore.ReadBlockstore, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) - if errors.Is(err, datastore.ErrNotFound) { + if errors.Is(err, datastore.ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { return nil, ErrNotFound } if err != nil { diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index 81df5d92ef..8b9dcb5d95 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -2,6 +2,7 @@ package eds import ( "context" + "errors" "fmt" "github.com/filecoin-project/dagstore/index" @@ -14,6 +15,9 @@ import ( const invertedIndexPath = "/inverted_index/" +// ErrNotFoundInIndex is returned instead of ErrNotFound if the multihash doesn't exist in the index +var ErrNotFoundInIndex = fmt.Errorf("does not exist in index") + // simpleInvertedIndex is an inverted index that only stores a single shard key per multihash. Its // implementation is modified from the default upstream implementation in dagstore/index. type simpleInvertedIndex struct { @@ -76,7 +80,7 @@ func (s *simpleInvertedIndex) GetShardsForMultihash(ctx context.Context, mh mult key := ds.NewKey(string(mh)) sbz, err := s.ds.Get(ctx, key) if err != nil { - return nil, fmt.Errorf("failed to lookup index for mh %s, err: %w", mh, err) + return nil, errors.Join(ErrNotFoundInIndex, err) } return []shard.Key{shard.KeyFromString(string(sbz))}, nil diff --git a/share/eds/retriever.go b/share/eds/retriever.go index a870e07e22..e7837ae23a 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -122,6 +122,7 @@ type retrievalSession struct { // newSession creates a new retrieval session and kicks off requesting process. func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHeader) (*retrievalSession, error) { size := len(dah.RowRoots) + treeFn := func(_ rsmt2d.Axis, index uint) rsmt2d.Tree { // use proofs adder if provided, to cache collected proofs while recomputing the eds var opts []nmt.Option @@ -152,6 +153,7 @@ func (r *Retriever) newSession(ctx context.Context, dah *da.DataAvailabilityHead for i := range ses.squareCellsLks { ses.squareCellsLks[i] = make([]sync.Mutex, size) } + go ses.request(ctx) return ses, nil } diff --git a/share/eds/store.go b/share/eds/store.go index fd679e2591..24a96c9fe4 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -119,7 +119,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { mounts: r, cache: cache, } - store.bs = newBlockstore(store, cache) + store.bs = newBlockstore(store, cache, ds) return store, nil } diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 5cc73aa7cb..ae90074625 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -163,7 +163,8 @@ func TestIPLDGetter(t *testing.T) { err = edsStore.Start(ctx) require.NoError(t, err) - bserv := bsrv.New(edsStore.Blockstore(), offline.Exchange(edsStore.Blockstore())) + bStore := edsStore.Blockstore() + bserv := bsrv.New(bStore, offline.Exchange(bStore)) sg := NewIPLDGetter(bserv) t.Run("GetShare", func(t *testing.T) { @@ -200,6 +201,12 @@ func TestIPLDGetter(t *testing.T) { retrievedEDS, err := sg.GetEDS(ctx, &dah) require.NoError(t, err) assert.True(t, randEds.Equals(retrievedEDS)) + + // Ensure blocks still exist after cleanup + colRoots, _ := retrievedEDS.ColRoots() + has, err := bStore.Has(ctx, ipld.MustCidFromNamespacedSha256(colRoots[0])) + assert.NoError(t, err) + assert.True(t, has) }) t.Run("GetSharesByNamespace", func(t *testing.T) { From 8deffba77b0e68f6a2229bc8266359eda704c584 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 29 Aug 2023 12:43:52 +0200 Subject: [PATCH 0764/1008] fix: fixing panic on GetShare with out of bounds indexes (#2605) GetShare would panic on RPC calls if the passed indicies were out of bounds. --- share/getter.go | 2 ++ share/getters/cascade.go | 8 ++++---- share/getters/getter_test.go | 8 ++++++++ share/getters/ipld.go | 6 ++++++ share/getters/store.go | 6 ++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/share/getter.go b/share/getter.go index a33ea7aa87..19e4498458 100644 --- a/share/getter.go +++ b/share/getter.go @@ -13,6 +13,8 @@ import ( var ( // ErrNotFound is used to indicate that requested data could not be found. ErrNotFound = errors.New("share: data not found") + // ErrOutOfBounds is used to indicate that a passed row or column index is out of bounds of the square size. + ErrOutOfBounds = errors.New("share: row or column index is larger than square size") ) // Getter interface provides a set of accessors for shares by the Root. diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 98f29c87ca..63d7713d3d 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -3,7 +3,6 @@ package getters import ( "context" "errors" - "fmt" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -38,9 +37,10 @@ func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, co attribute.Int("col", col), )) defer span.End() - if row >= len(root.RowRoots) || col >= len(root.ColumnRoots) { - err := fmt.Errorf("cascade/get-share: invalid indexes were provided:rowIndex=%d, colIndex=%d."+ - "squarewidth=%d", row, col, len(root.RowRoots)) + + upperBound := len(root.RowRoots) + if row >= upperBound || col >= upperBound { + err := share.ErrOutOfBounds span.RecordError(err) return nil, err } diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index ae90074625..02e075459b 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -107,6 +107,10 @@ func TestStoreGetter(t *testing.T) { } } + // doesn't panic on indexes too high + _, err := sg.GetShare(ctx, &dah, squareSize, squareSize) + require.ErrorIs(t, err, share.ErrOutOfBounds) + // root not found _, dah = randomEDS(t) _, err = sg.GetShare(ctx, &dah, 0, 0) @@ -184,6 +188,10 @@ func TestIPLDGetter(t *testing.T) { } } + // doesn't panic on indexes too high + _, err := sg.GetShare(ctx, &dah, squareSize+1, squareSize+1) + require.ErrorIs(t, err, share.ErrOutOfBounds) + // root not found _, dah = randomEDS(t) _, err = sg.GetShare(ctx, &dah, 0, 0) diff --git a/share/getters/ipld.go b/share/getters/ipld.go index af6e7673ba..a892e0fc82 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -48,6 +48,12 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in utils.SetStatusAndEnd(span, err) }() + upperBound := len(dah.RowRoots) + if row >= upperBound || col >= upperBound { + err := share.ErrOutOfBounds + span.RecordError(err) + return nil, err + } root, leaf := ipld.Translate(dah, row, col) // wrap the blockservice in a session if it has been signaled in the context. diff --git a/share/getters/store.go b/share/getters/store.go index 28c6c6d021..989649f795 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -43,6 +43,12 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i utils.SetStatusAndEnd(span, err) }() + upperBound := len(dah.RowRoots) + if row >= upperBound || col >= upperBound { + err := share.ErrOutOfBounds + span.RecordError(err) + return nil, err + } root, leaf := ipld.Translate(dah, row, col) bs, err := sg.store.CARBlockstore(ctx, dah.Hash()) if errors.Is(err, eds.ErrNotFound) { From 9d663cf7d948fefa961d9b3c8d0afb8dca3ec8ab Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 30 Aug 2023 13:12:01 +0200 Subject: [PATCH 0765/1008] chore: bump go-header and go-fraud (#2619) This PR bumps go-header and go-fraud which are both breaking - lots of changes here so please review carefully. Note that this also contains re-introduction of VerifyCommitLightTrusting which caused us issues in `mocha` due to rapid voting power changes. At the moment, there are no test utilities to test that specific case, so we need to wait to roll it out to `mocha`. Related: https://github.com/celestiaorg/celestia-app/issues/2382 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- api/docgen/examples.go | 2 +- core/exchange.go | 11 +-- core/header_test.go | 2 +- core/listener.go | 4 +- core/listener_test.go | 4 +- das/coordinator_test.go | 8 +-- das/daser.go | 8 +-- das/daser_test.go | 46 ++++++++---- das/state.go | 15 ++-- das/worker.go | 2 +- go.mod | 4 +- go.sum | 8 +-- header/header.go | 91 ++++++++++++++++-------- header/headertest/testing.go | 8 +-- header/headertest/verify_test.go | 49 +++---------- header/serde.go | 2 +- header/verify.go | 76 -------------------- nodebuilder/das/constructors.go | 6 +- nodebuilder/das/module.go | 5 +- nodebuilder/fraud/constructors.go | 72 +++++++++++-------- nodebuilder/fraud/fraud.go | 84 ++++++++++++++++++++++ nodebuilder/fraud/lifecycle.go | 24 ++++--- nodebuilder/fraud/module.go | 14 ++-- nodebuilder/fraud/service.go | 87 ---------------------- nodebuilder/fraud/unmarshaler.go | 32 +++++++++ nodebuilder/header/constructors.go | 33 +++++---- nodebuilder/header/module.go | 48 +++++++------ nodebuilder/header/module_test.go | 8 +-- nodebuilder/header/service.go | 8 +-- nodebuilder/header/service_test.go | 4 +- nodebuilder/module.go | 5 +- nodebuilder/p2p/pubsub.go | 13 ++-- nodebuilder/settings.go | 3 +- nodebuilder/state/core.go | 6 +- nodebuilder/state/module.go | 7 +- nodebuilder/testing.go | 5 +- nodebuilder/tests/api_test.go | 4 +- nodebuilder/tests/sync_test.go | 4 +- share/eds/byzantine/bad_encoding.go | 41 ++++------- share/eds/byzantine/bad_encoding_test.go | 2 +- share/eds/retriever_test.go | 4 +- share/p2p/discovery/discovery.go | 3 +- share/p2p/peers/manager.go | 2 +- share/p2p/peers/manager_test.go | 8 +-- state/core_access.go | 13 ++-- state/integration_test.go | 6 +- 46 files changed, 449 insertions(+), 442 deletions(-) delete mode 100644 header/verify.go delete mode 100644 nodebuilder/fraud/service.go create mode 100644 nodebuilder/fraud/unmarshaler.go diff --git a/api/docgen/examples.go b/api/docgen/examples.go index 3456880c4f..b873e7e050 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -56,7 +56,7 @@ var ExampleValues = map[reflect.Type]interface{}{ reflect.TypeOf(node.Full): node.Full, reflect.TypeOf(auth.Permission("admin")): auth.Permission("admin"), reflect.TypeOf(byzantine.BadEncoding): byzantine.BadEncoding, - reflect.TypeOf((*fraud.Proof)(nil)).Elem(): byzantine.CreateBadEncodingProof( + reflect.TypeOf((*fraud.Proof[*header.ExtendedHeader])(nil)).Elem(): byzantine.CreateBadEncodingProof( []byte("bad encoding proof"), 42, &byzantine.ErrByzantine{ diff --git a/core/exchange.go b/core/exchange.go index bed2404195..f8e1606a3e 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -78,7 +78,7 @@ func (ce *Exchange) GetVerifiedRange( from *header.ExtendedHeader, amount uint64, ) ([]*header.ExtendedHeader, error) { - headers, err := ce.GetRangeByHeight(ctx, uint64(from.Height())+1, amount) + headers, err := ce.GetRangeByHeight(ctx, from.Height()+1, amount) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende return nil, fmt.Errorf("extending block data for height %d: %w", &block.Height, err) } // construct extended header - eh, err := ce.construct(ctx, &block.Header, comm, vals, eds) + eh, err := ce.construct(&block.Header, comm, vals, eds) if err != nil { panic(fmt.Errorf("constructing extended header for height %d: %w", &block.Height, err)) } @@ -133,7 +133,10 @@ func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.Extende return eh, nil } -func (ce *Exchange) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (ce *Exchange) Head( + ctx context.Context, + _ ...libhead.HeadOption[*header.ExtendedHeader], +) (*header.ExtendedHeader, error) { log.Debug("requesting head") return ce.getExtendedHeaderByHeight(ctx, nil) } @@ -157,7 +160,7 @@ func (ce *Exchange) getExtendedHeaderByHeight(ctx context.Context, height *int64 return nil, fmt.Errorf("extending block data for height %d: %w", b.Header.Height, err) } // create extended header - eh, err := ce.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) + eh, err := ce.construct(&b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { panic(fmt.Errorf("constructing extended header for height %d: %w", b.Header.Height, err)) } diff --git a/core/header_test.go b/core/header_test.go index 1c89db9d6b..c942ea7875 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -33,7 +33,7 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { eds, err := extendBlock(b.Data, b.Header.Version.App) require.NoError(t, err) - headerExt, err := header.MakeExtendedHeader(ctx, &b.Header, comm, val, eds) + headerExt, err := header.MakeExtendedHeader(&b.Header, comm, val, eds) require.NoError(t, err) assert.Equal(t, header.EmptyDAH(), *headerExt.DAH) diff --git a/core/listener.go b/core/listener.go index 565fc62032..1c79fbbe71 100644 --- a/core/listener.go +++ b/core/listener.go @@ -160,7 +160,7 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS return fmt.Errorf("extending block data: %w", err) } // generate extended header - eh, err := cl.construct(ctx, &b.Header, &b.Commit, &b.ValidatorSet, eds) + eh, err := cl.construct(&b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { panic(fmt.Errorf("making extended header: %w", err)) } @@ -181,7 +181,7 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS if !syncing { err = cl.hashBroadcaster(ctx, shrexsub.Notification{ DataHash: eh.DataHash.Bytes(), - Height: uint64(eh.Height()), + Height: eh.Height(), }) if err != nil && !errors.Is(err, context.Canceled) { log.Errorw("listener: broadcasting data hash", diff --git a/core/listener_test.go b/core/listener_test.go index 7d4b12310a..8b3d05bea9 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -31,8 +31,8 @@ func TestListener(t *testing.T) { // create mocknet with two pubsub endpoints ps0, ps1 := createMocknetWithTwoPubsubEndpoints(ctx, t) subscriber := p2p.NewSubscriber[*header.ExtendedHeader](ps1, header.MsgID, networkID) - err := subscriber.AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult { - return pubsub.ValidationAccept + err := subscriber.SetVerifier(func(context.Context, *header.ExtendedHeader) error { + return nil }) require.NoError(t, err) require.NoError(t, subscriber.Start(ctx)) diff --git a/das/coordinator_test.go b/das/coordinator_test.go index 188cb0d222..55ed01dd4e 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -366,7 +366,7 @@ func (m *mockSampler) sample(ctx context.Context, h *header.ExtendedHeader) erro m.lock.Lock() defer m.lock.Unlock() - height := uint64(h.Height()) + height := h.Height() m.done[height]++ if len(m.done) > int(m.NetworkHead-m.SampleFrom) && !m.isFinished { @@ -503,7 +503,7 @@ func (o *checkOrder) middleWare(out sampleFn) sampleFn { if len(o.queue) > 0 { // check last item in queue to be same as input - if o.queue[0] != uint64(h.Height()) { + if o.queue[0] != h.Height() { defer o.lock.Unlock() return fmt.Errorf("expected height: %v,got: %v", o.queue[0], h.Height()) } @@ -573,7 +573,7 @@ func (l *lock) releaseAll(except ...uint64) { func (l *lock) middleWare(out sampleFn) sampleFn { return func(ctx context.Context, h *header.ExtendedHeader) error { l.m.Lock() - ch, blocked := l.blockList[uint64(h.Height())] + ch, blocked := l.blockList[h.Height()] l.m.Unlock() if !blocked { return out(ctx, h) @@ -589,7 +589,7 @@ func (l *lock) middleWare(out sampleFn) sampleFn { } func onceMiddleWare(out sampleFn) sampleFn { - db := make(map[int64]int) + db := make(map[uint64]int) m := sync.Mutex{} return func(ctx context.Context, h *header.ExtendedHeader) error { m.Lock() diff --git a/das/daser.go b/das/daser.go index d4ad0ee641..9d3e43a91b 100644 --- a/das/daser.go +++ b/das/daser.go @@ -25,7 +25,7 @@ type DASer struct { params Parameters da share.Availability - bcast fraud.Broadcaster + bcast fraud.Broadcaster[*header.ExtendedHeader] hsub libhead.Subscriber[*header.ExtendedHeader] // listens for new headers in the network getter libhead.Getter[*header.ExtendedHeader] // retrieves past headers @@ -47,7 +47,7 @@ func NewDASer( hsub libhead.Subscriber[*header.ExtendedHeader], getter libhead.Getter[*header.ExtendedHeader], dstore datastore.Datastore, - bcast fraud.Broadcaster, + bcast fraud.Broadcaster[*header.ExtendedHeader], shrexBroadcast shrexsub.BroadcastFn, options ...Option, ) (*DASer, error) { @@ -99,7 +99,7 @@ func (d *DASer) Start(ctx context.Context) error { // attempt to get head info. No need to handle error, later DASer // will be able to find new head from subscriber after it is started if h, err := d.getter.Head(ctx); err == nil { - cp.NetworkHead = uint64(h.Height()) + cp.NetworkHead = h.Height() } } log.Info("starting DASer from checkpoint: ", cp.String()) @@ -152,7 +152,7 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { var byzantineErr *byzantine.ErrByzantine if errors.As(err, &byzantineErr) { log.Warn("Propagating proof...") - sendErr := d.bcast.Broadcast(ctx, byzantine.CreateBadEncodingProof(h.Hash(), uint64(h.Height()), byzantineErr)) + sendErr := d.bcast.Broadcast(ctx, byzantine.CreateBadEncodingProof(h.Hash(), h.Height(), byzantineErr)) if sendErr != nil { log.Errorw("fraud proof propagating failed", "err", sendErr) } diff --git a/das/daser_test.go b/das/daser_test.go index 7398310a6b..68f6e01ef2 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -159,21 +159,37 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) // create fraud service and break one header - getter := func(ctx context.Context, height uint64) (libhead.Header, error) { + getter := func(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { return mockGet.GetByHeight(ctx, height) } - f := fraudserv.NewProofService(ps, net.Hosts()[0], getter, ds, false, "private") - require.NoError(t, f.Start(ctx)) + unmarshaler := fraud.MultiUnmarshaler[*header.ExtendedHeader]{ + Unmarshalers: map[fraud.ProofType]func([]byte) (fraud.Proof[*header.ExtendedHeader], error){ + byzantine.BadEncoding: func(data []byte) (fraud.Proof[*header.ExtendedHeader], error) { + befp := &byzantine.BadEncodingProof{} + return befp, befp.UnmarshalBinary(data) + }, + }, + } + + fserv := fraudserv.NewProofService[*header.ExtendedHeader](ps, + net.Hosts()[0], + getter, + unmarshaler, + ds, + false, + "private", + ) + require.NoError(t, fserv.Start(ctx)) mockGet.headers[1], _ = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() // create and start DASer - daser, err := NewDASer(avail, sub, mockGet, ds, f, newBroadcastMock(1)) + daser, err := NewDASer(avail, sub, mockGet, ds, fserv, newBroadcastMock(1)) require.NoError(t, err) resultCh := make(chan error) - go fraud.OnProof(newCtx, f, byzantine.BadEncoding, - func(fraud.Proof) { + go fraud.OnProof[*header.ExtendedHeader](newCtx, fserv, byzantine.BadEncoding, + func(fraud.Proof[*header.ExtendedHeader]) { resultCh <- daser.Stop(newCtx) }) @@ -210,10 +226,10 @@ func TestDASerSampleTimeout(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) sub := new(headertest.Subscriber) - f := new(fraudtest.DummyService) + fserv := &fraudtest.DummyService[*header.ExtendedHeader]{} // create and start DASer - daser, err := NewDASer(avail, sub, getter, ds, f, newBroadcastMock(1), WithSampleTimeout(1)) + daser, err := NewDASer(avail, sub, getter, ds, fserv, newBroadcastMock(1), WithSampleTimeout(1)) require.NoError(t, err) require.NoError(t, daser.Start(ctx)) @@ -235,9 +251,9 @@ func createDASerSubcomponents( bServ blockservice.BlockService, numGetter, numSub int, -) (*mockGetter, *headertest.Subscriber, *fraudtest.DummyService) { +) (*mockGetter, *headertest.Subscriber, *fraudtest.DummyService[*header.ExtendedHeader]) { mockGet, sub := createMockGetterAndSub(t, bServ, numGetter, numSub) - fraud := new(fraudtest.DummyService) + fraud := &fraudtest.DummyService[*header.ExtendedHeader]{} return mockGet, sub, fraud } @@ -313,7 +329,10 @@ func (m *mockGetter) generateHeaders(t *testing.T, bServ blockservice.BlockServi m.head = int64(startHeight + endHeight) } -func (m *mockGetter) Head(context.Context) (*header.ExtendedHeader, error) { +func (m *mockGetter) Head( + context.Context, + ...libhead.HeadOption[*header.ExtendedHeader], +) (*header.ExtendedHeader, error) { return m.headers[m.head], nil } @@ -354,7 +373,10 @@ func (m benchGetterStub) GetByHeight(context.Context, uint64) (*header.ExtendedH type getterStub struct{} -func (m getterStub) Head(context.Context) (*header.ExtendedHeader, error) { +func (m getterStub) Head( + context.Context, + ...libhead.HeadOption[*header.ExtendedHeader], +) (*header.ExtendedHeader, error) { return &header.ExtendedHeader{RawHeader: header.RawHeader{Height: 1}}, nil } diff --git a/das/state.go b/das/state.go index 6af0b7d8d8..bd3a018a40 100644 --- a/das/state.go +++ b/das/state.go @@ -132,30 +132,29 @@ func (s *coordinatorState) handleRetryResult(res result) { } } -func (s *coordinatorState) isNewHead(newHead int64) bool { +func (s *coordinatorState) isNewHead(newHead uint64) bool { // seen this header before - if uint64(newHead) <= s.networkHead { + if newHead <= s.networkHead { log.Warnf("received head height: %v, which is lower or the same as previously known: %v", newHead, s.networkHead) return false } return true } -func (s *coordinatorState) updateHead(newHead int64) { +func (s *coordinatorState) updateHead(newHead uint64) { if s.networkHead == s.sampleFrom { log.Infow("found first header, starting sampling") } - s.networkHead = uint64(newHead) + s.networkHead = newHead log.Debugw("updated head", "from_height", s.networkHead, "to_height", newHead) s.checkDone() } // recentJob creates a job to process a recent header. func (s *coordinatorState) recentJob(header *header.ExtendedHeader) job { - height := uint64(header.Height()) // move next, to prevent catchup job from processing same height - if s.next == height { + if s.next == header.Height() { s.next++ } s.nextJobID++ @@ -163,8 +162,8 @@ func (s *coordinatorState) recentJob(header *header.ExtendedHeader) job { id: s.nextJobID, jobType: recentJob, header: header, - from: height, - to: height, + from: header.Height(), + to: header.Height(), } } diff --git a/das/worker.go b/das/worker.go index 746324ec48..f2e8c4d821 100644 --- a/das/worker.go +++ b/das/worker.go @@ -135,7 +135,7 @@ func (w *worker) sample(ctx context.Context, timeout time.Duration, height uint6 if w.state.job.jobType == recentJob { err = w.broadcast(ctx, shrexsub.Notification{ DataHash: h.DataHash.Bytes(), - Height: uint64(h.Height()), + Height: h.Height(), }) if err != nil { log.Warn("failed to broadcast availability message", diff --git a/go.mod b/go.mod index 63f6879041..846f4316e6 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,8 @@ require ( github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.0.0-rc12 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 - github.com/celestiaorg/go-fraud v0.1.2 - github.com/celestiaorg/go-header v0.2.13 + github.com/celestiaorg/go-fraud v0.2.0 + github.com/celestiaorg/go-header v0.3.0 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.18.1 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 6adeef7f7c..09bd8ab26e 100644 --- a/go.sum +++ b/go.sum @@ -366,10 +366,10 @@ github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJ github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= -github.com/celestiaorg/go-fraud v0.1.2 h1:Bf7yIN3lZ4IR/Vlu5OtmcVCVNESBKEJ/xwu28rRKGA8= -github.com/celestiaorg/go-fraud v0.1.2/go.mod h1:kHZXQY+6gd1kYkoWRFFKgWyrLPWRgDN3vd1Ll9gE/oo= -github.com/celestiaorg/go-header v0.2.13 h1:sUJLXYs8ViPpxLXyIIaW3h4tPFgtVYMhzsLC4GHfS8I= -github.com/celestiaorg/go-header v0.2.13/go.mod h1:NhiWq97NtAYyRBu8quzYOUghQULjgOzO2Ql0iVEFOf0= +github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= +github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= +github.com/celestiaorg/go-header v0.3.0 h1:9fhxSgldPiWWq3yd9u7oSk5vYqaLV1JkeTnJdGcisFo= +github.com/celestiaorg/go-header v0.3.0/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= diff --git a/header/header.go b/header/header.go index d69b11d998..92f8538696 100644 --- a/header/header.go +++ b/header/header.go @@ -2,12 +2,12 @@ package header import ( "bytes" - "context" "encoding/json" "fmt" "time" tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/light" core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/appconsts" @@ -18,7 +18,6 @@ import ( // ConstructFn aliases a function that creates an ExtendedHeader. type ConstructFn = func( - context.Context, *core.Header, *core.Commit, *core.ValidatorSet, @@ -45,31 +44,8 @@ type ExtendedHeader struct { DAH *DataAvailabilityHeader `json:"dah"` } -func (eh *ExtendedHeader) New() libhead.Header { - return new(ExtendedHeader) -} - -func (eh *ExtendedHeader) IsZero() bool { - return eh == nil -} - -func (eh *ExtendedHeader) ChainID() string { - return eh.RawHeader.ChainID -} - -func (eh *ExtendedHeader) Height() int64 { - return eh.RawHeader.Height -} - -func (eh *ExtendedHeader) Time() time.Time { - return eh.RawHeader.Time -} - -var _ libhead.Header = &ExtendedHeader{} - // MakeExtendedHeader assembles new ExtendedHeader. func MakeExtendedHeader( - _ context.Context, h *core.Header, comm *core.Commit, vals *core.ValidatorSet, @@ -95,14 +71,34 @@ func MakeExtendedHeader( Commit: comm, ValidatorSet: vals, } - return eh, eh.Validate() + return eh, nil +} + +func (eh *ExtendedHeader) New() *ExtendedHeader { + return new(ExtendedHeader) +} + +func (eh *ExtendedHeader) IsZero() bool { + return eh == nil +} + +func (eh *ExtendedHeader) ChainID() string { + return eh.RawHeader.ChainID +} + +func (eh *ExtendedHeader) Height() uint64 { + return uint64(eh.RawHeader.Height) +} + +func (eh *ExtendedHeader) Time() time.Time { + return eh.RawHeader.Time } // Hash returns Hash of the wrapped RawHeader. // NOTE: It purposely overrides Hash method of RawHeader to get it directly from Commit without // recomputing. func (eh *ExtendedHeader) Hash() libhead.Hash { - return libhead.Hash(eh.Commit.BlockID.Hash) + return eh.Commit.BlockID.Hash.Bytes() } // LastHeader returns the Hash of the last wrapped RawHeader. @@ -158,7 +154,8 @@ func (eh *ExtendedHeader) Validate() error { return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash) } - if err := eh.ValidatorSet.VerifyCommitLight(eh.ChainID(), eh.Commit.BlockID, eh.Height(), eh.Commit); err != nil { + err = eh.ValidatorSet.VerifyCommitLight(eh.ChainID(), eh.Commit.BlockID, int64(eh.Height()), eh.Commit) + if err != nil { return fmt.Errorf("VerifyCommitLight error at height %d: %w", eh.Height(), err) } @@ -169,6 +166,42 @@ func (eh *ExtendedHeader) Validate() error { return nil } +// Verify validates given untrusted Header against trusted ExtendedHeader. +func (eh *ExtendedHeader) Verify(untrst *ExtendedHeader) error { + isAdjacent := eh.Height()+1 == untrst.Height() + if isAdjacent { + // Optimized verification for adjacent headers + // Check the validator hashes are the same + if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) { + return &libhead.VerifyError{ + Reason: fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", + eh.NextValidatorsHash, + untrst.ValidatorsHash, + ), + } + } + + if !bytes.Equal(untrst.LastHeader(), eh.Hash()) { + return &libhead.VerifyError{ + Reason: fmt.Errorf("expected new header to point to last header hash (%X), but got %X)", + eh.Hash(), + untrst.LastHeader(), + ), + } + } + + return nil + } + + if err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel); err != nil { + return &libhead.VerifyError{ + Reason: err, + SoftFailure: true, + } + } + return nil +} + // MarshalBinary marshals ExtendedHeader to binary. func (eh *ExtendedHeader) MarshalBinary() ([]byte, error) { return MarshalExtendedHeader(eh) @@ -240,3 +273,5 @@ func (eh *ExtendedHeader) UnmarshalJSON(data []byte) error { eh.RawHeader = *rawHeader return nil } + +var _ libhead.Header[*ExtendedHeader] = &ExtendedHeader{} diff --git a/header/headertest/testing.go b/header/headertest/testing.go index b20d389452..65ae8c950f 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -158,9 +158,9 @@ func (s *TestSuite) NextHeader() *header.ExtendedHeader { } func (s *TestSuite) GenRawHeader( - height int64, lastHeader, lastCommit, dataHash libhead.Hash) *header.RawHeader { + height uint64, lastHeader, lastCommit, dataHash libhead.Hash) *header.RawHeader { rh := RandRawHeader(s.t) - rh.Height = height + rh.Height = int64(height) rh.Time = time.Now() rh.LastBlockID = types.BlockID{Hash: bytes.HexBytes(lastHeader)} rh.LastCommitHash = bytes.HexBytes(lastCommit) @@ -299,7 +299,7 @@ func RandBlockID(*testing.T) types.BlockID { // FraudMaker creates a custom ConstructFn that breaks the block at the given height. func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService) header.ConstructFn { log.Warn("Corrupting block...", "height", faultHeight) - return func(ctx context.Context, + return func( h *types.Header, comm *types.Commit, vals *types.ValidatorSet, @@ -318,7 +318,7 @@ func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService } return eh, nil } - return header.MakeExtendedHeader(ctx, h, comm, vals, eds) + return header.MakeExtendedHeader(h, comm, vals, eds) } } diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go index 33bcf72642..7ef16afc8d 100644 --- a/header/headertest/verify_test.go +++ b/header/headertest/verify_test.go @@ -3,34 +3,32 @@ package headertest import ( "strconv" "testing" - "time" "github.com/stretchr/testify/assert" tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/celestiaorg/celestia-app/pkg/appconsts" - libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/celestia-node/header" ) func TestVerify(t *testing.T) { h := NewTestSuite(t, 2).GenExtendedHeaders(3) trusted, untrustedAdj, untrustedNonAdj := h[0], h[1], h[2] tests := []struct { - prepare func() libhead.Header + prepare func() *header.ExtendedHeader err bool }{ { - prepare: func() libhead.Header { return untrustedAdj }, + prepare: func() *header.ExtendedHeader { return untrustedAdj }, err: false, }, { - prepare: func() libhead.Header { + prepare: func() *header.ExtendedHeader { return untrustedNonAdj }, err: false, }, { - prepare: func() libhead.Header { + prepare: func() *header.ExtendedHeader { untrusted := *untrustedAdj untrusted.ValidatorsHash = tmrand.Bytes(32) return &untrusted @@ -38,7 +36,7 @@ func TestVerify(t *testing.T) { err: true, }, { - prepare: func() libhead.Header { + prepare: func() *header.ExtendedHeader { untrusted := *untrustedAdj untrusted.RawHeader.LastBlockID.Hash = tmrand.Bytes(32) return &untrusted @@ -46,37 +44,10 @@ func TestVerify(t *testing.T) { err: true, }, { - prepare: func() libhead.Header { - untrustedAdj.RawHeader.Time = untrustedAdj.RawHeader.Time.Add(time.Minute) - return untrustedAdj - }, - err: true, - }, - { - prepare: func() libhead.Header { - untrustedAdj.RawHeader.Time = untrustedAdj.RawHeader.Time.Truncate(time.Hour) - return untrustedAdj - }, - err: true, - }, - { - prepare: func() libhead.Header { - untrustedAdj.RawHeader.ChainID = "toaster" - return untrustedAdj - }, - err: true, - }, - { - prepare: func() libhead.Header { - untrustedAdj.RawHeader.Height++ - return untrustedAdj - }, - err: true, - }, - { - prepare: func() libhead.Header { - untrustedAdj.RawHeader.Version.App = appconsts.LatestVersion + 1 - return untrustedAdj + prepare: func() *header.ExtendedHeader { + untrusted := *untrustedNonAdj + untrusted.Commit = NewTestSuite(t, 2).Commit(RandRawHeader(t)) + return &untrusted }, err: true, }, diff --git a/header/serde.go b/header/serde.go index f4763e3b3b..a511a1352b 100644 --- a/header/serde.go +++ b/header/serde.go @@ -61,7 +61,7 @@ func UnmarshalExtendedHeader(data []byte) (*ExtendedHeader, error) { return nil, err } - return out, out.Validate() + return out, nil } func ExtendedHeaderToProto(eh *ExtendedHeader) (*header_pb.ExtendedHeader, error) { diff --git a/header/verify.go b/header/verify.go deleted file mode 100644 index 827f6c1d1b..0000000000 --- a/header/verify.go +++ /dev/null @@ -1,76 +0,0 @@ -package header - -import ( - "bytes" - "fmt" - "time" - - libhead "github.com/celestiaorg/go-header" -) - -// Verify validates given untrusted Header against trusted ExtendedHeader. -func (eh *ExtendedHeader) Verify(untrusted libhead.Header) error { - untrst, ok := untrusted.(*ExtendedHeader) - if !ok { - // if the header of the type was given, something very wrong happens - panic(fmt.Sprintf("invalid header type: expected %T, got %T", eh, untrusted)) - } - - if err := eh.verify(untrst); err != nil { - return &libhead.VerifyError{Reason: err} - } - - isAdjacent := eh.Height()+1 == untrst.Height() - if isAdjacent { - // Optimized verification for adjacent headers - // Check the validator hashes are the same - if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) { - return &libhead.VerifyError{ - Reason: fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", - eh.NextValidatorsHash, - untrst.ValidatorsHash, - ), - } - } - - if !bytes.Equal(untrst.LastHeader(), eh.Hash()) { - return &libhead.VerifyError{ - Reason: fmt.Errorf("expected new header to point to last header hash (%X), but got %X)", - eh.Hash(), - untrst.LastHeader(), - ), - } - } - - return nil - } - - return nil -} - -// clockDrift defines how much new header's time can drift into -// the future relative to the now time during verification. -var clockDrift = 10 * time.Second - -// verify performs basic verification of untrusted header. -func (eh *ExtendedHeader) verify(untrst libhead.Header) error { - if untrst.Height() <= eh.Height() { - return fmt.Errorf("untrusted header height(%d) <= current trusted header(%d)", untrst.Height(), eh.Height()) - } - - if untrst.ChainID() != eh.ChainID() { - return fmt.Errorf("untrusted header has different chain %s, not %s", untrst.ChainID(), eh.ChainID()) - } - - if !untrst.Time().After(eh.Time()) { - return fmt.Errorf("untrusted header time(%v) must be after current trusted header(%v)", untrst.Time(), eh.Time()) - } - - now := time.Now() - if !untrst.Time().Before(now.Add(clockDrift)) { - return fmt.Errorf( - "new untrusted header has a time from the future %v (now: %v, clockDrift: %v)", untrst.Time(), now, clockDrift) - } - - return nil -} diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index 18f6962f40..7c6b5bed4f 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -42,16 +42,16 @@ func newDASer( hsub libhead.Subscriber[*header.ExtendedHeader], store libhead.Store[*header.ExtendedHeader], batching datastore.Batching, - fraudServ fraud.Service, + fraudServ fraud.Service[*header.ExtendedHeader], bFn shrexsub.BroadcastFn, options ...das.Option, -) (*das.DASer, *modfraud.ServiceBreaker[*das.DASer], error) { +) (*das.DASer, *modfraud.ServiceBreaker[*das.DASer, *header.ExtendedHeader], error) { ds, err := das.NewDASer(da, hsub, store, batching, fraudServ, bFn, options...) if err != nil { return nil, nil, err } - return ds, &modfraud.ServiceBreaker[*das.DASer]{ + return ds, &modfraud.ServiceBreaker[*das.DASer, *header.ExtendedHeader]{ Service: ds, FraudServ: fraudServ, FraudType: byzantine.BadEncoding, diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index 61c935fd40..d9f7e700e2 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -6,6 +6,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/header" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -41,10 +42,10 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { baseComponents, fx.Provide(fx.Annotate( newDASer, - fx.OnStart(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*das.DASer]) error { + fx.OnStart(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*das.DASer, *header.ExtendedHeader]) error { return breaker.Start(ctx) }), - fx.OnStop(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*das.DASer]) error { + fx.OnStop(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*das.DASer, *header.ExtendedHeader]) error { return breaker.Stop(ctx) }), )), diff --git a/nodebuilder/fraud/constructors.go b/nodebuilder/fraud/constructors.go index a70ee3e3d4..eee85d4139 100644 --- a/nodebuilder/fraud/constructors.go +++ b/nodebuilder/fraud/constructors.go @@ -1,8 +1,6 @@ package fraud import ( - "context" - "github.com/ipfs/go-datastore" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" @@ -16,32 +14,46 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -func newFraudService(syncerEnabled bool) func( - fx.Lifecycle, - *pubsub.PubSub, - host.Host, - libhead.Store[*header.ExtendedHeader], - datastore.Batching, - p2p.Network, -) (Module, fraud.Service, error) { - return func( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore libhead.Store[*header.ExtendedHeader], - ds datastore.Batching, - network p2p.Network, - ) (Module, fraud.Service, error) { - getter := func(ctx context.Context, height uint64) (libhead.Header, error) { - return hstore.GetByHeight(ctx, height) - } - pservice := fraudserv.NewProofService(sub, host, getter, ds, syncerEnabled, network.String()) - lc.Append(fx.Hook{ - OnStart: pservice.Start, - OnStop: pservice.Stop, - }) - return &Service{ - Service: pservice, - }, pservice, nil - } +func fraudUnmarshaler() fraud.ProofUnmarshaler[*header.ExtendedHeader] { + return defaultProofUnmarshaler +} + +func newFraudServiceWithSync( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore libhead.Store[*header.ExtendedHeader], + registry fraud.ProofUnmarshaler[*header.ExtendedHeader], + ds datastore.Batching, + network p2p.Network, +) (Module, fraud.Service[*header.ExtendedHeader], error) { + syncerEnabled := true + pservice := fraudserv.NewProofService(sub, host, hstore.GetByHeight, registry, ds, syncerEnabled, network.String()) + lc.Append(fx.Hook{ + OnStart: pservice.Start, + OnStop: pservice.Stop, + }) + return &module{ + Service: pservice, + }, pservice, nil +} + +func newFraudServiceWithoutSync( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore libhead.Store[*header.ExtendedHeader], + registry fraud.ProofUnmarshaler[*header.ExtendedHeader], + ds datastore.Batching, + network p2p.Network, +) (Module, fraud.Service[*header.ExtendedHeader], error) { + syncerEnabled := false + pservice := fraudserv.NewProofService(sub, host, hstore.GetByHeight, registry, ds, syncerEnabled, network.String()) + lc.Append(fx.Hook{ + OnStart: pservice.Start, + OnStop: pservice.Stop, + }) + return &module{ + Service: pservice, + }, pservice, nil } diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 8d10d34e88..45c3863d6f 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -2,8 +2,12 @@ package fraud import ( "context" + "encoding/json" + "errors" "github.com/celestiaorg/go-fraud" + + "github.com/celestiaorg/celestia-node/header" ) var _ Module = (*API)(nil) @@ -35,3 +39,83 @@ func (api *API) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-cha func (api *API) Get(ctx context.Context, proofType fraud.ProofType) ([]Proof, error) { return api.Internal.Get(ctx, proofType) } + +var _ Module = (*module)(nil) + +// module is an implementation of Module that uses fraud.module as a backend. It is used to +// provide fraud proofs as a non-interface type to the API, and wrap fraud.Subscriber with a +// channel of Proofs. +type module struct { + fraud.Service[*header.ExtendedHeader] +} + +func (s *module) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) { + subscription, err := s.Service.Subscribe(proofType) + if err != nil { + return nil, err + } + proofs := make(chan Proof) + go func() { + defer close(proofs) + defer subscription.Cancel() + for { + proof, err := subscription.Proof(ctx) + if err != nil { + if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) { + log.Errorw("fetching proof from subscription", "err", err) + } + return + } + select { + case <-ctx.Done(): + return + case proofs <- Proof{Proof: proof}: + } + } + }() + return proofs, nil +} + +func (s *module) Get(ctx context.Context, proofType fraud.ProofType) ([]Proof, error) { + originalProofs, err := s.Service.Get(ctx, proofType) + if err != nil { + return nil, err + } + proofs := make([]Proof, len(originalProofs)) + for i, originalProof := range originalProofs { + proofs[i].Proof = originalProof + } + return proofs, nil +} + +// Proof embeds the fraud.Proof interface type to provide a concrete type for JSON serialization. +type Proof struct { + fraud.Proof[*header.ExtendedHeader] +} + +type fraudProofJSON struct { + ProofType fraud.ProofType `json:"proof_type"` + Data []byte `json:"data"` +} + +func (f *Proof) UnmarshalJSON(data []byte) error { + var fp fraudProofJSON + err := json.Unmarshal(data, &fp) + if err != nil { + return err + } + f.Proof, err = defaultProofUnmarshaler.Unmarshal(fp.ProofType, fp.Data) + return err +} + +func (f *Proof) MarshalJSON() ([]byte, error) { + marshaledProof, err := f.MarshalBinary() + if err != nil { + return nil, err + } + fraudProof := &fraudProofJSON{ + ProofType: f.Type(), + Data: marshaledProof, + } + return json.Marshal(fraudProof) +} diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go index cffa4d0f56..1a6702aafa 100644 --- a/nodebuilder/fraud/lifecycle.go +++ b/nodebuilder/fraud/lifecycle.go @@ -2,11 +2,13 @@ package fraud import ( "context" + "errors" "fmt" "github.com/ipfs/go-datastore" "github.com/celestiaorg/go-fraud" + libhead "github.com/celestiaorg/go-header" ) // service defines minimal interface with service lifecycle methods @@ -18,30 +20,30 @@ type service interface { // ServiceBreaker wraps any service with fraud proof subscription of a specific type. // If proof happens the service is Stopped automatically. // TODO(@Wondertan): Support multiple fraud types. -type ServiceBreaker[S service] struct { +type ServiceBreaker[S service, H libhead.Header[H]] struct { Service S FraudType fraud.ProofType - FraudServ fraud.Service + FraudServ fraud.Service[H] ctx context.Context cancel context.CancelFunc - sub fraud.Subscription + sub fraud.Subscription[H] } // Start starts the inner service if there are no fraud proofs stored. // Subscribes for fraud and stops the service whenever necessary. -func (breaker *ServiceBreaker[S]) Start(ctx context.Context) error { +func (breaker *ServiceBreaker[S, H]) Start(ctx context.Context) error { if breaker == nil { return nil } proofs, err := breaker.FraudServ.Get(ctx, breaker.FraudType) - switch err { + switch { default: return fmt.Errorf("getting proof(%s): %w", breaker.FraudType, err) - case nil: - return &fraud.ErrFraudExists{Proof: proofs} - case datastore.ErrNotFound: + case err == nil: + return &fraud.ErrFraudExists[H]{Proof: proofs} + case errors.Is(err, datastore.ErrNotFound): } err = breaker.Service.Start(ctx) @@ -60,7 +62,7 @@ func (breaker *ServiceBreaker[S]) Start(ctx context.Context) error { } // Stop stops the service and cancels subscription. -func (breaker *ServiceBreaker[S]) Stop(ctx context.Context) error { +func (breaker *ServiceBreaker[S, H]) Stop(ctx context.Context) error { if breaker == nil { return nil } @@ -75,13 +77,13 @@ func (breaker *ServiceBreaker[S]) Stop(ctx context.Context) error { return breaker.Service.Stop(ctx) } -func (breaker *ServiceBreaker[S]) awaitProof() { +func (breaker *ServiceBreaker[S, H]) awaitProof() { _, err := breaker.sub.Proof(breaker.ctx) if err != nil { return } - if err := breaker.Stop(breaker.ctx); err != nil && err != context.Canceled { + if err := breaker.Stop(breaker.ctx); err != nil && !errors.Is(err, context.Canceled) { log.Errorw("stopping service: %s", err.Error()) } } diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index 718b702f84..bf353f63c6 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -6,27 +6,31 @@ import ( "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) var log = logging.Logger("module/fraud") func ConstructModule(tp node.Type) fx.Option { - baseComponent := fx.Provide(func(serv fraud.Service) fraud.Getter { - return serv - }) + baseComponent := fx.Options( + fx.Provide(fraudUnmarshaler), + fx.Provide(func(serv fraud.Service[*header.ExtendedHeader]) fraud.Getter[*header.ExtendedHeader] { + return serv + }), + ) switch tp { case node.Light: return fx.Module( "fraud", baseComponent, - fx.Provide(newFraudService(true)), + fx.Provide(newFraudServiceWithSync), ) case node.Full, node.Bridge: return fx.Module( "fraud", baseComponent, - fx.Provide(newFraudService(false)), + fx.Provide(newFraudServiceWithoutSync), ) default: panic("invalid node type") diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go deleted file mode 100644 index 0337c375ef..0000000000 --- a/nodebuilder/fraud/service.go +++ /dev/null @@ -1,87 +0,0 @@ -package fraud - -import ( - "context" - "encoding/json" - - "github.com/celestiaorg/go-fraud" -) - -var _ Module = (*Service)(nil) - -// Service is an implementation of Module that uses fraud.Service as a backend. It is used to -// provide fraud proofs as a non-interface type to the API, and wrap fraud.Subscriber with a -// channel of Proofs. -type Service struct { - fraud.Service -} - -func (s *Service) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) { - subscription, err := s.Service.Subscribe(proofType) - if err != nil { - return nil, err - } - proofs := make(chan Proof) - go func() { - defer close(proofs) - for { - proof, err := subscription.Proof(ctx) - if err != nil { - if err != context.DeadlineExceeded && err != context.Canceled { - log.Errorw("fetching proof from subscription", "err", err) - } - return - } - select { - case <-ctx.Done(): - return - case proofs <- Proof{Proof: proof}: - } - } - }() - return proofs, nil -} - -func (s *Service) Get(ctx context.Context, proofType fraud.ProofType) ([]Proof, error) { - originalProofs, err := s.Service.Get(ctx, proofType) - if err != nil { - return nil, err - } - proofs := make([]Proof, len(originalProofs)) - for i, originalProof := range originalProofs { - proofs[i].Proof = originalProof - } - return proofs, nil -} - -// Proof embeds the fraud.Proof interface type to provide a concrete type for JSON serialization. -type Proof struct { - fraud.Proof -} - -type fraudProofJSON struct { - ProofType fraud.ProofType `json:"proof_type"` - Data []byte `json:"data"` -} - -func (f *Proof) UnmarshalJSON(data []byte) error { - var fp fraudProofJSON - err := json.Unmarshal(data, &fp) - if err != nil { - return err - } - f.Proof, err = fraud.Unmarshal(fp.ProofType, fp.Data) - return err -} - -func (f *Proof) MarshalJSON() ([]byte, error) { - marshaledProof, err := f.MarshalBinary() - if err != nil { - return nil, err - } - fraudProof := &fraudProofJSON{ - ProofType: f.Type(), - Data: marshaledProof, - } - return json.Marshal(fraudProof) -} diff --git a/nodebuilder/fraud/unmarshaler.go b/nodebuilder/fraud/unmarshaler.go new file mode 100644 index 0000000000..d5e0461f01 --- /dev/null +++ b/nodebuilder/fraud/unmarshaler.go @@ -0,0 +1,32 @@ +package fraud + +import ( + "github.com/celestiaorg/go-fraud" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" +) + +var defaultProofUnmarshaler proofRegistry + +type proofRegistry struct{} + +func (pr proofRegistry) List() []fraud.ProofType { + return []fraud.ProofType{ + byzantine.BadEncoding, + } +} + +func (pr proofRegistry) Unmarshal(proofType fraud.ProofType, data []byte) (fraud.Proof[*header.ExtendedHeader], error) { + switch proofType { + case byzantine.BadEncoding: + befp := &byzantine.BadEncodingProof{} + err := befp.UnmarshalBinary(data) + if err != nil { + return nil, err + } + return befp, nil + default: + return nil, &fraud.ErrNoUnmarshaler{ProofType: proofType} + } +} diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 7d70f0f5a8..267f0c30f7 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -15,21 +15,20 @@ import ( "github.com/celestiaorg/go-header/store" "github.com/celestiaorg/go-header/sync" - "github.com/celestiaorg/celestia-node/header" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) // newP2PExchange constructs a new Exchange for headers. -func newP2PExchange( +func newP2PExchange[H libhead.Header[H]]( lc fx.Lifecycle, bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host, conngater *conngater.BasicConnectionGater, cfg Config, -) (libhead.Exchange[*header.ExtendedHeader], error) { +) (libhead.Exchange[H], error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { return nil, err @@ -39,7 +38,7 @@ func newP2PExchange( ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - exchange, err := p2p.NewExchange[*header.ExtendedHeader](host, ids, conngater, + exchange, err := p2p.NewExchange[H](host, ids, conngater, p2p.WithParams(cfg.Client), p2p.WithNetworkID[p2p.ClientParameters](network.String()), p2p.WithChainID(network.String()), @@ -60,14 +59,14 @@ func newP2PExchange( } // newSyncer constructs new Syncer for headers. -func newSyncer( - ex libhead.Exchange[*header.ExtendedHeader], - fservice libfraud.Service, - store InitStore, - sub libhead.Subscriber[*header.ExtendedHeader], +func newSyncer[H libhead.Header[H]]( + ex libhead.Exchange[H], + fservice libfraud.Service[H], + store InitStore[H], + sub libhead.Subscriber[H], cfg Config, -) (*sync.Syncer[*header.ExtendedHeader], *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], error) { - syncer, err := sync.NewSyncer[*header.ExtendedHeader](ex, store, sub, +) (*sync.Syncer[H], *modfraud.ServiceBreaker[*sync.Syncer[H], H], error) { + syncer, err := sync.NewSyncer[H](ex, store, sub, sync.WithParams(cfg.Syncer), sync.WithBlockTime(modp2p.BlockTime), ) @@ -75,7 +74,7 @@ func newSyncer( return nil, nil, err } - return syncer, &modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]]{ + return syncer, &modfraud.ServiceBreaker[*sync.Syncer[H], H]{ Service: syncer, FraudType: byzantine.BadEncoding, FraudServ: fservice, @@ -84,16 +83,16 @@ func newSyncer( // InitStore is a type representing initialized header store. // NOTE: It is needed to ensure that Store is always initialized before Syncer is started. -type InitStore libhead.Store[*header.ExtendedHeader] +type InitStore[H libhead.Header[H]] libhead.Store[H] // newInitStore constructs an initialized store -func newInitStore( +func newInitStore[H libhead.Header[H]]( lc fx.Lifecycle, cfg Config, net modp2p.Network, - s libhead.Store[*header.ExtendedHeader], - ex libhead.Exchange[*header.ExtendedHeader], -) (InitStore, error) { + s libhead.Store[H], + ex libhead.Exchange[H], +) (InitStore[H], error) { trustedHash, err := cfg.trustedHash(net) if err != nil { return nil, err diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 77e7c5eb99..5e02e94fe1 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -22,7 +22,7 @@ import ( var log = logging.Logger("module/header") -func ConstructModule(tp node.Type, cfg *Config) fx.Option { +func ConstructModule[H libhead.Header[H]](tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate(tp) @@ -31,61 +31,63 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Error(cfgErr), fx.Provide(newHeaderService), fx.Provide(fx.Annotate( - func(ds datastore.Batching) (libhead.Store[*header.ExtendedHeader], error) { - return store.NewStore[*header.ExtendedHeader](ds, store.WithParams(cfg.Store)) + func(ds datastore.Batching) (libhead.Store[H], error) { + return store.NewStore[H](ds, store.WithParams(cfg.Store)) }, - fx.OnStart(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { - return store.Start(ctx) + fx.OnStart(func(ctx context.Context, str libhead.Store[H]) error { + s := str.(*store.Store[H]) + return s.Start(ctx) }), - fx.OnStop(func(ctx context.Context, store libhead.Store[*header.ExtendedHeader]) error { - return store.Stop(ctx) + fx.OnStop(func(ctx context.Context, str libhead.Store[H]) error { + s := str.(*store.Store[H]) + return s.Stop(ctx) }), )), - fx.Provide(newInitStore), - fx.Provide(func(subscriber *p2p.Subscriber[*header.ExtendedHeader]) libhead.Subscriber[*header.ExtendedHeader] { + fx.Provide(newInitStore[H]), + fx.Provide(func(subscriber *p2p.Subscriber[H]) libhead.Subscriber[H] { return subscriber }), fx.Provide(fx.Annotate( - newSyncer, + newSyncer[H], fx.OnStart(func( ctx context.Context, - breaker *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], + breaker *modfraud.ServiceBreaker[*sync.Syncer[H], H], ) error { return breaker.Start(ctx) }), fx.OnStop(func( ctx context.Context, - breaker *modfraud.ServiceBreaker[*sync.Syncer[*header.ExtendedHeader]], + breaker *modfraud.ServiceBreaker[*sync.Syncer[H], H], ) error { return breaker.Stop(ctx) }), )), fx.Provide(fx.Annotate( - func(ps *pubsub.PubSub, network modp2p.Network) *p2p.Subscriber[*header.ExtendedHeader] { - return p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, network.String()) + func(ps *pubsub.PubSub, network modp2p.Network) *p2p.Subscriber[H] { + return p2p.NewSubscriber[H](ps, header.MsgID, network.String()) }, - fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber[*header.ExtendedHeader]) error { + fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber[H]) error { return sub.Start(ctx) }), - fx.OnStop(func(ctx context.Context, sub *p2p.Subscriber[*header.ExtendedHeader]) error { + fx.OnStop(func(ctx context.Context, sub *p2p.Subscriber[H]) error { return sub.Stop(ctx) }), )), fx.Provide(fx.Annotate( func( host host.Host, - store libhead.Store[*header.ExtendedHeader], + store libhead.Store[H], network modp2p.Network, - ) (*p2p.ExchangeServer[*header.ExtendedHeader], error) { - return p2p.NewExchangeServer[*header.ExtendedHeader](host, store, + ) (*p2p.ExchangeServer[H], error) { + return p2p.NewExchangeServer[H](host, store, p2p.WithParams(cfg.Server), p2p.WithNetworkID[p2p.ServerParameters](network.String()), ) }, - fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer[*header.ExtendedHeader]) error { + fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer[H]) error { return server.Start(ctx) }), - fx.OnStop(func(ctx context.Context, server *p2p.ExchangeServer[*header.ExtendedHeader]) error { + fx.OnStop(func(ctx context.Context, server *p2p.ExchangeServer[H]) error { return server.Stop(ctx) }), )), @@ -96,13 +98,13 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { return fx.Module( "header", baseComponents, - fx.Provide(newP2PExchange), + fx.Provide(newP2PExchange[H]), ) case node.Bridge: return fx.Module( "header", baseComponents, - fx.Provide(func(subscriber *p2p.Subscriber[*header.ExtendedHeader]) libhead.Broadcaster[*header.ExtendedHeader] { + fx.Provide(func(subscriber *p2p.Subscriber[H]) libhead.Broadcaster[H] { return subscriber }), fx.Supply(header.MakeExtendedHeader), diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 89293e4ab4..6a35e35284 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -38,7 +38,7 @@ func TestConstructModule_StoreParams(t *testing.T) { fx.Provide(func() datastore.Batching { return datastore.NewMapDatastore() }), - ConstructModule(node.Light, &cfg), + ConstructModule[*header.ExtendedHeader](node.Light, &cfg), fx.Invoke( func(s libhead.Store[*header.ExtendedHeader]) { ss := s.(*store.Store[*header.ExtendedHeader]) @@ -72,10 +72,10 @@ func TestConstructModule_SyncerParams(t *testing.T) { fx.Provide(func() datastore.Batching { return datastore.NewMapDatastore() }), - fx.Provide(func() fraud.Service { + fx.Provide(func() fraud.Service[*header.ExtendedHeader] { return nil }), - ConstructModule(node.Light, &cfg), + ConstructModule[*header.ExtendedHeader](node.Light, &cfg), fx.Invoke(func(s *sync.Syncer[*header.ExtendedHeader]) { syncer = s }), @@ -100,7 +100,7 @@ func TestConstructModule_ExchangeParams(t *testing.T) { fx.Provide(func() datastore.Batching { return datastore.NewMapDatastore() }), - ConstructModule(node.Light, &cfg), + ConstructModule[*header.ExtendedHeader](node.Light, &cfg), fx.Provide(func(b datastore.Batching) (*conngater.BasicConnectionGater, error) { return conngater.NewBasicConnectionGater(b) }), diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index f410c04f04..2b208cb88d 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -65,9 +65,9 @@ func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.Exten switch { case err != nil: return nil, err - case uint64(head.Height()) == height: + case head.Height() == height: return head, nil - case uint64(head.Height())+1 < height: + case head.Height()+1 < height: return nil, fmt.Errorf("header: given height is from the future: "+ "networkHeight: %d, requestedHeight: %d", head.Height(), height) } @@ -78,10 +78,10 @@ func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.Exten switch { case err != nil: return nil, err - case uint64(head.Height()) == height: + case head.Height() == height: return head, nil // `+1` allows for one header network lag, e.g. user request header that is milliseconds away - case uint64(head.Height())+1 < height: + case head.Height()+1 < height: return nil, fmt.Errorf("header: syncing in progress: "+ "localHeadHeight: %d, requestedHeight: %d", head.Height(), height) default: diff --git a/nodebuilder/header/service_test.go b/nodebuilder/header/service_test.go index 6493d3d51d..14d5ada87d 100644 --- a/nodebuilder/header/service_test.go +++ b/nodebuilder/header/service_test.go @@ -25,9 +25,9 @@ func TestGetByHeightHandlesError(t *testing.T) { }) } -type errorSyncer[H libhead.Header] struct{} +type errorSyncer[H libhead.Header[H]] struct{} -func (d *errorSyncer[H]) Head(context.Context) (H, error) { +func (d *errorSyncer[H]) Head(context.Context, ...libhead.HeadOption[H]) (H, error) { var zero H return zero, fmt.Errorf("dummy error") } diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 719705e35c..3068113102 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -5,13 +5,14 @@ import ( "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/gateway" - "github.com/celestiaorg/celestia-node/nodebuilder/header" + modhead "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" @@ -46,7 +47,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store // modules provided by the node p2p.ConstructModule(tp, &cfg.P2P), state.ConstructModule(tp, &cfg.State, &cfg.Core), - header.ConstructModule(tp, &cfg.Header), + modhead.ConstructModule[*header.ExtendedHeader](tp, &cfg.Header), share.ConstructModule(tp, &cfg.Share), rpc.ConstructModule(tp, &cfg.RPC), gateway.ConstructModule(tp, &cfg.Gateway), diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go index 0061ab9eea..13d812e3ce 100644 --- a/nodebuilder/p2p/pubsub.go +++ b/nodebuilder/p2p/pubsub.go @@ -18,6 +18,8 @@ import ( "github.com/celestiaorg/go-fraud" "github.com/celestiaorg/go-fraud/fraudserv" headp2p "github.com/celestiaorg/go-header/p2p" + + "github.com/celestiaorg/celestia-node/header" ) func init() { @@ -66,7 +68,7 @@ func pubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { // * https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#peer-scoring // * lotus // * prysm - topicScores := topicScoreParams(params.Network) + topicScores := topicScoreParams(params) peerScores, err := peerScoreParams(params.Bootstrappers, cfg) if err != nil { return nil, err @@ -105,15 +107,16 @@ type pubSubParams struct { Host hst.Host Bootstrappers Bootstrappers Network Network + Unmarshaler fraud.ProofUnmarshaler[*header.ExtendedHeader] } -func topicScoreParams(network Network) map[string]*pubsub.TopicScoreParams { +func topicScoreParams(params pubSubParams) map[string]*pubsub.TopicScoreParams { mp := map[string]*pubsub.TopicScoreParams{ - headp2p.PubsubTopicID(network.String()): &headp2p.GossibSubScore, + headp2p.PubsubTopicID(params.Network.String()): &headp2p.GossibSubScore, } - for _, pt := range fraud.Registered() { - mp[fraudserv.PubsubTopicID(pt.String(), network.String())] = &fraudserv.GossibSubScore + for _, pt := range params.Unmarshaler.List() { + mp[fraudserv.PubsubTopicID(pt.String(), params.Network.String())] = &fraudserv.GossibSubScore } return mp diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 97440fa7dc..d56125209c 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -22,6 +22,7 @@ import ( "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/das" modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -80,7 +81,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti } state.WithMetrics(ca) }), - fx.Invoke(fraud.WithMetrics), + fx.Invoke(fraud.WithMetrics[*header.ExtendedHeader]), fx.Invoke(node.WithMetrics), fx.Invoke(modheader.WithMetrics), fx.Invoke(share.WithDiscoveryMetrics), diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index 4636e0f099..f8f8508540 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -18,11 +18,11 @@ func coreAccessor( corecfg core.Config, signer *apptypes.KeyringSigner, sync *sync.Syncer[*header.ExtendedHeader], - fraudServ libfraud.Service, -) (*state.CoreAccessor, Module, *modfraud.ServiceBreaker[*state.CoreAccessor]) { + fraudServ libfraud.Service[*header.ExtendedHeader], +) (*state.CoreAccessor, Module, *modfraud.ServiceBreaker[*state.CoreAccessor, *header.ExtendedHeader]) { ca := state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) - return ca, ca, &modfraud.ServiceBreaker[*state.CoreAccessor]{ + return ca, ca, &modfraud.ServiceBreaker[*state.CoreAccessor, *header.ExtendedHeader]{ Service: ca, FraudType: byzantine.BadEncoding, FraudServ: fraudServ, diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index fe90d023eb..733419a918 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -6,6 +6,7 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/core" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -26,10 +27,12 @@ func ConstructModule(tp node.Type, cfg *Config, coreCfg *core.Config) fx.Option fx.Error(cfgErr), fxutil.ProvideIf(coreCfg.IsEndpointConfigured(), fx.Annotate( coreAccessor, - fx.OnStart(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*state.CoreAccessor]) error { + fx.OnStart(func(ctx context.Context, + breaker *modfraud.ServiceBreaker[*state.CoreAccessor, *header.ExtendedHeader]) error { return breaker.Start(ctx) }), - fx.OnStop(func(ctx context.Context, breaker *modfraud.ServiceBreaker[*state.CoreAccessor]) error { + fx.OnStop(func(ctx context.Context, + breaker *modfraud.ServiceBreaker[*state.CoreAccessor, *header.ExtendedHeader]) error { return breaker.Stop(ctx) }), )), diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 6cb40a2b6c..36f2c2f47f 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -11,9 +11,10 @@ import ( apptypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fxutil" - "github.com/celestiaorg/celestia-node/nodebuilder/header" + modhead "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -47,7 +48,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti // temp dir for the eds store FIXME: Should be in mem fx.Replace(node.StorePath(t.TempDir())), // avoid requesting trustedPeer during initialization - fxutil.ReplaceAs(headertest.NewStore(t), new(header.InitStore)), + fxutil.ReplaceAs(headertest.NewStore(t), new(modhead.InitStore[*header.ExtendedHeader])), ) // in fact, we don't need core.Client in tests, but Bridge requires is a valid one diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 1bc1c261de..3a66c4e58c 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -80,12 +80,12 @@ func TestGetByHeight(t *testing.T) { networkHead, err := client.Header.NetworkHead(ctx) require.NoError(t, err) - _, err = client.Header.GetByHeight(ctx, uint64(networkHead.Height()+1)) + _, err = client.Header.GetByHeight(ctx, networkHead.Height()+1) require.Nil(t, err, "Requesting syncer.Head()+1 shouldn't return an error") networkHead, err = client.Header.NetworkHead(ctx) require.NoError(t, err) - _, err = client.Header.GetByHeight(ctx, uint64(networkHead.Height()+2)) + _, err = client.Header.GetByHeight(ctx, networkHead.Height()+2) require.ErrorContains(t, err, "given height is from the future") } diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index dfa3577599..234556a3aa 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -295,7 +295,7 @@ func TestSyncLightAgainstFull(t *testing.T) { require.NoError(t, err) bridgeHead, err := bridge.HeaderServ.LocalHead(ctx) require.NoError(t, err) - _, err = full.HeaderServ.WaitForHeight(ctx, uint64(bridgeHead.Height())) + _, err = full.HeaderServ.WaitForHeight(ctx, bridgeHead.Height()) require.NoError(t, err) // reset suite bootstrapper list and set full node as a bootstrapper for @@ -316,7 +316,7 @@ func TestSyncLightAgainstFull(t *testing.T) { require.NoError(t, err) fullHead, err := full.HeaderServ.LocalHead(ctx) require.NoError(t, err) - _, err = light.HeaderServ.WaitForHeight(ctx, uint64(fullHead.Height())) + _, err = light.HeaderServ.WaitForHeight(ctx, fullHead.Height()) require.NoError(t, err) // wait for the core block filling process to exit diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index 3c5bc6951b..e3a862e38a 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -7,7 +7,6 @@ import ( "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/go-fraud" - libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" @@ -22,10 +21,6 @@ const ( BadEncoding fraud.ProofType = "badencoding" + version ) -func init() { - fraud.Register(&BadEncodingProof{}) -} - type BadEncodingProof struct { headerHash []byte BlockHeight uint64 @@ -46,8 +41,7 @@ func CreateBadEncodingProof( hash []byte, height uint64, errByzantine *ErrByzantine, -) fraud.Proof { - +) fraud.Proof[*header.ExtendedHeader] { return &BadEncodingProof{ headerHash: hash, BlockHeight: height, @@ -112,34 +106,29 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { // Validate checks that provided Merkle Proofs correspond to the shares, // rebuilds bad row or col from received shares, computes Merkle Root // and compares it with block's Merkle Root. -func (p *BadEncodingProof) Validate(hdr libhead.Header) error { - header, ok := hdr.(*header.ExtendedHeader) - if !ok { - panic(fmt.Sprintf("invalid header type received during BEFP validation: expected %T, got %T", header, hdr)) - } - - if header.Height() != int64(p.BlockHeight) { +func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { + if hdr.Height() != p.BlockHeight { return fmt.Errorf("incorrect block height during BEFP validation: expected %d, got %d", - p.BlockHeight, header.Height(), + p.BlockHeight, hdr.Height(), ) } - if len(header.DAH.RowRoots) != len(header.DAH.ColumnRoots) { + if len(hdr.DAH.RowRoots) != len(hdr.DAH.ColumnRoots) { // NOTE: This should never happen as callers of this method should not feed it with a // malformed extended header. panic(fmt.Sprintf( "invalid extended header: length of row and column roots do not match. (rowRoots=%d) (colRoots=%d)", - len(header.DAH.RowRoots), - len(header.DAH.ColumnRoots)), + len(hdr.DAH.RowRoots), + len(hdr.DAH.ColumnRoots)), ) } // merkleRoots are the roots against which we are going to check the inclusion of the received // shares. Changing the order of the roots to prove the shares relative to the orthogonal axis, // because inside the rsmt2d library rsmt2d.Row = 0 and rsmt2d.Col = 1 - merkleRoots := header.DAH.RowRoots + merkleRoots := hdr.DAH.RowRoots if p.Axis == rsmt2d.Row { - merkleRoots = header.DAH.ColumnRoots + merkleRoots = hdr.DAH.ColumnRoots } if int(p.Index) >= len(merkleRoots) { @@ -196,7 +185,7 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { rebuiltShares, err := codec.Decode(shares) if err != nil { log.Infow("failed to decode shares at height", - "height", header.Height(), "err", err, + "height", hdr.Height(), "err", err, ) return nil } @@ -204,7 +193,7 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0:odsWidth]) if err != nil { log.Infow("failed to encode shares at height", - "height", header.Height(), "err", err, + "height", hdr.Height(), "err", err, ) return nil } @@ -215,7 +204,7 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { err = tree.Push(share) if err != nil { log.Infow("failed to build a tree from the reconstructed shares at height", - "height", header.Height(), "err", err, + "height", hdr.Height(), "err", err, ) return nil } @@ -224,15 +213,15 @@ func (p *BadEncodingProof) Validate(hdr libhead.Header) error { expectedRoot, err := tree.Root() if err != nil { log.Infow("failed to build a tree root at height", - "height", header.Height(), "err", err, + "height", hdr.Height(), "err", err, ) return nil } // root is a merkle root of the row/col where ErrByzantine occurred - root := header.DAH.RowRoots[p.Index] + root := hdr.DAH.RowRoots[p.Index] if p.Axis == rsmt2d.Col { - root = header.DAH.ColumnRoots[p.Index] + root = hdr.DAH.ColumnRoots[p.Index] } // comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block. diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 49cf64c2c2..b5dcea3452 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -86,7 +86,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { }, } - proof := CreateBadEncodingProof(h.Hash(), uint64(h.Height()), &fakeError) + proof := CreateBadEncodingProof(h.Hash(), h.Height(), &fakeError) err = proof.Validate(h) require.Error(t, err) } diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index ebccf0e384..12b1c11083 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -145,7 +145,7 @@ func TestFraudProofValidation(t *testing.T) { faultHeader, err := generateByzantineError(ctx, t, size, bServ) require.True(t, errors.As(err, &errByz)) - p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(faultHeader.Height()), errByz) + p := byzantine.CreateBadEncodingProof([]byte("hash"), faultHeader.Height(), errByz) err = p.Validate(faultHeader) require.NoError(t, err) }) @@ -197,7 +197,7 @@ func BenchmarkBEFPValidation(b *testing.B) { for i := 0; i < b.N; i++ { b.ReportAllocs() - p := byzantine.CreateBadEncodingProof([]byte("hash"), uint64(h.Height()), errByz) + p := byzantine.CreateBadEncodingProof([]byte("hash"), h.Height(), errByz) err = p.Validate(h) require.NoError(b, err) } diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index c880b9b3c2..f24df2c88b 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -199,7 +199,8 @@ func (d *Discovery) Advertise(ctx context.Context) { } // discoveryLoop ensures we always have '~peerLimit' connected peers. -// It initiates peer discovery upon request and restarts the process until the soft limit is reached. +// It initiates peer discovery upon request and restarts the process until the soft limit is +// reached. func (d *Discovery) discoveryLoop(ctx context.Context) { t := time.NewTicker(discoveryRetryTimeout) defer t.Stop() diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 2a7c1fee18..87f9361ee2 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -293,7 +293,7 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri m.validatedPool(h.DataHash.String()) // store first header for validation purposes - if m.initialHeight.CompareAndSwap(0, uint64(h.Height())) { + if m.initialHeight.CompareAndSwap(0, h.Height()) { log.Debugw("stored initial height", "height", h.Height()) } } diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index e10e820e84..ad04d2c7bd 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -274,7 +274,7 @@ func TestManager(t *testing.T) { // create shrexSub msg with height lower than first header from headerSub msg := shrexsub.Notification{ DataHash: share.DataHash("datahash"), - Height: uint64(h.Height() - 1), + Height: h.Height() - 1, } result := manager.Validate(ctx, "peer", msg) require.Equal(t, pubsub.ValidationIgnore, result) @@ -298,7 +298,7 @@ func TestManager(t *testing.T) { // create shrexSub msg with height lower than first header from headerSub msg := shrexsub.Notification{ DataHash: share.DataHash("datahash"), - Height: uint64(h.Height() - 1), + Height: h.Height() - 1, } result := manager.Validate(ctx, "peer", msg) require.Equal(t, pubsub.ValidationIgnore, result) @@ -537,7 +537,7 @@ func (s *subLock) Subscribe() (libhead.Subscription[*header.ExtendedHeader], err return s, nil } -func (s *subLock) AddValidator(func(context.Context, *header.ExtendedHeader) pubsub.ValidationResult) error { +func (s *subLock) SetVerifier(func(context.Context, *header.ExtendedHeader) error) error { panic("implement me") } @@ -561,6 +561,6 @@ func (s *subLock) Cancel() { func newShrexSubMsg(h *header.ExtendedHeader) shrexsub.Notification { return shrexsub.Notification{ DataHash: h.DataHash.Bytes(), - Height: uint64(h.Height()), + Height: h.Height(), } } diff --git a/state/core_access.go b/state/core_access.go index 43f5313c5f..d8f6894e24 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -180,9 +180,10 @@ func (ca *CoreAccessor) constructSignedTx( return ca.signer.EncodeTx(tx) } -// SubmitPayForBlob builds, signs, and synchronously submits a MsgPayForBlob. It blocks until the transaction -// is committed and returns the TxReponse. If gasLim is set to 0, the method will automatically estimate the -// gas limit. If the fee is negative, the method will use the nodes min gas price multiplied by the gas limit. +// SubmitPayForBlob builds, signs, and synchronously submits a MsgPayForBlob. It blocks until the +// transaction is committed and returns the TxReponse. If gasLim is set to 0, the method will +// automatically estimate the gas limit. If the fee is negative, the method will use the nodes min +// gas price multiplied by the gas limit. func (ca *CoreAccessor) SubmitPayForBlob( ctx context.Context, fee Int, @@ -201,8 +202,8 @@ func (ca *CoreAccessor) SubmitPayForBlob( appblobs[i] = &b.Blob } - // we only estimate gas if the user wants us to (by setting the gasLim to 0). In the future we may want - // to make these arguments optional. + // we only estimate gas if the user wants us to (by setting the gasLim to 0). In the future we may + // want to make these arguments optional. if gasLim == 0 { blobSizes := make([]uint32, len(blobs)) for i, blob := range blobs { @@ -294,7 +295,7 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B abciReq := abci.RequestQuery{ // TODO @renayay: once https://github.com/cosmos/cosmos-sdk/pull/12674 is merged, use const instead Path: fmt.Sprintf("store/%s/key", banktypes.StoreKey), - Height: head.Height() - 1, + Height: int64(head.Height() - 1), Data: prefixedAccountKey, Prove: true, } diff --git a/state/integration_test.go b/state/integration_test.go index 8862de1bf8..193e7bddc7 100644 --- a/state/integration_test.go +++ b/state/integration_test.go @@ -20,6 +20,7 @@ import ( "github.com/celestiaorg/celestia-app/test/util/testfactory" "github.com/celestiaorg/celestia-app/test/util/testnode" blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" @@ -95,7 +96,10 @@ type localHeader struct { client rpcclient.Client } -func (l localHeader) Head(ctx context.Context) (*header.ExtendedHeader, error) { +func (l localHeader) Head( + ctx context.Context, + _ ...libhead.HeadOption[*header.ExtendedHeader], +) (*header.ExtendedHeader, error) { latest, err := l.client.Block(ctx, nil) if err != nil { return nil, err From 47047f39dbd7bd344ce47102e7da3aa34a06bba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Ramon=20Ma=C3=B1es?= <32740567+jrmanes@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:56:28 +0200 Subject: [PATCH 0766/1008] chore(nodebuilder/p2p)!: Bump arabica-10 (#2639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hello team! This PR contains the updates for `arabica-10`: - the genesis hash - chain-id - bootstrappers thanks in advance! 🚀 Jose Ramon Mañes --------- Signed-off-by: Jose Ramon Mañes --- nodebuilder/p2p/bootstrap.go | 8 ++++---- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 376d95fb14..0718d8cf59 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -38,10 +38,10 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ Arabica: { - "/dns4/da-bridge-arabica-9.celestia-arabica.com/tcp/2121/p2p/12D3KooWBLvsfkbovAH74DbGGxHPpVW7DkvKdbQxhorrkv9tfGZU", - "/dns4/da-bridge-arabica-9-2.celestia-arabica.com/tcp/2121/p2p/12D3KooWNjJSk8JcY7VoLEjGGUz8CXp9Bxt495zXmdmccjaMPgHf", - "/dns4/da-full-1-arabica-9.celestia-arabica.com/tcp/2121/p2p/12D3KooWFUK2Z4WPsQN3p5n8tgBigxP32gbmABUet2UMby2Ha9ZK", - "/dns4/da-full-2-arabica-9.celestia-arabica.com/tcp/2121/p2p/12D3KooWKnmwsimoghxUT1DXr7f8yXbWCfmXDk4UGbQDsAks9XsN", + "/dns4/da-bridge.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWM3e9MWtyc8GkP8QRt74Riu17QuhGfZMytB2vq5NwkWAu", + "/dns4/da-bridge-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWKj8mcdiBGxQRe1jqhaMnh2tGoC3rPDmr5UH2q8H4WA9M", + "/dns4/da-full-1.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWBWkgmN7kmJSFovVrCjkeG47FkLGq7yEwJ2kEqNKCsBYk", + "/dns4/da-full-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWRByRF67a2kVM2j4MP5Po3jgTw7H2iL2Spu8aUwPkrRfP", }, Mocha: { "/dns4/bootstr-mocha-1.celestia-mocha.com/tcp/2121/p2p/12D3KooWDRSJMbH3PS4dRDa11H7Tk615aqTUgkeEKz4pwd4sS6fN", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 1fb6b10a55..31ea0b0ae9 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "7A5FABB19713D732D967B1DA84FA0DF5E87A7B62302D783F78743E216C1A3550", + Arabica: "5904E55478BA4B3002EE885621E007A2A6A2399662841912219AECD5D5CBE393", Mocha: "79A97034D569C4199A867439B1B7B77D4E1E1D9697212755E1CE6D920CDBB541", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index b1c3a5fbb7..29e0218bb2 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mocha // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-9" + Arabica Network = "arabica-10" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha-3" // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. From 9ff98c4b611bac7b1d08135a4c249e74b1e7afa2 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Sep 2023 11:58:43 +0200 Subject: [PATCH 0767/1008] chore(deps): bump golanci-lint workflow (#2641) Self explanatory. The reason for the fix in core_accessor was an implicit memory aliasing. It was taking the address of the loop variable, which doesn't change in between iterations ofc, so the every blob pointed to the last blob --- .github/workflows/go-ci.yml | 2 +- .golangci.yml | 2 +- cmd/celestia/rpc.go | 2 +- nodebuilder/config.go | 10 +++++----- state/core_access.go | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 7435b7912f..41b3a82994 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -25,7 +25,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3.6.0 with: - version: v1.52.2 + version: v1.54.2 go_mod_tidy_check: name: Go Mod Tidy Check diff --git a/.golangci.yml b/.golangci.yml index 5f7b13e6a5..a0f2754a9b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,7 @@ run: linters: enable: - bodyclose - - depguard + # - depguard as of v1.54.2, the default config throws errors on our repo - dogsled - dupl - errcheck diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index b8724fa789..c263496b26 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -23,7 +23,7 @@ import ( "github.com/celestiaorg/celestia-node/state" ) -const authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" +const authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" //nolint:gosec var requestURL string var authTokenFlag string diff --git a/nodebuilder/config.go b/nodebuilder/config.go index 670bbf9bbd..41f24d6d3d 100644 --- a/nodebuilder/config.go +++ b/nodebuilder/config.go @@ -114,7 +114,7 @@ func removeConfig(path string) error { func UpdateConfig(tp node.Type, path string) (err error) { path, err = storePath(path) if err != nil { - return + return err } flock, err := fslock.Lock(lockPath(path)) @@ -122,7 +122,7 @@ func UpdateConfig(tp node.Type, path string) (err error) { if err == fslock.ErrLocked { err = ErrOpened } - return + return err } defer flock.Unlock() //nolint: errcheck @@ -131,18 +131,18 @@ func UpdateConfig(tp node.Type, path string) (err error) { cfgPath := configPath(path) cfg, err := LoadConfig(cfgPath) if err != nil { - return + return err } cfg, err = updateConfig(cfg, newCfg) if err != nil { - return + return err } // save the updated config err = removeConfig(cfgPath) if err != nil { - return + return err } return SaveConfig(cfgPath, cfg) } diff --git a/state/core_access.go b/state/core_access.go index d8f6894e24..2a49e70a03 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -195,11 +195,11 @@ func (ca *CoreAccessor) SubmitPayForBlob( } appblobs := make([]*apptypes.Blob, len(blobs)) - for i, b := range blobs { - if err := b.Namespace().ValidateForBlob(); err != nil { + for i := range blobs { + if err := blobs[i].Namespace().ValidateForBlob(); err != nil { return nil, err } - appblobs[i] = &b.Blob + appblobs[i] = &blobs[i].Blob } // we only estimate gas if the user wants us to (by setting the gasLim to 0). In the future we may From da841121a8c9eb1834f2aac5079124a2917ecec4 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:33:07 +0800 Subject: [PATCH 0768/1008] feat(share/eds) add dagstore shards status metric (#2642) Adds metrics to expose shards statistics. --- share/eds/metrics.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/share/eds/metrics.go b/share/eds/metrics.go index 6547e239cd..1f430bf688 100644 --- a/share/eds/metrics.go +++ b/share/eds/metrics.go @@ -24,6 +24,8 @@ const ( longOpUnresolved longOpResult = "unresolved" longOpOK longOpResult = "ok" longOpFailed longOpResult = "failed" + + dagstoreShardStatusKey = "shard_status" ) var ( @@ -116,9 +118,31 @@ func (s *Store) WithMetrics() error { return err } + dagStoreShards, err := meter.Int64ObservableGauge("eds_store_dagstore_shards", + metric.WithDescription("dagstore amount of shards by status")) + if err != nil { + return err + } + if err = s.cache.withMetrics(); err != nil { return err } + + callback := func(ctx context.Context, observer metric.Observer) error { + stats := s.dgstr.Stats() + for status, amount := range stats { + observer.ObserveInt64(dagStoreShards, int64(amount), + metric.WithAttributes( + attribute.String(dagstoreShardStatusKey, status.String()), + )) + } + return nil + } + + if _, err := meter.RegisterCallback(callback, dagStoreShards); err != nil { + return err + } + s.metrics = &metrics{ putTime: putTime, getCARTime: getCARTime, From 613d229a70cd73c0b700c2383b7a92af703e6861 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Sep 2023 13:18:01 +0200 Subject: [PATCH 0769/1008] refactor(share)!: have NamespacedShares fields use snake_case in JSON (#2633) Related #2631 , does not fix without nmt PR and release --- share/getter.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/share/getter.go b/share/getter.go index 19e4498458..18d3873de1 100644 --- a/share/getter.go +++ b/share/getter.go @@ -13,7 +13,8 @@ import ( var ( // ErrNotFound is used to indicate that requested data could not be found. ErrNotFound = errors.New("share: data not found") - // ErrOutOfBounds is used to indicate that a passed row or column index is out of bounds of the square size. + // ErrOutOfBounds is used to indicate that a passed row or column index is out of bounds of the + // square size. ErrOutOfBounds = errors.New("share: row or column index is larger than square size") ) @@ -50,8 +51,8 @@ func (ns NamespacedShares) Flatten() []Share { // NamespacedRow represents all shares with proofs within a specific namespace of a single EDS row. type NamespacedRow struct { - Shares []Share - Proof *nmt.Proof + Shares []Share `json:"shares"` + Proof *nmt.Proof `json:"proof"` } // Verify validates NamespacedShares by checking every row with nmt inclusion proof. From ab56434799834415ba18915af02e9315c6dda15e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:39:27 +0000 Subject: [PATCH 0770/1008] chore(deps): bump golangci/golangci-lint-action from 3.6.0 to 3.7.0 (#2586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.6.0 to 3.7.0.
    Release notes

    Sourced from golangci/golangci-lint-action's releases.

    v3.7.0

    What's Changed

    Changes

    Documentation

    Dependencies

    ... (truncated)

    Commits
    • 3a91952 feat: working-directory with only-new-issues (#795)
    • 5e67631 build(deps): bump @​actions/cache from 3.2.1 to 3.2.2 (#825)
    • 18dad33 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.2.1 to 6.3.0 (#...
    • 945dc98 build(deps): bump @​types/node from 20.4.8 to 20.5.0 (#824)
    • cac24f5 build(deps-dev): bump eslint from 8.45.0 to 8.47.0 (#823)
    • ab66454 build(deps-dev): bump @​typescript-eslint/parser from 6.2.0 to 6.3.0 (#821)
    • a252750 build(deps): bump @​actions/http-client from 2.1.0 to 2.1.1 (#819)
    • 44d9998 build(deps-dev): bump eslint-config-prettier from 8.9.0 to 9.0.0 (#818)
    • b6bdfb3 build(deps-dev): bump prettier from 3.0.0 to 3.0.1 (#817)
    • c0cd965 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.2.0 to 6.2.1 (#...
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=3.6.0&new-version=3.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 41b3a82994..f68825bec1 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -23,7 +23,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: golangci-lint - uses: golangci/golangci-lint-action@v3.6.0 + uses: golangci/golangci-lint-action@v3.7.0 with: version: v1.54.2 From afc0504de67bf0ea20e528c9fbdad335df0f6f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:42:34 +0000 Subject: [PATCH 0771/1008] chore(deps): bump alpine from 3.18.2 to 3.18.3 (#2564) Bumps alpine from 3.18.2 to 3.18.3. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.18.2&new-version=3.18.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c8e9bb2e1c..1f1705c2f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ COPY . . RUN make build && make cel-key -FROM docker.io/alpine:3.18.2 +FROM docker.io/alpine:3.18.3 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From 80d6dbaa7278ffc590a75d556daffbb6ac37d657 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:43:14 +0200 Subject: [PATCH 0772/1008] chore(deps): bump cosmossdk.io/math from 1.1.1 to 1.1.2 (#2594) Bumps [cosmossdk.io/math](https://github.com/cosmos/cosmos-sdk) from 1.1.1 to 1.1.2.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cosmossdk.io/math&package-manager=go_modules&previous-version=1.1.1&new-version=1.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 846f4316e6..38b2068178 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1 require ( cosmossdk.io/errors v1.0.0 - cosmossdk.io/math v1.1.1 + cosmossdk.io/math v1.1.2 github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 diff --git a/go.sum b/go.sum index 09bd8ab26e..feaba34043 100644 --- a/go.sum +++ b/go.sum @@ -195,8 +195,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= -cosmossdk.io/math v1.1.1 h1:Eqx44E6fSvG055Z6VNiCLWA9fra0JSyP0kQX7VvNNfk= -cosmossdk.io/math v1.1.1/go.mod h1:uFRkSZDz38KjWjm6jN+/sI8tJWQxbGwxcjOTzapWSpE= +cosmossdk.io/math v1.1.2 h1:ORZetZCTyWkI5GlZ6CZS28fMHi83ZYf+A2vVnHNzZBM= +cosmossdk.io/math v1.1.2/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= From d75a743d7c31b4edfdb8e3dbe8dbca0008bea9cb Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 4 Sep 2023 15:09:42 +0300 Subject: [PATCH 0773/1008] tests(swamp): befp testing (#2584) --- das/daser_test.go | 3 +- header/headertest/fraud/testing.go | 100 +++++++++++++++++ header/headertest/testing.go | 48 --------- nodebuilder/fraud/lifecycle.go | 2 +- nodebuilder/p2p/opts.go | 6 ++ nodebuilder/tests/fraud_test.go | 168 +++++++++++------------------ nodebuilder/tests/swamp/swamp.go | 14 +++ share/eds/edstest/testing.go | 8 +- share/getters/cascade.go | 10 +- share/getters/ipld.go | 5 + 10 files changed, 208 insertions(+), 156 deletions(-) create mode 100644 header/headertest/fraud/testing.go diff --git a/das/daser_test.go b/das/daser_test.go index 68f6e01ef2..e4e74dc7ff 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -23,6 +23,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" + headerfraud "github.com/celestiaorg/celestia-node/header/headertest/fraud" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" @@ -180,7 +181,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { "private", ) require.NoError(t, fserv.Start(ctx)) - mockGet.headers[1], _ = headertest.CreateFraudExtHeader(t, mockGet.headers[1], bServ) + mockGet.headers[1] = headerfraud.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() // create and start DASer diff --git a/header/headertest/fraud/testing.go b/header/headertest/fraud/testing.go new file mode 100644 index 0000000000..6a5cda733d --- /dev/null +++ b/header/headertest/fraud/testing.go @@ -0,0 +1,100 @@ +package headerfraud + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-blockservice" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/bytes" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/types" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" +) + +// FraudMaker allows to produce an invalid header at the specified height in order to produce the +// BEFP. +type FraudMaker struct { + t *testing.T + + vals []types.PrivValidator + valSet *types.ValidatorSet + + // height of the invalid header + height int64 + + prevHash bytes.HexBytes +} + +func NewFraudMaker(t *testing.T, height int64, vals []types.PrivValidator, valSet *types.ValidatorSet) *FraudMaker { + return &FraudMaker{ + t: t, + vals: vals, + valSet: valSet, + height: height, + } +} + +func (f *FraudMaker) MakeExtendedHeader(odsSize int, edsStore *eds.Store) header.ConstructFn { + return func(h *types.Header, + comm *types.Commit, + vals *types.ValidatorSet, + eds *rsmt2d.ExtendedDataSquare, + ) (*header.ExtendedHeader, error) { + if h.Height < f.height { + return header.MakeExtendedHeader(h, comm, vals, eds) + } + + hdr := *h + if h.Height == f.height { + adder := ipld.NewProofsAdder(odsSize) + square := edstest.RandByzantineEDS(f.t, odsSize, nmt.NodeVisitor(adder.VisitFn())) + dah, err := da.NewDataAvailabilityHeader(square) + require.NoError(f.t, err) + hdr.DataHash = dah.Hash() + + ctx := ipld.CtxWithProofsAdder(context.Background(), adder) + require.NoError(f.t, edsStore.Put(ctx, h.DataHash.Bytes(), square)) + + *eds = *square + } + if h.Height > f.height { + hdr.LastBlockID.Hash = f.prevHash + } + + blockID := comm.BlockID + blockID.Hash = hdr.Hash() + voteSet := types.NewVoteSet(hdr.ChainID, hdr.Height, 0, tmproto.PrecommitType, f.valSet) + commit, err := headertest.MakeCommit(blockID, hdr.Height, 0, voteSet, f.vals, time.Now()) + require.NoError(f.t, err) + + *h = hdr + *comm = *commit + f.prevHash = h.Hash() + return header.MakeExtendedHeader(h, comm, vals, eds) + } +} +func CreateFraudExtHeader( + t *testing.T, + eh *header.ExtendedHeader, + serv blockservice.BlockService, +) *header.ExtendedHeader { + square := edstest.RandByzantineEDS(t, len(eh.DAH.RowRoots)) + err := ipld.ImportEDS(context.Background(), square, serv) + require.NoError(t, err) + dah, err := da.NewDataAvailabilityHeader(square) + require.NoError(t, err) + eh.DAH = &dah + eh.RawHeader.DataHash = dah.Hash() + return eh +} diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 65ae8c950f..f288556bd9 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -1,7 +1,6 @@ package headertest import ( - "context" "crypto/rand" "fmt" mrand "math/rand" @@ -9,8 +8,6 @@ import ( "testing" "time" - "github.com/ipfs/go-blockservice" - logging "github.com/ipfs/go-log/v2" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bytes" @@ -26,12 +23,8 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/share/eds/edstest" - "github.com/celestiaorg/celestia-node/share/ipld" ) -var log = logging.Logger("headertest") - // TestSuite provides everything you need to test chain of Headers. // If not, please don't hesitate to extend it for your case. type TestSuite struct { @@ -296,32 +289,6 @@ func RandBlockID(*testing.T) types.BlockID { return bid } -// FraudMaker creates a custom ConstructFn that breaks the block at the given height. -func FraudMaker(t *testing.T, faultHeight int64, bServ blockservice.BlockService) header.ConstructFn { - log.Warn("Corrupting block...", "height", faultHeight) - return func( - h *types.Header, - comm *types.Commit, - vals *types.ValidatorSet, - eds *rsmt2d.ExtendedDataSquare, - ) (*header.ExtendedHeader, error) { - if h.Height == faultHeight { - eh := &header.ExtendedHeader{ - RawHeader: *h, - Commit: comm, - ValidatorSet: vals, - } - - eh, dataSq := CreateFraudExtHeader(t, eh, bServ) - if eds != nil { - *eds = *dataSq - } - return eh, nil - } - return header.MakeExtendedHeader(h, comm, vals, eds) - } -} - func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader { valSet, vals := RandValidatorSet(10, 10) gen := RandRawHeader(t) @@ -348,21 +315,6 @@ func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedData return eh } -func CreateFraudExtHeader( - t *testing.T, - eh *header.ExtendedHeader, - serv blockservice.BlockService, -) (*header.ExtendedHeader, *rsmt2d.ExtendedDataSquare) { - square := edstest.RandByzantineEDS(t, len(eh.DAH.RowRoots)) - err := ipld.ImportEDS(context.Background(), square, serv) - require.NoError(t, err) - dah, err := da.NewDataAvailabilityHeader(square) - require.NoError(t, err) - eh.DAH = &dah - eh.RawHeader.DataHash = dah.Hash() - return eh, square -} - type Subscriber struct { headertest.Subscriber[*header.ExtendedHeader] } diff --git a/nodebuilder/fraud/lifecycle.go b/nodebuilder/fraud/lifecycle.go index 1a6702aafa..50f4e1035b 100644 --- a/nodebuilder/fraud/lifecycle.go +++ b/nodebuilder/fraud/lifecycle.go @@ -73,7 +73,7 @@ func (breaker *ServiceBreaker[S, H]) Stop(ctx context.Context) error { } breaker.sub.Cancel() - breaker.cancel() + defer breaker.cancel() return breaker.Service.Stop(ctx) } diff --git a/nodebuilder/p2p/opts.go b/nodebuilder/p2p/opts.go index 9501dfe8e1..8e5d714a64 100644 --- a/nodebuilder/p2p/opts.go +++ b/nodebuilder/p2p/opts.go @@ -3,6 +3,7 @@ package p2p import ( "encoding/hex" + "github.com/ipfs/go-blockservice" "github.com/libp2p/go-libp2p/core/crypto" hst "github.com/libp2p/go-libp2p/core/host" "go.uber.org/fx" @@ -34,3 +35,8 @@ func WithP2PKeyStr(key string) fx.Option { func WithHost(hst hst.Host) fx.Option { return fxutil.ReplaceAs(hst, new(HostBase)) } + +// WithBlockService allows to replace the default BlockService. +func WithBlockService(bServ blockservice.BlockService) fx.Option { + return fxutil.ReplaceAs(bServ, new(blockservice.BlockService)) +} diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index f652724d55..95c702c0c0 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -5,16 +5,20 @@ import ( "testing" "time" - mdutils "github.com/ipfs/go-merkledag/test" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" + "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/header/headertest" + headerfraud "github.com/celestiaorg/celestia-node/header/headertest/fraud" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) @@ -29,156 +33,114 @@ Steps: 4. Start a FN. 5. Subscribe to a fraud proof and wait when it will be received. 6. Check FN is not synced to 15. -Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. +Note: 15 is not available because DASer/Syncer will be stopped +before reaching this height due to receiving a fraud proof. Another note: this test disables share exchange to speed up test results. +7. Spawn a Light Node(LN) in order to sync a BEFP. +8. Ensure that the BEFP was received. +9. Try to start a Full Node(FN) that contains a BEFP in its store. */ -func TestFraudProofBroadcasting(t *testing.T) { - t.Skip("requires BEFP generation on app side to work") +func TestFraudProofHandling(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) const ( blocks = 15 - blockSize = 2 - blockTime = time.Millisecond * 300 + blockSize = 4 + blockTime = time.Second ) sw := swamp.NewSwamp(t, swamp.WithBlockTime(blockTime)) fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, blockSize, blocks) + set, val := sw.Validators(t) + fMaker := headerfraud.NewFraudMaker(t, 10, []types.PrivValidator{val}, set) + + tmpDir := t.TempDir() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + edsStore, err := eds.NewStore(tmpDir, ds) + require.NoError(t, err) + require.NoError(t, edsStore.Start(ctx)) + t.Cleanup(func() { + _ = edsStore.Stop(ctx) + }) cfg := nodebuilder.DefaultConfig(node.Bridge) - cfg.Share.UseShareExchange = false + // 1. bridge := sw.NewNodeWithConfig( node.Bridge, cfg, - core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv())), + core.WithHeaderConstructFn(fMaker.MakeExtendedHeader(16, edsStore)), + fx.Replace(edsStore), ) - - err := bridge.Start(ctx) + // 2. + err = bridge.Start(ctx) require.NoError(t, err) + // 3. cfg = nodebuilder.DefaultConfig(node.Full) - cfg.Share.UseShareExchange = false addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) - + cfg.Share.UseShareExchange = false store := nodebuilder.MockStore(t, cfg) full := sw.NewNodeWithStore(node.Full, store) + // 4. err = full.Start(ctx) require.NoError(t, err) - // subscribe to fraud proof before node starts helps - // to prevent flakiness when fraud proof is propagating before subscribing on it - subscr, err := full.FraudServ.Subscribe(ctx, byzantine.BadEncoding) + // 5. + subCtx, subCancel := context.WithCancel(ctx) + subscr, err := full.FraudServ.Subscribe(subCtx, byzantine.BadEncoding) require.NoError(t, err) - select { case p := <-subscr: require.Equal(t, 10, int(p.Height())) + subCancel() case <-ctx.Done(): - t.Fatal("fraud proof was not received in time") + subCancel() + t.Fatal("full node did not receive a fraud proof in time") } + // This is an obscure way to check if the Syncer was stopped. // If we cannot get a height header within a timeframe it means the syncer was stopped // FIXME: Eventually, this should be a check on service registry managing and keeping // lifecycles of each Module. - syncCtx, syncCancel := context.WithTimeout(context.Background(), btime) - _, err = full.HeaderServ.WaitForHeight(syncCtx, 100) + // 6. + syncCtx, syncCancel := context.WithTimeout(context.Background(), blockTime*5) + _, err = full.HeaderServ.WaitForHeight(syncCtx, 15) require.ErrorIs(t, err, context.DeadlineExceeded) syncCancel() - sw.StopNode(ctx, full) - - full = sw.NewNodeWithStore(node.Full, store) - - require.Error(t, full.Start(ctx)) - proofs, err := full.FraudServ.Get(ctx, byzantine.BadEncoding) - require.NoError(t, err) - require.NotNil(t, proofs) - require.NoError(t, <-fillDn) -} - -/* -Test-Case: Light node receives a fraud proof using Fraud Sync -Pre-Requisites: -- CoreClient is started by swamp. -Steps: -1. Create a Bridge Node(BN) with broken extended header at height 10. -2. Start a BN. -3. Create a Full Node(FN) with a connection to BN as a trusted peer. -4. Start a FN. -5. Subscribe to a fraud proof and wait when it will be received. -6. Start LN once a fraud proof is received and verified by FN. -7. Wait until LN will be connected to FN and fetch a fraud proof. -Note: this test disables share exchange to speed up test results. -*/ -func TestFraudProofSyncing(t *testing.T) { - t.Skip("requires BEFP generation on app side to work") - - const ( - blocks = 15 - bsize = 2 - btime = time.Millisecond * 300 - ) - sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) - - fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, blocks) - cfg := nodebuilder.DefaultConfig(node.Bridge) - cfg.Share.UseShareExchange = false - store := nodebuilder.MockStore(t, cfg) - bridge := sw.NewNodeWithStore( - node.Bridge, - store, - core.WithHeaderConstructFn(headertest.FraudMaker(t, 10, mdutils.Bserv())), - ) - - err := bridge.Start(ctx) - require.NoError(t, err) - addr := host.InfoFromHost(bridge.Host) - addrs, err := peer.AddrInfoToP2pAddrs(addr) - require.NoError(t, err) - - fullCfg := nodebuilder.DefaultConfig(node.Full) - fullCfg.Share.UseShareExchange = false - fullCfg.Header.TrustedPeers = append(fullCfg.Header.TrustedPeers, addrs[0].String()) - full := sw.NewNodeWithStore(node.Full, nodebuilder.MockStore(t, fullCfg)) - - lightCfg := nodebuilder.DefaultConfig(node.Light) - lightCfg.Header.TrustedPeers = append(lightCfg.Header.TrustedPeers, addrs[0].String()) - ln := sw.NewNodeWithStore(node.Light, nodebuilder.MockStore(t, lightCfg)) - require.NoError(t, full.Start(ctx)) + // 7. + cfg = nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + lnStore := nodebuilder.MockStore(t, cfg) + light := sw.NewNodeWithStore(node.Light, lnStore) + require.NoError(t, light.Start(ctx)) - subsFN, err := full.FraudServ.Subscribe(ctx, byzantine.BadEncoding) + // 8. + subCtx, subCancel = context.WithCancel(ctx) + subscr, err = light.FraudServ.Subscribe(subCtx, byzantine.BadEncoding) require.NoError(t, err) - select { - case <-subsFN: + case p := <-subscr: + require.Equal(t, 10, int(p.Height())) + subCancel() case <-ctx.Done(): - t.Fatal("full node didn't get FP in time") + subCancel() + t.Fatal("light node did not receive a fraud proof in time") } - // start LN to enforce syncing logic, not the PubSub's broadcasting - err = ln.Start(ctx) - require.NoError(t, err) - - // internal subscription for the fraud proof is done in order to ensure that light node - // receives the BEFP. - subsLN, err := ln.FraudServ.Subscribe(ctx, byzantine.BadEncoding) - require.NoError(t, err) - - // ensure that the full and light node are connected to speed up test - // alternatively, they would discover each other - err = ln.Host.Connect(ctx, *host.InfoFromHost(full.Host)) + // 9. + fN := sw.NewNodeWithStore(node.Full, store) + require.Error(t, fN.Start(ctx)) + proofs, err := fN.FraudServ.Get(ctx, byzantine.BadEncoding) require.NoError(t, err) + require.NotNil(t, proofs) - select { - case <-subsLN: - case <-ctx.Done(): - t.Fatal("light node didn't get FP in time") - } + sw.StopNode(ctx, bridge) + sw.StopNode(ctx, full) + sw.StopNode(ctx, light) require.NoError(t, <-fillDn) } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 58584912be..4f07d96b51 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -16,6 +16,8 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" "go.uber.org/fx" "golang.org/x/exp/maps" @@ -335,3 +337,15 @@ func (s *Swamp) SetBootstrapper(t *testing.T, bootstrappers ...*nodebuilder.Node s.Bootstrappers = append(s.Bootstrappers, addrs[0]) } } + +// Validators retrieves keys from the app node in order to build the validators. +func (s *Swamp) Validators(t *testing.T) (*types.ValidatorSet, types.PrivValidator) { + privPath := s.cfg.TmConfig.PrivValidatorKeyFile() + statePath := s.cfg.TmConfig.PrivValidatorStateFile() + priv := privval.LoadFilePV(privPath, statePath) + key, err := priv.GetPubKey() + require.NoError(t, err) + validator := types.NewValidator(key, 100) + set := types.NewValidatorSet([]*types.Validator{validator}) + return set, priv +} diff --git a/share/eds/edstest/testing.go b/share/eds/edstest/testing.go index ddca285f0c..f75e8b619b 100644 --- a/share/eds/edstest/testing.go +++ b/share/eds/edstest/testing.go @@ -7,17 +7,21 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/sharetest" ) -func RandByzantineEDS(t *testing.T, size int) *rsmt2d.ExtendedDataSquare { +func RandByzantineEDS(t *testing.T, size int, options ...nmt.Option) *rsmt2d.ExtendedDataSquare { eds := RandEDS(t, size) shares := eds.Flattened() copy(share.GetData(shares[0]), share.GetData(shares[1])) // corrupting eds - eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) + eds, err := rsmt2d.ImportExtendedDataSquare(shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(size), + options...)) require.NoError(t, err, "failure to recompute the extended data square") return eds } diff --git a/share/getters/cascade.go b/share/getters/cascade.go index 63d7713d3d..eb3e969c1c 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" ) var _ share.Getter = (*CascadeGetter)(nil) @@ -130,8 +131,15 @@ func cascadeGetters[V any]( continue } - err = errors.Join(err, getErr) span.RecordError(getErr, trace.WithAttributes(attribute.Int("getter_idx", i))) + var byzantineErr *byzantine.ErrByzantine + if errors.As(getErr, &byzantineErr) { + // short circuit if byzantine error was detected (to be able to handle it correctly + // and create the BEFP) + return zero, byzantineErr + } + + err = errors.Join(err, getErr) if ctx.Err() != nil { return zero, err } diff --git a/share/getters/ipld.go b/share/getters/ipld.go index a892e0fc82..8e11a389bd 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -16,6 +16,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -82,6 +83,10 @@ func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d // convert error to satisfy getter interface contract err = share.ErrNotFound } + var errByz *byzantine.ErrByzantine + if errors.As(err, &errByz) { + return nil, err + } if err != nil { return nil, fmt.Errorf("getter/ipld: failed to retrieve eds: %w", err) } From 42caaee5df1ebe31336e2534b644dcda5e030a14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:20:36 +0200 Subject: [PATCH 0774/1008] chore(deps): bump github.com/ipfs/go-ipld-cbor from 0.0.6 to 0.1.0 (#2622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/go-ipld-cbor](https://github.com/ipfs/go-ipld-cbor) from 0.0.6 to 0.1.0.
    Release notes

    Sourced from github.com/ipfs/go-ipld-cbor's releases.

    v0.1.0

    What's Changed

    • allow configuration of ipldStores default hash function by @​whyrusleeping in ipfs/go-ipld-cbor#86
    • adopt PL unified-ci
    • dependency upgrades to latest
    • Minimum go version bump to v1.20.

    Full Changelog: https://github.com/ipfs/go-ipld-cbor/compare/v0.0.6...v0.1.0

    Commits
    • 2d0b3fb v0.1.0 bump
    • c10cddb chore: update deps
    • f314046 chore: bump go.mod to Go 1.20 and run go fix
    • 16a0369 chore: add or force update version.json
    • da23a18 chore: add or force update .github/workflows/tagpush.yml
    • 8da0e33 chore: add or force update .github/workflows/release-check.yml
    • 5698d0f chore: add or force update .github/workflows/releaser.yml
    • 5f282bb chore: add or force update .github/workflows/go-check.yml
    • 2312e76 chore: add or force update .github/workflows/go-test.yml
    • 1dc9bde chore: delete templates [skip ci] (#91)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/go-ipld-cbor&package-manager=go_modules&previous-version=0.0.6&new-version=0.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 38b2068178..9206bddaea 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/ipfs/go-blockservice v0.5.1 // down 1 version, 0.5.2 is marked as deprecated and raises alerts github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 - github.com/ipfs/go-ipld-cbor v0.0.6 + github.com/ipfs/go-ipld-cbor v0.1.0 github.com/ipfs/go-ipld-format v0.5.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.11.0 diff --git a/go.sum b/go.sum index feaba34043..b2afe6be69 100644 --- a/go.sum +++ b/go.sum @@ -1147,8 +1147,9 @@ github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= -github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA= +github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= +github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k= github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= From 9415cf0b21c490aa23374b2d503bc3b62148f3f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:40:09 +0200 Subject: [PATCH 0775/1008] chore(deps): bump github.com/ipfs/go-ipld-format from 0.5.0 to 0.6.0 (#2621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/go-ipld-format](https://github.com/ipfs/go-ipld-format) from 0.5.0 to 0.6.0.
    Release notes

    Sourced from github.com/ipfs/go-ipld-format's releases.

    v0.6.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/ipfs/go-ipld-format/compare/v0.5.0...v0.6.0

    Commits
    • 608bf9d v0.6.0 bump
    • 93da70a chore: update deps
    • 6ce9a5e fix: stop using the deprecated io/ioutil package
    • 1c2f1ce chore: bump go.mod to Go 1.20 and run go fix
    • ad47740 ci: uci/copy-templates (#83)
    • 706e93c chore: delete templates [skip ci] (#82)
    • c3da866 chore: Update .github/workflows/stale.yml [skip ci]
    • ef09f69 chore: Update .github/workflows/stale.yml [skip ci]
    • 530b600 Bump golang.org/x/crypto from 0.0.0-20190211182817-74369b46fc67 to 0.1.0
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/go-ipld-format&package-manager=go_modules&previous-version=0.5.0&new-version=0.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 9206bddaea..7fb3056a9e 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ipld-cbor v0.1.0 - github.com/ipfs/go-ipld-format v0.5.0 + github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.11.0 github.com/ipld/go-car v0.6.2 diff --git a/go.sum b/go.sum index b2afe6be69..91e56dd9ca 100644 --- a/go.sum +++ b/go.sum @@ -1156,8 +1156,9 @@ github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-format v0.3.1/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= -github.com/ipfs/go-ipld-format v0.5.0 h1:WyEle9K96MSrvr47zZHKKcDxJ/vlpET6PSiQsAFO+Ds= github.com/ipfs/go-ipld-format v0.5.0/go.mod h1:ImdZqJQaEouMjCvqCe0ORUS+uoBmf7Hf+EO/jh+nk3M= +github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= +github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= github.com/ipfs/go-ipld-legacy v0.2.1 h1:mDFtrBpmU7b//LzLSypVrXsD8QxkEWxu5qVxN99/+tk= From a31a8081a1e4471d01320aa61e42abc4ebc338de Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Sep 2023 14:51:30 +0200 Subject: [PATCH 0776/1008] feat: local docker telemetry infra (#2296) Supersedes #1990 Adds local telemetry infra to the Makefile. This includes pyroscope, grafana, jaeger, an otel-collector, prometheus, and loki. --------- Co-authored-by: derrandz Co-authored-by: ramin --- Makefile | 12 +++ README.md | 10 ++- docker/telemetry/docker-compose.yml | 89 +++++++++++++++++++ .../telemetry/grafana/datasources/config.yml | 14 +++ docker/telemetry/loki/config.yml | 11 +++ docker/telemetry/otel-collector/config.yml | 32 +++++++ docker/telemetry/prometheus/prometheus.yml | 25 ++++++ docker/telemetry/promtail/config.yml | 29 ++++++ 8 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 docker/telemetry/docker-compose.yml create mode 100644 docker/telemetry/grafana/datasources/config.yml create mode 100644 docker/telemetry/loki/config.yml create mode 100644 docker/telemetry/otel-collector/config.yml create mode 100644 docker/telemetry/prometheus/prometheus.yml create mode 100644 docker/telemetry/promtail/config.yml diff --git a/Makefile b/Makefile index feef6172aa..d4b3d2572e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ SHELL=/usr/bin/env bash PROJECTNAME=$(shell basename "$(PWD)") +DIR_FULLPATH=$(shell pwd) versioningPath := "github.com/celestiaorg/celestia-node/nodebuilder/node" LDFLAGS=-ldflags="-X '$(versioningPath).buildTime=$(shell date)' -X '$(versioningPath).lastCommit=$(shell git rev-parse HEAD)' -X '$(versioningPath).semanticVersion=$(shell git describe --tags --dirty=-dev 2>/dev/null || git rev-parse --abbrev-ref HEAD)'" ifeq (${PREFIX},) @@ -176,3 +177,14 @@ adr-gen: @echo "--> Generating ADR" @curl -sSL https://raw.githubusercontent.com/celestiaorg/.github/main/adr-template.md > docs/architecture/adr-$(NUM)-$(TITLE).md .PHONY: adr-gen + +## telemetry-infra-up: launches local telemetry infrastructure. This includes grafana, jaeger, loki, pyroscope, and an otel-collector. +## you can access the grafana instance at localhost:3000 and login with admin:admin. +telemetry-infra-up: + PWD="${DIR_FULLPATH}/docker/telemetry" docker-compose -f ./docker/telemetry/docker-compose.yml up +.PHONY: telemetry-infra-up + +## telemetry-infra-down: tears the telemetry infrastructure down. The stores for grafana, prometheus, and loki will persist. +telemetry-infra-down: + PWD="${DIR_FULLPATH}/docker/telemetry" docker-compose -f ./docker/telemetry/docker-compose.yml down +.PHONY: telemetry-infra-down diff --git a/README.md b/README.md index 0711c2d223..89cb67dc15 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-avai - [API docs](#api-docs) - [Node types](#node-types) - [Run a node](#run-a-node) - - [Environment variables](#environment-variables) + - [Environment variables](#environment-variables) - [Package-specific documentation](#package-specific-documentation) - [Code of Conduct](#code-of-conduct) @@ -55,7 +55,7 @@ For more information on setting up a node and the hardware requirements needed, ## API docs -Celestia-node public API is documented [here](https://docs.celestia.org/category/node-api/). +The celestia-node public API is documented [here](https://docs.celestia.org/category/node-api/). ## Node types @@ -67,7 +67,7 @@ More information can be found [here](https://github.com/celestiaorg/celestia-nod ## Run a node -`` can be `bridge`, `full` or `light`. +`` can be: `bridge`, `full` or `light`. ```sh celestia init @@ -77,7 +77,9 @@ celestia init celestia start ``` -### Environment variables +Please refer to [this guide](https://docs.celestia.org/nodes/celestia-node/) for more information on running a node. + +## Environment variables | Variable | Explanation | Default value | Required | | ----------------------- | ----------------------------------- | ------------- | -------- | diff --git a/docker/telemetry/docker-compose.yml b/docker/telemetry/docker-compose.yml new file mode 100644 index 0000000000..9a95551b0c --- /dev/null +++ b/docker/telemetry/docker-compose.yml @@ -0,0 +1,89 @@ +--- +version: '3.8' + +services: + loki: + container_name: loki + image: grafana/loki:2.6.1 + expose: + - 3100 + ports: + - "3100:3100" + restart: unless-stopped + volumes: + - loki-data:/loki + + promtail: + container_name: promtail + image: grafana/promtail:latest + volumes: + # custom config will read logs from the containers of + # this project + - ${PWD}/promtail:/etc/promtail + # to read container labels and logs + - /var/run/docker.sock:/var/run/docker.sock + - /var/lib/docker/containers:/var/lib/docker/containers:ro + depends_on: + - loki + + prometheus: + container_name: prometheus + image: prom/prometheus + ports: + - 9000:9090 + volumes: + - ${PWD}/prometheus:/etc/prometheus + - prometheus-data:/prometheus + # yamllint disable-line rule:line-length + command: --web.enable-lifecycle --config.file=/etc/prometheus/prometheus.yml + extra_hosts: + - "host.docker.internal:host-gateway" + + otel-collector: + container_name: otel-collector + image: otel/opentelemetry-collector + command: ["--config=/root/otel-collector/config.yml"] + volumes: + - ${PWD}/otel-collector:/root/otel-collector/ + ports: + - "8888:8888" # Prometheus metrics exposed by the collector + - "8889:8889" # Prometheus exporter metrics + - "55681:55681" + - "13133:13133" # health_check extension + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP http receiver + + jaeger: + container_name: jaeger + image: jaegertracing/all-in-one:latest + ports: + - "16686:16686" + - "14268:14268" + - "14250:14250" + environment: + - COLLECTOR_OTLP_ENABLED=true + - LOG_LEVEL=debug + + grafana: + container_name: grafana + image: grafana/grafana:latest + user: "0" + ports: + - 3001:3000 + restart: unless-stopped + volumes: + - ${PWD}/grafana/:/etc/grafana/provisioning/ + - ${PWD}/grafana/:/var/lib/grafana/dashboards/ + - grafana-data:/var/lib/grafana + + pyroscope: + image: "pyroscope/pyroscope:latest" + ports: + - "4040:4040" + command: + - "server" + +volumes: + prometheus-data: + loki-data: + grafana-data: diff --git a/docker/telemetry/grafana/datasources/config.yml b/docker/telemetry/grafana/datasources/config.yml new file mode 100644 index 0000000000..a9ad009548 --- /dev/null +++ b/docker/telemetry/grafana/datasources/config.yml @@ -0,0 +1,14 @@ +--- +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + jsonData: + maxLines: 1000 diff --git a/docker/telemetry/loki/config.yml b/docker/telemetry/loki/config.yml new file mode 100644 index 0000000000..467188f09b --- /dev/null +++ b/docker/telemetry/loki/config.yml @@ -0,0 +1,11 @@ +--- +auth_enabled: true + +http_prefix: + +server: + http_listen_address: 0.0.0.0 + grpc_listen_address: 0.0.0.0 + http_listen_port: 3100 + grpc_listen_port: 9095 + log_level: info diff --git a/docker/telemetry/otel-collector/config.yml b/docker/telemetry/otel-collector/config.yml new file mode 100644 index 0000000000..c74c8ca93c --- /dev/null +++ b/docker/telemetry/otel-collector/config.yml @@ -0,0 +1,32 @@ +--- +extensions: + health_check: + +receivers: + otlp: + protocols: + grpc: + # endpoint: "0.0.0.0:4317" + http: + # endpoint: "0.0.0.0:4318" + +exporters: + prometheus: + endpoint: "otel-collector:8889" + send_timestamps: true + metric_expiration: 1800m + jaeger: + endpoint: "jaeger:14250" + tls: + insecure: true + +service: + extensions: [health_check] + pipelines: + metrics: + receivers: [otlp] + exporters: [prometheus] + traces: + receivers: [otlp] + processors: [] + exporters: [jaeger] diff --git a/docker/telemetry/prometheus/prometheus.yml b/docker/telemetry/prometheus/prometheus.yml new file mode 100644 index 0000000000..7cc7395305 --- /dev/null +++ b/docker/telemetry/prometheus/prometheus.yml @@ -0,0 +1,25 @@ +--- +global: + scrape_interval: 15s + scrape_timeout: 10s + evaluation_interval: 15s + +scrape_configs: + - job_name: 'collector' + metrics_path: /metrics + honor_timestamps: true + scrape_interval: 15s + scrape_timeout: 10s + scheme: http + static_configs: + - targets: + - 'otel-collector:8889' + - job_name: 'p2p-metrics' + metrics_path: /metrics + honor_timestamps: true + scrape_interval: 15s + scrape_timeout: 10s + scheme: http + static_configs: + - targets: + - 'host.docker.internal:8890' diff --git a/docker/telemetry/promtail/config.yml b/docker/telemetry/promtail/config.yml new file mode 100644 index 0000000000..95267000a9 --- /dev/null +++ b/docker/telemetry/promtail/config.yml @@ -0,0 +1,29 @@ +# https://grafana.com/docs/loki/latest/clients/promtail/configuration/ +# https://docs.docker.com/engine/api/v1.41/#operation/ContainerList +--- +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3100/loki/api/v1/push + +scrape_configs: + - job_name: flog_scrape + docker_sd_configs: + - host: unix:///var/run/docker.sock + refresh_interval: 5s + filters: + - name: label + values: ["logging=promtail"] + relabel_configs: + - source_labels: ['__meta_docker_container_name'] + regex: '/(.*)' + target_label: 'container' + - source_labels: ['__meta_docker_container_log_stream'] + target_label: 'logstream' + - source_labels: ['__meta_docker_container_label_logging_jobname'] + target_label: 'job' From a01e51d74147f2573e7d7791ab07f05f6a46861c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:15:44 +0200 Subject: [PATCH 0777/1008] chore(deps): bump github.com/ipfs/go-block-format from 0.1.2 to 0.2.0 (#2623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/go-block-format](https://github.com/ipfs/go-block-format) from 0.1.2 to 0.2.0.
    Release notes

    Sourced from github.com/ipfs/go-block-format's releases.

    v0.2.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/ipfs/go-block-format/compare/v0.1.2...v0.2.0

    Commits
    • f2d9400 v0.2.0 bump
    • ee6647c chore!: go@1.20, update deps, uci, remove gx
    • ffa6dd1 build(deps): bump golang.org/x/sys
    • 441d112 chore: Update .github/workflows/stale.yml [skip ci]
    • 1f4e90a chore: Update .github/workflows/stale.yml [skip ci]
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/go-block-format&package-manager=go_modules&previous-version=0.1.2&new-version=0.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7fb3056a9e..c44e35e920 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.11.0 - github.com/ipfs/go-block-format v0.1.2 + github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-blockservice v0.5.1 // down 1 version, 0.5.2 is marked as deprecated and raises alerts github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 diff --git a/go.sum b/go.sum index 91e56dd9ca..31b11a82e0 100644 --- a/go.sum +++ b/go.sum @@ -1040,8 +1040,9 @@ github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/ github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= -github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= github.com/ipfs/go-block-format v0.1.2/go.mod h1:mACVcrxarQKstUU3Yf/RdwbC4DzPV6++rO2a3d+a/KE= +github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= +github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= From 81d6d3be82ea0be2e94fc0a6aa42e271ae0ec143 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:36:11 -0500 Subject: [PATCH 0778/1008] chore!: bump celestia-app to v1.0.0-rc13 (#2646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bumps app to v1.0.0-rc13, core to v1.26.2-tm-v0.34.28, the sdk to v1.18.0-sdk-v0.46.14, and nmt to v0.19.0 per release notes: this is a consensus breaking change that is targetting mocha-4 and arabica-10 besides the already mentioned changes, this bumps app to go 1.21 🎉 and has the simplified programmatic transaction signing repo (we got rid of the KeyringSigner since it was originally designed for tx malleation). This package can be found here, and there are a few (hopefully straightforward) examples here, here, and here. When you all are ready, we'd like to remove the KeyringSigner, and would be delighted to help migrate. --- go.mod | 9 +++++---- go.sum | 29 ++++++++++++++++------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index c44e35e920..8c1fbb1349 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,12 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc12 + github.com/celestiaorg/celestia-app v1.0.0-rc13 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.3.0 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/nmt v0.18.1 + github.com/celestiaorg/nmt v0.19.0 github.com/celestiaorg/rsmt2d v0.11.0 github.com/cosmos/cosmos-sdk v0.46.14 github.com/cosmos/cosmos-sdk/api v0.1.0 @@ -310,6 +310,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -336,10 +337,10 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28 ) diff --git a/go.sum b/go.sum index 31b11a82e0..d738f67e10 100644 --- a/go.sum +++ b/go.sum @@ -356,12 +356,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc12 h1:ko9hPD4oz1UTS4ZqzikGVQ0wXi5+4kEhDb7decx5Ehs= -github.com/celestiaorg/celestia-app v1.0.0-rc12/go.mod h1:vXvKEudUpdJCvUr79qVKKJ0Xo7ofsuU80+Hs9aKGjvU= -github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28 h1:eXS3v26nob8Xs2+flKHVxcTzhzQW44KgTcooR3OxnK4= -github.com/celestiaorg/celestia-core v1.24.0-tm-v0.34.28/go.mod h1:J/GsBjoTZaFz71VeyrLZbG8rV+Rzi6oFEUZUipQ97hQ= -github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14 h1:PckXGxLJjXv97VO3xS8NPHN5oO83X5nvJLbc/4s8jUM= -github.com/celestiaorg/cosmos-sdk v1.17.0-sdk-v0.46.14/go.mod h1:70Go8qNy7YAb1PUcHCChRHNX2ke7c9jgUIEklUX+Mac= +github.com/celestiaorg/celestia-app v1.0.0-rc13 h1:BM9lBJw+RcAiFIUmVDd3XBYyXV9rKJBi8mDqc2wok1o= +github.com/celestiaorg/celestia-app v1.0.0-rc13/go.mod h1:JYu6i1NxJw26TVZ+XSllUdnw0Fw3nGNk5f3wm6RIcys= +github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28 h1:2efXQaggLFknz0wQufr4nUEz5G7pSVHS1j7NuJDsvII= +github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28/go.mod h1:++dNzzzjP9jYg+NopN9G8sg1HEZ58lv1TPtg71evZ0E= +github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14 h1:dDfoQJOlVNj4HufJ1lBLTo2k3/L/255MIiKmEQziDmw= +github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14/go.mod h1:kkdiHo/zG6ar80730+bG1owdMAQXrGp4utFu7mbfADo= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJx5/hgZ+IeOLEIoLsAveJN/7/ZtQQtPSVw= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= @@ -376,8 +376,8 @@ github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.18.1 h1:zU3apzW4y0fs0ilQA74XnEYW8FvRv0CUK2LXK66L3rA= -github.com/celestiaorg/nmt v0.18.1/go.mod h1:0l8q6UYRju1xNrxtvV6NwPdW3lfsN6KuZ0htRnModdc= +github.com/celestiaorg/nmt v0.19.0 h1:9VXFeI/gt+q8h5HeCE0RjXJhOxsFzxJUjHrkvF9CMYE= +github.com/celestiaorg/nmt v0.19.0/go.mod h1:Oz15Ub6YPez9uJV0heoU4WpFctxazuIhKyUtaYNio7E= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.11.0 h1:lcto/637WyTEZR3dLRoNvyuExfnUbxvdvKi3qz/2V4k= @@ -2091,8 +2091,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -2147,8 +2148,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= @@ -2369,6 +2370,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h1:hGXzO5bhhSHZnKvrDaXB82Y9DRFour0Nz/KrBh7reWw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= @@ -2861,8 +2864,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From a3220f4503f7b0666b1860148e910658fe130d85 Mon Sep 17 00:00:00 2001 From: ramin Date: Tue, 5 Sep 2023 08:17:45 +0100 Subject: [PATCH 0779/1008] fix(blob): typo, sumitter -> submitter (#2648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview - simple change in `blob/service.go` where a typo of sumitter existed in the `Service` struct, renamed to submitter ## Checklist - [√] New and updated code has appropriate documentation - [√] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [n/a] Visual proof for any user facing features like CLI or documentation updates - [n/a] Linked issues closed with keywords --------- Co-authored-by: Ryan --- blob/service.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/blob/service.go b/blob/service.go index da14fc06c7..31cd259efc 100644 --- a/blob/service.go +++ b/blob/service.go @@ -32,7 +32,7 @@ type Submitter interface { type Service struct { // accessor dials the given celestia-core endpoint to submit blobs. - blobSumitter Submitter + blobSubmitter Submitter // shareGetter retrieves the EDS to fetch all shares from the requested header. shareGetter share.Getter // headerGetter fetches header by the provided height @@ -45,9 +45,9 @@ func NewService( headerGetter func(context.Context, uint64) (*header.ExtendedHeader, error), ) *Service { return &Service{ - blobSumitter: submitter, - shareGetter: getter, - headerGetter: headerGetter, + blobSubmitter: submitter, + shareGetter: getter, + headerGetter: headerGetter, } } @@ -58,7 +58,7 @@ func NewService( func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { log.Debugw("submitting blobs", "amount", len(blobs)) - resp, err := s.blobSumitter.SubmitPayForBlob(ctx, types.OneInt().Neg(), 0, blobs) + resp, err := s.blobSubmitter.SubmitPayForBlob(ctx, types.OneInt().Neg(), 0, blobs) if err != nil { return 0, err } From f9cf180ab4eb8cac0ffa6ac3b9723d4c77799e3a Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:22:44 +0200 Subject: [PATCH 0780/1008] chore(nodebuilder/tests): Clean up integration tests in `p2p_test` (#2166) Cleans up swamp tests in `p2p_test.go`. Based on #2162 --- nodebuilder/tests/p2p_test.go | 190 ++++++++++++++------------------- nodebuilder/tests/sync_test.go | 1 + 2 files changed, 79 insertions(+), 112 deletions(-) diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 613dface94..083712dfdd 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" @@ -18,22 +17,22 @@ import ( ) /* -Test-Case: Full/Light Nodes connection to Bridge as a Bootstapper +Test-Case: Full/Light Nodes connection to Bridge as a Bootstrapper Steps: 1. Create a Bridge Node(BN) 2. Start a BN -3. Create full/light nodes with bridge node as bootsrapped peer +3. Create full/light nodes with bridge node as bootstrap peer 4. Start full/light nodes 5. Check that nodes are connected to bridge */ -func TestUseBridgeNodeAsBootstraper(t *testing.T) { - sw := swamp.NewSwamp(t) - - bridge := sw.NewBridgeNode() - +func TestBridgeNodeAsBootstrapper(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) + sw := swamp.NewSwamp(t) + + // create and start BN + bridge := sw.NewBridgeNode() err := bridge.Start(ctx) require.NoError(t, err) @@ -41,39 +40,14 @@ func TestUseBridgeNodeAsBootstraper(t *testing.T) { full := sw.NewFullNode(nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr})) light := sw.NewLightNode(nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr})) - nodes := []*nodebuilder.Node{full, light} - for index := range nodes { - require.NoError(t, nodes[index].Start(ctx)) - assert.Equal(t, *addr, nodes[index].Bootstrappers[0]) - assert.True(t, nodes[index].Host.Network().Connectedness(addr.ID) == network.Connected) - } -} - -/* -Test-Case: Add peer to blacklist -Steps: -1. Create a Full Node(BN) -2. Start a FN -3. Create a Light Node(LN) -5. Start a LN -6. Explicitly block FN id by LN -7. Check FN is allowed to dial with LN -8. Check LN is not allowed to dial with FN -*/ -func TestAddPeerToBlackList(t *testing.T) { - sw := swamp.NewSwamp(t) - full := sw.NewFullNode() - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) - require.NoError(t, full.Start(ctx)) - addr := host.InfoFromHost(full.Host) - light := sw.NewLightNode() - require.NoError(t, light.Start(ctx)) - require.NoError(t, light.ConnGater.BlockPeer(addr.ID)) - - require.True(t, full.ConnGater.InterceptPeerDial(host.InfoFromHost(light.Host).ID)) - require.False(t, light.ConnGater.InterceptPeerDial(addr.ID)) + for _, nd := range []*nodebuilder.Node{full, light} { + // start node and ensure that BN is correctly set as bootstrapper + require.NoError(t, nd.Start(ctx)) + assert.Equal(t, *addr, nd.Bootstrappers[0]) + // ensure that node is actually connected to BN + assert.True(t, nd.Host.Network().Connectedness(addr.ID) == network.Connected) + } } /* @@ -86,131 +60,123 @@ Steps: 5. Ensure that nodes are connected to bridge 6. Wait until light will find full node 7. Check that full and light nodes are connected to each other - 8. Stop FN and ensure that it's not connected to LN */ -func TestBootstrapNodesFromBridgeNode(t *testing.T) { +func TestFullDiscoveryViaBootstrapper(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) + t.Cleanup(cancel) + + const defaultTimeInterval = time.Second * 2 + sw := swamp.NewSwamp(t) + + // create and start a BN cfg := nodebuilder.DefaultConfig(node.Bridge) - const defaultTimeInterval = time.Second * 10 setTimeInterval(cfg, defaultTimeInterval) - bridge := sw.NewNodeWithConfig(node.Bridge, cfg) - - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) - t.Cleanup(cancel) - err := bridge.Start(ctx) require.NoError(t, err) - bridgeAddr := host.InfoFromHost(bridge.Host) + // use BN as the bootstrapper + bootstrapper := host.InfoFromHost(bridge.Host) + + // create FN with BN as bootstrapper cfg = nodebuilder.DefaultConfig(node.Full) setTimeInterval(cfg, defaultTimeInterval) full := sw.NewNodeWithConfig( node.Full, cfg, - nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}), + nodebuilder.WithBootstrappers([]peer.AddrInfo{*bootstrapper}), ) + // create LN with BN as bootstrapper cfg = nodebuilder.DefaultConfig(node.Light) setTimeInterval(cfg, defaultTimeInterval) - cfg.P2P.PeerExchange = true light := sw.NewNodeWithConfig( node.Light, cfg, - nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}), + nodebuilder.WithBootstrappers([]peer.AddrInfo{*bootstrapper}), ) + + // start FN and LN and ensure they are both connected to BN as a bootstrapper nodes := []*nodebuilder.Node{full, light} - ch := make(chan struct{}) - sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) - require.NoError(t, err) - defer sub.Close() for index := range nodes { require.NoError(t, nodes[index].Start(ctx)) - assert.Equal(t, *bridgeAddr, nodes[index].Bootstrappers[0]) - assert.True(t, nodes[index].Host.Network().Connectedness(bridgeAddr.ID) == network.Connected) + assert.Equal(t, *bootstrapper, nodes[index].Bootstrappers[0]) + assert.True(t, nodes[index].Host.Network().Connectedness(bootstrapper.ID) == network.Connected) } - addrFull := host.InfoFromHost(full.Host) - go func() { - for e := range sub.Out() { - connStatus := e.(event.EvtPeerConnectednessChanged) - if connStatus.Peer == full.Host.ID() && connStatus.Connectedness == network.NotConnected { - ch <- struct{}{} - } + + for { + if ctx.Err() != nil { + t.Fatal(ctx.Err()) + } + if light.Host.Network().Connectedness(host.InfoFromHost(full.Host).ID) == network.Connected { + // LN discovered FN successfully and is now connected + break } - }() - - // ensure that the light node is connected to the full node - assert.True(t, light.Host.Network().Connectedness(addrFull.ID) == network.Connected) - - sw.Disconnect(t, light, full) - require.NoError(t, full.Stop(ctx)) - select { - case <-ctx.Done(): - t.Fatal("peer was not disconnected") - case <-ch: - assert.True(t, light.Host.Network().Connectedness(addrFull.ID) == network.NotConnected) } } /* -Test-Case: Restart full node discovery after one node is disconnected +Test-Case: Full node discovery of disconnected full nodes Steps: 1. Create a Bridge Node(BN) 2. Start a BN -3. Create 2 full nodes with bridge node as bootstrapper peer and start them -4. Check that nodes are connected to each other -5. Create one more node with disabled discovery -6. Disconnect FNs from each other -7. Check that the last FN is connected to one of the nodes +3. Create 2 FNs with bridge node as bootstrapper peer and start them +4. Check that the FNs discover each other +5. Disconnect the FNs +6. Create one more node with discovery process disabled (however advertisement is still enabled) +7. Check that the FN with discovery disabled is still found by the other two FNs *NOTE*: this test will take some time because it relies on several cycles of peer discovery */ func TestRestartNodeDiscovery(t *testing.T) { - sw := swamp.NewSwamp(t) - cfg := nodebuilder.DefaultConfig(node.Bridge) - const defaultTimeInterval = time.Second * 2 - const fullNodes = 2 - - setTimeInterval(cfg, defaultTimeInterval) - cfg.Share.Discovery.PeersLimit = fullNodes - bridge := sw.NewNodeWithConfig(node.Bridge, cfg) - ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) + const ( + defaultTimeInterval = time.Second * 2 + numFulls = 2 + ) + + sw := swamp.NewSwamp(t) + + // create and start a BN as a bootstrapper + fullCfg := nodebuilder.DefaultConfig(node.Bridge) + setTimeInterval(fullCfg, defaultTimeInterval) + bridge := sw.NewNodeWithConfig(node.Bridge, fullCfg) err := bridge.Start(ctx) require.NoError(t, err) + bridgeAddr := host.InfoFromHost(bridge.Host) - nodes := make([]*nodebuilder.Node, fullNodes) - cfg = nodebuilder.DefaultConfig(node.Full) - setTimeInterval(cfg, defaultTimeInterval) - cfg.Share.Discovery.PeersLimit = fullNodes + fullCfg = nodebuilder.DefaultConfig(node.Full) + setTimeInterval(fullCfg, defaultTimeInterval) nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*bridgeAddr}) - for index := 0; index < fullNodes; index++ { - nodes[index] = sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) - } - for index := 0; index < fullNodes; index++ { + // create two FNs and start them, ensuring they are connected to BN as + // bootstrapper + nodes := make([]*nodebuilder.Node, numFulls) + for index := 0; index < numFulls; index++ { + nodes[index] = sw.NewNodeWithConfig(node.Full, fullCfg, nodesConfig) require.NoError(t, nodes[index].Start(ctx)) assert.True(t, nodes[index].Host.Network().Connectedness(bridgeAddr.ID) == network.Connected) } - // ensure full nodes are connected to each other + // ensure FNs are connected to each other require.True(t, nodes[0].Host.Network().Connectedness(nodes[1].Host.ID()) == network.Connected) - // create one more node with disabled discovery - cfg = nodebuilder.DefaultConfig(node.Full) - setTimeInterval(cfg, defaultTimeInterval) - cfg.Share.Discovery.PeersLimit = 0 - node := sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) - connectSub, err := nodes[0].Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) - require.NoError(t, err) - defer connectSub.Close() + // disconnect the FNs sw.Disconnect(t, nodes[0], nodes[1]) - require.NoError(t, node.Start(ctx)) - // ensure that the last node is connected to one of the nodes - require.True(t, nodes[0].Host.Network().Connectedness(node.Host.ID()) == network.Connected) + // create and start one more FN with disabled discovery + fullCfg.Share.Discovery.PeersLimit = 0 + disabledDiscoveryFN := sw.NewNodeWithConfig(node.Full, fullCfg, nodesConfig) + err = disabledDiscoveryFN.Start(ctx) + require.NoError(t, err) + + // ensure that the FN with disabled discovery is discovered by both of the + // running FNs that have discovery enabled + require.True(t, nodes[0].Host.Network().Connectedness(disabledDiscoveryFN.Host.ID()) == network.Connected) + require.True(t, nodes[1].Host.Network().Connectedness(disabledDiscoveryFN.Host.ID()) == network.Connected) } func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 234556a3aa..65db1332ff 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -297,6 +297,7 @@ func TestSyncLightAgainstFull(t *testing.T) { require.NoError(t, err) _, err = full.HeaderServ.WaitForHeight(ctx, bridgeHead.Height()) require.NoError(t, err) + assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // reset suite bootstrapper list and set full node as a bootstrapper for // LN to connect to From da8244e00fc434139fb1fd21414683660553c239 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 5 Sep 2023 11:06:33 +0200 Subject: [PATCH 0781/1008] refactor(blob): removing jsonProof (#2652) Closes #2634 . nmt.Proof has json serialization built in now so jsonProof is unnecessary and results in missing fields from the original struct. Only adds additional fields to the JSON response so not breaking --- blob/blob.go | 37 ------------------------------------- blob/service_test.go | 5 +++-- 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/blob/blob.go b/blob/blob.go index e9ad2b6255..c5fd04c782 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -12,7 +12,6 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/ipld" ) // Commitment is a Merkle Root of the subtree built from shares of the Blob. @@ -62,42 +61,6 @@ func (p Proof) equal(input Proof) error { return nil } -type jsonProof struct { - Start int `json:"start"` - End int `json:"end"` - Nodes [][]byte `json:"nodes"` -} - -func (p *Proof) MarshalJSON() ([]byte, error) { - proofs := make([]jsonProof, 0, p.Len()) - for _, pp := range *p { - proofs = append(proofs, jsonProof{ - Start: pp.Start(), - End: pp.End(), - Nodes: pp.Nodes(), - }) - } - - return json.Marshal(proofs) -} - -func (p *Proof) UnmarshalJSON(data []byte) error { - var proofs []jsonProof - err := json.Unmarshal(data, &proofs) - if err != nil { - return err - } - - nmtProofs := make([]*nmt.Proof, len(proofs)) - for i, jProof := range proofs { - nmtProof := nmt.NewInclusionProof(jProof.Start, jProof.End, jProof.Nodes, ipld.NMTIgnoreMaxNamespace) - nmtProofs[i] = &nmtProof - } - - *p = nmtProofs - return nil -} - // Blob represents any application-specific binary data that anyone can submit to Celestia. type Blob struct { types.Blob `json:"blob"` diff --git a/blob/service_test.go b/blob/service_test.go index 1dcabe7129..a45fe631af 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/json" "testing" "time" @@ -272,14 +273,14 @@ func TestBlobService_Get(t *testing.T) { doFn: func() (interface{}, error) { proof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) require.NoError(t, err) - return proof.MarshalJSON() + return json.Marshal(proof) }, expectedResult: func(i interface{}, err error) { require.NoError(t, err) jsonData, ok := i.([]byte) require.True(t, ok) var proof Proof - require.NoError(t, proof.UnmarshalJSON(jsonData)) + require.NoError(t, json.Unmarshal(jsonData, &proof)) newProof, err := service.GetProof(ctx, 1, blobs0[1].Namespace(), blobs0[1].Commitment) require.NoError(t, err) From 456d169582dc02aafb7d5df891da5973d54a20b7 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 5 Sep 2023 11:43:52 +0200 Subject: [PATCH 0782/1008] feat(eds/store): remove corrupted blocks from store (#2625) Related to #2335 If an OpShardFail is found or corruption is detected from GetSharesByNamespace, the shard is removed --- nodebuilder/share/constructors.go | 18 +++++++ nodebuilder/share/module.go | 79 ++++++++++++++++++++----------- share/eds/accessor_cache.go | 8 ++++ share/eds/metrics.go | 20 ++++++++ share/eds/store.go | 50 ++++++++++++++----- share/eds/store_test.go | 38 +++++++++++++++ share/getters/getter_test.go | 29 ++++++++++++ share/getters/shrex_test.go | 3 +- share/getters/store.go | 12 +++++ share/p2p/peers/manager.go | 28 ++++++++--- share/p2p/peers/manager_test.go | 5 +- share/p2p/peers/options.go | 17 +++++++ share/share.go | 14 ++++++ 13 files changed, 269 insertions(+), 52 deletions(-) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index a1b7e39713..b962038d17 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -87,6 +87,24 @@ func lightGetter( return getters.NewCascadeGetter(cascade) } +// ShrexGetter is added to bridge nodes for the case that a shard is removed +// after detected shard corruption. This ensures the block is fetched and stored +// by shrex the next time the data is retrieved (meaning shard recovery is +// manual after corruption is detected). +func bridgeGetter( + store *eds.Store, + storeGetter *getters.StoreGetter, + shrexGetter *getters.ShrexGetter, + cfg Config, +) share.Getter { + var cascade []share.Getter + cascade = append(cascade, storeGetter) + if cfg.UseShareExchange { + cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + } + return getters.NewCascadeGetter(cascade) +} + func fullGetter( store *eds.Store, storeGetter *getters.StoreGetter, diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index b924cf8167..f7a84bf526 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -5,9 +5,12 @@ import ( "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/libs/fxutil" + libhead "github.com/celestiaorg/go-header" + + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/share" @@ -48,6 +51,33 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option ), ) + shrexGetterComponents := fx.Options( + fx.Provide(func() peers.Parameters { + return cfg.PeerManagerParams + }), + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { + cfg.ShrExNDParams.WithNetworkID(network.String()) + return shrexnd.NewClient(cfg.ShrExNDParams, host) + }, + ), + fx.Provide( + func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { + cfg.ShrExEDSParams.WithNetworkID(network.String()) + return shrexeds.NewClient(cfg.ShrExEDSParams, host) + }, + ), + fx.Provide(fx.Annotate( + getters.NewShrexGetter, + fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { + return getter.Stop(ctx) + }), + )), + ) + bridgeAndFullComponents := fx.Options( fx.Provide(getters.NewStoreGetter), fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), @@ -112,32 +142,25 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option }), ) - shrexGetterComponents := fx.Options( - fx.Provide(func() peers.Parameters { - return cfg.PeerManagerParams - }), - fx.Provide(peers.NewManager), - fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { - cfg.ShrExNDParams.WithNetworkID(network.String()) - return shrexnd.NewClient(cfg.ShrExNDParams, host) - }, - ), + peerManagerWithShrexPools := fx.Options( fx.Provide( - func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { - cfg.ShrExEDSParams.WithNetworkID(network.String()) - return shrexeds.NewClient(cfg.ShrExEDSParams, host) + func( + params peers.Parameters, + discovery *disc.Discovery, + host host.Host, + connGater *conngater.BasicConnectionGater, + shrexSub *shrexsub.PubSub, + headerSub libhead.Subscriber[*header.ExtendedHeader], + ) (*peers.Manager, error) { + return peers.NewManager( + params, + discovery, + host, + connGater, + peers.WithShrexSubPools(shrexSub, headerSub), + ) }, ), - fx.Provide(fx.Annotate( - getters.NewShrexGetter, - fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, getter *getters.ShrexGetter) error { - return getter.Stop(ctx) - }), - )), ) switch tp { @@ -145,10 +168,10 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module( "share", baseComponents, + fx.Provide(peers.NewManager), bridgeAndFullComponents, - fxutil.ProvideAs(func(getter *getters.StoreGetter) share.Getter { - return getter - }), + shrexGetterComponents, + fx.Provide(bridgeGetter), fx.Invoke(func(lc fx.Lifecycle, sub *shrexsub.PubSub) error { lc.Append(fx.Hook{ OnStart: sub.Start, @@ -160,6 +183,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option case node.Full: return fx.Module( "share", + peerManagerWithShrexPools, baseComponents, bridgeAndFullComponents, shrexGetterComponents, @@ -175,6 +199,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option light.WithSampleAmount(cfg.LightAvailability.SampleAmount), } }), + peerManagerWithShrexPools, shrexGetterComponents, fx.Invoke(ensureEmptyEDSInBS), fx.Provide(getters.NewIPLDGetter), diff --git a/share/eds/accessor_cache.go b/share/eds/accessor_cache.go index cd0f0537fa..9f70178be6 100644 --- a/share/eds/accessor_cache.go +++ b/share/eds/accessor_cache.go @@ -69,6 +69,14 @@ func (bc *blockstoreCache) evictFn() func(_ interface{}, val interface{}) { } } +func (bc *blockstoreCache) Remove(key shard.Key) bool { + lk := &bc.stripedLocks[shardKeyToStriped(key)] + lk.Lock() + defer lk.Unlock() + + return bc.cache.Remove(key) +} + // Get retrieves the blockstore for a given shard key from the cache. If the blockstore is not in // the cache, it returns an errCacheMiss func (bc *blockstoreCache) Get(shardContainingCid shard.Key) (*accessorWithBlockstore, error) { diff --git a/share/eds/metrics.go b/share/eds/metrics.go index 1f430bf688..9d4b2a53ef 100644 --- a/share/eds/metrics.go +++ b/share/eds/metrics.go @@ -47,6 +47,8 @@ type metrics struct { listTime metric.Float64Histogram getAccessorTime metric.Float64Histogram + shardFailureCount metric.Int64Counter + longOpTime metric.Float64Histogram gcTime metric.Float64Histogram } @@ -106,6 +108,12 @@ func (s *Store) WithMetrics() error { return err } + shardFailureCount, err := meter.Int64Counter("eds_store_shard_failure_counter", + metric.WithDescription("eds store OpShardFail counter")) + if err != nil { + return err + } + longOpTime, err := meter.Float64Histogram("eds_store_long_operation_time_histogram", metric.WithDescription("eds store long operation time histogram(s)")) if err != nil { @@ -153,6 +161,7 @@ func (s *Store) WithMetrics() error { hasTime: hasTime, listTime: listTime, getAccessorTime: getAccessorTime, + shardFailureCount: shardFailureCount, longOpTime: longOpTime, gcTime: gcTime, } @@ -170,6 +179,17 @@ func (m *metrics) observeGCtime(ctx context.Context, dur time.Duration, failed b attribute.Bool(failedKey, failed))) } +func (m *metrics) observeShardFailure(ctx context.Context, shardKey string) { + if m == nil { + return + } + if ctx.Err() != nil { + ctx = context.Background() + } + + m.shardFailureCount.Add(ctx, 1, metric.WithAttributes(attribute.String("shard_key", shardKey))) +} + func (m *metrics) observePut(ctx context.Context, dur time.Duration, result putResult, size uint) { if m == nil { return diff --git a/share/eds/store.go b/share/eds/store.go index 24a96c9fe4..fa9a7f7c7e 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "context" - "encoding/hex" "errors" "fmt" "io" @@ -64,6 +63,8 @@ type Store struct { // lastGCResult is only stored on the store for testing purposes. lastGCResult atomic.Pointer[dagstore.GCResult] + shardFailures chan dagstore.ShardResult + metrics *metrics } @@ -92,6 +93,8 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { if err != nil { return nil, fmt.Errorf("failed to create index: %w", err) } + + failureChan := make(chan dagstore.ShardResult) dagStore, err := dagstore.NewDAGStore( dagstore.Config{ TransientsDir: basepath + transientsPath, @@ -99,6 +102,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { Datastore: ds, MountRegistry: r, TopLevelIndex: invertedIdx, + FailureCh: failureChan, }, ) if err != nil { @@ -111,13 +115,14 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { } store := &Store{ - basepath: basepath, - dgstr: dagStore, - carIdx: fsRepo, - invertedIdx: invertedIdx, - gcInterval: defaultGCInterval, - mounts: r, - cache: cache, + basepath: basepath, + dgstr: dagStore, + carIdx: fsRepo, + invertedIdx: invertedIdx, + gcInterval: defaultGCInterval, + mounts: r, + shardFailures: failureChan, + cache: cache, } store.bs = newBlockstore(store, cache, ds) return store, nil @@ -139,6 +144,8 @@ func (s *Store) Start(ctx context.Context) error { if s.gcInterval != 0 { go s.gc(runCtx) } + + go s.watchForFailures(runCtx) return nil } @@ -172,6 +179,23 @@ func (s *Store) gc(ctx context.Context) { } } +func (s *Store) watchForFailures(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case res := <-s.shardFailures: + log.Errorw("removing shard after failure", "key", res.Key, "err", res.Error) + s.metrics.observeShardFailure(ctx, res.Key.String()) + k := share.MustDataHashFromString(res.Key.String()) + err := s.Remove(ctx, k) + if err != nil { + log.Errorw("failed to remove shard after failure", "key", res.Key, "err", err) + } + } + } +} + // Put stores the given data square with DataRoot's hash as a key. // // The square is verified on the Exchange level, and Put only stores the square, trusting it. @@ -437,6 +461,11 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) error { func (s *Store) remove(ctx context.Context, root share.DataHash) (err error) { key := root.String() + + // Remove from accessor cache, so that existing readers are closed and + // DestroyShard can be executed. + s.cache.Remove(shard.KeyFromString(key)) + ch := make(chan dagstore.ShardResult, 1) err = s.dgstr.DestroyShard(ctx, shard.KeyFromString(key), ch, dagstore.DestroyOpts{}) if err != nil { @@ -535,10 +564,7 @@ func (s *Store) list() ([]share.DataHash, error) { shards := s.dgstr.AllShardsInfo() hashes := make([]share.DataHash, 0, len(shards)) for shrd := range shards { - hash, err := hex.DecodeString(shrd.String()) - if err != nil { - return nil, err - } + hash := share.MustDataHashFromString(shrd.String()) hashes = append(hashes, hash) } return hashes, nil diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 4b263e7062..4f1d7f4c8b 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -122,6 +122,44 @@ func TestEDSStore(t *testing.T) { assert.ErrorContains(t, err, "no such file or directory") }) + t.Run("Remove after OpShardFail", func(t *testing.T) { + eds, dah := randomEDS(t) + + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + // assert that shard now exists + ok, err := edsStore.Has(ctx, dah.Hash()) + assert.NoError(t, err) + assert.True(t, ok) + + // assert that file now exists + path := edsStore.basepath + blocksPath + dah.String() + _, err = os.Stat(path) + assert.NoError(t, err) + + err = os.Remove(path) + assert.NoError(t, err) + + _, err = edsStore.GetCAR(ctx, dah.Hash()) + assert.Error(t, err) + + ticker := time.NewTicker(time.Millisecond * 100) + defer ticker.Stop() + for { + select { + case <-ticker.C: + has, err := edsStore.Has(ctx, dah.Hash()) + if err == nil && !has { + // shard no longer exists after OpShardFail was detected from GetCAR call + return + } + case <-ctx.Done(): + t.Fatal("timeout waiting for shard to be removed") + } + } + }) + t.Run("Has", func(t *testing.T) { eds, dah := randomEDS(t) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 02e075459b..d19d0cf174 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -2,6 +2,7 @@ package getters import ( "context" + "os" "testing" "time" @@ -153,6 +154,34 @@ func TestStoreGetter(t *testing.T) { _, err = sg.GetSharesByNamespace(ctx, &root, namespace) require.ErrorIs(t, err, share.ErrNotFound) }) + + t.Run("GetSharesFromNamespace removes corrupted shard", func(t *testing.T) { + randEds, namespace, dah := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, dah.Hash(), randEds) + require.NoError(t, err) + + // available + shares, err := sg.GetSharesByNamespace(ctx, &dah, namespace) + require.NoError(t, err) + require.NoError(t, shares.Verify(&dah, namespace)) + assert.Len(t, shares.Flatten(), 2) + + // 'corrupt' existing CAR by overwriting with a random EDS + f, err := os.OpenFile(tmpDir+"/blocks/"+dah.String(), os.O_WRONLY, 0644) + require.NoError(t, err) + edsToOverwriteWith, dah := randomEDS(t) + err = eds.WriteEDS(ctx, edsToOverwriteWith, f) + require.NoError(t, err) + + shares, err = sg.GetSharesByNamespace(ctx, &dah, namespace) + require.ErrorIs(t, err, share.ErrNotFound) + require.Nil(t, shares) + + // corruption detected, shard is removed + has, err := edsStore.Has(ctx, dah.Hash()) + require.False(t, has) + require.NoError(t, err) + }) } func TestIPLDGetter(t *testing.T) { diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 0ca807d0d4..236fae36e1 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -227,11 +227,10 @@ func testManager( } manager, err := peers.NewManager( peers.DefaultParameters(), - headerSub, - shrexSub, disc, host, connGater, + peers.WithShrexSubPools(shrexSub, headerSub), ) return manager, err } diff --git a/share/getters/store.go b/share/getters/store.go index 989649f795..5eca956faa 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -121,6 +121,18 @@ func (sg *StoreGetter) GetSharesByNamespace( // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) shares, err = collectSharesByNamespace(ctx, blockGetter, root, namespace) + if errors.Is(err, ipld.ErrNodeNotFound) { + // IPLD node not found after the index pointed to this shard and the CAR blockstore has been + // opened successfully is a strong indicator of corruption. We remove the block on bridges + // and fulls and return share.ErrNotFound to ensure the data is retrieved by the next + // getter. Note that this recovery is manual and will only be restored by an RPC call to + // fetch the same datahash that was removed. + err = sg.store.Remove(ctx, root.Hash()) + if err != nil { + log.Errorf("getter/store: failed to remove CAR after detected corruption: %w", err) + } + err = share.ErrNotFound + } if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 87f9361ee2..caef242eec 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -99,11 +99,10 @@ type syncPool struct { func NewManager( params Parameters, - headerSub libhead.Subscriber[*header.ExtendedHeader], - shrexSub *shrexsub.PubSub, discovery *discovery.Discovery, host host.Host, connGater *conngater.BasicConnectionGater, + options ...Option, ) (*Manager, error) { if err := params.Validate(); err != nil { return nil, err @@ -111,8 +110,6 @@ func NewManager( s := &Manager{ params: params, - headerSub: headerSub, - shrexSub: shrexSub, connGater: connGater, disc: discovery, host: host, @@ -122,6 +119,13 @@ func NewManager( disconnectedPeersDone: make(chan struct{}), } + for _, opt := range options { + err := opt(s) + if err != nil { + return nil, err + } + } + s.fullNodes = newPool(s.params.PeerCooldown) discovery.WithOnPeersUpdate( @@ -147,12 +151,17 @@ func (m *Manager) Start(startCtx context.Context) error { ctx, cancel := context.WithCancel(context.Background()) m.cancel = cancel + // pools will only be populated with senders of shrexsub notifications if the WithShrexSubPools + // option is used. + if m.shrexSub == nil && m.headerSub == nil { + return nil + } + validatorFn := m.metrics.validationObserver(m.Validate) err := m.shrexSub.AddValidator(validatorFn) if err != nil { return fmt.Errorf("registering validator: %w", err) } - err = m.shrexSub.Start(startCtx) if err != nil { return fmt.Errorf("starting shrexsub: %w", err) @@ -168,16 +177,21 @@ func (m *Manager) Start(startCtx context.Context) error { return fmt.Errorf("subscribing to libp2p events: %w", err) } - go m.subscribeDisconnectedPeers(ctx, sub) go m.subscribeHeader(ctx, headerSub) + go m.subscribeDisconnectedPeers(ctx, sub) go m.GC(ctx) - return nil } func (m *Manager) Stop(ctx context.Context) error { m.cancel() + // we do not need to wait for headersub and disconnected peers to finish + // here, since they were never started + if m.headerSub == nil && m.shrexSub == nil { + return nil + } + select { case <-m.headerSubDone: case <-ctx.Done(): diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index ad04d2c7bd..c60a737baa 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -419,8 +419,6 @@ func TestIntegration(t *testing.T) { require.NoError(t, err) fnPeerManager, err := NewManager( DefaultParameters(), - nil, - nil, fnDisc, nil, connGater, @@ -469,11 +467,10 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten } manager, err := NewManager( DefaultParameters(), - headerSub, - shrexSub, disc, host, connGater, + WithShrexSubPools(shrexSub, headerSub), ) if err != nil { return nil, err diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index cfda906071..97ec30df4a 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -3,6 +3,11 @@ package peers import ( "fmt" "time" + + libhead "github.com/celestiaorg/go-header" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) type Parameters struct { @@ -21,6 +26,8 @@ type Parameters struct { EnableBlackListing bool } +type Option func(*Manager) error + // Validate validates the values in Parameters func (p *Parameters) Validate() error { if p.PoolValidationTimeout <= 0 { @@ -56,6 +63,16 @@ func DefaultParameters() Parameters { } } +// WithShrexSubPools passes a shrexsub and headersub instance to be used to populate and validate +// pools from shrexsub notifications. +func WithShrexSubPools(shrexSub *shrexsub.PubSub, headerSub libhead.Subscriber[*header.ExtendedHeader]) Option { + return func(m *Manager) error { + m.shrexSub = shrexSub + m.headerSub = headerSub + return nil + } +} + // WithMetrics turns on metric collection in peer manager. func (m *Manager) WithMetrics() error { metrics, err := initMetrics(m) diff --git a/share/share.go b/share/share.go index 02ccd73909..4079028d82 100644 --- a/share/share.go +++ b/share/share.go @@ -2,6 +2,7 @@ package share import ( "bytes" + "encoding/hex" "fmt" "github.com/celestiaorg/celestia-app/pkg/appconsts" @@ -57,3 +58,16 @@ func (dh DataHash) String() string { func (dh DataHash) IsEmptyRoot() bool { return bytes.Equal(EmptyRoot().Hash(), dh) } + +// MustDataHashFromString converts a hex string to a valid datahash. +func MustDataHashFromString(datahash string) DataHash { + dh, err := hex.DecodeString(datahash) + if err != nil { + panic(fmt.Sprintf("datahash conversion: passed string was not valid hex: %s", datahash)) + } + err = DataHash(dh).Validate() + if err != nil { + panic(fmt.Sprintf("datahash validation: passed hex string failed: %s", err)) + } + return dh +} From c1139e4faf196d5fed6cb51a626c7102e427f340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 11:04:05 +0100 Subject: [PATCH 0783/1008] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp from 0.39.0 to 0.40.0 (#2635) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) from 0.39.0 to 0.40.0.
    Release notes

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's releases.

    Release v1.17.0/v0.40.0/v0.5.0

    Added

    • Export the ManualReader struct in go.opentelemetry.io/otel/sdk/metric. (#4244)
    • Export the PeriodicReader struct in go.opentelemetry.io/otel/sdk/metric. (#4244)
    • Add support for exponential histogram aggregations. A histogram can be configured as an exponential histogram using a view with "go.opentelemetry.io/otel/sdk/metric".ExponentialHistogram as the aggregation. (#4245)
    • Export the Exporter struct in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc. (#4272)
    • Export the Exporter struct in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#4272)
    • The exporters in go.opentelemetry.io/otel/exporters/otlp/otlpmetric now support the OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE environment variable. (#4287)
    • Add WithoutCounterSuffixes option in go.opentelemetry.io/otel/exporters/prometheus to disable addition of _total suffixes. (#4306)
    • Add info and debug logging to the metric SDK in go.opentelemetry.io/otel/sdk/metric. (#4315)
    • The go.opentelemetry.io/otel/semconv/v1.21.0 package. The package contains semantic conventions from the v1.21.0 version of the OpenTelemetry Semantic Conventions. (#4362)
    • Accept 201 to 299 HTTP status as success in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp and go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#4365)
    • Document the Temporality and Aggregation methods of the "go.opentelemetry.io/otel/sdk/metric".Exporter" need to be concurrent safe. (#4381)
    • Expand the set of units supported by the Prometheus exporter, and don't add unit suffixes if they are already present in go.opentelemetry.op/otel/exporters/prometheus (#4374)
    • Move the Aggregation interface and its implementations from go.opentelemetry.io/otel/sdk/metric/aggregation to go.opentelemetry.io/otel/sdk/metric. (#4435)
    • The exporters in go.opentelemetry.io/otel/exporters/otlp/otlpmetric now support the OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION environment variable. (#4437)
    • Add the NewAllowKeysFilter and NewDenyKeysFilter functions to go.opentelemetry.io/otel/attribute to allow convenient creation of allow-keys and deny-keys filters. (#4444)

    Changed

    • Starting from v1.21.0 of semantic conventions, go.opentelemetry.io/otel/semconv/{version}/httpconv and go.opentelemetry.io/otel/semconv/{version}/netconv packages will no longer be published. (#4145)
    • Log duplicate instrument conflict at a warning level instead of info in go.opentelemetry.io/otel/sdk/metric. (#4202)
    • Return an error on the creation of new instruments in go.opentelemetry.io/otel/sdk/metric if their name doesn't pass regexp validation. (#4210)
    • NewManualReader in go.opentelemetry.io/otel/sdk/metric returns *ManualReader instead of Reader. (#4244)
    • NewPeriodicReader in go.opentelemetry.io/otel/sdk/metric returns *PeriodicReader instead of Reader. (#4244)
    • Count the Collect time in the PeriodicReader timeout in go.opentelemetry.io/otel/sdk/metric. (#4221)
    • The function New in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc returns *Exporter instead of "go.opentelemetry.io/otel/sdk/metric".Exporter. (#4272)
    • The function New in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp returns *Exporter instead of "go.opentelemetry.io/otel/sdk/metric".Exporter. (#4272)
    • If an attribute set is omitted from an async callback, the previous value will no longer be exported in go.opentelemetry.io/otel/sdk/metric. (#4290)
    • If an attribute set is observed multiple times in an async callback in go.opentelemetry.io/otel/sdk/metric, the values will be summed instead of the last observation winning. (#4289)
    • Allow the explicit bucket histogram aggregation to be used for the up-down counter, observable counter, observable up-down counter, and observable gauge in the go.opentelemetry.io/otel/sdk/metric package. (#4332)
    • Restrict Meters in go.opentelemetry.io/otel/sdk/metric to only register and collect instruments it created. (#4333)
    • PeriodicReader.Shutdown and PeriodicReader.ForceFlush in go.opentelemetry.io/otel/sdk/metric now apply the periodic reader's timeout to the operation if the user provided context does not contain a deadline. (#4356, #4377)
    • Upgrade all use of go.opentelemetry.io/otel/semconv to use v1.21.0. (#4408)
    • Increase instrument name maximum length from 63 to 255 characters in go.opentelemetry.io/otel/sdk/metric. (#4434)
    • Add go.opentelemetry.op/otel/sdk/metric.WithProducer as an Option for "go.opentelemetry.io/otel/sdk/metric".NewManualReader and "go.opentelemetry.io/otel/sdk/metric".NewPeriodicReader. (#4346)

    Removed

    • Remove Reader.RegisterProducer in go.opentelemetry.io/otel/metric. Use the added WithProducer option instead. (#4346)
    • Remove Reader.ForceFlush in go.opentelemetry.io/otel/metric. Notice that PeriodicReader.ForceFlush is still available. (#4375)

    Fixed

    • Correctly format log messages from the go.opentelemetry.io/otel/exporters/zipkin exporter. (#4143)

    ... (truncated)

    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's changelog.

    [1.17.0/0.40.0/0.0.5] 2023-08-28

    Added

    • Export the ManualReader struct in go.opentelemetry.io/otel/sdk/metric. (#4244)
    • Export the PeriodicReader struct in go.opentelemetry.io/otel/sdk/metric. (#4244)
    • Add support for exponential histogram aggregations. A histogram can be configured as an exponential histogram using a view with "go.opentelemetry.io/otel/sdk/metric".ExponentialHistogram as the aggregation. (#4245)
    • Export the Exporter struct in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc. (#4272)
    • Export the Exporter struct in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#4272)
    • The exporters in go.opentelemetry.io/otel/exporters/otlp/otlpmetric now support the OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE environment variable. (#4287)
    • Add WithoutCounterSuffixes option in go.opentelemetry.io/otel/exporters/prometheus to disable addition of _total suffixes. (#4306)
    • Add info and debug logging to the metric SDK in go.opentelemetry.io/otel/sdk/metric. (#4315)
    • The go.opentelemetry.io/otel/semconv/v1.21.0 package. The package contains semantic conventions from the v1.21.0 version of the OpenTelemetry Semantic Conventions. (#4362)
    • Accept 201 to 299 HTTP status as success in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp and go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#4365)
    • Document the Temporality and Aggregation methods of the "go.opentelemetry.io/otel/sdk/metric".Exporter" need to be concurrent safe. (#4381)
    • Expand the set of units supported by the Prometheus exporter, and don't add unit suffixes if they are already present in go.opentelemetry.op/otel/exporters/prometheus (#4374)
    • Move the Aggregation interface and its implementations from go.opentelemetry.io/otel/sdk/metric/aggregation to go.opentelemetry.io/otel/sdk/metric. (#4435)
    • The exporters in go.opentelemetry.io/otel/exporters/otlp/otlpmetric now support the OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION environment variable. (#4437)
    • Add the NewAllowKeysFilter and NewDenyKeysFilter functions to go.opentelemetry.io/otel/attribute to allow convenient creation of allow-keys and deny-keys filters. (#4444)
    • Support Go 1.21. (#4463)

    Changed

    • Starting from v1.21.0 of semantic conventions, go.opentelemetry.io/otel/semconv/{version}/httpconv and go.opentelemetry.io/otel/semconv/{version}/netconv packages will no longer be published. (#4145)
    • Log duplicate instrument conflict at a warning level instead of info in go.opentelemetry.io/otel/sdk/metric. (#4202)
    • Return an error on the creation of new instruments in go.opentelemetry.io/otel/sdk/metric if their name doesn't pass regexp validation. (#4210)
    • NewManualReader in go.opentelemetry.io/otel/sdk/metric returns *ManualReader instead of Reader. (#4244)
    • NewPeriodicReader in go.opentelemetry.io/otel/sdk/metric returns *PeriodicReader instead of Reader. (#4244)
    • Count the Collect time in the PeriodicReader timeout in go.opentelemetry.io/otel/sdk/metric. (#4221)
    • The function New in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc returns *Exporter instead of "go.opentelemetry.io/otel/sdk/metric".Exporter. (#4272)
    • The function New in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp returns *Exporter instead of "go.opentelemetry.io/otel/sdk/metric".Exporter. (#4272)
    • If an attribute set is omitted from an async callback, the previous value will no longer be exported in go.opentelemetry.io/otel/sdk/metric. (#4290)
    • If an attribute set is observed multiple times in an async callback in go.opentelemetry.io/otel/sdk/metric, the values will be summed instead of the last observation winning. (#4289)
    • Allow the explicit bucket histogram aggregation to be used for the up-down counter, observable counter, observable up-down counter, and observable gauge in the go.opentelemetry.io/otel/sdk/metric package. (#4332)
    • Restrict Meters in go.opentelemetry.io/otel/sdk/metric to only register and collect instruments it created. (#4333)
    • PeriodicReader.Shutdown and PeriodicReader.ForceFlush in go.opentelemetry.io/otel/sdk/metric now apply the periodic reader's timeout to the operation if the user provided context does not contain a deadline. (#4356, #4377)
    • Upgrade all use of go.opentelemetry.io/otel/semconv to use v1.21.0. (#4408)
    • Increase instrument name maximum length from 63 to 255 characters in go.opentelemetry.io/otel/sdk/metric. (#4434)
    • Add go.opentelemetry.op/otel/sdk/metric.WithProducer as an Option for "go.opentelemetry.io/otel/sdk/metric".NewManualReader and "go.opentelemetry.io/otel/sdk/metric".NewPeriodicReader. (#4346)

    Removed

    • Remove Reader.RegisterProducer in go.opentelemetry.io/otel/metric. Use the added WithProducer option instead. (#4346)
    • Remove Reader.ForceFlush in go.opentelemetry.io/otel/metric. Notice that PeriodicReader.ForceFlush is still available. (#4375)

    Fixed

    ... (truncated)

    Commits
    • 3c476ce Release v1.17.0/v0.40.0/v0.0.5 (#4464)
    • 183e081 Bump github.com/golangci/golangci-lint in /internal/tools (#4465)
    • 6be116e Add testing support for Go 1.21 (#4463)
    • 69611bd Switch Stream back to having an AttributeFilter field and add `New*Filter...
    • f15ae16 Fix the broken sentence (#4456)
    • 16ce491 Fix guard of measured value to not record empty (#4452)
    • 9b47674 Make getBin and scaleChange methods of expoHistogramDataPoint (#4451)
    • 9d9b71f Remove the expoHistogramValues type (#4450)
    • a5ff7af Ignore +/- Inf and NaN for exponential histogram measurement (#4446)
    • d78820e Deprecate exporters/jaeger (#4423)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp&package-manager=go_modules&previous-version=0.39.0&new-version=0.40.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Co-authored-by: ramin --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 8c1fbb1349..470dde9bed 100644 --- a/go.mod +++ b/go.mod @@ -61,14 +61,14 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 - go.opentelemetry.io/otel v1.16.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 + go.opentelemetry.io/otel v1.17.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 - go.opentelemetry.io/otel/metric v1.16.0 - go.opentelemetry.io/otel/sdk v1.16.0 - go.opentelemetry.io/otel/sdk/metric v0.39.0 - go.opentelemetry.io/otel/trace v1.16.0 + go.opentelemetry.io/otel/metric v1.17.0 + go.opentelemetry.io/otel/sdk v1.17.0 + go.opentelemetry.io/otel/sdk/metric v0.40.0 + go.opentelemetry.io/otel/trace v1.17.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 go.uber.org/zap v1.25.0 @@ -308,8 +308,8 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect diff --git a/go.sum b/go.sum index d738f67e10..3835cf9304 100644 --- a/go.sum +++ b/go.sum @@ -2358,14 +2358,14 @@ go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzox go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 h1:f6BwB2OACc3FCbYVznctQ9V6KK7Vq6CjmYXJ7DeSs4E= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0/go.mod h1:UqL5mZ3qs6XYhDnZaW1Ps4upD+PX6LipH40AoeuIlwU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 h1:IZXpCEtI7BbX01DRQEWTGDkvjMB6hEhiEZXS+eg2YqY= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0/go.mod h1:xY111jIZtWb+pUUgT4UiiSonAaY2cD2Ts5zvuKLki3o= +go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= +go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 h1:eU0ffpYuEY7eQ75K+nKr9CI5KcY8h+GPk/9DDlEO1NI= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0/go.mod h1:9P5RK5JS2sjKepuCkqFwPp3etwV/57E0eigLw18Mn1k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 h1:MZbjiZeMmn5wFMORhozpouGKDxj9POHTuU5UA8msBQk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0/go.mod h1:C7tOYVCJmrDTCwxNny0MuUtnDIR3032vFHYke0F2ZrU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 h1:SZaSbubADNhH2Gxm+1GaZ/cFsGiYefZoodMMX79AOd4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0/go.mod h1:N65FzQDfQH7NY7umgb0U+7ypGKVYKwwE24L6KXT4OA8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8= @@ -2373,20 +2373,20 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= +go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= -go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= -go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= -go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= +go.opentelemetry.io/otel/sdk v1.17.0 h1:FLN2X66Ke/k5Sg3V623Q7h7nt3cHXaW1FOvKKrW0IpE= +go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= +go.opentelemetry.io/otel/sdk/metric v0.40.0 h1:qOM29YaGcxipWjL5FzpyZDpCYrDREvX0mVlmXdOjCHU= +go.opentelemetry.io/otel/sdk/metric v0.40.0/go.mod h1:dWxHtdzdJvg+ciJUKLTKwrMe5P6Dv3FyDbh8UkfgkVs= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= +go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= From 1751d62deeb750c13f7085e76374b5af1796318f Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 5 Sep 2023 12:19:09 +0200 Subject: [PATCH 0784/1008] chore(deps): migrate to boxo (#2595) Thanks to https://github.com/ipfs/boxo/pull/407, we can now fully migrate to boxo and get rid of the oldest `replace` directive in our go.mod for `verifcid`. TODO: Issue about go-car/v2 migration --------- Co-authored-by: Ryan --- Makefile | 5 +++- blob/service_test.go | 7 ++--- das/daser_test.go | 10 +++---- go.mod | 11 +++---- go.sum | 15 +++++----- header/headertest/fraud/testing.go | 5 ++-- nodebuilder/node.go | 2 +- nodebuilder/p2p/ipld.go | 12 -------- nodebuilder/p2p/module.go | 3 +- nodebuilder/share/constructors.go | 2 +- share/availability/cache/availability_test.go | 4 +-- share/availability/cache/testing.go | 6 ++-- share/availability/full/testing.go | 4 +-- share/availability/light/testing.go | 8 ++--- share/availability/test/testing.go | 4 +-- share/eds/adapters.go | 2 +- share/eds/byzantine/bad_encoding_test.go | 5 ++-- share/eds/byzantine/byzantine.go | 2 +- share/eds/byzantine/share_proof.go | 2 +- share/eds/byzantine/share_proof_test.go | 5 ++-- share/eds/retriever.go | 2 +- share/eds/retriever_test.go | 15 +++++----- share/getters/getter_test.go | 6 ++-- share/getters/ipld.go | 2 +- share/getters/utils.go | 2 +- share/ipld/add.go | 5 ++-- share/ipld/blockserv.go | 30 +++++++++++++++++++ share/ipld/get.go | 2 +- share/ipld/get_shares.go | 2 +- share/ipld/get_shares_test.go | 25 +++++++--------- share/ipld/namespace_data.go | 2 +- share/ipld/nmt.go | 2 +- share/ipld/nmt_adder.go | 4 +-- 33 files changed, 111 insertions(+), 102 deletions(-) delete mode 100644 nodebuilder/p2p/ipld.go create mode 100644 share/ipld/blockserv.go diff --git a/Makefile b/Makefile index d4b3d2572e..8813b65d23 100644 --- a/Makefile +++ b/Makefile @@ -169,7 +169,10 @@ lint-imports: ## sort-imports: Sort Go imports. sort-imports: - @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/"$(PROJECTNAME)"" -output stdout . + @for file in `find . -type f -name '*.go'`; \ + do goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/"$(PROJECTNAME)"" $$file \ + || exit 1; \ + done; .PHONY: sort-imports ## adr-gen: Generate ADR from template. Must set NUM and TITLE parameters. diff --git a/blob/service_test.go b/blob/service_test.go index a45fe631af..64296b969c 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -10,7 +10,6 @@ import ( ds "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -324,7 +323,7 @@ func TestService_GetSingleBlobWithoutPadding(t *testing.T) { rawShares = append(rawShares, append(rawShares0, padding0.ToBytes())...) rawShares = append(rawShares, append(rawShares1, padding1.ToBytes())...) - bs := mdutils.Bserv() + bs := ipld.NewMemBlockservice() batching := ds_sync.MutexWrap(ds.NewMapDatastore()) headerStore, err := store.NewStore[*header.ExtendedHeader](batching) require.NoError(t, err) @@ -375,7 +374,7 @@ func TestService_GetAllWithoutPadding(t *testing.T) { rawShares = append(rawShares, append(rawShares0, padding0.ToBytes())...) } - bs := mdutils.Bserv() + bs := ipld.NewMemBlockservice() batching := ds_sync.MutexWrap(ds.NewMapDatastore()) headerStore, err := store.NewStore[*header.ExtendedHeader](batching) require.NoError(t, err) @@ -397,7 +396,7 @@ func TestService_GetAllWithoutPadding(t *testing.T) { } func createService(ctx context.Context, t *testing.T, blobs []*Blob) *Service { - bs := mdutils.Bserv() + bs := ipld.NewMemBlockservice() batching := ds_sync.MutexWrap(ds.NewMapDatastore()) headerStore, err := store.NewStore[*header.ExtendedHeader](batching) require.NoError(t, err) diff --git a/das/daser_test.go b/das/daser_test.go index e4e74dc7ff..3e5bce0b97 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -6,10 +6,9 @@ import ( "time" "github.com/golang/mock/gomock" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" pubsub "github.com/libp2p/go-libp2p-pubsub" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/assert" @@ -31,6 +30,7 @@ import ( availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" ) var timeout = time.Second * 15 @@ -39,7 +39,7 @@ var timeout = time.Second * 15 // the DASer checkpoint is updated to network head. func TestDASerLifecycle(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() avail := light.TestAvailability(getters.NewIPLDGetter(bServ)) // 15 headers from the past and 15 future headers mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) @@ -79,7 +79,7 @@ func TestDASerLifecycle(t *testing.T) { func TestDASer_Restart(t *testing.T) { ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() avail := light.TestAvailability(getters.NewIPLDGetter(bServ)) // 15 headers from the past and 15 future headers mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) @@ -147,7 +147,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { t.Cleanup(cancel) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() // create mock network net, err := mocknet.FullMeshLinked(1) require.NoError(t, err) diff --git a/go.mod b/go.mod index 470dde9bed..ab1c199e97 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.21 toolchain go1.21.0 -replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch - require ( cosmossdk.io/errors v1.0.0 cosmossdk.io/math v1.1.2 @@ -32,15 +30,13 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/golang-lru v1.0.2 github.com/imdario/mergo v0.3.16 - github.com/ipfs/boxo v0.11.0 - github.com/ipfs/go-block-format v0.2.0 - github.com/ipfs/go-blockservice v0.5.1 // down 1 version, 0.5.2 is marked as deprecated and raises alerts + github.com/ipfs/boxo v0.12.0 + github.com/ipfs/go-block-format v0.1.2 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ipld-cbor v0.1.0 github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 - github.com/ipfs/go-merkledag v0.11.0 github.com/ipld/go-car v0.6.2 github.com/libp2p/go-libp2p v0.30.0 github.com/libp2p/go-libp2p-kad-dht v0.25.0 @@ -200,15 +196,16 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.12.2 // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/ipfs/bbloom v0.0.4 // indirect + github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.0 github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect - github.com/ipfs/go-ipfs-exchange-offline v0.3.0 // indirect github.com/ipfs/go-ipfs-pq v0.0.3 // indirect github.com/ipfs/go-ipfs-util v0.0.3 // indirect github.com/ipfs/go-ipld-legacy v0.2.1 // indirect github.com/ipfs/go-log v1.0.5 // indirect + github.com/ipfs/go-merkledag v0.11.0 // indirect github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-peertaskqueue v0.8.1 // indirect github.com/ipfs/go-verifcid v0.0.2 // indirect diff --git a/go.sum b/go.sum index 3835cf9304..267e05154a 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,6 @@ github.com/celestiaorg/go-header v0.3.0 h1:9fhxSgldPiWWq3yd9u7oSk5vYqaLV1JkeTnJd github.com/celestiaorg/go-header v0.3.0/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= -github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= -github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= github.com/celestiaorg/nmt v0.19.0 h1:9VXFeI/gt+q8h5HeCE0RjXJhOxsFzxJUjHrkvF9CMYE= @@ -1023,8 +1021,8 @@ github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1: github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.11.0 h1:urMxhZ3xoF4HssJVD3+0ssGT9pptEfHfbL8DYdoWFlg= -github.com/ipfs/boxo v0.11.0/go.mod h1:8IfDmp+FzFGcF4zjAgHMVPpwYw4AjN9ePEzDfkaYJ1w= +github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ= +github.com/ipfs/boxo v0.12.0/go.mod h1:xAnfiU6PtxWCnRqu7dcXQ10bB5/kvI1kXRotuGqGBhg= github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= @@ -1040,16 +1038,14 @@ github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/ github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= +github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= github.com/ipfs/go-block-format v0.1.2/go.mod h1:mACVcrxarQKstUU3Yf/RdwbC4DzPV6++rO2a3d+a/KE= -github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= -github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= +github.com/ipfs/go-blockservice v0.5.0 h1:B2mwhhhVQl2ntW2EIpaWPwSCxSuqr5fFA93Ms4bYLEY= github.com/ipfs/go-blockservice v0.5.0/go.mod h1:W6brZ5k20AehbmERplmERn8o2Ni3ZZubvAxaIUeaT6w= -github.com/ipfs/go-blockservice v0.5.1 h1:9pAtkyKAz/skdHTh0kH8VulzWp+qmSDD0aI17TYP/s0= -github.com/ipfs/go-blockservice v0.5.1/go.mod h1:VpMblFEqG67A/H2sHKAemeH9vlURVavlysbdUI632yk= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -1219,6 +1215,9 @@ github.com/ipfs/go-unixfsnode v1.5.2/go.mod h1:NlOebRwYx8lMCNMdhAhEspYPBD3obp7TE github.com/ipfs/go-unixfsnode v1.7.1/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk= github.com/ipfs/go-unixfsnode v1.7.4 h1:iLvKyAVKUYOIAW2t4kDYqsT7VLGj31eXJE2aeqGfbwA= github.com/ipfs/go-unixfsnode v1.7.4/go.mod h1:PVfoyZkX1B34qzT3vJO4nsLUpRCyhnMuHBznRcXirlk= +github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= +github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= +github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= github.com/ipfs/interface-go-ipfs-core v0.9.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0= github.com/ipfs/interface-go-ipfs-core v0.10.0/go.mod h1:F3EcmDy53GFkF0H3iEJpfJC320fZ/4G60eftnItrrJ0= github.com/ipld/go-car v0.5.0/go.mod h1:ppiN5GWpjOZU9PgpAZ9HbZd9ZgSpwPMr48fGRJOWmvE= diff --git a/header/headertest/fraud/testing.go b/header/headertest/fraud/testing.go index 6a5cda733d..e2ff13a4e0 100644 --- a/header/headertest/fraud/testing.go +++ b/header/headertest/fraud/testing.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -46,7 +46,8 @@ func NewFraudMaker(t *testing.T, height int64, vals []types.PrivValidator, valSe } func (f *FraudMaker) MakeExtendedHeader(odsSize int, edsStore *eds.Store) header.ConstructFn { - return func(h *types.Header, + return func( + h *types.Header, comm *types.Commit, vals *types.ValidatorSet, eds *rsmt2d.ExtendedDataSquare, diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 6d83e6c4c3..58a182276c 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -6,8 +6,8 @@ import ( "fmt" "strings" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/boxo/exchange" - "github.com/ipfs/go-blockservice" logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" diff --git a/nodebuilder/p2p/ipld.go b/nodebuilder/p2p/ipld.go deleted file mode 100644 index ad32cc39f5..0000000000 --- a/nodebuilder/p2p/ipld.go +++ /dev/null @@ -1,12 +0,0 @@ -package p2p - -import ( - "github.com/ipfs/boxo/blockstore" - "github.com/ipfs/boxo/exchange" - "github.com/ipfs/go-blockservice" -) - -// blockService constructs IPFS's BlockService for fetching arbitrary Merkle structures. -func blockService(bs blockstore.Blockstore, ex exchange.Interface) blockservice.BlockService { - return blockservice.New(bs, ex) -} diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index e7f60956c7..1ddff02173 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -6,6 +6,7 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/share/ipld" ) var log = logging.Logger("module/p2p") @@ -27,7 +28,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Provide(routedHost), fx.Provide(pubSub), fx.Provide(dataExchange), - fx.Provide(blockService), + fx.Provide(ipld.NewBlockservice), fx.Provide(peerRouting), fx.Provide(contentRouting), fx.Provide(addrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index b962038d17..0e8d17f208 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -5,7 +5,7 @@ import ( "errors" "github.com/filecoin-project/dagstore" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index 03df47f848..ccc11a1d28 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -9,7 +9,6 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,6 +16,7 @@ import ( "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/ipld" ) // TestCacheAvailability tests to ensure that the successful result of a @@ -86,7 +86,7 @@ func TestCacheAvailability_NoDuplicateSampling(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // create root to cache - root := availability_test.RandFillBS(t, 16, mdutils.Bserv()) + root := availability_test.RandFillBS(t, 16, ipld.NewMemBlockservice()) // wrap dummyAvailability with a datastore ds := sync.MutexWrap(datastore.NewMapDatastore()) ca := NewShareAvailability(&dummyAvailability{counter: 0}, ds) diff --git a/share/availability/cache/testing.go b/share/availability/cache/testing.go index 978d51b6b5..00a1520114 100644 --- a/share/availability/cache/testing.go +++ b/share/availability/cache/testing.go @@ -5,18 +5,18 @@ import ( ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" "github.com/celestiaorg/celestia-node/share/availability/light" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" ) // LightAvailabilityWithLocalRandSquare wraps light.GetterWithRandSquare with cache availability func LightAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availability, *share.Root) { - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() store := dssync.MutexWrap(ds.NewMapDatastore()) getter := getters.NewIPLDGetter(bServ) avail := NewShareAvailability( @@ -28,7 +28,7 @@ func LightAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availabili // FullAvailabilityWithLocalRandSquare wraps full.GetterWithRandSquare with cache availability func FullAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availability, *share.Root) { - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() store := dssync.MutexWrap(ds.NewMapDatastore()) getter := getters.NewIPLDGetter(bServ) avail := NewShareAvailability( diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index 680eb7abaf..4638fde1bc 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -4,20 +4,20 @@ import ( "testing" "time" - mdutils "github.com/ipfs/go-merkledag/test" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/p2p/discovery/routing" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p/discovery" ) // GetterWithRandSquare provides a share.Getter filled with 'n' NMT // trees of 'n' random shares, essentially storing a whole square. func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.Root) { - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() getter := getters.NewIPLDGetter(bServ) return getter, availability_test.RandFillBS(t, n, bServ) } diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go index 59163d6356..991d143a62 100644 --- a/share/availability/light/testing.go +++ b/share/availability/light/testing.go @@ -3,18 +3,18 @@ package light import ( "testing" - "github.com/ipfs/go-blockservice" - mdutils "github.com/ipfs/go-merkledag/test" + "github.com/ipfs/boxo/blockservice" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" + "github.com/celestiaorg/celestia-node/share/ipld" ) // GetterWithRandSquare provides a share.Getter filled with 'n' NMT trees of 'n' random shares, // essentially storing a whole square. func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.Root) { - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() getter := getters.NewIPLDGetter(bServ) return getter, availability_test.RandFillBS(t, n, bServ) } @@ -22,7 +22,7 @@ func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.Root) { // EmptyGetter provides an unfilled share.Getter with corresponding blockservice.BlockService than // can be filled by the test. func EmptyGetter() (share.Getter, blockservice.BlockService) { - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() getter := getters.NewIPLDGetter(bServ) return getter, bServ } diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 8977678198..8973ee8cb1 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -6,9 +6,9 @@ import ( "github.com/ipfs/boxo/bitswap" "github.com/ipfs/boxo/bitswap/network" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/routing/offline" - "github.com/ipfs/go-blockservice" ds "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" record "github.com/libp2p/go-libp2p-record" @@ -94,7 +94,7 @@ func (dn *TestDagNet) NewTestNodeWithBlockstore(dstore ds.Datastore, bstore bloc ) nd := &TestNode{ net: dn, - BlockService: blockservice.New(bstore, bs), + BlockService: ipld.NewBlockservice(bstore, bs), Host: hst, } dn.nodes = append(dn.nodes, nd) diff --git a/share/eds/adapters.go b/share/eds/adapters.go index fe498400b3..8bf2340d91 100644 --- a/share/eds/adapters.go +++ b/share/eds/adapters.go @@ -5,8 +5,8 @@ import ( "sync" "github.com/filecoin-project/dagstore" + "github.com/ipfs/boxo/blockservice" blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ) diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index b5dcea3452..9d16955ee3 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" core "github.com/tendermint/tendermint/types" @@ -22,7 +21,7 @@ import ( func TestBadEncodingFraudProof(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() square := edstest.RandByzantineEDS(t, 16) dah, err := da.NewDataAvailabilityHeader(square) @@ -48,7 +47,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() squareSize := 8 shares := sharetest.RandShares(t, squareSize*squareSize) diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index 0fcd78273e..dfdf681f04 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "golang.org/x/sync/errgroup" "github.com/celestiaorg/celestia-app/pkg/da" diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 411fd85818..98b58ebbec 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -4,7 +4,7 @@ import ( "context" "crypto/sha256" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go index db1db64f80..a9021d806d 100644 --- a/share/eds/byzantine/share_proof_test.go +++ b/share/eds/byzantine/share_proof_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/ipfs/go-cid" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" @@ -21,7 +20,7 @@ func TestGetProof(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel() - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() shares := sharetest.RandShares(t, width*width) in, err := ipld.AddShares(ctx, shares, bServ) @@ -58,7 +57,7 @@ func TestGetProofs(t *testing.T) { const width = 4 ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel() - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() shares := sharetest.RandShares(t, width*width) in, err := ipld.AddShares(ctx, shares, bServ) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index e7837ae23a..c2966c3953 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "go.opentelemetry.io/otel" diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 12b1c11083..f3f7ccca64 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -7,8 +7,7 @@ import ( "testing" "time" - "github.com/ipfs/go-blockservice" - mdutils "github.com/ipfs/go-merkledag/test" + "github.com/ipfs/boxo/blockservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +29,7 @@ func TestRetriever_Retrieve(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() r := NewRetriever(bServ) type test struct { @@ -73,7 +72,7 @@ func TestRetriever_ByzantineError(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - bserv := mdutils.Bserv() + bserv := ipld.NewMemBlockservice() shares := edstest.RandEDS(t, width).Flattened() _, err := ipld.ImportShares(ctx, shares, bserv) require.NoError(t, err) @@ -110,7 +109,7 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() r := NewRetriever(bServ) // generate EDS @@ -136,7 +135,7 @@ func TestRetriever_MultipleRandQuadrants(t *testing.T) { func TestFraudProofValidation(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() odsSize := []int{2, 4, 16, 32, 64, 128} for _, size := range odsSize { @@ -178,7 +177,7 @@ BenchmarkBEFPValidation/ods_size:128 259 4934375 ns/op 5418406 B func BenchmarkBEFPValidation(b *testing.B) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer b.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() r := NewRetriever(bServ) t := &testing.T{} odsSize := []int{2, 4, 16, 32, 64, 128} @@ -217,7 +216,7 @@ func BenchmarkNewErrByzantineData(b *testing.B) { odsSize := []int{2, 4, 16, 32, 64, 128} ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() r := NewRetriever(bServ) t := &testing.T{} for _, size := range odsSize { diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index d19d0cf174..ddab46f290 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -7,10 +7,8 @@ import ( "time" "github.com/ipfs/boxo/exchange/offline" - bsrv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -37,7 +35,7 @@ func TestTeeGetter(t *testing.T) { err = edsStore.Start(ctx) require.NoError(t, err) - bServ := mdutils.Bserv() + bServ := ipld.NewMemBlockservice() ig := NewIPLDGetter(bServ) tg := NewTeeGetter(ig, edsStore) @@ -197,7 +195,7 @@ func TestIPLDGetter(t *testing.T) { require.NoError(t, err) bStore := edsStore.Blockstore() - bserv := bsrv.New(bStore, offline.Exchange(bStore)) + bserv := ipld.NewBlockservice(bStore, offline.Exchange(edsStore.Blockstore())) sg := NewIPLDGetter(bserv) t.Run("GetShare", func(t *testing.T) { diff --git a/share/getters/ipld.go b/share/getters/ipld.go index 8e11a389bd..4024a67f5b 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -7,7 +7,7 @@ import ( "sync" "sync/atomic" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" diff --git a/share/getters/utils.go b/share/getters/utils.go index 5305c5c737..e3f24b3857 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "go.opentelemetry.io/otel" diff --git a/share/ipld/add.go b/share/ipld/add.go index 7e5909669d..fbb743148c 100644 --- a/share/ipld/add.go +++ b/share/ipld/add.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" @@ -52,7 +52,8 @@ func AddShares( func ImportShares( ctx context.Context, shares [][]byte, - adder blockservice.BlockService) (*rsmt2d.ExtendedDataSquare, error) { + adder blockservice.BlockService, +) (*rsmt2d.ExtendedDataSquare, error) { if len(shares) == 0 { return nil, fmt.Errorf("ipld: importing empty data") } diff --git a/share/ipld/blockserv.go b/share/ipld/blockserv.go new file mode 100644 index 0000000000..2ed2a21c77 --- /dev/null +++ b/share/ipld/blockserv.go @@ -0,0 +1,30 @@ +package ipld + +import ( + "github.com/ipfs/boxo/blockservice" + "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/exchange" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" +) + +// NewBlockservice constructs Blockservice for fetching NMTrees. +func NewBlockservice(bs blockstore.Blockstore, exchange exchange.Interface) blockservice.BlockService { + return blockservice.New(bs, exchange, blockservice.WithAllowlist(defaultAllowlist)) +} + +// NewMemBlockservice constructs Blockservice for fetching NMTrees with in-memory blockstore. +func NewMemBlockservice() blockservice.BlockService { + bstore := blockstore.NewBlockstore(sync.MutexWrap(datastore.NewMapDatastore())) + return NewBlockservice(bstore, nil) +} + +// defaultAllowlist keeps default list of hashes allowed in the network. +var defaultAllowlist allowlist + +type allowlist struct{} + +func (a allowlist) IsAllowed(code uint64) bool { + // we allow all codes except home-baked sha256NamespaceFlagged + return code == sha256NamespaceFlagged +} diff --git a/share/ipld/get.go b/share/ipld/get.go index 35f601853d..f263877dc0 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "github.com/gammazero/workerpool" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" "go.opentelemetry.io/otel/attribute" diff --git a/share/ipld/get_shares.go b/share/ipld/get_shares.go index 0bed240fdc..3fda86941f 100644 --- a/share/ipld/get_shares.go +++ b/share/ipld/get_shares.go @@ -3,7 +3,7 @@ package ipld import ( "context" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" diff --git a/share/ipld/get_shares_test.go b/share/ipld/get_shares_test.go index dd6c3a6636..580efcb69b 100644 --- a/share/ipld/get_shares_test.go +++ b/share/ipld/get_shares_test.go @@ -11,13 +11,8 @@ import ( "testing" "time" - "github.com/ipfs/boxo/blockstore" - "github.com/ipfs/boxo/exchange/offline" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" - ds "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,7 +30,7 @@ func TestGetShare(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - bServ := mdutils.Bserv() + bServ := NewMemBlockservice() // generate random shares for the nmt shares := sharetest.RandShares(t, size*size) @@ -155,7 +150,7 @@ func removeRandShares(data [][]byte, d int) [][]byte { func TestGetSharesByNamespace(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := NewMemBlockservice() var tests = []struct { rawData []share.Share @@ -201,7 +196,7 @@ func TestGetSharesByNamespace(t *testing.T) { func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := NewMemBlockservice() shares := sharetest.RandShares(t, 16) @@ -245,7 +240,7 @@ func TestCollectLeavesByNamespace_IncompleteData(t *testing.T) { func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := NewMemBlockservice() shares := sharetest.RandShares(t, 1024) @@ -290,7 +285,7 @@ func TestCollectLeavesByNamespace_AbsentNamespaceId(t *testing.T) { func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := NewMemBlockservice() shares := sharetest.RandShares(t, 16) @@ -332,7 +327,7 @@ func TestCollectLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testi func TestGetSharesWithProofsByNamespace(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) - bServ := mdutils.Bserv() + bServ := NewMemBlockservice() var tests = []struct { rawData []share.Share @@ -431,13 +426,13 @@ func TestBatchSize(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(tt.origWidth)) defer cancel() - bs := blockstore.NewBlockstore(dssync.MutexWrap(ds.NewMapDatastore())) + bs := NewMemBlockservice() randEds := edstest.RandEDS(t, tt.origWidth) - _, err := AddShares(ctx, randEds.FlattenedODS(), blockservice.New(bs, offline.Exchange(bs))) + _, err := AddShares(ctx, randEds.FlattenedODS(), bs) require.NoError(t, err) - out, err := bs.AllKeysChan(ctx) + out, err := bs.Blockstore().AllKeysChan(ctx) require.NoError(t, err) var count int diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index 38dfdb2169..a3f468ee47 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -7,7 +7,7 @@ import ( "sync" "sync/atomic" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" "go.opentelemetry.io/otel/attribute" diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index e7d6b4b513..b43d786a5f 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -8,8 +8,8 @@ import ( "hash" "math/rand" + "github.com/ipfs/boxo/blockservice" blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" diff --git a/share/ipld/nmt_adder.go b/share/ipld/nmt_adder.go index d090c679d9..7ce52859b2 100644 --- a/share/ipld/nmt_adder.go +++ b/share/ipld/nmt_adder.go @@ -5,10 +5,10 @@ import ( "fmt" "sync" - "github.com/ipfs/go-blockservice" + "github.com/ipfs/boxo/blockservice" + "github.com/ipfs/boxo/ipld/merkledag" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - "github.com/ipfs/go-merkledag" "github.com/celestiaorg/nmt" ) From 34bc95f4bc7c8c7e7265a05a74c1a5878cd72297 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:52:59 +0000 Subject: [PATCH 0785/1008] chore(deps): bump github.com/ipfs/go-ipfs-ds-help from 1.1.0 to 1.1.1 (#2624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/go-ipfs-ds-help](https://github.com/ipfs/go-ipfs-ds-help) from 1.1.0 to 1.1.1.
    Release notes

    Sourced from github.com/ipfs/go-ipfs-ds-help's releases.

    v1.1.1

    What's Changed

    Full Changelog: https://github.com/ipfs/go-ipfs-ds-help/compare/v1.1.0...v1.1.1

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/go-ipfs-ds-help&package-manager=go_modules&previous-version=1.1.0&new-version=1.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 3 ++- share/eds/blockstore.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ab1c199e97..e8b8fb5795 100644 --- a/go.mod +++ b/go.mod @@ -199,7 +199,7 @@ require ( github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect - github.com/ipfs/go-ipfs-ds-help v1.1.0 + github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect github.com/ipfs/go-ipfs-exchange-interface v0.2.1 // indirect github.com/ipfs/go-ipfs-pq v0.0.3 // indirect github.com/ipfs/go-ipfs-util v0.0.3 // indirect diff --git a/go.sum b/go.sum index 267e05154a..8360dee057 100644 --- a/go.sum +++ b/go.sum @@ -1109,8 +1109,9 @@ github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1I github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v0.0.1/go.mod h1:gtP9xRaZXqIQRh1HRpp595KbBEdgqWFxefeVKOV8sxo= github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= -github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= +github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= +github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM= github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 22ef28f821..2abe4219f1 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -7,11 +7,11 @@ import ( "github.com/filecoin-project/dagstore" bstore "github.com/ipfs/boxo/blockstore" + dshelp "github.com/ipfs/boxo/datastore/dshelp" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" - dshelp "github.com/ipfs/go-ipfs-ds-help" ipld "github.com/ipfs/go-ipld-format" ) From 96388ea79a5f738a1fe633e40c7a412aadcfd07e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:38:01 +0200 Subject: [PATCH 0786/1008] chore(go.mod): bump go-header (#2657) Bumps go-header. Resolves https://github.com/celestiaorg/go-header/issues/102 which was blocking sync for arabica-10 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e8b8fb5795..c978655477 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/celestiaorg/celestia-app v1.0.0-rc13 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.3.0 + github.com/celestiaorg/go-header v0.3.1 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.19.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 8360dee057..5c4e483b6f 100644 --- a/go.sum +++ b/go.sum @@ -368,8 +368,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.3.0 h1:9fhxSgldPiWWq3yd9u7oSk5vYqaLV1JkeTnJdGcisFo= -github.com/celestiaorg/go-header v0.3.0/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.3.1 h1:rJuFPVMoI20Du4KHWLz0IMllEIp0XgIWi9lHQLPWmq8= +github.com/celestiaorg/go-header v0.3.1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= From c146a0a7a47b7abe02746ef583bc31ad52d130f5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:07:32 +0200 Subject: [PATCH 0787/1008] feat(nodebuilder/header): Bootstrap from previously seen peers (#2507) Provides `PIDStore` to header module so that it can be used in `peerTracker` and replaces mem `peerstore.Peerstore` with on-disk `peerstore.Peerstore` so that `peerTracker` can quickly bootstrap itself with previously-seen peers and allow syncer to initialise its sync target from tracked peers rather than trusted so long as it has a subjective head within the trusting period. Overrides #2133 Closes #1851, mitigates issues resulting from #1623 Swamp integration tests to follow (tracked in #2506) ### Future note: This PR introduces a soon-to-be deprecated feature from libp2p (on-disk peerstore). Once libp2p deprecates and removes this feature, the PIDStore will have to become a PeerAddrStore such that it can save addr info of good peers to disk instead of just their IDs. --- go.mod | 1 + go.sum | 5 +++++ libs/pidstore/pidstore.go | 14 ++++++++++++-- libs/pidstore/pidstore_test.go | 14 +++++++++++++- nodebuilder/header/constructors.go | 5 +++-- nodebuilder/header/module.go | 4 ++++ nodebuilder/header/module_test.go | 3 +++ nodebuilder/p2p/misc.go | 9 +++++---- 8 files changed, 46 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index c978655477..0890bf64f1 100644 --- a/go.mod +++ b/go.mod @@ -185,6 +185,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect diff --git a/go.sum b/go.sum index 5c4e483b6f..7d9c2fba70 100644 --- a/go.sum +++ b/go.sum @@ -214,6 +214,7 @@ github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo8 github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= @@ -535,6 +536,7 @@ github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzA github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= @@ -965,6 +967,8 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= +github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= @@ -1081,6 +1085,7 @@ github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaH github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= +github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= diff --git a/libs/pidstore/pidstore.go b/libs/pidstore/pidstore.go index 2d4eb870a8..7e416d98a0 100644 --- a/libs/pidstore/pidstore.go +++ b/libs/pidstore/pidstore.go @@ -24,10 +24,20 @@ type PeerIDStore struct { } // NewPeerIDStore creates a new peer ID store backed by the given datastore. -func NewPeerIDStore(ds datastore.Datastore) *PeerIDStore { - return &PeerIDStore{ +func NewPeerIDStore(ctx context.Context, ds datastore.Datastore) (*PeerIDStore, error) { + pidstore := &PeerIDStore{ ds: namespace.Wrap(ds, storePrefix), } + // check if pidstore is already initialized, and if not, + // initialize the pidstore + exists, err := pidstore.ds.Has(ctx, peersKey) + if err != nil { + return nil, err + } + if !exists { + return pidstore, pidstore.Put(ctx, []peer.ID{}) + } + return pidstore, nil } // Load loads the peers from datastore and returns them. diff --git a/libs/pidstore/pidstore_test.go b/libs/pidstore/pidstore_test.go index eafceff3fe..d8d214c83e 100644 --- a/libs/pidstore/pidstore_test.go +++ b/libs/pidstore/pidstore_test.go @@ -19,7 +19,19 @@ func TestPutLoad(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer t.Cleanup(cancel) - peerstore := NewPeerIDStore(sync.MutexWrap(datastore.NewMapDatastore())) + ds := sync.MutexWrap(datastore.NewMapDatastore()) + + t.Run("unitialized-pidstore", func(t *testing.T) { + testPutLoad(ctx, ds, t) + }) + t.Run("initialized-pidstore", func(t *testing.T) { + testPutLoad(ctx, ds, t) + }) +} + +func testPutLoad(ctx context.Context, ds datastore.Datastore, t *testing.T) { + peerstore, err := NewPeerIDStore(ctx, ds) + require.NoError(t, err) ids, err := generateRandomPeerList(10) require.NoError(t, err) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 267f0c30f7..984d551434 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -23,11 +23,12 @@ import ( // newP2PExchange constructs a new Exchange for headers. func newP2PExchange[H libhead.Header[H]]( lc fx.Lifecycle, + cfg Config, bpeers modp2p.Bootstrappers, network modp2p.Network, host host.Host, conngater *conngater.BasicConnectionGater, - cfg Config, + pidstore p2p.PeerIDStore, ) (libhead.Exchange[H], error) { peers, err := cfg.trustedPeers(bpeers) if err != nil { @@ -42,6 +43,7 @@ func newP2PExchange[H libhead.Header[H]]( p2p.WithParams(cfg.Client), p2p.WithNetworkID[p2p.ClientParameters](network.String()), p2p.WithChainID(network.String()), + p2p.WithPeerIDStore[p2p.ClientParameters](pidstore), ) if err != nil { return nil, err @@ -55,7 +57,6 @@ func newP2PExchange[H libhead.Header[H]]( }, }) return exchange, nil - } // newSyncer constructs new Syncer for headers. diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 5e02e94fe1..eaee6b047f 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -15,6 +15,7 @@ import ( "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/pidstore" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" @@ -99,6 +100,9 @@ func ConstructModule[H libhead.Header[H]](tp node.Type, cfg *Config) fx.Option { "header", baseComponents, fx.Provide(newP2PExchange[H]), + fx.Provide(func(ctx context.Context, ds datastore.Batching) (p2p.PeerIDStore, error) { + return pidstore.NewPeerIDStore(ctx, ds) + }), ) case node.Bridge: return fx.Module( diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index 6a35e35284..c31cf546d8 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -20,6 +20,7 @@ import ( "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/pidstore" "github.com/celestiaorg/celestia-node/nodebuilder/node" modp2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) @@ -94,6 +95,8 @@ func TestConstructModule_ExchangeParams(t *testing.T) { var exchangeServer *p2p.ExchangeServer[*header.ExtendedHeader] app := fxtest.New(t, + fx.Provide(pidstore.NewPeerIDStore), + fx.Provide(context.Background), fx.Supply(modp2p.Private), fx.Supply(modp2p.Bootstrappers{}), fx.Provide(libp2p.New), diff --git a/nodebuilder/p2p/misc.go b/nodebuilder/p2p/misc.go index 9b7d8d8108..0d842e0601 100644 --- a/nodebuilder/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -1,12 +1,13 @@ package p2p import ( + "context" "time" "github.com/ipfs/go-datastore" connmgri "github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/peerstore" - "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" + "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds" //nolint:staticcheck "github.com/libp2p/go-libp2p/p2p/net/conngater" "github.com/libp2p/go-libp2p/p2p/net/connmgr" @@ -71,7 +72,7 @@ func connectionGater(ds datastore.Batching) (*conngater.BasicConnectionGater, er return conngater.NewBasicConnectionGater(ds) } -// peerStore constructs a PeerStore. -func peerStore() (peerstore.Peerstore, error) { - return pstoremem.NewPeerstore() +// peerStore constructs an on-disk PeerStore. +func peerStore(ctx context.Context, ds datastore.Batching) (peerstore.Peerstore, error) { + return pstoreds.NewPeerstore(ctx, ds, pstoreds.DefaultOpts()) } From a5e01b9ca1c7384b5b6807ac2455bd8f89593167 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 6 Sep 2023 12:32:07 +0200 Subject: [PATCH 0788/1008] feat(modp2p): listen on WebTransport by default (#2660) --- nodebuilder/p2p/config.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nodebuilder/p2p/config.go b/nodebuilder/p2p/config.go index 589945ae9f..99f3fe8879 100644 --- a/nodebuilder/p2p/config.go +++ b/nodebuilder/p2p/config.go @@ -40,6 +40,8 @@ type Config struct { func DefaultConfig(tp node.Type) Config { return Config{ ListenAddresses: []string{ + "/ip4/0.0.0.0/udp/2121/quic-v1/webtransport", + "/ip6/::/udp/2121/quic-v1/webtransport", "/ip4/0.0.0.0/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", "/ip4/0.0.0.0/tcp/2121", @@ -47,6 +49,9 @@ func DefaultConfig(tp node.Type) Config { }, AnnounceAddresses: []string{}, NoAnnounceAddresses: []string{ + "/ip4/127.0.0.1/udp/2121/quic-v1/webtransport", + "/ip4/0.0.0.0/udp/2121/quic-v1/webtransport", + "/ip6/::/udp/2121/quic-v1/webtransport", "/ip4/0.0.0.0/udp/2121/quic-v1", "/ip4/127.0.0.1/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", From 1c84b0409b6b57d909869cd07c2bb4224d039833 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:44:41 +0800 Subject: [PATCH 0789/1008] fix(share/eds): skip concurrent writes on edsstore Put (#2613) Short circuit parallel operations Closes #2608 --- share/eds/store.go | 7 +++++++ share/eds/store_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/share/eds/store.go b/share/eds/store.go index fa9a7f7c7e..19cbd83057 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "os" + "sync" "sync/atomic" "time" @@ -63,6 +64,8 @@ type Store struct { // lastGCResult is only stored on the store for testing purposes. lastGCResult atomic.Pointer[dagstore.GCResult] + // stripedLocks is used to synchronize parallel operations + stripedLocks [256]sync.Mutex shardFailures chan dagstore.ShardResult metrics *metrics @@ -221,6 +224,10 @@ func (s *Store) Put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext } func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.ExtendedDataSquare) (err error) { + lk := &s.stripedLocks[root[len(root)-1]] + lk.Lock() + defer lk.Unlock() + // if root already exists, short-circuit if has, _ := s.Has(ctx, root); has { return dagstore.ErrShardExists diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 4f1d7f4c8b..616e5c2874 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -3,9 +3,11 @@ package eds import ( "context" "os" + "sync" "testing" "time" + "github.com/filecoin-project/dagstore" "github.com/filecoin-project/dagstore/shard" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" @@ -209,6 +211,30 @@ func TestEDSStore(t *testing.T) { assert.Contains(t, hashesOut, hash) } }) + + t.Run("Parallel put", func(t *testing.T) { + const amount = 20 + eds, dah := randomEDS(t) + + wg := sync.WaitGroup{} + for i := 1; i < amount; i++ { + wg.Add(1) + go func() { + defer wg.Done() + err := edsStore.Put(ctx, dah.Hash(), eds) + if err != nil { + require.ErrorIs(t, err, dagstore.ErrShardExists) + } + }() + } + wg.Wait() + + eds, err := edsStore.Get(ctx, dah.Hash()) + require.NoError(t, err) + newDah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) + require.Equal(t, dah.Hash(), newDah.Hash()) + }) } // TestEDSStore_GC verifies that unused transient shards are collected by the GC periodically. From 7601c08a59097de8e5d3a5771d70d5359fd1dc1c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:45:17 +0800 Subject: [PATCH 0790/1008] chore(lint/sort-imports): fix absolute path for sort-imports (#2640) If repo folder name is different from "celestia-node" lint and sort-imprort commands from Makefile will not work. --- Makefile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8813b65d23..30b0c18be3 100644 --- a/Makefile +++ b/Makefile @@ -159,20 +159,18 @@ openrpc-gen: .PHONY: openrpc-gen ## lint-imports: Lint only Go imports. +## flag -set-exit-status doesn't exit with code 1 as it should, so we use find until it is fixed by goimports-reviser lint-imports: @echo "--> Running imports linter" @for file in `find . -type f -name '*.go'`; \ - do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/"$(PROJECTNAME)"" -output stdout $$file \ + do goimports-reviser -list-diff -set-exit-status -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout $$file \ || exit 1; \ done; .PHONY: lint-imports ## sort-imports: Sort Go imports. sort-imports: - @for file in `find . -type f -name '*.go'`; \ - do goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/"$(PROJECTNAME)"" $$file \ - || exit 1; \ - done; + @goimports-reviser -company-prefixes "github.com/celestiaorg" -project-name "github.com/celestiaorg/celestia-node" -output stdout ./... .PHONY: sort-imports ## adr-gen: Generate ADR from template. Must set NUM and TITLE parameters. From cb46dc1f23575cbdafbecb287cca3aaa6a34d451 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 7 Sep 2023 11:45:35 +0200 Subject: [PATCH 0791/1008] chore: cleanup usage of `share.Root`(DAH) (#2481) Somewhere we were using Root and somewhere DAH. Also, we mostly use DAH generator from the app. These PRs clean up all the usages and localize the Root/DAH type in share pkg as was originally intended. Up to discussion is rename of share.Root to share.DAH to minimize discrepancies between app and node. Alternatively, if we wanna keep Root naming as it is, we should also rename DAH name on the ExtendedHeader(and son tags) and in other places, which is breaking. Now, I think that calling it Root was not right and we should be calling the DataHash type Root, which is in fact the Merkle root of all the shares in the EDS. --- core/eds_test.go | 3 +- core/header_test.go | 3 +- core/listener_test.go | 3 +- das/coordinator_test.go | 3 +- das/daser_test.go | 4 +- header/header.go | 28 ++++----- header/headertest/testing.go | 18 +++--- nodebuilder/share/share_test.go | 4 +- share/availability.go | 11 ++++ share/availability/cache/availability.go | 7 +-- share/availability/cache/availability_test.go | 4 +- share/availability/light/availability_test.go | 11 ++-- share/availability/test/testing.go | 6 +- share/eds/byzantine/bad_encoding_test.go | 5 +- share/eds/eds.go | 3 +- share/eds/eds_test.go | 6 +- share/eds/edstest/testing.go | 5 +- share/eds/store.go | 5 +- share/eds/store_test.go | 8 +-- share/empty.go | 7 +-- share/getters/getter_test.go | 49 ++++++++------- share/getters/shrex_test.go | 59 +++++-------------- share/getters/testing.go | 4 +- share/p2p/shrexeds/exchange_test.go | 9 ++- share/p2p/shrexnd/exchange_test.go | 5 +- 25 files changed, 117 insertions(+), 153 deletions(-) diff --git a/core/eds_test.go b/core/eds_test.go index 6a2026ee58..723cb77ad1 100644 --- a/core/eds_test.go +++ b/core/eds_test.go @@ -9,7 +9,6 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" ) @@ -47,7 +46,7 @@ func TestEmptySquareWithZeroTxs(t *testing.T) { eds, err = app.ExtendBlock(data, appconsts.LatestVersion) require.NoError(t, err) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) assert.Equal(t, share.EmptyRoot().Hash(), dah.Hash()) } diff --git a/core/header_test.go b/core/header_test.go index c942ea7875..ee5d10170e 100644 --- a/core/header_test.go +++ b/core/header_test.go @@ -10,6 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" + "github.com/celestiaorg/celestia-node/share" ) func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { @@ -36,7 +37,7 @@ func TestMakeExtendedHeaderForEmptyBlock(t *testing.T) { headerExt, err := header.MakeExtendedHeader(&b.Header, comm, val, eds) require.NoError(t, err) - assert.Equal(t, header.EmptyDAH(), *headerExt.DAH) + assert.Equal(t, share.EmptyRoot(), headerExt.DAH) } func TestMismatchedDataHash_ComputedRoot(t *testing.T) { diff --git a/core/listener_test.go b/core/listener_test.go index 8b3d05bea9..90bdefb72f 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -17,6 +17,7 @@ import ( "github.com/celestiaorg/celestia-node/header" nodep2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -96,7 +97,7 @@ func TestListenerWithNonEmptyBlocks(t *testing.T) { require.NoError(t, err) t.Cleanup(sub.Cancel) - empty := header.EmptyDAH() + empty := share.EmptyRoot() // TODO extract 16 for i := 0; i < 16; i++ { _, err := cctx.FillBlock(16, cfg.Accounts, flags.BroadcastBlock) diff --git a/das/coordinator_test.go b/das/coordinator_test.go index 55ed01dd4e..1ae54c470f 100644 --- a/das/coordinator_test.go +++ b/das/coordinator_test.go @@ -13,6 +13,7 @@ import ( "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -431,7 +432,7 @@ func (m *mockSampler) discover(ctx context.Context, newHeight uint64, emit liste emit(ctx, &header.ExtendedHeader{ Commit: &types.Commit{}, RawHeader: header.RawHeader{Height: int64(newHeight)}, - DAH: &header.DataAvailabilityHeader{RowRoots: make([][]byte, 0)}, + DAH: &share.Root{RowRoots: make([][]byte, 0)}, }) } diff --git a/das/daser_test.go b/das/daser_test.go index 3e5bce0b97..a14da325f2 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -365,7 +365,7 @@ type benchGetterStub struct { func newBenchGetter() benchGetterStub { return benchGetterStub{header: &header.ExtendedHeader{ - DAH: &header.DataAvailabilityHeader{RowRoots: make([][]byte, 0)}}} + DAH: &share.Root{RowRoots: make([][]byte, 0)}}} } func (m benchGetterStub) GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) { @@ -385,7 +385,7 @@ func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.Exten return &header.ExtendedHeader{ Commit: &types.Commit{}, RawHeader: header.RawHeader{Height: int64(height)}, - DAH: &header.DataAvailabilityHeader{RowRoots: make([][]byte, 0)}}, nil + DAH: &share.Root{RowRoots: make([][]byte, 0)}}, nil } func (m getterStub) GetRangeByHeight(context.Context, uint64, uint64) ([]*header.ExtendedHeader, error) { diff --git a/header/header.go b/header/header.go index 92f8538696..1129284817 100644 --- a/header/header.go +++ b/header/header.go @@ -11,9 +11,10 @@ import ( core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/appconsts" - "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" ) // ConstructFn aliases a function that creates an ExtendedHeader. @@ -24,11 +25,6 @@ type ConstructFn = func( *rsmt2d.ExtendedDataSquare, ) (*ExtendedHeader, error) -type DataAvailabilityHeader = da.DataAvailabilityHeader - -// EmptyDAH provides DAH of the empty block. -var EmptyDAH = da.MinDataAvailabilityHeader - // RawHeader is an alias to core.Header. It is // "raw" because it is not yet wrapped to include // the DataAvailabilityHeader. @@ -39,9 +35,9 @@ type RawHeader = core.Header // block headers and perform Data Availability Sampling. type ExtendedHeader struct { RawHeader `json:"header"` - Commit *core.Commit `json:"commit"` - ValidatorSet *core.ValidatorSet `json:"validator_set"` - DAH *DataAvailabilityHeader `json:"dah"` + Commit *core.Commit `json:"commit"` + ValidatorSet *core.ValidatorSet `json:"validator_set"` + DAH *share.Root `json:"dah"` } // MakeExtendedHeader assembles new ExtendedHeader. @@ -52,22 +48,22 @@ func MakeExtendedHeader( eds *rsmt2d.ExtendedDataSquare, ) (*ExtendedHeader, error) { var ( - dah DataAvailabilityHeader + dah *share.Root err error ) switch eds { case nil: - dah = EmptyDAH() + dah = share.EmptyRoot() default: - dah, err = da.NewDataAvailabilityHeader(eds) - } - if err != nil { - return nil, err + dah, err = share.NewRoot(eds) + if err != nil { + return nil, err + } } eh := &ExtendedHeader{ RawHeader: *h, - DAH: &dah, + DAH: dah, Commit: comm, ValidatorSet: vals, } diff --git a/header/headertest/testing.go b/header/headertest/testing.go index f288556bd9..0b3c6a34dc 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -17,12 +17,12 @@ import ( "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" - "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/headertest" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" ) // TestSuite provides everything you need to test chain of Headers. @@ -52,7 +52,7 @@ func NewTestSuite(t *testing.T, num int) *TestSuite { } func (s *TestSuite) genesis() *header.ExtendedHeader { - dah := header.EmptyDAH() + dah := share.EmptyRoot() gen := RandRawHeader(s.t) @@ -70,7 +70,7 @@ func (s *TestSuite) genesis() *header.ExtendedHeader { RawHeader: *gen, Commit: commit, ValidatorSet: s.valSet, - DAH: &dah, + DAH: dah, } require.NoError(s.t, eh.Validate()) return eh @@ -137,14 +137,14 @@ func (s *TestSuite) NextHeader() *header.ExtendedHeader { return s.head } - dah := da.MinDataAvailabilityHeader() + dah := share.EmptyRoot() height := s.Head().Height() + 1 rh := s.GenRawHeader(height, s.Head().Hash(), libhead.Hash(s.Head().Commit.Hash()), dah.Hash()) s.head = &header.ExtendedHeader{ RawHeader: *rh, Commit: s.Commit(rh), ValidatorSet: s.valSet, - DAH: &dah, + DAH: dah, } require.NoError(s.t, s.head.Validate()) return s.head @@ -203,7 +203,7 @@ func (s *TestSuite) nextProposer() *types.Validator { // RandExtendedHeader provides an ExtendedHeader fixture. func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { - dah := header.EmptyDAH() + dah := share.EmptyRoot() rh := RandRawHeader(t) rh.DataHash = dah.Hash() @@ -220,7 +220,7 @@ func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { RawHeader: *rh, Commit: commit, ValidatorSet: valSet, - DAH: &dah, + DAH: dah, } } @@ -292,7 +292,7 @@ func RandBlockID(*testing.T) types.BlockID { func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader { valSet, vals := RandValidatorSet(10, 10) gen := RandRawHeader(t) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) gen.DataHash = dah.Hash() @@ -309,7 +309,7 @@ func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedData RawHeader: *gen, Commit: commit, ValidatorSet: valSet, - DAH: &dah, + DAH: dah, } require.NoError(t, eh.Validate()) return eh diff --git a/nodebuilder/share/share_test.go b/nodebuilder/share/share_test.go index 7c440a6dbf..57119b5003 100644 --- a/nodebuilder/share/share_test.go +++ b/nodebuilder/share/share_test.go @@ -9,8 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" ) @@ -27,7 +25,7 @@ func Test_EmptyCARExists(t *testing.T) { require.NoError(t, err) eds := share.EmptyExtendedDataSquare() - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) // add empty EDS to store diff --git a/share/availability.go b/share/availability.go index b6b44271c8..cd4b3a777f 100644 --- a/share/availability.go +++ b/share/availability.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" ) // ErrNotAvailable is returned whenever DA sampling fails. @@ -14,6 +15,16 @@ var ErrNotAvailable = errors.New("share: data not available") // In practice, it is a commitment to all the Data in a square. type Root = da.DataAvailabilityHeader +// NewRoot generates Root(DataAvailabilityHeader) using the +// provided extended data square. +func NewRoot(eds *rsmt2d.ExtendedDataSquare) (*Root, error) { + dah, err := da.NewDataAvailabilityHeader(eds) + if err != nil { + return nil, err + } + return &dah, nil +} + // Availability defines interface for validation of Shares' availability. type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go index d6496f7ff8..a530d94d1d 100644 --- a/share/availability/cache/availability.go +++ b/share/availability/cache/availability.go @@ -10,14 +10,11 @@ import ( "github.com/ipfs/go-datastore/namespace" logging "github.com/ipfs/go-log/v2" - "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" ) var ( - log = logging.Logger("share/cache") - minRoot = da.MinDataAvailabilityHeader() + log = logging.Logger("share/cache") cacheAvailabilityPrefix = datastore.NewKey("sampling_result") writeBatchSize = 2048 @@ -96,5 +93,5 @@ func rootKey(root *share.Root) datastore.Key { // isMinRoot returns whether the given root is a minimum (empty) // DataAvailabilityHeader (DAH). func isMinRoot(root *share.Root) bool { - return bytes.Equal(minRoot.Hash(), root.Hash()) + return bytes.Equal(share.EmptyRoot().Hash(), root.Hash()) } diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go index ccc11a1d28..458ce5cc95 100644 --- a/share/availability/cache/availability_test.go +++ b/share/availability/cache/availability_test.go @@ -110,9 +110,7 @@ func TestCacheAvailability_MinRoot(t *testing.T) { defer cancel() fullLocalAvail, _ := FullAvailabilityWithLocalRandSquare(t, 16) - minDAH := da.MinDataAvailabilityHeader() - - err := fullLocalAvail.SharesAvailable(ctx, &minDAH) + err := fullLocalAvail.SharesAvailable(ctx, share.EmptyRoot()) assert.NoError(t, err) } diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 48813a33f9..549ea14ce5 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -9,9 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" - - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/sharetest" @@ -33,8 +30,8 @@ func TestSharesAvailableFailed(t *testing.T) { getter, _ := GetterWithRandSquare(t, 16) avail := TestAvailability(getter) - empty := header.EmptyDAH() - err := avail.SharesAvailable(ctx, &empty) + empty := share.EmptyRoot() + err := avail.SharesAvailable(ctx, empty) assert.Error(t, err) } @@ -132,10 +129,10 @@ func TestGetShares(t *testing.T) { eds, err := getter.GetEDS(ctx, dah) require.NoError(t, err) - gotDAH, err := da.NewDataAvailabilityHeader(eds) + gotDAH, err := share.NewRoot(eds) require.NoError(t, err) - require.True(t, dah.Equals(&gotDAH)) + require.True(t, dah.Equals(gotDAH)) } func TestService_GetSharesByNamespaceNotFound(t *testing.T) { diff --git a/share/availability/test/testing.go b/share/availability/test/testing.go index 8973ee8cb1..64e8d23bb7 100644 --- a/share/availability/test/testing.go +++ b/share/availability/test/testing.go @@ -17,8 +17,6 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/sharetest" @@ -34,9 +32,9 @@ func RandFillBS(t *testing.T, n int, bServ blockservice.BlockService) *share.Roo func FillBS(t *testing.T, bServ blockservice.BlockService, shares []share.Share) *share.Root { eds, err := ipld.AddShares(context.TODO(), shares, bServ) require.NoError(t, err) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) - return &dah + return dah } type TestNode struct { diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 9d16955ee3..3e245c1ab3 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -13,6 +13,7 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/sharetest" @@ -55,7 +56,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { eds, err := ipld.AddShares(ctx, shares, bServ) require.NoError(t, err) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) // get an arbitrary row @@ -77,7 +78,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { RawHeader: core.Header{ Height: 420, }, - DAH: &dah, + DAH: dah, Commit: &core.Commit{ BlockID: core.BlockID{ Hash: []byte("made up hash"), diff --git a/share/eds/eds.go b/share/eds/eds.go index e689aec31c..e0433a1b6b 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -13,7 +13,6 @@ import ( "github.com/ipld/go-car" "github.com/ipld/go-car/util" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -260,7 +259,7 @@ func ReadEDS(ctx context.Context, r io.Reader, root share.DataHash) (eds *rsmt2d return nil, fmt.Errorf("share: computing eds: %w", err) } - newDah, err := da.NewDataAvailabilityHeader(eds) + newDah, err := share.NewRoot(eds) if err != nil { return nil, err } diff --git a/share/eds/eds_test.go b/share/eds/eds_test.go index 6ebca8b779..ffb05343b9 100644 --- a/share/eds/eds_test.go +++ b/share/eds/eds_test.go @@ -138,7 +138,7 @@ func TestWriteEDSInQuadrantOrder(t *testing.T) { func TestReadWriteRoundtrip(t *testing.T) { eds := writeRandomEDS(t) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) f := openWrittenEDS(t) defer f.Close() @@ -196,7 +196,7 @@ func BenchmarkReadWriteEDS(b *testing.B) { b.Cleanup(cancel) for originalDataWidth := 4; originalDataWidth <= 64; originalDataWidth *= 2 { eds := edstest.RandEDS(b, originalDataWidth) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(b, err) b.Run(fmt.Sprintf("Writing %dx%d", originalDataWidth, originalDataWidth), func(b *testing.B) { b.ReportAllocs() @@ -268,7 +268,7 @@ func createTestData(t *testing.T, testDir string) { //nolint:unused err = WriteEDS(ctx, eds, f) require.NoError(t, err, "writing EDS to file") f.Close() - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) header, err := json.MarshalIndent(dah, "", "") diff --git a/share/eds/edstest/testing.go b/share/eds/edstest/testing.go index f75e8b619b..bf5e664f90 100644 --- a/share/eds/edstest/testing.go +++ b/share/eds/edstest/testing.go @@ -5,7 +5,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -39,11 +38,11 @@ func RandEDSWithNamespace( t require.TestingT, namespace share.Namespace, size int, -) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader) { +) (*rsmt2d.ExtendedDataSquare, *share.Root) { shares := sharetest.RandSharesWithNamespace(t, namespace, size*size) eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) require.NoError(t, err, "failure to recompute the extended data square") - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) return eds, dah } diff --git a/share/eds/store.go b/share/eds/store.go index 19cbd83057..974f147292 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -24,7 +24,6 @@ import ( "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" @@ -394,13 +393,13 @@ func (s *Store) getDAH(ctx context.Context, root share.DataHash) (*share.Root, e } // dahFromCARHeader returns the DataAvailabilityHeader stored in the CIDs of a CARv1 header. -func dahFromCARHeader(carHeader *carv1.CarHeader) *header.DataAvailabilityHeader { +func dahFromCARHeader(carHeader *carv1.CarHeader) *share.Root { rootCount := len(carHeader.Roots) rootBytes := make([][]byte, 0, rootCount) for _, root := range carHeader.Roots { rootBytes = append(rootBytes, ipld.NamespacedSha256FromCID(root)) } - return &header.DataAvailabilityHeader{ + return &share.Root{ RowRoots: rootBytes[:rootCount/2], ColumnRoots: rootBytes[rootCount/2:], } diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 616e5c2874..21239e320a 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -352,7 +352,7 @@ func BenchmarkStore(b *testing.B) { // pause the timer for initializing test data b.StopTimer() eds := edstest.RandEDS(b, 128) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(b, err) b.StartTimer() @@ -368,7 +368,7 @@ func BenchmarkStore(b *testing.B) { // pause the timer for initializing test data b.StopTimer() eds := edstest.RandEDS(b, 128) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(b, err) _ = edsStore.Put(ctx, dah.Hash(), eds) b.StartTimer() @@ -387,9 +387,9 @@ func newStore(t *testing.T) (*Store, error) { return NewStore(tmpDir, ds) } -func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { +func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root) { eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) return eds, dah diff --git a/share/empty.go b/share/empty.go index 07d48f2f07..ef3d088e1d 100644 --- a/share/empty.go +++ b/share/empty.go @@ -54,16 +54,15 @@ func initEmpty() { } emptyBlockEDS = eds - dah, err := da.NewDataAvailabilityHeader(eds) + emptyBlockRoot, err = NewRoot(eds) if err != nil { panic(fmt.Errorf("failed to create empty DAH: %w", err)) } minDAH := da.MinDataAvailabilityHeader() - if !bytes.Equal(minDAH.Hash(), dah.Hash()) { + if !bytes.Equal(minDAH.Hash(), emptyBlockRoot.Hash()) { panic(fmt.Sprintf("mismatch in calculated minimum DAH and minimum DAH from celestia-app, "+ - "expected %s, got %s", minDAH.String(), dah.String())) + "expected %s, got %s", minDAH.String(), emptyBlockRoot.String())) } - emptyBlockRoot = &dah // precompute Hash, so it's cached internally to avoid potential races emptyBlockRoot.Hash() diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index ddab46f290..bee7b3a963 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" "github.com/celestiaorg/rsmt2d" @@ -49,7 +48,7 @@ func TestTeeGetter(t *testing.T) { assert.False(t, ok) assert.NoError(t, err) - retrievedEDS, err := tg.GetEDS(ctx, &dah) + retrievedEDS, err := tg.GetEDS(ctx, dah) require.NoError(t, err) require.True(t, randEds.Equals(retrievedEDS)) @@ -67,12 +66,12 @@ func TestTeeGetter(t *testing.T) { _, err := ipld.ImportShares(ctx, randEds.Flattened(), bServ) require.NoError(t, err) - retrievedEDS, err := tg.GetEDS(ctx, &dah) + retrievedEDS, err := tg.GetEDS(ctx, dah) require.NoError(t, err) require.True(t, randEds.Equals(retrievedEDS)) // no error should be returned, even though the EDS identified by the DAH already exists - retrievedEDS, err = tg.GetEDS(ctx, &dah) + retrievedEDS, err = tg.GetEDS(ctx, dah) require.NoError(t, err) require.True(t, randEds.Equals(retrievedEDS)) }) @@ -100,19 +99,19 @@ func TestStoreGetter(t *testing.T) { squareSize := int(randEds.Width()) for i := 0; i < squareSize; i++ { for j := 0; j < squareSize; j++ { - share, err := sg.GetShare(ctx, &dah, i, j) + share, err := sg.GetShare(ctx, dah, i, j) require.NoError(t, err) assert.Equal(t, randEds.GetCell(uint(i), uint(j)), share) } } // doesn't panic on indexes too high - _, err := sg.GetShare(ctx, &dah, squareSize, squareSize) + _, err := sg.GetShare(ctx, dah, squareSize, squareSize) require.ErrorIs(t, err, share.ErrOutOfBounds) // root not found _, dah = randomEDS(t) - _, err = sg.GetShare(ctx, &dah, 0, 0) + _, err = sg.GetShare(ctx, dah, 0, 0) require.ErrorIs(t, err, share.ErrNotFound) }) @@ -121,7 +120,7 @@ func TestStoreGetter(t *testing.T) { err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) - retrievedEDS, err := sg.GetEDS(ctx, &dah) + retrievedEDS, err := sg.GetEDS(ctx, dah) require.NoError(t, err) assert.True(t, randEds.Equals(retrievedEDS)) @@ -136,14 +135,14 @@ func TestStoreGetter(t *testing.T) { err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) - shares, err := sg.GetSharesByNamespace(ctx, &dah, namespace) + shares, err := sg.GetSharesByNamespace(ctx, dah, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(&dah, namespace)) + require.NoError(t, shares.Verify(dah, namespace)) assert.Len(t, shares.Flatten(), 2) // namespace not found randNamespace := sharetest.RandV0Namespace() - emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, randNamespace) + emptyShares, err := sg.GetSharesByNamespace(ctx, dah, randNamespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) @@ -159,9 +158,9 @@ func TestStoreGetter(t *testing.T) { require.NoError(t, err) // available - shares, err := sg.GetSharesByNamespace(ctx, &dah, namespace) + shares, err := sg.GetSharesByNamespace(ctx, dah, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(&dah, namespace)) + require.NoError(t, shares.Verify(dah, namespace)) assert.Len(t, shares.Flatten(), 2) // 'corrupt' existing CAR by overwriting with a random EDS @@ -171,7 +170,7 @@ func TestStoreGetter(t *testing.T) { err = eds.WriteEDS(ctx, edsToOverwriteWith, f) require.NoError(t, err) - shares, err = sg.GetSharesByNamespace(ctx, &dah, namespace) + shares, err = sg.GetSharesByNamespace(ctx, dah, namespace) require.ErrorIs(t, err, share.ErrNotFound) require.Nil(t, shares) @@ -209,19 +208,19 @@ func TestIPLDGetter(t *testing.T) { squareSize := int(randEds.Width()) for i := 0; i < squareSize; i++ { for j := 0; j < squareSize; j++ { - share, err := sg.GetShare(ctx, &dah, i, j) + share, err := sg.GetShare(ctx, dah, i, j) require.NoError(t, err) assert.Equal(t, randEds.GetCell(uint(i), uint(j)), share) } } // doesn't panic on indexes too high - _, err := sg.GetShare(ctx, &dah, squareSize+1, squareSize+1) + _, err := sg.GetShare(ctx, dah, squareSize+1, squareSize+1) require.ErrorIs(t, err, share.ErrOutOfBounds) // root not found _, dah = randomEDS(t) - _, err = sg.GetShare(ctx, &dah, 0, 0) + _, err = sg.GetShare(ctx, dah, 0, 0) require.ErrorIs(t, err, share.ErrNotFound) }) @@ -233,7 +232,7 @@ func TestIPLDGetter(t *testing.T) { err = edsStore.Put(ctx, dah.Hash(), randEds) require.NoError(t, err) - retrievedEDS, err := sg.GetEDS(ctx, &dah) + retrievedEDS, err := sg.GetEDS(ctx, dah) require.NoError(t, err) assert.True(t, randEds.Equals(retrievedEDS)) @@ -253,14 +252,14 @@ func TestIPLDGetter(t *testing.T) { require.NoError(t, err) // first check that shares are returned correctly if they exist - shares, err := sg.GetSharesByNamespace(ctx, &dah, namespace) + shares, err := sg.GetSharesByNamespace(ctx, dah, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(&dah, namespace)) + require.NoError(t, shares.Verify(dah, namespace)) assert.Len(t, shares.Flatten(), 2) // namespace not found randNamespace := sharetest.RandV0Namespace() - emptyShares, err := sg.GetSharesByNamespace(ctx, &dah, randNamespace) + emptyShares, err := sg.GetSharesByNamespace(ctx, dah, randNamespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) @@ -272,16 +271,16 @@ func TestIPLDGetter(t *testing.T) { }) } -func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, share.Root) { +func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root) { eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) return eds, dah } // randomEDSWithDoubledNamespace generates a random EDS and ensures that there are two shares in the // middle that share a namespace. -func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedDataSquare, []byte, share.Root) { +func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedDataSquare, []byte, *share.Root) { n := size * size randShares := sharetest.RandShares(t, n) idx1 := (n - 1) / 2 @@ -303,7 +302,7 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData wrapper.NewConstructor(uint64(size)), ) require.NoError(t, err, "failure to recompute the extended data square") - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) return eds, share.GetNamespace(randShares[idx1]), dah diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 236fae36e1..38611ed0ef 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -1,12 +1,9 @@ package getters import ( - "bytes" "context" "encoding/binary" "errors" - "math/rand" - "sort" "testing" "time" @@ -19,8 +16,6 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/wrapper" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" @@ -69,16 +64,16 @@ func TestShrexGetter(t *testing.T) { // generate test data namespace := sharetest.RandV0Namespace() - randEDS, dah := singleNamespaceEds(t, namespace, 64) + randEDS, dah := edstest.RandEDSWithNamespace(t, namespace, 64) require.NoError(t, edsStore.Put(ctx, dah.Hash(), randEDS)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - got, err := getter.GetSharesByNamespace(ctx, &dah, namespace) + got, err := getter.GetSharesByNamespace(ctx, dah, namespace) require.NoError(t, err) - require.NoError(t, got.Verify(&dah, namespace)) + require.NoError(t, got.Verify(dah, namespace)) }) t.Run("ND_err_not_found", func(t *testing.T) { @@ -92,7 +87,7 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - _, err := getter.GetSharesByNamespace(ctx, &dah, namespace) + _, err := getter.GetSharesByNamespace(ctx, dah, namespace) require.ErrorIs(t, err, share.ErrNotFound) }) @@ -108,16 +103,16 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - namespace, err := addToNamespace(maxNamespace, -1) + nID, err := addToNamespace(maxNamespace, -1) require.NoError(t, err) // check for namespace to be between max and min namespace in root - require.Len(t, filterRootsByNamespace(&dah, namespace), 1) + require.Len(t, filterRootsByNamespace(dah, nID), 1) - emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, namespace) + emptyShares, err := getter.GetSharesByNamespace(ctx, dah, nID) require.NoError(t, err) // no shares should be returned require.Empty(t, emptyShares.Flatten()) - require.Nil(t, emptyShares.Verify(&dah, namespace)) + require.Nil(t, emptyShares.Verify(dah, nID)) }) t.Run("ND_namespace_not_in_dah", func(t *testing.T) { @@ -135,13 +130,13 @@ func TestShrexGetter(t *testing.T) { namespace, err := addToNamespace(maxNamesapce, 1) require.NoError(t, err) // check for namespace to be not in root - require.Len(t, filterRootsByNamespace(&dah, namespace), 0) + require.Len(t, filterRootsByNamespace(dah, namespace), 0) - emptyShares, err := getter.GetSharesByNamespace(ctx, &dah, namespace) + emptyShares, err := getter.GetSharesByNamespace(ctx, dah, namespace) require.NoError(t, err) // no shares should be returned require.Empty(t, emptyShares.Flatten()) - require.Nil(t, emptyShares.Verify(&dah, namespace)) + require.Nil(t, emptyShares.Verify(dah, namespace)) }) t.Run("EDS_Available", func(t *testing.T) { @@ -156,7 +151,7 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - got, err := getter.GetEDS(ctx, &dah) + got, err := getter.GetEDS(ctx, dah) require.NoError(t, err) require.Equal(t, randEDS.Flattened(), got.Flattened()) }) @@ -172,7 +167,7 @@ func TestShrexGetter(t *testing.T) { }) cancel() - _, err := getter.GetEDS(ctx, &dah) + _, err := getter.GetEDS(ctx, dah) require.ErrorIs(t, err, context.Canceled) }) @@ -187,7 +182,7 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - _, err := getter.GetEDS(ctx, &dah) + _, err := getter.GetEDS(ctx, dah) require.ErrorIs(t, err, share.ErrNotFound) }) } @@ -200,9 +195,9 @@ func newStore(t *testing.T) (*eds.Store, error) { return eds.NewStore(tmpDir, ds) } -func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader, share.Namespace) { +func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root, share.Namespace) { eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) max := nmt.MaxNamespace(dah.RowRoots[(len(dah.RowRoots))/2-1], share.NamespaceSize) return eds, dah, max @@ -374,25 +369,3 @@ func TestAddToNamespace(t *testing.T) { }) } } - -func singleNamespaceEds( - t require.TestingT, - namespace share.Namespace, - size int, -) (*rsmt2d.ExtendedDataSquare, da.DataAvailabilityHeader) { - shares := make([]share.Share, size*size) - rnd := rand.New(rand.NewSource(time.Now().Unix())) - for i := range shares { - shr := make([]byte, share.Size) - copy(share.GetNamespace(shr), namespace) - _, err := rnd.Read(share.GetData(shr)) - require.NoError(t, err) - shares[i] = shr - } - sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) - eds, err := rsmt2d.ComputeExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), wrapper.NewConstructor(uint64(size))) - require.NoError(t, err, "failure to recompute the extended data square") - dah, err := da.NewDataAvailabilityHeader(eds) - require.NoError(t, err) - return eds, dah -} diff --git a/share/getters/testing.go b/share/getters/testing.go index 71c6231f3c..aa0cc0e479 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -17,11 +17,11 @@ import ( // TestGetter provides a testing SingleEDSGetter and the root of the EDS it holds. func TestGetter(t *testing.T) (share.Getter, *share.Root) { eds := edstest.RandEDS(t, 8) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) return &SingleEDSGetter{ EDS: eds, - }, &dah + }, dah } // SingleEDSGetter contains a single EDS where data is retrieved from. diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index 14e220a8f9..e9305a96b9 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -14,8 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" - + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/edstest" "github.com/celestiaorg/celestia-node/share/p2p" @@ -35,7 +34,7 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is immediately available t.Run("EDS_Available", func(t *testing.T) { eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) err = store.Put(ctx, dah.Hash(), eds) require.NoError(t, err) @@ -48,7 +47,7 @@ func TestExchange_RequestEDS(t *testing.T) { // Testcase: EDS is unavailable initially, but is found after multiple requests t.Run("EDS_AvailableAfterDelay", func(t *testing.T) { eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) lock := make(chan struct{}) @@ -85,7 +84,7 @@ func TestExchange_RequestEDS(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) _, err = client.RequestEDS(timeoutCtx, dah.Hash(), server.host.ID()) require.ErrorIs(t, err, p2p.ErrNotFound) diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 1ca0736cd6..50324844b1 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -13,7 +13,6 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -45,12 +44,12 @@ func TestExchange_RequestND_NotFound(t *testing.T) { t.Cleanup(cancel) eds := edstest.RandEDS(t, 4) - dah, err := da.NewDataAvailabilityHeader(eds) + dah, err := share.NewRoot(eds) require.NoError(t, err) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) randNamespace := dah.RowRoots[(len(dah.RowRoots)-1)/2][:share.NamespaceSize] - emptyShares, err := client.RequestND(ctx, &dah, randNamespace, server.host.ID()) + emptyShares, err := client.RequestND(ctx, dah, randNamespace, server.host.ID()) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) }) From 9957618d71ad0299f36a28ec640e80e1c88277cb Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 7 Sep 2023 13:02:55 +0300 Subject: [PATCH 0792/1008] improvement!(blob/service): add options to submit (#2630) --- blob/service.go | 22 ++++++++++++++++++++-- cmd/celestia/blob.go | 2 +- nodebuilder/blob/blob.go | 8 ++++---- nodebuilder/blob/mocks/api.go | 11 +++++------ nodebuilder/node/admin.go | 2 +- nodebuilder/tests/api_test.go | 2 +- nodebuilder/tests/blob_test.go | 2 +- 7 files changed, 33 insertions(+), 16 deletions(-) diff --git a/blob/service.go b/blob/service.go index 31cd259efc..05139cdfb5 100644 --- a/blob/service.go +++ b/blob/service.go @@ -51,14 +51,32 @@ func NewService( } } +// SubmitOptions contains the information about fee and gasLimit price in order to configure the Submit request. +type SubmitOptions struct { + Fee int64 + GasLimit uint64 +} + +// DefaultSubmitOptions creates a default fee and gas price values. +func DefaultSubmitOptions() *SubmitOptions { + return &SubmitOptions{ + Fee: -1, + GasLimit: 0, + } +} + // Submit sends PFB transaction and reports the height in which it was included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. // Handles gas estimation and fee calculation. -func (s *Service) Submit(ctx context.Context, blobs []*Blob) (uint64, error) { +func (s *Service) Submit(ctx context.Context, blobs []*Blob, options *SubmitOptions) (uint64, error) { log.Debugw("submitting blobs", "amount", len(blobs)) - resp, err := s.blobSubmitter.SubmitPayForBlob(ctx, types.OneInt().Neg(), 0, blobs) + if options == nil { + options = DefaultSubmitOptions() + } + + resp, err := s.blobSubmitter.SubmitPayForBlob(ctx, types.NewInt(options.Fee), options.GasLimit, blobs) if err != nil { return 0, err } diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go index 8ab130c24d..0d09268257 100644 --- a/cmd/celestia/blob.go +++ b/cmd/celestia/blob.go @@ -118,7 +118,7 @@ var submitCmd = &cobra.Command{ return fmt.Errorf("error creating a blob:%v", err) } - height, err := client.Blob.Submit(cmd.Context(), []*blob.Blob{parsedBlob}) + height, err := client.Blob.Submit(cmd.Context(), []*blob.Blob{parsedBlob}, nil) response := struct { Height uint64 `json:"height"` diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go index aae502824c..5e29d3b90c 100644 --- a/nodebuilder/blob/blob.go +++ b/nodebuilder/blob/blob.go @@ -16,7 +16,7 @@ type Module interface { // Submit sends Blobs and reports the height in which they were included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. - Submit(_ context.Context, _ []*blob.Blob) (height uint64, _ error) + Submit(_ context.Context, _ []*blob.Blob, _ *blob.SubmitOptions) (height uint64, _ error) // Get retrieves the blob by commitment under the given namespace and height. Get(_ context.Context, height uint64, _ share.Namespace, _ blob.Commitment) (*blob.Blob, error) // GetAll returns all blobs under the given namespaces and height. @@ -30,7 +30,7 @@ type Module interface { type API struct { Internal struct { - Submit func(context.Context, []*blob.Blob) (uint64, error) `perm:"write"` + Submit func(context.Context, []*blob.Blob, *blob.SubmitOptions) (uint64, error) `perm:"write"` Get func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Blob, error) `perm:"read"` GetAll func(context.Context, uint64, []share.Namespace) ([]*blob.Blob, error) `perm:"read"` GetProof func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Proof, error) `perm:"read"` @@ -38,8 +38,8 @@ type API struct { } } -func (api *API) Submit(ctx context.Context, blobs []*blob.Blob) (uint64, error) { - return api.Internal.Submit(ctx, blobs) +func (api *API) Submit(ctx context.Context, blobs []*blob.Blob, options *blob.SubmitOptions) (uint64, error) { + return api.Internal.Submit(ctx, blobs, options) } func (api *API) Get( diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index 5cd34b74b6..6a994f4d7c 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - blob "github.com/celestiaorg/celestia-node/blob" share "github.com/celestiaorg/celestia-node/share" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -98,16 +97,16 @@ func (mr *MockModuleMockRecorder) Included(arg0, arg1, arg2, arg3, arg4 interfac } // Submit mocks base method. -func (m *MockModule) Submit(arg0 context.Context, arg1 []*blob.Blob) (uint64, error) { +func (m *MockModule) Submit(arg0 context.Context, arg1 []*blob.Blob, arg2 *blob.SubmitOptions) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Submit", arg0, arg1) + ret := m.ctrl.Call(m, "Submit", arg0, arg1, arg2) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } // Submit indicates an expected call of Submit. -func (mr *MockModuleMockRecorder) Submit(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockModuleMockRecorder) Submit(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockModule)(nil).Submit), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockModule)(nil).Submit), arg0, arg1, arg2) } diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index c6c97625ef..03c9cf1b52 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -10,7 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/authtoken" ) -const APIVersion = "v0.2.1" +const APIVersion = "v0.2.2" type module struct { tp Type diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 3a66c4e58c..0e225668bb 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -119,7 +119,7 @@ func TestBlobRPC(t *testing.T) { ) require.NoError(t, err) - height, err := client.Blob.Submit(ctx, []*blob.Blob{newBlob}) + height, err := client.Blob.Submit(ctx, []*blob.Blob{newBlob}, nil) require.NoError(t, err) require.True(t, height != 0) } diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index a9e3a0464b..80fee071fb 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -55,7 +55,7 @@ func TestBlobModule(t *testing.T) { lightNode := sw.NewNodeWithConfig(node.Light, lightCfg) require.NoError(t, lightNode.Start(ctx)) - height, err := fullNode.BlobServ.Submit(ctx, blobs) + height, err := fullNode.BlobServ.Submit(ctx, blobs, nil) require.NoError(t, err) _, err = fullNode.HeaderServ.WaitForHeight(ctx, height) From d803cf847cfac3640b1272b3accc7f5940068888 Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Thu, 7 Sep 2023 13:41:03 +0200 Subject: [PATCH 0793/1008] chore!: bump mocha chain with new bootstrappers and genesis hash (#2668) ## Overview - [x] Genesis hash [taken here](https://github.com/celestiaorg/networks/blob/master/mocha-4/genesis_hash.txt) - [x] Bootstrappers list updated - [x] Genesis hash updated - [x] Chain-ID updated ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- nodebuilder/p2p/bootstrap.go | 7 ++++--- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 0718d8cf59..47b39eed31 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -44,9 +44,10 @@ var bootstrapList = map[Network][]string{ "/dns4/da-full-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWRByRF67a2kVM2j4MP5Po3jgTw7H2iL2Spu8aUwPkrRfP", }, Mocha: { - "/dns4/bootstr-mocha-1.celestia-mocha.com/tcp/2121/p2p/12D3KooWDRSJMbH3PS4dRDa11H7Tk615aqTUgkeEKz4pwd4sS6fN", - "/dns4/bootstr-mocha-2.celestia-mocha.com/tcp/2121/p2p/12D3KooWEk7cxtjQCC7kC84Uhs2j6dAHjdbwYnPcvUAqmj6Zsry2", - "/dns4/bootstr-mocha-3.celestia-mocha.com/tcp/2121/p2p/12D3KooWBE4QcFXZzENf2VRo6Y5LBvp9gzmpYRHKCvgGzEYj7Hdn", + "/dns4/da-bridge-mocha-4.celestia-mocha.com/tcp/2121/p2p/12D3KooWCBAbQbJSpCpCGKzqz3rAN4ixYbc63K68zJg9aisuAajg", + "/dns4/da-bridge-mocha-4-2.celestia-mocha.com/tcp/2121/p2p/12D3KooWK6wJkScGQniymdWtBwBuU36n6BRXp9rCDDUD6P5gJr3G", + "/dns4/da-full-1-mocha-4.celestia-mocha.com/tcp/2121/p2p/12D3KooWCUHPLqQXZzpTx1x3TAsdn3vYmTNDhzg66yG8hqoxGGN8", + "/dns4/da-full-2-mocha-4.celestia-mocha.com/tcp/2121/p2p/12D3KooWR6SHsXPkkvhCRn6vp1RqSefgaT1X1nMNvrVjU2o3GoYy", }, BlockspaceRace: { "/dns4/bootstr-incent-3.celestia.tools/tcp/2121/p2p/12D3KooWNzdKcHagtvvr6qtjcPTAdCN6ZBiBLH8FBHbihxqu4GZx", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 31ea0b0ae9..5066db5177 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -24,7 +24,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Arabica: "5904E55478BA4B3002EE885621E007A2A6A2399662841912219AECD5D5CBE393", - Mocha: "79A97034D569C4199A867439B1B7B77D4E1E1D9697212755E1CE6D920CDBB541", + Mocha: "B93BBE20A0FBFDF955811B6420F8433904664D45DB4BF51022BE4200C1A1680D", BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index 29e0218bb2..b1f9be8401 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -14,7 +14,7 @@ const ( // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica-10" // Mocha testnet. See: celestiaorg/networks. - Mocha Network = "mocha-3" + Mocha Network = "mocha-4" // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. BlockspaceRace Network = "blockspacerace-0" // Private can be used to set up any private network, including local testing setups. From a3d4cd571aefa8763389e571fe43e74d6bc26a50 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Thu, 7 Sep 2023 08:10:25 -0400 Subject: [PATCH 0794/1008] feat(headertest): Validate exact error types in header tests (#2644) ## Overview Closes: #2645 ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --------- Co-authored-by: Hlib Kanunnikov --- header/header.go | 15 ++++++++++++--- header/headertest/verify_test.go | 19 ++++++++----------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/header/header.go b/header/header.go index 1129284817..9778684b7d 100644 --- a/header/header.go +++ b/header/header.go @@ -3,6 +3,7 @@ package header import ( "bytes" "encoding/json" + "errors" "fmt" "time" @@ -162,6 +163,12 @@ func (eh *ExtendedHeader) Validate() error { return nil } +var ( + ErrValidatorHashMismatch = errors.New("validator hash mismatch") + ErrLastHeaderHashMismatch = errors.New("last header hash mismatch") + ErrVerifyCommitLightTrustingFailed = errors.New("commit light trusting verification failed") +) + // Verify validates given untrusted Header against trusted ExtendedHeader. func (eh *ExtendedHeader) Verify(untrst *ExtendedHeader) error { isAdjacent := eh.Height()+1 == untrst.Height() @@ -170,7 +177,8 @@ func (eh *ExtendedHeader) Verify(untrst *ExtendedHeader) error { // Check the validator hashes are the same if !bytes.Equal(untrst.ValidatorsHash, eh.NextValidatorsHash) { return &libhead.VerifyError{ - Reason: fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", + Reason: fmt.Errorf("%w: expected (%X), but got (%X)", + ErrValidatorHashMismatch, eh.NextValidatorsHash, untrst.ValidatorsHash, ), @@ -179,7 +187,8 @@ func (eh *ExtendedHeader) Verify(untrst *ExtendedHeader) error { if !bytes.Equal(untrst.LastHeader(), eh.Hash()) { return &libhead.VerifyError{ - Reason: fmt.Errorf("expected new header to point to last header hash (%X), but got %X)", + Reason: fmt.Errorf("%w: expected (%X), but got (%X)", + ErrLastHeaderHashMismatch, eh.Hash(), untrst.LastHeader(), ), @@ -191,7 +200,7 @@ func (eh *ExtendedHeader) Verify(untrst *ExtendedHeader) error { if err := eh.ValidatorSet.VerifyCommitLightTrusting(eh.ChainID(), untrst.Commit, light.DefaultTrustLevel); err != nil { return &libhead.VerifyError{ - Reason: err, + Reason: fmt.Errorf("%w: %w", ErrVerifyCommitLightTrustingFailed, err), SoftFailure: true, } } diff --git a/header/headertest/verify_test.go b/header/headertest/verify_test.go index 7ef16afc8d..82795ca5ff 100644 --- a/header/headertest/verify_test.go +++ b/header/headertest/verify_test.go @@ -1,6 +1,7 @@ package headertest import ( + "errors" "strconv" "testing" @@ -15,17 +16,17 @@ func TestVerify(t *testing.T) { trusted, untrustedAdj, untrustedNonAdj := h[0], h[1], h[2] tests := []struct { prepare func() *header.ExtendedHeader - err bool + err error }{ { prepare: func() *header.ExtendedHeader { return untrustedAdj }, - err: false, + err: nil, }, { prepare: func() *header.ExtendedHeader { return untrustedNonAdj }, - err: false, + err: nil, }, { prepare: func() *header.ExtendedHeader { @@ -33,7 +34,7 @@ func TestVerify(t *testing.T) { untrusted.ValidatorsHash = tmrand.Bytes(32) return &untrusted }, - err: true, + err: header.ErrValidatorHashMismatch, }, { prepare: func() *header.ExtendedHeader { @@ -41,7 +42,7 @@ func TestVerify(t *testing.T) { untrusted.RawHeader.LastBlockID.Hash = tmrand.Bytes(32) return &untrusted }, - err: true, + err: header.ErrLastHeaderHashMismatch, }, { prepare: func() *header.ExtendedHeader { @@ -49,18 +50,14 @@ func TestVerify(t *testing.T) { untrusted.Commit = NewTestSuite(t, 2).Commit(RandRawHeader(t)) return &untrusted }, - err: true, + err: header.ErrVerifyCommitLightTrustingFailed, }, } for i, test := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { err := trusted.Verify(test.prepare()) - if test.err { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } + assert.ErrorIs(t, errors.Unwrap(err), test.err) }) } } From b1a6349ca839d838740319f2a19818a7481f087e Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Thu, 7 Sep 2023 07:10:46 -0500 Subject: [PATCH 0795/1008] chore: bump celestia-app to v1.0.0-rc14 (#2662) ## Overview This PR bumps celestia-app to [v1.0.0-rc14](https://github.com/celestiaorg/celestia-app/releases/tag/v1.0.0-rc14). Per Upgrade Notice in release notes, this change is not breaking. It only contains changes to the Makefile and spec/doc backports. There is technically two non breaking changes to the code as well, but the first is an arguably redundant check on the first block (which all networks are already past), and the second is a non breaking bump to dependencies. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- go.mod | 6 +++--- go.sum | 33 +++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 0890bf64f1..7521ed74d6 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc13 + github.com/celestiaorg/celestia-app v1.0.0-rc14 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.3.1 @@ -135,7 +135,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect - github.com/ethereum/go-ethereum v1.12.0 // indirect + github.com/ethereum/go-ethereum v1.12.2 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -189,7 +189,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect + github.com/holiman/uint256 v1.2.3 // indirect github.com/huin/goupnp v1.2.0 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect diff --git a/go.sum b/go.sum index 7d9c2fba70..cea5e19c5b 100644 --- a/go.sum +++ b/go.sum @@ -316,6 +316,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= +github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -357,8 +359,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc13 h1:BM9lBJw+RcAiFIUmVDd3XBYyXV9rKJBi8mDqc2wok1o= -github.com/celestiaorg/celestia-app v1.0.0-rc13/go.mod h1:JYu6i1NxJw26TVZ+XSllUdnw0Fw3nGNk5f3wm6RIcys= +github.com/celestiaorg/celestia-app v1.0.0-rc14 h1:kC3ljQzxBjQM1s8aw0dOnrn5pNbrfyfd+lS155S7QAc= +github.com/celestiaorg/celestia-app v1.0.0-rc14/go.mod h1:6aD4sFGQIe5XPYejLDKrDKsNWlDu9/P3e54pCLPywNk= github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28 h1:2efXQaggLFknz0wQufr4nUEz5G7pSVHS1j7NuJDsvII= github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28/go.mod h1:++dNzzzjP9jYg+NopN9G8sg1HEZ58lv1TPtg71evZ0E= github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14 h1:dDfoQJOlVNj4HufJ1lBLTo2k3/L/255MIiKmEQziDmw= @@ -447,8 +449,12 @@ github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4 github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= +github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= +github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= @@ -497,6 +503,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHH github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= +github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= +github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -594,9 +602,11 @@ github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakR github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= github.com/etclabscore/go-openrpc-reflect v0.0.37 h1:IH0e7JqIvR9OhbbFWi/BHIkXrqbR3Zyia3RJ733eT6c= github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= +github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= +github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= -github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/ethereum/go-ethereum v1.12.2 h1:eGHJ4ij7oyVqUQn48LBz3B7pvQ8sV0wGJiIE6gDq/6Y= +github.com/ethereum/go-ethereum v1.12.2/go.mod h1:1cRAEV+rp/xX0zraSCBnu9Py3HQ+geRMj3HdR+k0wfI= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -980,11 +990,13 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= @@ -1769,6 +1781,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -2222,6 +2236,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= @@ -2277,8 +2293,8 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU= +github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -3281,6 +3297,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From 3f9e88b471169027f8a0ea3b60ff095f2b15f861 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 8 Sep 2023 10:28:16 +0200 Subject: [PATCH 0796/1008] chore: bump libp2p (#2676) Closes #2674 --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 7521ed74d6..bbf68e5b35 100644 --- a/go.mod +++ b/go.mod @@ -38,11 +38,11 @@ require ( github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipld/go-car v0.6.2 - github.com/libp2p/go-libp2p v0.30.0 - github.com/libp2p/go-libp2p-kad-dht v0.25.0 + github.com/libp2p/go-libp2p v0.31.0 + github.com/libp2p/go-libp2p-kad-dht v0.25.1 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 - github.com/libp2p/go-libp2p-routing-helpers v0.7.1 + github.com/libp2p/go-libp2p-routing-helpers v0.7.2 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.11.0 @@ -165,7 +165,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect @@ -269,12 +269,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.2 // indirect - github.com/quic-go/quic-go v0.37.6 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect + github.com/quic-go/quic-go v0.38.1 // indirect github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect diff --git a/go.sum b/go.sum index cea5e19c5b..ab86f8bdb0 100644 --- a/go.sum +++ b/go.sum @@ -862,8 +862,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= @@ -1411,8 +1411,8 @@ github.com/libp2p/go-libp2p v0.22.0/go.mod h1:UDolmweypBSjQb2f7xutPnwZ/fxioLbMBx github.com/libp2p/go-libp2p v0.23.4/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= github.com/libp2p/go-libp2p v0.25.0/go.mod h1:vXHmFpcfl+xIGN4qW58Bw3a0/SKGAesr5/T4IuJHE3o= github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= -github.com/libp2p/go-libp2p v0.30.0 h1:9EZwFtJPFBcs/yJTnP90TpN1hgrT/EsFfM+OZuwV87U= -github.com/libp2p/go-libp2p v0.30.0/go.mod h1:nr2g5V7lfftwgiJ78/HrID+pwvayLyqKCEirT2Y3Byg= +github.com/libp2p/go-libp2p v0.31.0 h1:LFShhP8F6xthWiBBq3euxbKjZsoRajVEyBS9snfHxYg= +github.com/libp2p/go-libp2p v0.31.0/go.mod h1:W/FEK1c/t04PbRH3fA9i5oucu5YcgrG0JVoBWT1B7Eg= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= @@ -1469,8 +1469,8 @@ github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQO github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.19.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= github.com/libp2p/go-libp2p-kad-dht v0.21.0/go.mod h1:Bhm9diAFmc6qcWAr084bHNL159srVZRKADdp96Qqd1I= -github.com/libp2p/go-libp2p-kad-dht v0.25.0 h1:T2SXQ/VlXTQVLChWY/+OyOsmGMRJvB5kiR+eJt7jtvI= -github.com/libp2p/go-libp2p-kad-dht v0.25.0/go.mod h1:P6fz+J+u4tPigvS5J0kxQ1isksqAhmXiS/pNaEw/nFI= +github.com/libp2p/go-libp2p-kad-dht v0.25.1 h1:ofFNrf6MMEy4vi3R1VbJ7LOcTn3Csh0cDcaWHTxtWNA= +github.com/libp2p/go-libp2p-kad-dht v0.25.1/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= @@ -1514,8 +1514,8 @@ github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3qu github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-resource-manager v0.2.1/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= github.com/libp2p/go-libp2p-routing-helpers v0.4.0/go.mod h1:dYEAgkVhqho3/YKxfOEGdFMIcWfAFNlZX8iAIihYA2E= -github.com/libp2p/go-libp2p-routing-helpers v0.7.1 h1:kc0kWCZecbBPAiFEHhxfGJZPqjg1g9zV+X+ovR4Tmnc= -github.com/libp2p/go-libp2p-routing-helpers v0.7.1/go.mod h1:cHStPSRC/wgbfpb5jYdMP7zaSmc2wWcb1mkzNr6AR8o= +github.com/libp2p/go-libp2p-routing-helpers v0.7.2 h1:xJMFyhQ3Iuqnk9Q2dYE1eUTzsah7NLw3Qs2zjUV78T0= +github.com/libp2p/go-libp2p-routing-helpers v0.7.2/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= @@ -2054,8 +2054,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -2067,8 +2067,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.2 h1:OX2qdUQsS8RSkn/3C8isD7f/P0YiZQlRbAlecAaj/R8= github.com/pyroscope-io/client v0.7.2/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8= @@ -2081,11 +2081,11 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1 github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= -github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= -github.com/quic-go/quic-go v0.37.6 h1:2IIUmQzT5YNxAiaPGjs++Z4hGOtIR0q79uS5qE9ccfY= -github.com/quic-go/quic-go v0.37.6/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= +github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE= +github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4= github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= From 4f047c252761dcc9e4b07f86f03f1cf84bc0e2e3 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 8 Sep 2023 15:09:55 +0200 Subject: [PATCH 0797/1008] fix(modshare): start peer manager after Syncer (#2677) Fixes panic: ``` panic: invalid type received %!s() goroutine 602 [running]: github.com/celestiaorg/go-header/p2p.(*subscription[...]).NextHeader(0x103fbeaa0?, {0x103fdcd50, 0x14000723310?}) /Users/rford/go/pkg/mod/github.com/celestiaorg/go-header@v0.3.1/p2p/subscription.go:44 +0x2ac github.com/celestiaorg/celestia-node/share/p2p/peers.(*Manager).subscribeHeader(0x1400158c960, {0x103fdcd50, 0x14000723310}, {0x103fbe928?, 0x14000676250?}) /Users/rford/Programming/Celestia/celestia-node/share/p2p/peers/manager.go:299 +0xc0 created by github.com/celestiaorg/celestia-node/share/p2p/peers.(*Manager).Start in goroutine 262 /Users/rford/Programming/Celestia/celestia-node/share/p2p/peers/manager.go:180 +0x410 ``` --- nodebuilder/share/module.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index f7a84bf526..35f8813111 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -9,6 +9,7 @@ import ( "go.uber.org/fx" libhead "github.com/celestiaorg/go-header" + "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -151,6 +152,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option connGater *conngater.BasicConnectionGater, shrexSub *shrexsub.PubSub, headerSub libhead.Subscriber[*header.ExtendedHeader], + // we must ensure Syncer is started before PeerManager + // so that Syncer registers header validator before PeerManager subscribes to headers + _ *sync.Syncer[*header.ExtendedHeader], ) (*peers.Manager, error) { return peers.NewManager( params, From c6504537de96c5e52c65391b7d9e7a65a9bef26f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 8 Sep 2023 16:17:59 +0300 Subject: [PATCH 0798/1008] feat(cmd/blob): add fee and gasLimit flags (#2669) --- cmd/celestia/blob.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go index 0d09268257..6d397525f5 100644 --- a/cmd/celestia/blob.go +++ b/cmd/celestia/blob.go @@ -14,7 +14,12 @@ import ( "github.com/celestiaorg/celestia-node/share" ) -var base64Flag bool +var ( + base64Flag bool + + fee int64 + gasLimit uint64 +) func init() { blobCmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd) @@ -31,6 +36,20 @@ func init() { false, "printed blob's data as a base64 string", ) + + submitCmd.PersistentFlags().Int64Var( + &fee, + "fee", + -1, + "specifies fee for blob submission", + ) + + submitCmd.PersistentFlags().Uint64Var( + &gasLimit, + "gas.limit", + 0, + "specifies max gas for the blob submission", + ) } var blobCmd = &cobra.Command{ @@ -118,7 +137,11 @@ var submitCmd = &cobra.Command{ return fmt.Errorf("error creating a blob:%v", err) } - height, err := client.Blob.Submit(cmd.Context(), []*blob.Blob{parsedBlob}, nil) + height, err := client.Blob.Submit( + cmd.Context(), + []*blob.Blob{parsedBlob}, + &blob.SubmitOptions{Fee: fee, GasLimit: gasLimit}, + ) response := struct { Height uint64 `json:"height"` From a770ca15a11456f438e447dbe5d690bdfe10f803 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:11:24 +0200 Subject: [PATCH 0799/1008] chore(nodebuilder/p2p): BlockTime from 15 sec --> 10 sec (#2692) Block times will be closer to 10 seconds on average than 15, so we should shift this to make calculations based on block time in our codebase more accurate. This will also fix issues with syncing `arabica-10` --- das/options.go | 2 +- nodebuilder/p2p/network.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/das/options.go b/das/options.go index 665fc5a7d6..e38e488c04 100644 --- a/das/options.go +++ b/das/options.go @@ -52,7 +52,7 @@ func DefaultParameters() Parameters { ConcurrencyLimit: concurrencyLimit, BackgroundStoreInterval: 10 * time.Minute, SampleFrom: 1, - // SampleTimeout = block time * max amount of catchup workers + // SampleTimeout = approximate block time (with a bit of wiggle room) * max amount of catchup workers SampleTimeout: 15 * time.Second * time.Duration(concurrencyLimit), } } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index b1f9be8401..a6197aa321 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -21,7 +21,7 @@ const ( Private Network = "private" // BlockTime is a network block time. // TODO @renaynay @Wondertan (#790) - BlockTime = time.Second * 15 + BlockTime = time.Second * 10 ) // Network is a type definition for DA network run by Celestia Node. From 6cc54e7eed7b27d2ede56ab64c9d84be4689084a Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:30:23 +0200 Subject: [PATCH 0800/1008] refactor(nodebuilder/header): get rid of `InitStore` (#2678) Not needed anymore --- nodebuilder/header/constructors.go | 25 +++++++++++++++++-------- nodebuilder/header/module.go | 14 -------------- nodebuilder/header/module_test.go | 9 +++++++-- nodebuilder/testing.go | 4 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 984d551434..be4bcbd427 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -3,6 +3,7 @@ package header import ( "context" + "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" @@ -63,7 +64,7 @@ func newP2PExchange[H libhead.Header[H]]( func newSyncer[H libhead.Header[H]]( ex libhead.Exchange[H], fservice libfraud.Service[H], - store InitStore[H], + store libhead.Store[H], sub libhead.Subscriber[H], cfg Config, ) (*sync.Syncer[H], *modfraud.ServiceBreaker[*sync.Syncer[H], H], error) { @@ -82,18 +83,19 @@ func newSyncer[H libhead.Header[H]]( }, nil } -// InitStore is a type representing initialized header store. -// NOTE: It is needed to ensure that Store is always initialized before Syncer is started. -type InitStore[H libhead.Header[H]] libhead.Store[H] - // newInitStore constructs an initialized store func newInitStore[H libhead.Header[H]]( lc fx.Lifecycle, cfg Config, net modp2p.Network, - s libhead.Store[H], + ds datastore.Batching, ex libhead.Exchange[H], -) (InitStore[H], error) { +) (libhead.Store[H], error) { + s, err := store.NewStore[H](ds, store.WithParams(cfg.Store)) + if err != nil { + return nil, err + } + trustedHash, err := cfg.trustedHash(net) if err != nil { return nil, err @@ -101,7 +103,14 @@ func newInitStore[H libhead.Header[H]]( lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { - return store.Init(ctx, s, ex, trustedHash) + err = store.Init[H](ctx, s, ex, trustedHash) + if err != nil { + return err + } + return s.Start(ctx) + }, + OnStop: func(ctx context.Context) error { + return s.Stop(ctx) }, }) diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index eaee6b047f..557cbcfab7 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -11,7 +11,6 @@ import ( libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/p2p" - "github.com/celestiaorg/go-header/store" "github.com/celestiaorg/go-header/sync" "github.com/celestiaorg/celestia-node/header" @@ -31,19 +30,6 @@ func ConstructModule[H libhead.Header[H]](tp node.Type, cfg *Config) fx.Option { fx.Supply(*cfg), fx.Error(cfgErr), fx.Provide(newHeaderService), - fx.Provide(fx.Annotate( - func(ds datastore.Batching) (libhead.Store[H], error) { - return store.NewStore[H](ds, store.WithParams(cfg.Store)) - }, - fx.OnStart(func(ctx context.Context, str libhead.Store[H]) error { - s := str.(*store.Store[H]) - return s.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, str libhead.Store[H]) error { - s := str.(*store.Store[H]) - return s.Stop(ctx) - }), - )), fx.Provide(newInitStore[H]), fx.Provide(func(subscriber *p2p.Subscriber[H]) libhead.Subscriber[H] { return subscriber diff --git a/nodebuilder/header/module_test.go b/nodebuilder/header/module_test.go index c31cf546d8..23ca41050f 100644 --- a/nodebuilder/header/module_test.go +++ b/nodebuilder/header/module_test.go @@ -36,8 +36,13 @@ func TestConstructModule_StoreParams(t *testing.T) { app := fxtest.New(t, fx.Supply(modp2p.Private), - fx.Provide(func() datastore.Batching { - return datastore.NewMapDatastore() + fx.Supply(modp2p.Bootstrappers{}), + fx.Provide(context.Background), + fx.Provide(libp2p.New), + fx.Provide(conngater.NewBasicConnectionGater), + fx.Provide(func() (datastore.Batching, datastore.Datastore) { + ds := datastore.NewMapDatastore() + return ds, ds }), ConstructModule[*header.ExtendedHeader](node.Light, &cfg), fx.Invoke( diff --git a/nodebuilder/testing.go b/nodebuilder/testing.go index 36f2c2f47f..8d49772aef 100644 --- a/nodebuilder/testing.go +++ b/nodebuilder/testing.go @@ -9,12 +9,12 @@ import ( "go.uber.org/fx" apptypes "github.com/celestiaorg/celestia-app/x/blob/types" + libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/libs/fxutil" - modhead "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -48,7 +48,7 @@ func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Opti // temp dir for the eds store FIXME: Should be in mem fx.Replace(node.StorePath(t.TempDir())), // avoid requesting trustedPeer during initialization - fxutil.ReplaceAs(headertest.NewStore(t), new(modhead.InitStore[*header.ExtendedHeader])), + fxutil.ReplaceAs(headertest.NewStore(t), new(libhead.Store[*header.ExtendedHeader])), ) // in fact, we don't need core.Client in tests, but Bridge requires is a valid one From 8ca777e1a3f04b7d0494e3bae8a1c1bcf68c5d8c Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Mon, 11 Sep 2023 16:19:44 +0200 Subject: [PATCH 0801/1008] chore: fix unknown in ghcr registry (#2693) Closes #2412 --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1f1705c2f3..bebd61a644 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.21-alpine3.18 as builder +FROM --platform=$BUILDPLATFORM docker.io/golang:1.21-alpine3.18 as builder # hadolint ignore=DL3018 RUN apk update && apk add --no-cache \ @@ -15,7 +15,7 @@ COPY . . RUN make build && make cel-key -FROM docker.io/alpine:3.18.3 +FROM --platform=$BUILDPLATFORM docker.io/alpine:3.18.3 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 @@ -30,6 +30,8 @@ ENV P2P_NETWORK mocha # hadolint ignore=DL3018 RUN apk update && apk add --no-cache \ bash \ + curl \ + jq \ # Creates a user with $UID and $GID=$UID && adduser ${USER_NAME} \ -D \ From 3c8b9c003cc961c01bfab2e8924f079056e68ebb Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Tue, 12 Sep 2023 10:50:59 +0200 Subject: [PATCH 0802/1008] chore: bump docker ci to v0.2.3 (#2696) --- .github/workflows/docker-build-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 2ce01c81df..c2589ff141 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -18,6 +18,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.2 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.3 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From 9b9d8d5f3d899137e144be214320ded0d92fb775 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:32:26 +0000 Subject: [PATCH 0803/1008] chore(deps): Bump actions/checkout from 3 to 4 (#2690) --- .github/workflows/ci_release.yml | 6 +++--- .github/workflows/go-ci.yml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index e10a032681..e653b2653a 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -32,14 +32,14 @@ jobs: yamllint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.2 markdown-lint: name: Markdown Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 18 @@ -57,7 +57,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' }} permissions: "write-all" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Version Release uses: celestiaorg/.github/.github/actions/version-release@v0.2.2 with: diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index f68825bec1..288a9cfb2f 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: set up go uses: actions/setup-go@v4 @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: set up go uses: actions/setup-go@v4 @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: set up go uses: actions/setup-go@v4 From 4b8cd1265a8c9e2961de64dd99c410e8d1ea9cc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:04:15 +0200 Subject: [PATCH 0804/1008] chore(deps): Bump golang.org/x/crypto from 0.12.0 to 0.13.0 (#2688) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.13.0.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.12.0&new-version=0.13.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index bbf68e5b35..0dafd16aed 100644 --- a/go.mod +++ b/go.mod @@ -68,10 +68,10 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 go.uber.org/zap v1.25.0 - golang.org/x/crypto v0.12.0 + golang.org/x/crypto v0.13.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 - golang.org/x/text v0.12.0 + golang.org/x/text v0.13.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 ) @@ -315,8 +315,8 @@ require ( golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect diff --git a/go.sum b/go.sum index ab86f8bdb0..0d1546da5c 100644 --- a/go.sum +++ b/go.sum @@ -2505,8 +2505,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2851,8 +2851,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2861,8 +2861,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2877,8 +2877,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 5a47b1a125625d4f4a1fda0855d27a4a05c5e128 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:11:48 +0000 Subject: [PATCH 0805/1008] chore(deps): Bump github.com/ipfs/go-block-format from 0.1.2 to 0.2.0 (#2682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/go-block-format](https://github.com/ipfs/go-block-format) from 0.1.2 to 0.2.0.
    Release notes

    Sourced from github.com/ipfs/go-block-format's releases.

    v0.2.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/ipfs/go-block-format/compare/v0.1.2...v0.2.0

    Commits
    • f2d9400 v0.2.0 bump
    • ee6647c chore!: go@1.20, update deps, uci, remove gx
    • ffa6dd1 build(deps): bump golang.org/x/sys
    • 441d112 chore: Update .github/workflows/stale.yml [skip ci]
    • 1f4e90a chore: Update .github/workflows/stale.yml [skip ci]
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/go-block-format&package-manager=go_modules&previous-version=0.1.2&new-version=0.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 0dafd16aed..a8cc31e5a0 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.12.0 - github.com/ipfs/go-block-format v0.1.2 + github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ipld-cbor v0.1.0 diff --git a/go.sum b/go.sum index 0d1546da5c..30eadc04c4 100644 --- a/go.sum +++ b/go.sum @@ -1054,8 +1054,9 @@ github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/ github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-block-format v0.1.1/go.mod h1:+McEIT+g52p+zz5xGAABGSOKrzmrdX97bc0USBdWPUs= -github.com/ipfs/go-block-format v0.1.2 h1:GAjkfhVx1f4YTODS6Esrj1wt2HhrtwTnhEr+DyPUaJo= github.com/ipfs/go-block-format v0.1.2/go.mod h1:mACVcrxarQKstUU3Yf/RdwbC4DzPV6++rO2a3d+a/KE= +github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= +github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= From 5ed602c5277543920ed133e922530bd6861e675a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:31:40 +0200 Subject: [PATCH 0806/1008] chore(deps): Bump github.com/libp2p/go-libp2p-routing-helpers from 0.7.2 to 0.7.3 (#2687) Bumps [github.com/libp2p/go-libp2p-routing-helpers](https://github.com/libp2p/go-libp2p-routing-helpers) from 0.7.2 to 0.7.3.
    Commits
    • b207376 chore: release v0.7.3
    • 95acf72 nit: invert if
    • ee65248 fix: for getValueOrErrorParallel do not return values if they come with errors
    • aefde64 test: add test to make sure we return not found when we get errors back with ...
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/libp2p/go-libp2p-routing-helpers&package-manager=go_modules&previous-version=0.7.2&new-version=0.7.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8cc31e5a0..05e175366b 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/libp2p/go-libp2p-kad-dht v0.25.1 github.com/libp2p/go-libp2p-pubsub v0.9.3 github.com/libp2p/go-libp2p-record v0.2.0 - github.com/libp2p/go-libp2p-routing-helpers v0.7.2 + github.com/libp2p/go-libp2p-routing-helpers v0.7.3 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.11.0 diff --git a/go.sum b/go.sum index 30eadc04c4..b12350ca11 100644 --- a/go.sum +++ b/go.sum @@ -1515,8 +1515,8 @@ github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3qu github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-resource-manager v0.2.1/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= github.com/libp2p/go-libp2p-routing-helpers v0.4.0/go.mod h1:dYEAgkVhqho3/YKxfOEGdFMIcWfAFNlZX8iAIihYA2E= -github.com/libp2p/go-libp2p-routing-helpers v0.7.2 h1:xJMFyhQ3Iuqnk9Q2dYE1eUTzsah7NLw3Qs2zjUV78T0= -github.com/libp2p/go-libp2p-routing-helpers v0.7.2/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8= +github.com/libp2p/go-libp2p-routing-helpers v0.7.3 h1:u1LGzAMVRK9Nqq5aYDVOiq/HaB93U9WWczBzGyAC5ZY= +github.com/libp2p/go-libp2p-routing-helpers v0.7.3/go.mod h1:cN4mJAD/7zfPKXBcs9ze31JGYAZgzdABEm+q/hkswb8= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= From 3f5ca0734f2d70aae7a5cf6a2962672f60bdb65b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:34:52 +0200 Subject: [PATCH 0807/1008] chore(deps): Bump go.opentelemetry.io/contrib/instrumentation/runtime from 0.42.0 to 0.43.0 (#2685) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 05e175366b..65397d401d 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 - go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 go.opentelemetry.io/otel v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 diff --git a/go.sum b/go.sum index b12350ca11..a3fe98b643 100644 --- a/go.sum +++ b/go.sum @@ -2374,8 +2374,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEasIt2kmh/WmXUznUksChApTgBGc= -go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8= +go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 h1:NunhgxcK14rU7Hw2gKtV6uCSyohkXPisqneRFjnZNKQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0/go.mod h1:rwb7icgpDjIhhHqv1qPGw6dDjAdAR7IKAe4PQdzBbsg= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= From bf31c4f0271441fd9ef80f7ac168d1ff4de383d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:44:39 +0000 Subject: [PATCH 0808/1008] chore(deps): Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.16.0 to 1.17.0 (#2683) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 ++--- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 65397d401d..bb65be4a9d 100644 --- a/go.mod +++ b/go.mod @@ -59,8 +59,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 go.opentelemetry.io/otel v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0 go.opentelemetry.io/otel/metric v1.17.0 go.opentelemetry.io/otel/sdk v1.17.0 go.opentelemetry.io/otel/sdk/metric v0.40.0 @@ -306,7 +306,6 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect diff --git a/go.sum b/go.sum index a3fe98b643..e29fef53dc 100644 --- a/go.sum +++ b/go.sum @@ -2382,16 +2382,14 @@ go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+n go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 h1:eU0ffpYuEY7eQ75K+nKr9CI5KcY8h+GPk/9DDlEO1NI= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0/go.mod h1:9P5RK5JS2sjKepuCkqFwPp3etwV/57E0eigLw18Mn1k= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 h1:MZbjiZeMmn5wFMORhozpouGKDxj9POHTuU5UA8msBQk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0/go.mod h1:C7tOYVCJmrDTCwxNny0MuUtnDIR3032vFHYke0F2ZrU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 h1:SZaSbubADNhH2Gxm+1GaZ/cFsGiYefZoodMMX79AOd4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0/go.mod h1:N65FzQDfQH7NY7umgb0U+7ypGKVYKwwE24L6KXT4OA8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h1:hGXzO5bhhSHZnKvrDaXB82Y9DRFour0Nz/KrBh7reWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 h1:U5GYackKpVKlPrd/5gKMlrTlP2dCESAAFU682VCpieY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0/go.mod h1:aFsJfCEnLzEu9vRRAcUiB/cpRTbVsNdF3OHSPpdjxZQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0 h1:kvWMtSUNVylLVrOE4WLUmBtgziYoCIYUNSpTYtMzVJI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0/go.mod h1:SExUrRYIXhDgEKG4tkiQovd2HTaELiHUsuK08s5Nqx4= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= From 28bd438d365f57f4e8f28b282fe8d9115f5329b3 Mon Sep 17 00:00:00 2001 From: ramin Date: Wed, 13 Sep 2023 10:23:46 +0100 Subject: [PATCH 0809/1008] Stale issue github workflow (#2667) --- .github/workflows/stale.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..d1088e5f33 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,18 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 8 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: 14 + days-before-issue-stale: 90 + days-before-issue-close: 21 + exempt-issue-labels: 'keep-open' + start-date: '2023-09-01T00:00:00Z' From 9260a8b697ec770e6be873947923cab516921c90 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 13 Sep 2023 11:43:34 +0200 Subject: [PATCH 0810/1008] refactor(swamp): use RPC client instead of service pointers (#2699) Replaces #2356 First step of #2337 --- nodebuilder/node.go | 2 + nodebuilder/tests/api_test.go | 48 +++++++++++--------- nodebuilder/tests/blob_test.go | 19 ++++---- nodebuilder/tests/fraud_test.go | 12 +++-- nodebuilder/tests/nd_test.go | 22 ++++++--- nodebuilder/tests/p2p_test.go | 39 ++++++++++++---- nodebuilder/tests/reconstruct_test.go | 39 ++++++++++------ nodebuilder/tests/sync_test.go | 64 ++++++++++++++++----------- 8 files changed, 158 insertions(+), 87 deletions(-) diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 58a182276c..3e6950a6ae 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/cristalhq/jwt" "github.com/ipfs/boxo/blockservice" "github.com/ipfs/boxo/exchange" logging "github.com/ipfs/go-log/v2" @@ -48,6 +49,7 @@ type Node struct { Network p2p.Network Bootstrappers p2p.Bootstrappers Config *Config + AdminSigner jwt.Signer // rpc components RPCServer *rpc.Server // not optional diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 0e225668bb..2fd4b2d3da 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -13,11 +13,27 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/blob/blobtest" + "github.com/celestiaorg/celestia-node/libs/authtoken" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) +func getAdminClient(ctx context.Context, nd *nodebuilder.Node, t *testing.T) *client.Client { + t.Helper() + + signer := nd.AdminSigner + listenAddr := "ws://" + nd.RPCServer.ListenAddr() + + jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) + require.NoError(t, err) + + client, err := client.NewClient(ctx, listenAddr, jwt) + require.NoError(t, err) + + return client +} + func TestNodeModule(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) @@ -66,26 +82,20 @@ func TestGetByHeight(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - adminPerms := []auth.Permission{"public", "read", "write", "admin"} - jwt, err := bridge.AdminServ.AuthNew(ctx, adminPerms) - require.NoError(t, err) - - bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() - client, err := client.NewClient(ctx, bridgeAddr, jwt) - require.NoError(t, err) + rpcClient := getAdminClient(ctx, bridge, t) // let a few blocks be produced - _, err = client.Header.WaitForHeight(ctx, 3) + _, err = rpcClient.Header.WaitForHeight(ctx, 3) require.NoError(t, err) - networkHead, err := client.Header.NetworkHead(ctx) + networkHead, err := rpcClient.Header.NetworkHead(ctx) require.NoError(t, err) - _, err = client.Header.GetByHeight(ctx, networkHead.Height()+1) + _, err = rpcClient.Header.GetByHeight(ctx, networkHead.Height()+1) require.Nil(t, err, "Requesting syncer.Head()+1 shouldn't return an error") - networkHead, err = client.Header.NetworkHead(ctx) + networkHead, err = rpcClient.Header.NetworkHead(ctx) require.NoError(t, err) - _, err = client.Header.GetByHeight(ctx, networkHead.Height()+2) + _, err = rpcClient.Header.GetByHeight(ctx, networkHead.Height()+2) require.ErrorContains(t, err, "given height is from the future") } @@ -101,13 +111,7 @@ func TestBlobRPC(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - adminPerms := []auth.Permission{"public", "read", "write", "admin"} - jwt, err := bridge.AdminServ.AuthNew(ctx, adminPerms) - require.NoError(t, err) - - bridgeAddr := "http://" + bridge.RPCServer.ListenAddr() - client, err := client.NewClient(ctx, bridgeAddr, jwt) - require.NoError(t, err) + rpcClient := getAdminClient(ctx, bridge, t) appBlobs, err := blobtest.GenerateV0Blobs([]int{8}, false) require.NoError(t, err) @@ -119,7 +123,7 @@ func TestBlobRPC(t *testing.T) { ) require.NoError(t, err) - height, err := client.Blob.Submit(ctx, []*blob.Blob{newBlob}, nil) + height, err := rpcClient.Blob.Submit(ctx, []*blob.Blob{newBlob}, nil) require.NoError(t, err) require.True(t, height != 0) } @@ -147,9 +151,11 @@ func TestHeaderSubscription(t *testing.T) { err = light.Start(ctx) require.NoError(t, err) + lightClient := getAdminClient(ctx, light, t) + // subscribe to headers via the light node's RPC header subscription subctx, subcancel := context.WithCancel(ctx) - sub, err := light.HeaderServ.Subscribe(subctx) + sub, err := lightClient.Header.Subscribe(subctx) require.NoError(t, err) // listen for 5 headers for i := 0; i < 5; i++ { diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index 80fee071fb..2078fbfa74 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -55,12 +55,15 @@ func TestBlobModule(t *testing.T) { lightNode := sw.NewNodeWithConfig(node.Light, lightCfg) require.NoError(t, lightNode.Start(ctx)) - height, err := fullNode.BlobServ.Submit(ctx, blobs, nil) + fullClient := getAdminClient(ctx, fullNode, t) + lightClient := getAdminClient(ctx, lightNode, t) + + height, err := fullClient.Blob.Submit(ctx, blobs, nil) require.NoError(t, err) - _, err = fullNode.HeaderServ.WaitForHeight(ctx, height) + _, err = fullClient.Header.WaitForHeight(ctx, height) require.NoError(t, err) - _, err = lightNode.HeaderServ.WaitForHeight(ctx, height) + _, err = lightClient.Header.WaitForHeight(ctx, height) require.NoError(t, err) var test = []struct { @@ -70,7 +73,7 @@ func TestBlobModule(t *testing.T) { { name: "Get", doFn: func(t *testing.T) { - blob1, err := fullNode.BlobServ.Get(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) + blob1, err := fullClient.Blob.Get(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) require.NoError(t, err) require.Equal(t, blobs[0], blob1) }, @@ -78,7 +81,7 @@ func TestBlobModule(t *testing.T) { { name: "GetAll", doFn: func(t *testing.T) { - newBlobs, err := fullNode.BlobServ.GetAll(ctx, height, []share.Namespace{blobs[0].Namespace()}) + newBlobs, err := fullClient.Blob.GetAll(ctx, height, []share.Namespace{blobs[0].Namespace()}) require.NoError(t, err) require.Len(t, newBlobs, len(appBlobs0)) require.True(t, bytes.Equal(blobs[0].Commitment, newBlobs[0].Commitment)) @@ -88,10 +91,10 @@ func TestBlobModule(t *testing.T) { { name: "Included", doFn: func(t *testing.T) { - proof, err := fullNode.BlobServ.GetProof(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) + proof, err := fullClient.Blob.GetProof(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) require.NoError(t, err) - included, err := lightNode.BlobServ.Included( + included, err := lightClient.Blob.Included( ctx, height, blobs[0].Namespace(), @@ -114,7 +117,7 @@ func TestBlobModule(t *testing.T) { ) require.NoError(t, err) - b, err := fullNode.BlobServ.Get(ctx, height, newBlob.Namespace(), newBlob.Commitment) + b, err := fullClient.Blob.Get(ctx, height, newBlob.Namespace(), newBlob.Commitment) assert.Nil(t, b) require.Error(t, err) require.ErrorIs(t, err, blob.ErrBlobNotFound) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 95c702c0c0..cd5be80517 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -89,9 +89,11 @@ func TestFraudProofHandling(t *testing.T) { err = full.Start(ctx) require.NoError(t, err) + fullClient := getAdminClient(ctx, full, t) + // 5. subCtx, subCancel := context.WithCancel(ctx) - subscr, err := full.FraudServ.Subscribe(subCtx, byzantine.BadEncoding) + subscr, err := fullClient.Fraud.Subscribe(subCtx, byzantine.BadEncoding) require.NoError(t, err) select { case p := <-subscr: @@ -108,7 +110,7 @@ func TestFraudProofHandling(t *testing.T) { // lifecycles of each Module. // 6. syncCtx, syncCancel := context.WithTimeout(context.Background(), blockTime*5) - _, err = full.HeaderServ.WaitForHeight(syncCtx, 15) + _, err = fullClient.Header.WaitForHeight(syncCtx, 15) require.ErrorIs(t, err, context.DeadlineExceeded) syncCancel() @@ -118,10 +120,11 @@ func TestFraudProofHandling(t *testing.T) { lnStore := nodebuilder.MockStore(t, cfg) light := sw.NewNodeWithStore(node.Light, lnStore) require.NoError(t, light.Start(ctx)) + lightClient := getAdminClient(ctx, light, t) // 8. subCtx, subCancel = context.WithCancel(ctx) - subscr, err = light.FraudServ.Subscribe(subCtx, byzantine.BadEncoding) + subscr, err = lightClient.Fraud.Subscribe(subCtx, byzantine.BadEncoding) require.NoError(t, err) select { case p := <-subscr: @@ -135,7 +138,8 @@ func TestFraudProofHandling(t *testing.T) { // 9. fN := sw.NewNodeWithStore(node.Full, store) require.Error(t, fN.Start(ctx)) - proofs, err := fN.FraudServ.Get(ctx, byzantine.BadEncoding) + fNClient := getAdminClient(ctx, fN, t) + proofs, err := fNClient.Fraud.Get(ctx, byzantine.BadEncoding) require.NoError(t, err) require.NotNil(t, proofs) diff --git a/nodebuilder/tests/nd_test.go b/nodebuilder/tests/nd_test.go index cb02e2a178..64d672cddc 100644 --- a/nodebuilder/tests/nd_test.go +++ b/nodebuilder/tests/nd_test.go @@ -45,6 +45,9 @@ func TestShrexNDFromLights(t *testing.T) { err = light.Start(ctx) require.NoError(t, err) + bridgeClient := getAdminClient(ctx, bridge, t) + lightClient := getAdminClient(ctx, light, t) + // wait for chain to be filled require.NoError(t, <-fillDn) @@ -54,7 +57,7 @@ func TestShrexNDFromLights(t *testing.T) { // the block that actually has transactions. We can get this data from the // response returned by FillBlock. for i := 16; i < blocks; i++ { - h, err := bridge.HeaderServ.GetByHeight(ctx, uint64(i)) + h, err := bridgeClient.Header.GetByHeight(ctx, uint64(i)) require.NoError(t, err) reqCtx, cancel := context.WithTimeout(ctx, time.Second*5) @@ -62,9 +65,9 @@ func TestShrexNDFromLights(t *testing.T) { // ensure to fetch random namespace (not the reserved namespace) namespace := h.DAH.RowRoots[1][:share.NamespaceSize] - expected, err := bridge.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + expected, err := bridgeClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) require.NoError(t, err) - got, err := light.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + got, err := lightClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) require.NoError(t, err) require.True(t, len(got[0].Shares) > 0) @@ -113,12 +116,15 @@ func TestShrexNDFromLightsWithBadFulls(t *testing.T) { require.NoError(t, startFullNodes(ctx, fulls...)) require.NoError(t, light.Start(ctx)) + bridgeClient := getAdminClient(ctx, bridge, t) + lightClient := getAdminClient(ctx, light, t) + // wait for chain to fill up require.NoError(t, <-fillDn) // first 2 blocks are not filled with data for i := 3; i < blocks; i++ { - h, err := bridge.HeaderServ.GetByHeight(ctx, uint64(i)) + h, err := bridgeClient.Header.GetByHeight(ctx, uint64(i)) require.NoError(t, err) if len(h.DAH.RowRoots) != bsize*2 { @@ -133,16 +139,18 @@ func TestShrexNDFromLightsWithBadFulls(t *testing.T) { // ensure to fetch random namespace (not the reserved namespace) namespace := h.DAH.RowRoots[1][:share.NamespaceSize] - expected, err := bridge.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + expected, err := bridgeClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) require.NoError(t, err) require.True(t, len(expected[0].Shares) > 0) // choose a random full to test - gotFull, err := fulls[len(fulls)/2].ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + fN := fulls[len(fulls)/2] + fnClient := getAdminClient(ctx, fN, t) + gotFull, err := fnClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) require.NoError(t, err) require.True(t, len(gotFull[0].Shares) > 0) - gotLight, err := light.ShareServ.GetSharesByNamespace(reqCtx, h.DAH, namespace) + gotLight, err := lightClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) require.NoError(t, err) require.True(t, len(gotLight[0].Shares) > 0) diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 083712dfdd..d05846f40c 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -46,7 +46,10 @@ func TestBridgeNodeAsBootstrapper(t *testing.T) { require.NoError(t, nd.Start(ctx)) assert.Equal(t, *addr, nd.Bootstrappers[0]) // ensure that node is actually connected to BN - assert.True(t, nd.Host.Network().Connectedness(addr.ID) == network.Connected) + client := getAdminClient(ctx, nd, t) + connectedenss, err := client.P2P.Connectedness(ctx, addr.ID) + require.NoError(t, err) + assert.Equal(t, connectedenss, network.Connected) } } @@ -102,15 +105,22 @@ func TestFullDiscoveryViaBootstrapper(t *testing.T) { for index := range nodes { require.NoError(t, nodes[index].Start(ctx)) assert.Equal(t, *bootstrapper, nodes[index].Bootstrappers[0]) - assert.True(t, nodes[index].Host.Network().Connectedness(bootstrapper.ID) == network.Connected) + // ensure that node is actually connected to BN + client := getAdminClient(ctx, nodes[index], t) + connectedness, err := client.P2P.Connectedness(ctx, bootstrapper.ID) + require.NoError(t, err) + assert.Equal(t, connectedness, network.Connected) } for { if ctx.Err() != nil { t.Fatal(ctx.Err()) } - if light.Host.Network().Connectedness(host.InfoFromHost(full.Host).ID) == network.Connected { - // LN discovered FN successfully and is now connected + // LN discovered FN successfully and is now connected + client := getAdminClient(ctx, light, t) + connectedness, err := client.P2P.Connectedness(ctx, host.InfoFromHost(full.Host).ID) + require.NoError(t, err) + if connectedness == network.Connected { break } } @@ -158,11 +168,19 @@ func TestRestartNodeDiscovery(t *testing.T) { for index := 0; index < numFulls; index++ { nodes[index] = sw.NewNodeWithConfig(node.Full, fullCfg, nodesConfig) require.NoError(t, nodes[index].Start(ctx)) - assert.True(t, nodes[index].Host.Network().Connectedness(bridgeAddr.ID) == network.Connected) + client := getAdminClient(ctx, nodes[index], t) + connectedness, err := client.P2P.Connectedness(ctx, bridgeAddr.ID) + require.NoError(t, err) + assert.Equal(t, connectedness, network.Connected) } // ensure FNs are connected to each other - require.True(t, nodes[0].Host.Network().Connectedness(nodes[1].Host.ID()) == network.Connected) + fullClient1 := getAdminClient(ctx, nodes[0], t) + fullClient2 := getAdminClient(ctx, nodes[1], t) + + connectedness, err := fullClient1.P2P.Connectedness(ctx, nodes[1].Host.ID()) + require.NoError(t, err) + assert.Equal(t, connectedness, network.Connected) // disconnect the FNs sw.Disconnect(t, nodes[0], nodes[1]) @@ -175,8 +193,13 @@ func TestRestartNodeDiscovery(t *testing.T) { // ensure that the FN with disabled discovery is discovered by both of the // running FNs that have discovery enabled - require.True(t, nodes[0].Host.Network().Connectedness(disabledDiscoveryFN.Host.ID()) == network.Connected) - require.True(t, nodes[1].Host.Network().Connectedness(disabledDiscoveryFN.Host.ID()) == network.Connected) + connectedness, err = fullClient1.P2P.Connectedness(ctx, disabledDiscoveryFN.Host.ID()) + require.NoError(t, err) + assert.Equal(t, connectedness, network.Connected) + + connectedness, err = fullClient2.P2P.Connectedness(ctx, disabledDiscoveryFN.Host.ID()) + require.NoError(t, err) + assert.Equal(t, connectedness, network.Connected) } func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index d8640c5249..3c2e8b1f83 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -53,10 +53,11 @@ func TestFullReconstructFromBridge(t *testing.T) { bridge := sw.NewBridgeNode() err := bridge.Start(ctx) require.NoError(t, err) + bridgeClient := getAdminClient(ctx, bridge, t) // TODO: This is required to avoid flakes coming from unfinished retry // mechanism for the same peer in go-header - _, err = bridge.HeaderServ.WaitForHeight(ctx, uint64(blocks)) + _, err = bridgeClient.Header.WaitForHeight(ctx, uint64(blocks)) require.NoError(t, err) cfg := nodebuilder.DefaultConfig(node.Full) @@ -65,17 +66,18 @@ func TestFullReconstructFromBridge(t *testing.T) { full := sw.NewNodeWithConfig(node.Full, cfg) err = full.Start(ctx) require.NoError(t, err) + fullClient := getAdminClient(ctx, full, t) errg, bctx := errgroup.WithContext(ctx) for i := 1; i <= blocks+1; i++ { i := i errg.Go(func() error { - h, err := full.HeaderServ.WaitForHeight(bctx, uint64(i)) + h, err := fullClient.Header.WaitForHeight(bctx, uint64(i)) if err != nil { return err } - return full.ShareServ.SharesAvailable(bctx, h.DAH) + return fullClient.Share.SharesAvailable(bctx, h.DAH) }) } require.NoError(t, <-fillDn) @@ -106,10 +108,11 @@ func TestFullReconstructFromFulls(t *testing.T) { sw.SetBootstrapper(t, bridge) require.NoError(t, bridge.Start(ctx)) + bridgeClient := getAdminClient(ctx, bridge, t) // TODO: This is required to avoid flakes coming from unfinished retry // mechanism for the same peer in go-header - _, err := bridge.HeaderServ.WaitForHeight(ctx, uint64(blocks)) + _, err := bridgeClient.Header.WaitForHeight(ctx, uint64(blocks)) require.NoError(t, err) lights1 := make([]*nodebuilder.Node, lnodes/2) @@ -175,6 +178,9 @@ func TestFullReconstructFromFulls(t *testing.T) { require.NoError(t, full1.Start(ctx)) require.NoError(t, full2.Start(ctx)) + fullClient1 := getAdminClient(ctx, full1, t) + fullClient2 := getAdminClient(ctx, full2, t) + // Form topology for i := 0; i < lnodes/2; i++ { // Separate light nodes into two subnetworks @@ -198,17 +204,17 @@ func TestFullReconstructFromFulls(t *testing.T) { sw.Disconnect(t, full1, bridge) sw.Disconnect(t, full2, bridge) - h, err := full1.HeaderServ.WaitForHeight(ctx, uint64(10+blocks-1)) + h, err := fullClient1.Header.WaitForHeight(ctx, uint64(10+blocks-1)) require.NoError(t, err) // Ensure that the full nodes cannot reconstruct before being connected to each other ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*30) errg, errCtx = errgroup.WithContext(ctxErr) errg.Go(func() error { - return full1.ShareServ.SharesAvailable(errCtx, h.DAH) + return fullClient1.Share.SharesAvailable(errCtx, h.DAH) }) errg.Go(func() error { - return full2.ShareServ.SharesAvailable(errCtx, h.DAH) + return fullClient1.Share.SharesAvailable(errCtx, h.DAH) }) require.Error(t, errg.Wait()) cancelErr() @@ -218,13 +224,13 @@ func TestFullReconstructFromFulls(t *testing.T) { errg, bctx := errgroup.WithContext(ctx) for i := 10; i < blocks+11; i++ { - h, err := full1.HeaderServ.WaitForHeight(bctx, uint64(i)) + h, err := fullClient1.Header.WaitForHeight(bctx, uint64(i)) require.NoError(t, err) errg.Go(func() error { - return full1.ShareServ.SharesAvailable(bctx, h.DAH) + return fullClient1.Share.SharesAvailable(bctx, h.DAH) }) errg.Go(func() error { - return full2.ShareServ.SharesAvailable(bctx, h.DAH) + return fullClient2.Share.SharesAvailable(bctx, h.DAH) }) } @@ -278,12 +284,14 @@ func TestFullReconstructFromLights(t *testing.T) { } bootstrapper := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, bootstrapper.Start(ctx)) - require.NoError(t, bridge.Start(ctx)) bootstrapperAddr := host.InfoFromHost(bootstrapper.Host) + require.NoError(t, bridge.Start(ctx)) + bridgeClient := getAdminClient(ctx, bridge, t) + // TODO: This is required to avoid flakes coming from unfinished retry // mechanism for the same peer in go-header - _, err = bridge.HeaderServ.WaitForHeight(ctx, uint64(blocks)) + _, err = bridgeClient.Header.WaitForHeight(ctx, uint64(blocks)) require.NoError(t, err) cfg = nodebuilder.DefaultConfig(node.Full) @@ -313,8 +321,11 @@ func TestFullReconstructFromLights(t *testing.T) { return light.Start(errCtx) }) } + require.NoError(t, errg.Wait()) require.NoError(t, full.Start(ctx)) + fullClient := getAdminClient(ctx, full, t) + for i := 0; i < lnodes; i++ { select { case <-ctx.Done(): @@ -328,12 +339,12 @@ func TestFullReconstructFromLights(t *testing.T) { for i := 1; i <= blocks+1; i++ { i := i errg.Go(func() error { - h, err := full.HeaderServ.WaitForHeight(bctx, uint64(i)) + h, err := fullClient.Header.WaitForHeight(bctx, uint64(i)) if err != nil { return err } - return full.ShareServ.SharesAvailable(bctx, h.DAH) + return fullClient.Share.SharesAvailable(bctx, h.DAH) }) } require.NoError(t, <-fillDn) diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 65db1332ff..0bb0e1c757 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -56,8 +56,9 @@ func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { // start bridge and wait for it to sync to 20 err := bridge.Start(ctx) require.NoError(t, err) + bridgeClient := getAdminClient(ctx, bridge, t) - h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) + h, err := bridgeClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) @@ -68,16 +69,17 @@ func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { // start light node and wait for it to sync 20 blocks err = light.Start(ctx) require.NoError(t, err) - h, err = light.HeaderServ.WaitForHeight(ctx, numBlocks) + lightClient := getAdminClient(ctx, light, t) + h, err = lightClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check that the light node has also sampled over the block at height 20 - err = light.ShareServ.SharesAvailable(ctx, h.DAH) + err = lightClient.Share.SharesAvailable(ctx, h.DAH) assert.NoError(t, err) // wait until the entire chain (up to network head) has been sampled - err = light.DASer.WaitCatchUp(ctx) + err = lightClient.DAS.WaitCatchUp(ctx) require.NoError(t, err) }) @@ -87,16 +89,17 @@ func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { // let full node sync 20 blocks err = full.Start(ctx) require.NoError(t, err) - h, err = full.HeaderServ.WaitForHeight(ctx, numBlocks) + fullClient := getAdminClient(ctx, full, t) + h, err = fullClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check to ensure the full node can sync the 20th block's data - err = full.ShareServ.SharesAvailable(ctx, h.DAH) + err = fullClient.Share.SharesAvailable(ctx, h.DAH) assert.NoError(t, err) // wait for full node to sync up the blocks from genesis -> network head. - err = full.DASer.WaitCatchUp(ctx) + err = fullClient.DAS.WaitCatchUp(ctx) require.NoError(t, err) }) @@ -144,7 +147,8 @@ func TestSyncAgainstBridge_EmptyChain(t *testing.T) { // start bridge and wait for it to sync to 20 err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) + bridgeClient := getAdminClient(ctx, bridge, t) + h, err := bridgeClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) @@ -155,16 +159,17 @@ func TestSyncAgainstBridge_EmptyChain(t *testing.T) { // start light node and wait for it to sync 20 blocks err = light.Start(ctx) require.NoError(t, err) - h, err = light.HeaderServ.WaitForHeight(ctx, numBlocks) + lightClient := getAdminClient(ctx, light, t) + h, err = lightClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check that the light node has also sampled over the block at height 20 - err = light.ShareServ.SharesAvailable(ctx, h.DAH) + err = lightClient.Share.SharesAvailable(ctx, h.DAH) assert.NoError(t, err) // wait until the entire chain (up to network head) has been sampled - err = light.DASer.WaitCatchUp(ctx) + err = lightClient.DAS.WaitCatchUp(ctx) require.NoError(t, err) }) @@ -174,16 +179,17 @@ func TestSyncAgainstBridge_EmptyChain(t *testing.T) { // let full node sync 20 blocks err = full.Start(ctx) require.NoError(t, err) - h, err = full.HeaderServ.WaitForHeight(ctx, numBlocks) + fullClient := getAdminClient(ctx, full, t) + h, err = fullClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check to ensure the full node can sync the 20th block's data - err = full.ShareServ.SharesAvailable(ctx, h.DAH) + err = fullClient.Share.SharesAvailable(ctx, h.DAH) assert.NoError(t, err) // wait for full node to sync up the blocks from genesis -> network head. - err = full.DASer.WaitCatchUp(ctx) + err = fullClient.DAS.WaitCatchUp(ctx) require.NoError(t, err) }) } @@ -219,7 +225,8 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { // and let bridge node sync up 20 blocks err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) + bridgeClient := getAdminClient(ctx, bridge, t) + h, err := bridgeClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) @@ -228,7 +235,8 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { // start light node and let it sync to 20 err = light.Start(ctx) require.NoError(t, err) - h, err = light.HeaderServ.WaitForHeight(ctx, numBlocks) + lightClient := getAdminClient(ctx, light, t) + h, err = lightClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) require.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) @@ -239,7 +247,7 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { // ensure when light node comes back up, it can sync the remainder of the chain it // missed while sleeping - h, err = light.HeaderServ.WaitForHeight(ctx, 40) + h, err = lightClient.Header.WaitForHeight(ctx, 40) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, 40)) @@ -284,7 +292,8 @@ func TestSyncLightAgainstFull(t *testing.T) { // start a bridge node and wait for it to sync up 20 blocks err := bridge.Start(ctx) require.NoError(t, err) - h, err := bridge.HeaderServ.WaitForHeight(ctx, numBlocks) + bridgeClient := getAdminClient(ctx, bridge, t) + h, err := bridgeClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) @@ -293,9 +302,10 @@ func TestSyncLightAgainstFull(t *testing.T) { // start FN and wait for it to sync up to head of BN err = full.Start(ctx) require.NoError(t, err) - bridgeHead, err := bridge.HeaderServ.LocalHead(ctx) + fullClient := getAdminClient(ctx, full, t) + bridgeHead, err := bridgeClient.Header.LocalHead(ctx) require.NoError(t, err) - _, err = full.HeaderServ.WaitForHeight(ctx, bridgeHead.Height()) + _, err = fullClient.Header.WaitForHeight(ctx, bridgeHead.Height()) require.NoError(t, err) assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) @@ -315,9 +325,10 @@ func TestSyncLightAgainstFull(t *testing.T) { // start LN and wait for it to sync up to network head against the head of the FN err = light.Start(ctx) require.NoError(t, err) - fullHead, err := full.HeaderServ.LocalHead(ctx) + lightClient := getAdminClient(ctx, light, t) + fullHead, err := fullClient.Header.LocalHead(ctx) require.NoError(t, err) - _, err = light.HeaderServ.WaitForHeight(ctx, fullHead.Height()) + _, err = lightClient.Header.WaitForHeight(ctx, fullHead.Height()) require.NoError(t, err) // wait for the core block filling process to exit @@ -359,7 +370,8 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { // let it sync to network head err := bridge.Start(ctx) require.NoError(t, err) - _, err = bridge.HeaderServ.WaitForHeight(ctx, numBlocks) + bridgeClient := getAdminClient(ctx, bridge, t) + _, err = bridgeClient.Header.WaitForHeight(ctx, numBlocks) require.NoError(t, err) // create a FN with BN as trusted peer @@ -368,7 +380,8 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { // let FN sync to network head err = full.Start(ctx) require.NoError(t, err) - err = full.HeaderServ.SyncWait(ctx) + fullClient := getAdminClient(ctx, full, t) + err = fullClient.Header.SyncWait(ctx) require.NoError(t, err) // add full node as a bootstrapper for the suite @@ -380,7 +393,8 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { // let LN sync to network head err = light.Start(ctx) require.NoError(t, err) - err = light.HeaderServ.SyncWait(ctx) + lightClient := getAdminClient(ctx, light, t) + err = lightClient.Header.SyncWait(ctx) require.NoError(t, err) // wait for the core block filling process to exit From 25ac0020e73a80a2ed3b27cf7d6a5d550995cbe9 Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Wed, 13 Sep 2023 08:34:54 -0400 Subject: [PATCH 0811/1008] Goreleaser (#2661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview **UPDATE** This PR adds building binaries to the release process by using [goreleaser](https://goreleaser.com/). The CI release process is largely unchanged. Releases can be trigger by either manually triggering the `ci_release` workflow, or they can be triggered by pushing a version tag. If the team has not been using the CI to generate releases, so I added a `make goreleaser-release` command to build the binaries for the release locally so that they can be manually uploaded. Result of running `make goreleaser-release`: ``` build └── goreleaser ├── artifacts.json ├── celestia-node_Darwin_arm64.tar.gz ├── celestia-node_Darwin_x86_64.tar.gz ├── celestia-node_Linux_arm64.tar.gz ├── celestia-node_Linux_x86_64.tar.gz ├── celestia-node_darwin_amd64_v1 │   └── celestia ├── celestia-node_darwin_arm64 │   └── celestia ├── celestia-node_linux_amd64_v1 │   └── celestia ├── celestia-node_linux_arm64 │   └── celestia ├── checksums.txt ├── config.yaml └── metadata.json ``` The `.goreleaser.yaml` file is 90% stock generated from `goreleaser init`. The celestia node specific items are: ```yaml builds: - main: ./cmd/celestia binary: celestia env: # NOTE: goreleaser doesn't fully support CGO natively. If CGO is needed # for any node features, this should be removed and a workaround might # need to be created. # REF: https://goreleaser.com/limitations/cgo/ - CGO_ENABLED=0 - VersioningPath={{ "github.com/celestiaorg/celestia-node/nodebuilder/node" }} goos: - linux - darwin goarch: - amd64 - arm64 ldflags: # Ref: https://goreleaser.com/customization/templates/#common-fields # # .CommitDate is used to help with reproducible builds, ensuring that the # same date is always used # # .FullCommit is git commit hash goreleaser is using for the release # # .Version is the version being released - -X "{{ .Env.VersioningPath }}.buildTime={{ .CommitDate }}" - -X "{{ .Env.VersioningPath }}.lastCommit={{ .FullCommit }}" - -X "{{ .Env.VersioningPath }}.semanticVersion={{ .Version }}" dist: ./build/goreleaser ``` For building locally, i added a `make goreleaser-build` command. The binaries are put into a `build/goreleaser` directory. The `make goreleaser` command lists the `goreleaser` commands and also checks the version as a way to verify you have `goreleaser` installed. Result of running `make goreleaser-build`: ``` build └── goreleaser ├── artifacts.json ├── celestia-node_darwin_amd64_v1 │ └── celestia ├── config.yaml └── metadata.json ``` Successful github action run: https://github.com/MSevey/celestia-node/actions/runs/6123729144/job/16622244813 Example release generated: https://github.com/MSevey/celestia-node/releases Created #2445 as a follow up discussion how to add signing. --------- Co-authored-by: Ismail Khoffi Co-authored-by: ramin --- .github/workflows/ci_release.yml | 42 +++++++++++++++++++++--- .goreleaser.yaml | 55 ++++++++++++++++++++++++++++++++ .yamllint.yml | 9 ++++++ Makefile | 17 ++++++++++ 4 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 .goreleaser.yaml create mode 100644 .yamllint.yml diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index e653b2653a..841db11bfa 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -50,19 +50,51 @@ jobs: go-ci: uses: ./.github/workflows/go-ci.yml - # Make a release if this is a manually trigger job, i.e. workflow_dispatch - release: + # If this was a workflow dispatch event, we need to generate and push a tag + # for goreleaser to grab + version_bump: needs: [hadolint, yamllint, markdown-lint, go-ci] runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} permissions: "write-all" steps: - uses: actions/checkout@v4 + - name: Bump version and push tag + # Placing the if condition here is a workaround for needing to block + # on this step during workflow dispatch events but the step not + # needing to run on tags. If we had the if condition on the full + # version_bump section, it would skip and not run, which would result + # in goreleaser not running either. + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: mathieudutour/github-tag-action@v6.0 + - name: Version Release uses: celestiaorg/.github/.github/actions/version-release@v0.2.2 with: - github-token: ${{secrets.GITHUB_TOKEN}} - version-bump: ${{inputs.version}} + github_token: ${{ secrets.GITHUB_TOKEN }} + default_bump: ${{ inputs.version }} + + # Generate the release with goreleaser to include pre-built binaries + goreleaser: + needs: version_bump + runs-on: ubuntu-latest + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) + permissions: "write-all" + steps: + - uses: actions/checkout@v3 + - run: git fetch --force --tags + - uses: actions/setup-go@v4 + with: + go-version: 1.21 + # Generate the binaries and release + - uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # TODO: permission issue, but not worth fixing as this should be refactored # into the celestiaorg/.github repo, at which point any permission issues will diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000000..70336dabc0 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,55 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +before: + hooks: + - go mod tidy +builds: + - main: ./cmd/celestia + binary: celestia + env: + # NOTE: goreleaser doesn't fully support CGO natively. If CGO is needed + # for any node features, this should be removed and a workaround might + # need to be created. + # REF: https://goreleaser.com/limitations/cgo/ + - CGO_ENABLED=0 + - VersioningPath={{ "github.com/celestiaorg/celestia-node/nodebuilder/node" }} + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + ldflags: + # Ref: https://goreleaser.com/customization/templates/#common-fields + # + # .CommitDate is used to help with reproducible builds, ensuring that the + # same date is always used + # + # .FullCommit is git commit hash goreleaser is using for the release + # + # .Version is the version being released + - -X "{{ .Env.VersioningPath }}.buildTime={{ .CommitDate }}" + - -X "{{ .Env.VersioningPath }}.lastCommit={{ .FullCommit }}" + - -X "{{ .Env.VersioningPath }}.semanticVersion={{ .Version }}" +dist: ./build/goreleaser +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of + # uname. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000000..cd2a9e8293 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,9 @@ +--- +# Built from docs https://yamllint.readthedocs.io/en/stable/configuration.html +extends: default + +rules: + # 120 chars should be enough, but don't fail if a line is longer + line-length: + max: 120 + level: warning diff --git a/Makefile b/Makefile index 30b0c18be3..d90075431a 100644 --- a/Makefile +++ b/Makefile @@ -189,3 +189,20 @@ telemetry-infra-up: telemetry-infra-down: PWD="${DIR_FULLPATH}/docker/telemetry" docker-compose -f ./docker/telemetry/docker-compose.yml down .PHONY: telemetry-infra-down + +## goreleaser: List Goreleaser commands and checks if GoReleaser is installed. +goreleaser: Makefile + @echo " Choose a goreleaser command to run:" + @sed -n 's/^## goreleaser/goreleaser/p' $< | column -t -s ':' | sed -e 's/^/ /' + @goreleaser --version +.PHONY: goreleaser + +## goreleaser-build: Builds the celestia binary using GoReleaser for your local OS. +goreleaser-build: + goreleaser build --snapshot --clean --single-target +.PHONY: goreleaser-build + +## goreleaser-release: Builds the release celestia binaries as defined in .goreleaser.yaml. This requires there be a git tag for the release in the local git history. +goreleaser-release: + goreleaser release --clean --fail-fast --skip-publish +.PHONY: goreleaser-release From 7c1f8f30ef62ebb8ee3782aab68019d66a04a26b Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 14 Sep 2023 19:46:47 +0200 Subject: [PATCH 0812/1008] fix(shrexeds): close stream (#2705) --- share/p2p/shrexeds/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/share/p2p/shrexeds/client.go b/share/p2p/shrexeds/client.go index 387594186e..7602bb5fb0 100644 --- a/share/p2p/shrexeds/client.go +++ b/share/p2p/shrexeds/client.go @@ -89,6 +89,7 @@ func (c *Client) doRequest( if err != nil { return nil, fmt.Errorf("failed to open stream: %w", err) } + defer stream.Close() c.setStreamDeadlines(ctx, stream) From 4d9858599052fca6dcc8841bd0a12430cd333266 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:26:45 +0800 Subject: [PATCH 0813/1008] feat(share/eds): Rework accessor cache (#2612) **Motivation** Previously, all accessors went into cache. They were closed only when they were evicted from it and there was no way to manually close one. Now, when we need to cache accessors only in some cases and in some not to, this approach will not work. Enabling control of accessor close required major cache rework. New cache features: - Old cache didn't allow to close and release of an accessor. Now, Instead of returning just a reader/blockstore, cache returns object that composed of builder and `io.Closer` (Accessor interface). This allows caller to have an ability to close and release accessor once it is done with reading from it. - Items stored in cache also provide io.Closer`, but it is noop. Lifecycle of cached items is controlled by cache itself. For now objects are released right after they are evicted from cache, but it could be changed to smarter release based on current readers count. Will be implemented as separate issue. - Lazy blockstore builder. Old cache created blockstore every time accessor was acquired, even when only reader was used and blockstore was not needed. Creating blockstore is costly operation that involves building index, so this was reworked to create blockstore, only when it is requested and store it for subsequent requests. - adds cache unit tests. - adds metrics for cache methods to track cache hits/miss. - general cleanup of unused methods/logic. It will be easier for reviewer to start from `store.go` and `blockstore.go` and then go into new `cache` pkg --- go.mod | 4 +- nodebuilder/store_test.go | 1 + share/eds/accessor_cache.go | 178 ------------------ share/eds/blockstore.go | 63 ++++--- share/eds/blockstore_test.go | 3 + share/eds/cache/accessor_cache.go | 173 +++++++++++++++++ share/eds/cache/accessor_cache_test.go | 246 +++++++++++++++++++++++++ share/eds/cache/cache.go | 49 +++++ share/eds/cache/metrics.go | 66 +++++++ share/eds/cache/noop.go | 31 ++++ share/eds/metrics.go | 24 +-- share/eds/ods_test.go | 6 + share/eds/store.go | 148 ++++++++------- share/eds/store_test.go | 154 ++++++++++++---- share/eds/utils.go | 44 +++++ share/getters/store.go | 10 + share/p2p/shrexeds/server.go | 9 +- 17 files changed, 876 insertions(+), 333 deletions(-) delete mode 100644 share/eds/accessor_cache.go create mode 100644 share/eds/cache/accessor_cache.go create mode 100644 share/eds/cache/accessor_cache_test.go create mode 100644 share/eds/cache/cache.go create mode 100644 share/eds/cache/metrics.go create mode 100644 share/eds/cache/noop.go create mode 100644 share/eds/utils.go diff --git a/go.mod b/go.mod index bb65be4a9d..d3a5771c23 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.4 - github.com/hashicorp/golang-lru v1.0.2 + github.com/hashicorp/golang-lru/v2 v2.0.5 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.12.0 github.com/ipfs/go-block-format v0.2.0 @@ -185,8 +185,8 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/holiman/uint256 v1.2.3 // indirect diff --git a/nodebuilder/store_test.go b/nodebuilder/store_test.go index 8a39849060..8ea56a280f 100644 --- a/nodebuilder/store_test.go +++ b/nodebuilder/store_test.go @@ -154,6 +154,7 @@ func TestStoreRestart(t *testing.T) { require.NoError(t, err) _, err = eds.ReadEDS(ctx, odsReader, h) require.NoError(t, err) + require.NoError(t, edsReader.Close()) } } diff --git a/share/eds/accessor_cache.go b/share/eds/accessor_cache.go deleted file mode 100644 index 9f70178be6..0000000000 --- a/share/eds/accessor_cache.go +++ /dev/null @@ -1,178 +0,0 @@ -package eds - -import ( - "context" - "errors" - "fmt" - "reflect" - "sync" - - "github.com/filecoin-project/dagstore" - "github.com/filecoin-project/dagstore/shard" - lru "github.com/hashicorp/golang-lru" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -var ( - defaultCacheSize = 128 - errCacheMiss = errors.New("accessor not found in blockstore cache") -) - -// accessorWithBlockstore is the value that we store in the blockstore cache -type accessorWithBlockstore struct { - sa *dagstore.ShardAccessor - // blockstore is stored separately because each access to the blockstore over the shard accessor - // reopens the underlying CAR. - bs dagstore.ReadBlockstore -} - -type blockstoreCache struct { - // stripedLocks prevents simultaneous RW access to the blockstore cache for a shard. Instead - // of using only one lock or one lock per key, we stripe the shard keys across 256 locks. 256 is - // chosen because it 0-255 is the range of values we get looking at the last byte of the key. - stripedLocks [256]sync.Mutex - // caches the blockstore for a given shard for shard read affinity i.e. - // further reads will likely be from the same shard. Maps (shard key -> blockstore). - cache *lru.Cache - - metrics *cacheMetrics -} - -func newBlockstoreCache(cacheSize int) (*blockstoreCache, error) { - bc := &blockstoreCache{} - // instantiate the blockstore cache - bslru, err := lru.NewWithEvict(cacheSize, bc.evictFn()) - if err != nil { - return nil, fmt.Errorf("failed to instantiate blockstore cache: %w", err) - } - bc.cache = bslru - return bc, nil -} - -func (bc *blockstoreCache) evictFn() func(_ interface{}, val interface{}) { - return func(_ interface{}, val interface{}) { - // ensure we close the blockstore for a shard when it's evicted so dagstore can gc it. - abs, ok := val.(*accessorWithBlockstore) - if !ok { - panic(fmt.Sprintf( - "casting value from cache to accessorWithBlockstore: %s", - reflect.TypeOf(val), - )) - } - - err := abs.sa.Close() - if err != nil { - log.Errorf("couldn't close accessor after cache eviction: %s", err) - } - bc.metrics.observeEvicted(err != nil) - } -} - -func (bc *blockstoreCache) Remove(key shard.Key) bool { - lk := &bc.stripedLocks[shardKeyToStriped(key)] - lk.Lock() - defer lk.Unlock() - - return bc.cache.Remove(key) -} - -// Get retrieves the blockstore for a given shard key from the cache. If the blockstore is not in -// the cache, it returns an errCacheMiss -func (bc *blockstoreCache) Get(shardContainingCid shard.Key) (*accessorWithBlockstore, error) { - lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)] - lk.Lock() - defer lk.Unlock() - - return bc.unsafeGet(shardContainingCid) -} - -func (bc *blockstoreCache) unsafeGet(shardContainingCid shard.Key) (*accessorWithBlockstore, error) { - // We've already ensured that the given shard has the cid/multihash we are looking for. - val, ok := bc.cache.Get(shardContainingCid) - if !ok { - return nil, errCacheMiss - } - - accessor, ok := val.(*accessorWithBlockstore) - if !ok { - panic(fmt.Sprintf( - "casting value from cache to accessorWithBlockstore: %s", - reflect.TypeOf(val), - )) - } - return accessor, nil -} - -// Add adds a blockstore for a given shard key to the cache. -func (bc *blockstoreCache) Add( - shardContainingCid shard.Key, - accessor *dagstore.ShardAccessor, -) (*accessorWithBlockstore, error) { - lk := &bc.stripedLocks[shardKeyToStriped(shardContainingCid)] - lk.Lock() - defer lk.Unlock() - - return bc.unsafeAdd(shardContainingCid, accessor) -} - -func (bc *blockstoreCache) unsafeAdd( - shardContainingCid shard.Key, - accessor *dagstore.ShardAccessor, -) (*accessorWithBlockstore, error) { - blockStore, err := accessor.Blockstore() - if err != nil { - return nil, fmt.Errorf("failed to get blockstore from accessor: %w", err) - } - - newAccessor := &accessorWithBlockstore{ - bs: blockStore, - sa: accessor, - } - bc.cache.Add(shardContainingCid, newAccessor) - return newAccessor, nil -} - -// shardKeyToStriped returns the index of the lock to use for a given shard key. We use the last -// byte of the shard key as the pseudo-random index. -func shardKeyToStriped(sk shard.Key) byte { - return sk.String()[len(sk.String())-1] -} - -type cacheMetrics struct { - evictedCounter metric.Int64Counter -} - -func (bc *blockstoreCache) withMetrics() error { - evictedCounter, err := meter.Int64Counter("eds_blockstore_cache_evicted_counter", - metric.WithDescription("eds blockstore cache evicted event counter")) - if err != nil { - return err - } - - cacheSize, err := meter.Int64ObservableGauge("eds_blockstore_cache_size", - metric.WithDescription("total amount of items in blockstore cache"), - ) - if err != nil { - return err - } - - callback := func(ctx context.Context, observer metric.Observer) error { - observer.ObserveInt64(cacheSize, int64(bc.cache.Len())) - return nil - } - _, err = meter.RegisterCallback(callback, cacheSize) - if err != nil { - return err - } - bc.metrics = &cacheMetrics{evictedCounter: evictedCounter} - return nil -} - -func (m *cacheMetrics) observeEvicted(failed bool) { - if m == nil { - return - } - m.evictedCounter.Add(context.Background(), 1, metric.WithAttributes( - attribute.Bool(failedKey, failed))) -} diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 2abe4219f1..349d6f58ba 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -5,14 +5,14 @@ import ( "errors" "fmt" - "github.com/filecoin-project/dagstore" bstore "github.com/ipfs/boxo/blockstore" dshelp "github.com/ipfs/boxo/datastore/dshelp" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" ipld "github.com/ipfs/go-ipld-format" + + "github.com/celestiaorg/celestia-node/share/eds/cache" ) var _ bstore.Blockstore = (*blockstore)(nil) @@ -32,18 +32,9 @@ var ( // implementation to allow for the blockstore operations to be routed to the underlying stores. type blockstore struct { store *Store - cache *blockstoreCache ds datastore.Batching } -func newBlockstore(store *Store, cache *blockstoreCache, ds datastore.Batching) *blockstore { - return &blockstore{ - store: store, - cache: cache, - ds: namespace.Wrap(ds, blockstoreCacheKey), - } -} - func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { @@ -63,6 +54,11 @@ func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) + if err == nil { + defer closeAndLog("blockstore", blockstr) + return blockstr.Get(ctx, cid) + } + if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { k := dshelp.MultihashToDsKey(cid.Hash()) blockData, err := bs.ds.Get(ctx, k) @@ -72,15 +68,18 @@ func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error // nmt's GetNode expects an ipld.ErrNotFound when a cid is not found. return nil, ipld.ErrNotFound{Cid: cid} } - if err != nil { - log.Debugf("failed to get blockstore for cid %s: %s", cid, err) - return nil, err - } - return blockstr.Get(ctx, cid) + + log.Debugf("failed to get blockstore for cid %s: %s", cid, err) + return nil, err } func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { blockstr, err := bs.getReadOnlyBlockstore(ctx, cid) + if err == nil { + defer closeAndLog("blockstore", blockstr) + return blockstr.GetSize(ctx, cid) + } + if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { k := dshelp.MultihashToDsKey(cid.Hash()) size, err := bs.ds.GetSize(ctx, k) @@ -90,10 +89,9 @@ func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { // nmt's GetSize expects an ipld.ErrNotFound when a cid is not found. return 0, ipld.ErrNotFound{Cid: cid} } - if err != nil { - return 0, err - } - return blockstr.GetSize(ctx, cid) + + log.Debugf("failed to get size for cid %s: %s", cid, err) + return 0, err } func (bs *blockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { @@ -139,7 +137,7 @@ func (bs *blockstore) HashOnRead(bool) { } // getReadOnlyBlockstore finds the underlying blockstore of the shard that contains the given CID. -func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (dagstore.ReadBlockstore, error) { +func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (*BlockstoreCloser, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) if errors.Is(err, datastore.ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { return nil, ErrNotFound @@ -148,11 +146,28 @@ func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (d return nil, fmt.Errorf("failed to find shards containing multihash: %w", err) } - // a share can exist in multiple EDSes, so just take the first one. + // check if cache contains any of accessors shardKey := keys[0] - accessor, err := bs.store.getCachedAccessor(ctx, shardKey) + if accessor, err := bs.store.cache.Get(shardKey); err == nil { + return blockstoreCloser(accessor) + } + + // load accessor to the cache and use it as blockstoreCloser + accessor, err := bs.store.cache.GetOrLoad(ctx, shardKey, bs.store.getAccessor) if err != nil { return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err) } - return accessor.bs, nil + return blockstoreCloser(accessor) +} + +// blockstoreCloser constructs new BlockstoreCloser from cache.Accessor +func blockstoreCloser(ac cache.Accessor) (*BlockstoreCloser, error) { + bs, err := ac.Blockstore() + if err != nil { + return nil, fmt.Errorf("eds/store: failed to get blockstore: %w", err) + } + return &BlockstoreCloser{ + ReadBlockstore: bs, + Closer: ac, + }, nil } diff --git a/share/eds/blockstore_test.go b/share/eds/blockstore_test.go index 745797bf42..d9dbf7ed30 100644 --- a/share/eds/blockstore_test.go +++ b/share/eds/blockstore_test.go @@ -37,6 +37,9 @@ func TestBlockstore_Operations(t *testing.T) { topLevelBS := edsStore.Blockstore() carBS, err := edsStore.CARBlockstore(ctx, dah.Hash()) require.NoError(t, err) + defer func() { + require.NoError(t, carBS.Close()) + }() root, err := edsStore.GetDAH(ctx, dah.Hash()) require.NoError(t, err) diff --git a/share/eds/cache/accessor_cache.go b/share/eds/cache/accessor_cache.go new file mode 100644 index 0000000000..ff955f8c45 --- /dev/null +++ b/share/eds/cache/accessor_cache.go @@ -0,0 +1,173 @@ +package cache + +import ( + "context" + "fmt" + "io" + "sync" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/shard" + lru "github.com/hashicorp/golang-lru/v2" +) + +var _ Cache = (*AccessorCache)(nil) + +// AccessorCache implements the Cache interface using an LRU cache backend. +type AccessorCache struct { + // The name is a prefix that will be used for cache metrics if they are enabled. + name string + // stripedLocks prevents simultaneous RW access to the blockstore cache for a shard. Instead + // of using only one lock or one lock per key, we stripe the shard keys across 256 locks. 256 is + // chosen because it 0-255 is the range of values we get looking at the last byte of the key. + stripedLocks [256]sync.Mutex + // Caches the blockstore for a given shard for shard read affinity, i.e., further reads will likely + // be from the same shard. Maps (shard key -> blockstore). + cache *lru.Cache[shard.Key, *accessorWithBlockstore] + + metrics *metrics +} + +// accessorWithBlockstore is the value that we store in the blockstore Cache. It implements the +// Accessor interface. +type accessorWithBlockstore struct { + sync.RWMutex + shardAccessor Accessor + // The blockstore is stored separately because each access to the blockstore over the shard + // accessor reopens the underlying CAR. + bs dagstore.ReadBlockstore +} + +// Blockstore implements the Blockstore of the Accessor interface. It creates the blockstore on the +// first request and reuses the created instance for all subsequent requests. +func (s *accessorWithBlockstore) Blockstore() (dagstore.ReadBlockstore, error) { + s.Lock() + defer s.Unlock() + var err error + if s.bs == nil { + s.bs, err = s.shardAccessor.Blockstore() + } + return s.bs, err +} + +// Reader returns a new copy of the reader to read data. +func (s *accessorWithBlockstore) Reader() io.Reader { + return s.shardAccessor.Reader() +} + +func NewAccessorCache(name string, cacheSize int) (*AccessorCache, error) { + bc := &AccessorCache{ + name: name, + } + // Instantiate the blockstore Cache. + bslru, err := lru.NewWithEvict[shard.Key, *accessorWithBlockstore](cacheSize, bc.evictFn()) + if err != nil { + return nil, fmt.Errorf("failed to instantiate blockstore cache: %w", err) + } + bc.cache = bslru + return bc, nil +} + +// evictFn will be invoked when an item is evicted from the cache. +func (bc *AccessorCache) evictFn() func(shard.Key, *accessorWithBlockstore) { + return func(_ shard.Key, abs *accessorWithBlockstore) { + err := abs.shardAccessor.Close() + if err != nil { + bc.metrics.observeEvicted(true) + log.Errorf("couldn't close accessor after cache eviction: %s", err) + return + } + bc.metrics.observeEvicted(false) + } +} + +// Get retrieves the Accessor for a given shard key from the Cache. If the Accessor is not in +// the Cache, it returns an errCacheMiss. +func (bc *AccessorCache) Get(key shard.Key) (Accessor, error) { + lk := &bc.stripedLocks[shardKeyToStriped(key)] + lk.Lock() + defer lk.Unlock() + + accessor, err := bc.get(key) + if err != nil { + bc.metrics.observeGet(false) + return nil, err + } + bc.metrics.observeGet(true) + return newCloser(accessor), nil +} + +func (bc *AccessorCache) get(key shard.Key) (*accessorWithBlockstore, error) { + abs, ok := bc.cache.Get(key) + if !ok { + return nil, errCacheMiss + } + return abs, nil +} + +// GetOrLoad attempts to get an item from the cache, and if not found, invokes +// the provided loader function to load it. +func (bc *AccessorCache) GetOrLoad( + ctx context.Context, + key shard.Key, + loader func(context.Context, shard.Key) (Accessor, error), +) (Accessor, error) { + lk := &bc.stripedLocks[shardKeyToStriped(key)] + lk.Lock() + defer lk.Unlock() + + abs, err := bc.get(key) + if err == nil { + bc.metrics.observeGet(true) + return newCloser(abs), nil + } + + // accessor not found in cache, so load new one using loader + accessor, err := loader(ctx, key) + if err != nil { + return nil, fmt.Errorf("unable to load accessor: %w", err) + } + + abs = &accessorWithBlockstore{ + shardAccessor: accessor, + } + + // Create a new accessor first to increment the reference count in it, so it cannot get evicted + // from the inner lru cache before it is used. + ac := newCloser(abs) + bc.cache.Add(key, abs) + return ac, nil +} + +// Remove removes the Accessor for a given key from the cache. +func (bc *AccessorCache) Remove(key shard.Key) error { + // The cache will call evictFn on removal, where accessor close will be called. + bc.cache.Remove(key) + return nil +} + +// EnableMetrics enables metrics for the cache. +func (bc *AccessorCache) EnableMetrics() error { + var err error + bc.metrics, err = newMetrics(bc) + return err +} + +// accessorCloser is a temporary object before reference counting is implemented. +type accessorCloser struct { + *accessorWithBlockstore + io.Closer +} + +func newCloser(abs *accessorWithBlockstore) *accessorCloser { + return &accessorCloser{ + accessorWithBlockstore: abs, + Closer: io.NopCloser(nil), + } +} + +// shardKeyToStriped returns the index of the lock to use for a given shard key. We use the last +// byte of the shard key as the pseudo-random index. +func shardKeyToStriped(sk shard.Key) byte { + return sk.String()[len(sk.String())-1] +} diff --git a/share/eds/cache/accessor_cache_test.go b/share/eds/cache/accessor_cache_test.go new file mode 100644 index 0000000000..5e928e85cc --- /dev/null +++ b/share/eds/cache/accessor_cache_test.go @@ -0,0 +1,246 @@ +package cache + +import ( + "bytes" + "context" + "errors" + "io" + "testing" + "time" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/shard" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" +) + +func TestAccessorCache(t *testing.T) { + t.Run("add / get item from cache", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{ + data: []byte("test_data"), + } + loaded, err := cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + + // check if item exists + got, err := cache.Get(key) + require.NoError(t, err) + + l, err := io.ReadAll(loaded.Reader()) + require.NoError(t, err) + require.Equal(t, mock.data, l) + g, err := io.ReadAll(got.Reader()) + require.NoError(t, err) + require.Equal(t, mock.data, g) + }) + + t.Run("get blockstore from accessor", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{} + accessor, err := cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + + // check if item exists + _, err = cache.Get(key) + require.NoError(t, err) + + // blockstore should be created only after first request + require.Equal(t, 0, mock.returnedBs) + + // try to get blockstore + _, err = accessor.Blockstore() + require.NoError(t, err) + + // second call to blockstore should return same blockstore + _, err = accessor.Blockstore() + require.NoError(t, err) + require.Equal(t, 1, mock.returnedBs) + }) + + t.Run("remove an item", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{} + ac, err := cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + err = ac.Close() + require.NoError(t, err) + + err = cache.Remove(key) + require.NoError(t, err) + + // accessor should be closed on removal + mock.checkClosed(t, true) + + // check if item exists + _, err = cache.Get(key) + require.ErrorIs(t, err, errCacheMiss) + }) + + t.Run("successive reads should read the same data", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{data: []byte("test")} + accessor, err := cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + + loaded, err := io.ReadAll(accessor.Reader()) + require.NoError(t, err) + require.Equal(t, mock.data, loaded) + + for i := 0; i < 2; i++ { + accessor, err = cache.Get(key) + require.NoError(t, err) + got, err := io.ReadAll(accessor.Reader()) + require.NoError(t, err) + require.Equal(t, mock.data, got) + } + }) + + t.Run("removed by eviction", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{} + ac1, err := cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + err = ac1.Close() + require.NoError(t, err) + + // add second item + key2 := shard.KeyFromString("key2") + ac2, err := cache.GetOrLoad(ctx, key2, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + err = ac2.Close() + require.NoError(t, err) + + // accessor should be closed on removal by eviction + mock.checkClosed(t, true) + + // check if item evicted + _, err = cache.Get(key) + require.ErrorIs(t, err, errCacheMiss) + }) + + t.Run("close on accessor is noop", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{} + _, err = cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + + // check if item exists + accessor, err := cache.Get(key) + require.NoError(t, err) + require.NotNil(t, accessor) + + // close on returned accessor should not close inner reader + err = accessor.Close() + require.NoError(t, err) + + // check that close was not performed on inner accessor + mock.checkClosed(t, false) + }) +} + +type mockAccessor struct { + data []byte + isClosed bool + returnedBs int +} + +func (m *mockAccessor) Reader() io.Reader { + return bytes.NewBuffer(m.data) +} + +func (m *mockAccessor) Blockstore() (dagstore.ReadBlockstore, error) { + if m.returnedBs > 0 { + return nil, errors.New("blockstore already returned") + } + m.returnedBs++ + return rbsMock{}, nil +} + +func (m *mockAccessor) Close() error { + if m.isClosed { + return errors.New("already closed") + } + m.isClosed = true + return nil +} + +func (m *mockAccessor) checkClosed(t *testing.T, expected bool) { + // item will be removed in background, so give it some time to settle + time.Sleep(time.Millisecond * 100) + require.Equal(t, expected, m.isClosed) +} + +// rbsMock is a dagstore.ReadBlockstore mock +type rbsMock struct{} + +func (r rbsMock) Has(context.Context, cid.Cid) (bool, error) { + panic("implement me") +} + +func (r rbsMock) Get(_ context.Context, _ cid.Cid) (blocks.Block, error) { + panic("implement me") +} + +func (r rbsMock) GetSize(context.Context, cid.Cid) (int, error) { + panic("implement me") +} + +func (r rbsMock) AllKeysChan(context.Context) (<-chan cid.Cid, error) { + panic("implement me") +} + +func (r rbsMock) HashOnRead(bool) { + panic("implement me") +} diff --git a/share/eds/cache/cache.go b/share/eds/cache/cache.go new file mode 100644 index 0000000000..13e207d7c0 --- /dev/null +++ b/share/eds/cache/cache.go @@ -0,0 +1,49 @@ +package cache + +import ( + "context" + "errors" + "io" + + "github.com/filecoin-project/dagstore" + "github.com/filecoin-project/dagstore/shard" + logging "github.com/ipfs/go-log/v2" + "go.opentelemetry.io/otel" +) + +var ( + log = logging.Logger("share/eds/cache") + meter = otel.Meter("eds_store_cache") +) + +var ( + errCacheMiss = errors.New("accessor not found in blockstore cache") +) + +// Cache is an interface that defines the basic Cache operations. +type Cache interface { + // Get retrieves an item from the Cache. + Get(shard.Key) (Accessor, error) + + // GetOrLoad attempts to get an item from the Cache and, if not found, invokes + // the provided loader function to load it into the Cache. + GetOrLoad( + ctx context.Context, + key shard.Key, + loader func(context.Context, shard.Key) (Accessor, error), + ) (Accessor, error) + + // Remove removes an item from Cache. + Remove(shard.Key) error + + // EnableMetrics enables metrics in Cache + EnableMetrics() error +} + +// Accessor is a interface type returned by cache, that allows to read raw data by reader or create +// readblockstore +type Accessor interface { + Blockstore() (dagstore.ReadBlockstore, error) + Reader() io.Reader + io.Closer +} diff --git a/share/eds/cache/metrics.go b/share/eds/cache/metrics.go new file mode 100644 index 0000000000..21e52fec10 --- /dev/null +++ b/share/eds/cache/metrics.go @@ -0,0 +1,66 @@ +package cache + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +const ( + cacheFoundKey = "found" + failedKey = "failed" +) + +type metrics struct { + getCounter metric.Int64Counter + evictedCounter metric.Int64Counter +} + +func newMetrics(bc *AccessorCache) (*metrics, error) { + evictedCounter, err := meter.Int64Counter("eds_blockstore_cache_"+bc.name+"_evicted_counter", + metric.WithDescription("eds blockstore cache evicted event counter")) + if err != nil { + return nil, err + } + + getCounter, err := meter.Int64Counter("eds_blockstore_cache_"+bc.name+"_get_counter", + metric.WithDescription("eds blockstore cache evicted event counter")) + if err != nil { + return nil, err + } + + cacheSize, err := meter.Int64ObservableGauge("eds_blockstore_cache_"+bc.name+"_size", + metric.WithDescription("total amount of items in blockstore cache"), + ) + if err != nil { + return nil, err + } + + callback := func(ctx context.Context, observer metric.Observer) error { + observer.ObserveInt64(cacheSize, int64(bc.cache.Len())) + return nil + } + _, err = meter.RegisterCallback(callback, cacheSize) + + return &metrics{ + getCounter: getCounter, + evictedCounter: evictedCounter, + }, err +} + +func (m *metrics) observeEvicted(failed bool) { + if m == nil { + return + } + m.evictedCounter.Add(context.Background(), 1, metric.WithAttributes( + attribute.Bool(failedKey, failed))) +} + +func (m *metrics) observeGet(found bool) { + if m == nil { + return + } + m.getCounter.Add(context.Background(), 1, metric.WithAttributes( + attribute.Bool(cacheFoundKey, found))) +} diff --git a/share/eds/cache/noop.go b/share/eds/cache/noop.go new file mode 100644 index 0000000000..2af94feb1b --- /dev/null +++ b/share/eds/cache/noop.go @@ -0,0 +1,31 @@ +package cache + +import ( + "context" + + "github.com/filecoin-project/dagstore/shard" +) + +var _ Cache = (*NoopCache)(nil) + +// NoopCache implements noop version of Cache interface +type NoopCache struct{} + +func (n NoopCache) Get(shard.Key) (Accessor, error) { + return nil, errCacheMiss +} + +func (n NoopCache) GetOrLoad( + context.Context, shard.Key, + func(context.Context, shard.Key) (Accessor, error), +) (Accessor, error) { + return nil, nil +} + +func (n NoopCache) Remove(shard.Key) error { + return nil +} + +func (n NoopCache) EnableMetrics() error { + return nil +} diff --git a/share/eds/metrics.go b/share/eds/metrics.go index 9d4b2a53ef..8f87643f17 100644 --- a/share/eds/metrics.go +++ b/share/eds/metrics.go @@ -12,7 +12,6 @@ import ( const ( failedKey = "failed" sizeKey = "eds_size" - cachedKey = "cached" putResultKey = "result" putOK putResult = "ok" @@ -45,7 +44,6 @@ type metrics struct { getTime metric.Float64Histogram hasTime metric.Float64Histogram listTime metric.Float64Histogram - getAccessorTime metric.Float64Histogram shardFailureCount metric.Int64Counter @@ -102,12 +100,6 @@ func (s *Store) WithMetrics() error { return err } - getAccessorTime, err := meter.Float64Histogram("eds_store_get_accessor_time_histogram", - metric.WithDescription("eds store get accessor time histogram(s)")) - if err != nil { - return err - } - shardFailureCount, err := meter.Int64Counter("eds_store_shard_failure_counter", metric.WithDescription("eds store OpShardFail counter")) if err != nil { @@ -132,7 +124,7 @@ func (s *Store) WithMetrics() error { return err } - if err = s.cache.withMetrics(); err != nil { + if err = s.cache.EnableMetrics(); err != nil { return err } @@ -160,7 +152,6 @@ func (s *Store) WithMetrics() error { getTime: getTime, hasTime: hasTime, listTime: listTime, - getAccessorTime: getAccessorTime, shardFailureCount: shardFailureCount, longOpTime: longOpTime, gcTime: gcTime, @@ -299,16 +290,3 @@ func (m *metrics) observeList(ctx context.Context, dur time.Duration, failed boo m.listTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) } - -func (m *metrics) observeGetAccessor(ctx context.Context, dur time.Duration, cached, failed bool) { - if m == nil { - return - } - if ctx.Err() != nil { - ctx = context.Background() - } - - m.getAccessorTime.Record(ctx, dur.Seconds(), metric.WithAttributes( - attribute.Bool(cachedKey, cached), - attribute.Bool(failedKey, failed))) -} diff --git a/share/eds/ods_test.go b/share/eds/ods_test.go index 5b6ed5568b..0f7c69e708 100644 --- a/share/eds/ods_test.go +++ b/share/eds/ods_test.go @@ -32,6 +32,9 @@ func TestODSReader(t *testing.T) { // get CAR reader from store r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) + defer func() { + require.NoError(t, r.Close()) + }() // create ODSReader wrapper based on car reader to limit reads to ODS only odsR, err := ODSReader(r) @@ -81,6 +84,9 @@ func TestODSReaderReconstruction(t *testing.T) { // get CAR reader from store r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) + defer func() { + require.NoError(t, r.Close()) + }() // create ODSReader wrapper based on car reader to limit reads to ODS only odsR, err := ODSReader(r) diff --git a/share/eds/store.go b/share/eds/store.go index 974f147292..e8caf4c35a 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -26,6 +26,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/cache" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -38,6 +39,8 @@ const ( // shards that are currently available but inactive, or errored. // We don't use transient files right now, so GC is turned off by default. defaultGCInterval = 0 + + defaultCacheSize = 128 ) var ErrNotFound = errors.New("eds not found in store") @@ -52,8 +55,8 @@ type Store struct { dgstr *dagstore.DAGStore mounts *mount.Registry - cache *blockstoreCache - bs bstore.Blockstore + bs *blockstore + cache cache.Cache carIdx index.FullIndexRepo invertedIdx *simpleInvertedIndex @@ -111,9 +114,9 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to create DAGStore: %w", err) } - cache, err := newBlockstoreCache(defaultCacheSize) + accessorCache, err := cache.NewAccessorCache("cache", defaultCacheSize) if err != nil { - return nil, fmt.Errorf("failed to create blockstore cache: %w", err) + return nil, fmt.Errorf("failed to create recent blocks cache: %w", err) } store := &Store{ @@ -124,9 +127,9 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { gcInterval: defaultGCInterval, mounts: r, shardFailures: failureChan, - cache: cache, + cache: accessorCache, } - store.bs = newBlockstore(store, cache, ds) + store.bs = newBlockstore(store, ds) return store, nil } @@ -177,7 +180,6 @@ func (s *Store) gc(ctx context.Context) { } s.lastGCResult.Store(res) } - } } @@ -237,7 +239,7 @@ func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext if err != nil { return err } - defer f.Close() + defer closeAndLog("car file", f) // save encoded eds into buffer mount := &inMemoryOnceMount{ @@ -261,17 +263,35 @@ func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext return fmt.Errorf("failed to initiate shard registration: %w", err) } + var result dagstore.ShardResult select { + case result = <-ch: case <-ctx.Done(): - // if context finished before result was received, track result in separate goroutine + // if the context finished before the result was received, track the result in a separate goroutine go trackLateResult("put", ch, s.metrics, time.Minute*5) return ctx.Err() - case result := <-ch: - if result.Error != nil { - return fmt.Errorf("failed to register shard: %w", result.Error) - } - return nil } + + if result.Error != nil { + return fmt.Errorf("failed to register shard: %w", result.Error) + } + + // the accessor returned in the result will be nil, so the shard needs to be acquired first to + // become available in the cache. It might take some time, and the result should not affect the put + // operation, so do it in a goroutine + // TODO: Ideally, only recent blocks should be put in the cache, but there is no way right now to + // check such a condition. + go func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + _, err := s.cache.GetOrLoad(ctx, result.Key, s.getAccessor) + if err != nil { + log.Warnw("unable to put accessor to recent blocks accessors cache", "err", err) + return + } + }() + + return nil } // waitForResult waits for a result from the res channel for a maximum duration specified by @@ -292,7 +312,7 @@ func trackLateResult(opName string, res <-chan dagstore.ShardResult, metrics *me } if result.Error != nil { metrics.observeLongOp(context.Background(), opName, time.Since(tnow), longOpFailed) - log.Errorf("failed to register shard after context expired: %v ago, err: %w", time.Since(tnow), result.Error) + log.Errorf("failed to register shard after context expired: %v ago, err: %s", time.Since(tnow), result.Error) return } metrics.observeLongOp(context.Background(), opName, time.Since(tnow), longOpOK) @@ -309,7 +329,7 @@ func trackLateResult(opName string, res <-chan dagstore.ShardResult, metrics *me // // The shard is cached in the Store, so subsequent calls to GetCAR with the same root will use the // same reader. The cache is responsible for closing the underlying reader. -func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { +func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) { ctx, span := tracer.Start(ctx, "store/get-car") tnow := time.Now() r, err := s.getCAR(ctx, root) @@ -318,13 +338,21 @@ func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.Reader, err return r, err } -func (s *Store) getCAR(ctx context.Context, root share.DataHash) (io.Reader, error) { - key := root.String() - accessor, err := s.getCachedAccessor(ctx, shard.KeyFromString(key)) +func (s *Store) getCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) { + key := shard.KeyFromString(root.String()) + accessor, err := s.cache.Get(key) + if err == nil { + return newReadCloser(accessor), nil + } + // If the accessor is not found in the cache, create a new one from dagstore. We don't put the + // accessor in the cache here because getCAR is used by shrex-eds. There is a lower probability, + // compared to other cache put triggers, that the same block will be requested again soon. + shardAccessor, err := s.getAccessor(ctx, key) if err != nil { return nil, fmt.Errorf("failed to get accessor: %w", err) } - return accessor.sa.Reader(), nil + + return newReadCloser(shardAccessor), nil } // Blockstore returns an IPFS blockstore providing access to individual shares/nodes of all EDS @@ -342,25 +370,31 @@ func (s *Store) Blockstore() bstore.Blockstore { func (s *Store) CARBlockstore( ctx context.Context, root share.DataHash, -) (dagstore.ReadBlockstore, error) { +) (*BlockstoreCloser, error) { ctx, span := tracer.Start(ctx, "store/car-blockstore") tnow := time.Now() - r, err := s.carBlockstore(ctx, root) + cbs, err := s.carBlockstore(ctx, root) s.metrics.observeCARBlockstore(ctx, time.Since(tnow), err != nil) utils.SetStatusAndEnd(span, err) - return r, err + return cbs, err } func (s *Store) carBlockstore( ctx context.Context, root share.DataHash, -) (dagstore.ReadBlockstore, error) { +) (*BlockstoreCloser, error) { key := shard.KeyFromString(root.String()) - accessor, err := s.getCachedAccessor(ctx, key) + accessor, err := s.cache.Get(key) + if err == nil { + return blockstoreCloser(accessor) + } + + // if the accessor is not found in the cache, create a new one from dagstore + sa, err := s.getAccessor(ctx, key) if err != nil { - return nil, fmt.Errorf("eds/store: failed to get accessor: %w", err) + return nil, fmt.Errorf("failed to get accessor: %w", err) } - return accessor.bs, nil + return blockstoreCloser(sa) } // GetDAH returns the DataAvailabilityHeader for the EDS identified by DataHash. @@ -374,13 +408,13 @@ func (s *Store) GetDAH(ctx context.Context, root share.DataHash) (*share.Root, e } func (s *Store) getDAH(ctx context.Context, root share.DataHash) (*share.Root, error) { - key := shard.KeyFromString(root.String()) - accessor, err := s.getCachedAccessor(ctx, key) + r, err := s.getCAR(ctx, root) if err != nil { - return nil, fmt.Errorf("eds/store: failed to get accessor: %w", err) + return nil, fmt.Errorf("eds/store: failed to get CAR file: %w", err) } + defer closeAndLog("car reader", r) - carHeader, err := carv1.ReadHeader(bufio.NewReader(accessor.sa.Reader())) + carHeader, err := carv1.ReadHeader(bufio.NewReader(r)) if err != nil { return nil, fmt.Errorf("eds/store: failed to read car header: %w", err) } @@ -405,7 +439,7 @@ func dahFromCARHeader(carHeader *carv1.CarHeader) *share.Root { } } -func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*dagstore.ShardAccessor, error) { +func (s *Store) getAccessor(ctx context.Context, key shard.Key) (cache.Accessor, error) { ch := make(chan dagstore.ShardResult, 1) err := s.dgstr.AcquireShard(ctx, key, ch, dagstore.AcquireOpts{}) if err != nil { @@ -427,33 +461,6 @@ func (s *Store) getAccessor(ctx context.Context, key shard.Key) (*dagstore.Shard } } -func (s *Store) getCachedAccessor(ctx context.Context, key shard.Key) (*accessorWithBlockstore, error) { - lk := &s.cache.stripedLocks[shardKeyToStriped(key)] - lk.Lock() - defer lk.Unlock() - - tnow := time.Now() - accessor, err := s.cache.unsafeGet(key) - if err != nil && err != errCacheMiss { - log.Errorf("unexpected error while reading key from bs cache %s: %s", key, err) - } - if accessor != nil { - s.metrics.observeGetAccessor(ctx, time.Since(tnow), true, false) - return accessor, nil - } - - // wasn't found in cache, so acquire it and add to cache - shardAccessor, err := s.getAccessor(ctx, key) - if err != nil { - s.metrics.observeGetAccessor(ctx, time.Since(tnow), false, err != nil) - return nil, err - } - - a, err := s.cache.unsafeAdd(key, shardAccessor) - s.metrics.observeGetAccessor(ctx, time.Since(tnow), false, err != nil) - return a, err -} - // Remove removes EDS from Store by the given share.Root hash and cleans up all // the indexing. func (s *Store) Remove(ctx context.Context, root share.DataHash) error { @@ -466,14 +473,13 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) error { } func (s *Store) remove(ctx context.Context, root share.DataHash) (err error) { - key := root.String() - - // Remove from accessor cache, so that existing readers are closed and - // DestroyShard can be executed. - s.cache.Remove(shard.KeyFromString(key)) - + key := shard.KeyFromString(root.String()) + // remove open links to accessor from cache + if err := s.cache.Remove(key); err != nil { + log.Warnw("remove accessor from cache", "err", err) + } ch := make(chan dagstore.ShardResult, 1) - err = s.dgstr.DestroyShard(ctx, shard.KeyFromString(key), ch, dagstore.DestroyOpts{}) + err = s.dgstr.DestroyShard(ctx, key, ch, dagstore.DestroyOpts{}) if err != nil { return fmt.Errorf("failed to initiate shard destruction: %w", err) } @@ -488,7 +494,7 @@ func (s *Store) remove(ctx context.Context, root share.DataHash) (err error) { return ctx.Err() } - dropped, err := s.carIdx.DropFullIndex(shard.KeyFromString(key)) + dropped, err := s.carIdx.DropFullIndex(key) if !dropped { log.Warnf("failed to drop index for %s", key) } @@ -496,7 +502,7 @@ func (s *Store) remove(ctx context.Context, root share.DataHash) (err error) { return fmt.Errorf("failed to drop index for %s: %w", key, err) } - err = os.Remove(s.basepath + blocksPath + key) + err = os.Remove(s.basepath + blocksPath + root.String()) if err != nil { return fmt.Errorf("failed to remove CAR file: %w", err) } @@ -522,11 +528,13 @@ func (s *Store) get(ctx context.Context, root share.DataHash) (eds *rsmt2d.Exten utils.SetStatusAndEnd(span, err) }() - f, err := s.GetCAR(ctx, root) + r, err := s.getCAR(ctx, root) if err != nil { return nil, fmt.Errorf("failed to get CAR file: %w", err) } - eds, err = ReadEDS(ctx, f, root) + defer closeAndLog("car reader", r) + + eds, err = ReadEDS(ctx, r, root) if err != nil { return nil, fmt.Errorf("failed to read EDS from CAR file: %w", err) } diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 21239e320a..0d5283e2f2 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -2,6 +2,7 @@ package eds import ( "context" + "io" "os" "sync" "testing" @@ -9,6 +10,7 @@ import ( "github.com/filecoin-project/dagstore" "github.com/filecoin-project/dagstore/shard" + "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" "github.com/ipld/go-car" @@ -19,6 +21,7 @@ import ( "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/cache" "github.com/celestiaorg/celestia-node/share/eds/edstest" ) @@ -72,6 +75,9 @@ func TestEDSStore(t *testing.T) { r, err := edsStore.GetCAR(ctx, dah.Hash()) assert.NoError(t, err) + defer func() { + require.NoError(t, r.Close()) + }() carReader, err := car.NewCarReader(r) assert.NoError(t, err) @@ -107,6 +113,9 @@ func TestEDSStore(t *testing.T) { _, err = os.Stat(edsStore.basepath + blocksPath + dah.String()) assert.NoError(t, err) + // accessor will be registered in cache async on put, so give it some time to settle + time.Sleep(time.Millisecond * 100) + err = edsStore.Remove(ctx, dah.Hash()) assert.NoError(t, err) @@ -143,6 +152,13 @@ func TestEDSStore(t *testing.T) { err = os.Remove(path) assert.NoError(t, err) + // accessor will be registered in cache async on put, so give it some time to settle + time.Sleep(time.Millisecond * 100) + + // remove non-failed accessor from cache + err = edsStore.cache.Remove(shard.KeyFromString(dah.String())) + assert.NoError(t, err) + _, err = edsStore.GetCAR(ctx, dah.Hash()) assert.Error(t, err) @@ -177,22 +193,18 @@ func TestEDSStore(t *testing.T) { assert.True(t, ok) }) - t.Run("BlockstoreCache", func(t *testing.T) { + t.Run("RecentBlocksCache", func(t *testing.T) { eds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) - // key isnt in cache yet, so get returns errCacheMiss + // accessor will be registered in cache async on put, so give it some time to settle + time.Sleep(time.Millisecond * 100) + + // check, that the key is in the cache after put shardKey := shard.KeyFromString(dah.String()) _, err = edsStore.cache.Get(shardKey) - assert.ErrorIs(t, err, errCacheMiss) - - // now get it, so that the key is in the cache - _, err = edsStore.CARBlockstore(ctx, dah.Hash()) assert.NoError(t, err) - _, err = edsStore.cache.Get(shardKey) - assert.NoError(t, err, errCacheMiss) }) t.Run("List", func(t *testing.T) { @@ -256,6 +268,14 @@ func TestEDSStore_GC(t *testing.T) { err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) + // accessor will be registered in cache async on put, so give it some time to settle + time.Sleep(time.Millisecond * 100) + + // remove links to the shard from cache + key := shard.KeyFromString(share.DataHash(dah.Hash()).String()) + err = edsStore.cache.Remove(key) + require.NoError(t, err) + // doesn't exist yet assert.NotContains(t, edsStore.lastGCResult.Load().Shards, shardKey) @@ -281,20 +301,43 @@ func Test_BlockstoreCache(t *testing.T) { err = edsStore.Start(ctx) require.NoError(t, err) + // store eds to the store with noopCache to allow clean cache after put + swap := edsStore.cache + edsStore.cache = cache.NoopCache{} eds, dah := randomEDS(t) err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) - // key isnt in cache yet, so get returns errCacheMiss + // get any key from saved eds + bs, err := edsStore.carBlockstore(ctx, dah.Hash()) + require.NoError(t, err) + defer func() { + require.NoError(t, bs.Close()) + }() + keys, err := bs.AllKeysChan(ctx) + require.NoError(t, err) + var key cid.Cid + select { + case key = <-keys: + case <-ctx.Done(): + t.Fatal("context timeout") + } + + // swap back original cache + edsStore.cache = swap + + // key shouldn't be in cache yet, check for returned errCacheMiss shardKey := shard.KeyFromString(dah.String()) _, err = edsStore.cache.Get(shardKey) - assert.ErrorIs(t, err, errCacheMiss) + require.Error(t, err) - // now get it, so that the key is in the cache - _, err = edsStore.getCachedAccessor(ctx, shardKey) - assert.NoError(t, err) + // now get it from blockstore, to trigger storing to cache + _, err = edsStore.Blockstore().Get(ctx, key) + require.NoError(t, err) + + // should be no errCacheMiss anymore _, err = edsStore.cache.Get(shardKey) - assert.NoError(t, err, errCacheMiss) + require.NoError(t, err) } // Test_CachedAccessor verifies that the reader represented by a cached accessor can be read from @@ -312,26 +355,67 @@ func Test_CachedAccessor(t *testing.T) { err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) - shardKey := shard.KeyFromString(dah.String()) - // adds to cache - cachedAccessor, err := edsStore.getCachedAccessor(ctx, shardKey) - assert.NoError(t, err) - - // first read - carReader, err := car.NewCarReader(cachedAccessor.sa.Reader()) - assert.NoError(t, err) - firstBlock, err := carReader.Next() - assert.NoError(t, err) - - // second read - cachedAccessor, err = edsStore.getCachedAccessor(ctx, shardKey) - assert.NoError(t, err) - carReader, err = car.NewCarReader(cachedAccessor.sa.Reader()) - assert.NoError(t, err) - secondBlock, err := carReader.Next() - assert.NoError(t, err) - - assert.Equal(t, firstBlock, secondBlock) + // accessor will be registered in cache async on put, so give it some time to settle + time.Sleep(time.Millisecond * 100) + + // accessor should be in cache + cachedAccessor, err := edsStore.cache.Get(shard.KeyFromString(dah.String())) + require.NoError(t, err) + + // first read from cached accessor + firstBlock, err := io.ReadAll(cachedAccessor.Reader()) + require.NoError(t, err) + require.NoError(t, cachedAccessor.Close()) + + // second read from cached accessor + cachedAccessor, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) + require.NoError(t, err) + secondBlock, err := io.ReadAll(cachedAccessor.Reader()) + require.NoError(t, err) + require.NoError(t, cachedAccessor.Close()) + + require.Equal(t, firstBlock, secondBlock) +} + +// Test_CachedAccessor verifies that the reader represented by a accessor obtained directly from +// dagstore can be read from multiple times, without exhausting the underlying reader. +func Test_NotCachedAccessor(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + edsStore, err := newStore(t) + require.NoError(t, err) + err = edsStore.Start(ctx) + require.NoError(t, err) + // replace cache with noopCache to + edsStore.cache = cache.NoopCache{} + + eds, dah := randomEDS(t) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(t, err) + + // accessor will be registered in cache async on put, so give it some time to settle + time.Sleep(time.Millisecond * 100) + + // accessor should be in cache + _, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) + require.Error(t, err) + + // first read from direct accessor + carReader, err := edsStore.getCAR(ctx, dah.Hash()) + require.NoError(t, err) + firstBlock, err := io.ReadAll(carReader) + require.NoError(t, err) + require.NoError(t, carReader.Close()) + + // second read from direct accessor + carReader, err = edsStore.getCAR(ctx, dah.Hash()) + require.NoError(t, err) + secondBlock, err := io.ReadAll(carReader) + require.NoError(t, err) + require.NoError(t, carReader.Close()) + + require.Equal(t, firstBlock, secondBlock) } func BenchmarkStore(b *testing.B) { diff --git a/share/eds/utils.go b/share/eds/utils.go new file mode 100644 index 0000000000..e7b24a9aee --- /dev/null +++ b/share/eds/utils.go @@ -0,0 +1,44 @@ +package eds + +import ( + "io" + + "github.com/filecoin-project/dagstore" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + + "github.com/celestiaorg/celestia-node/share/eds/cache" +) + +// readCloser is a helper struct, that combines io.Reader and io.Closer +type readCloser struct { + io.Reader + io.Closer +} + +// BlockstoreCloser represents a blockstore that can also be closed. It combines the functionality +// of a dagstore.ReadBlockstore with that of an io.Closer. +type BlockstoreCloser struct { + dagstore.ReadBlockstore + io.Closer +} + +func newReadCloser(ac cache.Accessor) io.ReadCloser { + return readCloser{ + ac.Reader(), + ac, + } +} + +func newBlockstore(store *Store, ds datastore.Batching) *blockstore { + return &blockstore{ + store: store, + ds: namespace.Wrap(ds, blockstoreCacheKey), + } +} + +func closeAndLog(name string, closer io.Closer) { + if err := closer.Close(); err != nil { + log.Warnw("closing "+name, "err", err) + } +} diff --git a/share/getters/store.go b/share/getters/store.go index 5eca956faa..415c9f047f 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -58,6 +58,11 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) } + defer func() { + if err := bs.Close(); err != nil { + log.Warnw("closing blockstore", "err", err) + } + }() // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) @@ -117,6 +122,11 @@ func (sg *StoreGetter) GetSharesByNamespace( if err != nil { return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) } + defer func() { + if err := bs.Close(); err != nil { + log.Warnw("closing blockstore", "err", err) + } + }() // wrap the read-only CAR blockstore in a getter blockGetter := eds.NewBlockGetter(bs) diff --git a/share/p2p/shrexeds/server.go b/share/p2p/shrexeds/server.go index fffa0e8152..11b99a3438 100644 --- a/share/p2p/shrexeds/server.go +++ b/share/p2p/shrexeds/server.go @@ -100,8 +100,15 @@ func (s *Server) handleStream(stream network.Stream) { // we do not close the reader, so that other requests will not need to re-open the file. // closing is handled by the LRU cache. edsReader, err := s.store.GetCAR(ctx, hash) - status := p2p_pb.Status_OK + var status p2p_pb.Status switch { + case err == nil: + defer func() { + if err := edsReader.Close(); err != nil { + log.Warnw("closing car reader", "err", err) + } + }() + status = p2p_pb.Status_OK case errors.Is(err, eds.ErrNotFound): logger.Warnw("server: request hash not found") s.metrics.ObserveRequests(ctx, 1, p2p.StatusNotFound) From dd15372963b17443417ccefa692e602991ca1015 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:21:01 +0200 Subject: [PATCH 0814/1008] chore(deps): Bump actions/checkout from 3 to 4 (#2718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
    Release notes

    Sourced from actions/checkout's releases.

    v4.0.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/checkout/compare/v3...v4.0.0

    v3.6.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

    v3.5.3

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

    v3.5.2

    What's Changed

    Full Changelog: https://github.com/actions/checkout/compare/v3.5.1...v3.5.2

    v3.5.1

    What's Changed

    New Contributors

    ... (truncated)

    Changelog

    Sourced from actions/checkout's changelog.

    Changelog

    v4.0.0

    v3.6.0

    v3.5.3

    v3.5.2

    v3.5.1

    v3.5.0

    v3.4.0

    v3.3.0

    v3.2.0

    v3.1.0

    v3.0.2

    v3.0.1

    ... (truncated)

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 841db11bfa..6e9293cc38 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -82,7 +82,7 @@ jobs: (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) permissions: "write-all" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: git fetch --force --tags - uses: actions/setup-go@v4 with: From 319674645c05b7965543f23b403952b9e49ecdd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:36:01 +0000 Subject: [PATCH 0815/1008] chore(deps): Bump go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp from 0.40.0 to 0.41.0 (#2713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) from 0.40.0 to 0.41.0.
    Release notes

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's releases.

    Release v1.19.0-rc.1/v0.42.0-rc.1

    This is a release candidate for the v1.19.0/v0.42.0 release. That release is expected to include the v1 release of the OpenTelemetry Go metric SDK and will provide stability guarantees of that SDK. See our versioning policy for more information about these stability guarantees.

    Changed

    • Allow '/' characters in metric instrument names. (#4501)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the Prometheus metric on every Collect if we know the scope is invalid. (#4499)

    New Contributors

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go/compare/v1.18.0...v1.19.0-rc.1

    Release v1.18.0/v0.41.0/v0.0.6

    This release drops the compatibility guarantee of Go 1.19.

    Added

    • Add WithProducer option in go.opentelemetry.op/otel/exporters/prometheus to restore the ability to register producers on the prometheus exporter's manual reader. (#4473)
    • Add IgnoreValue option in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest to allow ignoring values when comparing metrics. (#4447)

    Deprecated

    • The NewMetricExporter in go.opentelemetry.io/otel/bridge/opencensus was deprecated in v0.35.0 (#3541). The deprecation notice format for the function has been corrected to trigger Go documentation and build tooling. (#4470)

    Removed

    • Removed the deprecated go.opentelemetry.io/otel/exporters/jaeger package. (#4467)
    • Removed the deprecated go.opentelemetry.io/otel/example/jaeger package. (#4467)
    • Removed the deprecated go.opentelemetry.io/otel/sdk/metric/aggregation package. (#4468)
    • Removed the deprecated internal packages in go.opentelemetry.io/otel/exporters/otlp and its sub-packages. (#4469)
    • Dropped guaranteed support for versions of Go less than 1.20. (#4481)

    New Contributors

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go/compare/v1.17.0...v1.18.0

    Release v1.17.0/v0.40.0/v0.5.0

    Added

    • Export the ManualReader struct in go.opentelemetry.io/otel/sdk/metric. (#4244)
    • Export the PeriodicReader struct in go.opentelemetry.io/otel/sdk/metric. (#4244)

    ... (truncated)

    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's changelog.

    [1.18.0/0.41.0/0.0.6] 2023-09-12

    This release drops the compatibility guarantee of [Go 1.19].

    Added

    • Add WithProducer option in go.opentelemetry.op/otel/exporters/prometheus to restore the ability to register producers on the prometheus exporter's manual reader. (#4473)
    • Add IgnoreValue option in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest to allow ignoring values when comparing metrics. (#4447)

    Changed

    • Use a TestingT interface instead of *testing.T struct in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest. (#4483)

    Deprecated

    • The NewMetricExporter in go.opentelemetry.io/otel/bridge/opencensus was deprecated in v0.35.0 (#3541). The deprecation notice format for the function has been corrected to trigger Go documentation and build tooling. (#4470)

    Removed

    • Removed the deprecated go.opentelemetry.io/otel/exporters/jaeger package. (#4467)
    • Removed the deprecated go.opentelemetry.io/otel/example/jaeger package. (#4467)
    • Removed the deprecated go.opentelemetry.io/otel/sdk/metric/aggregation package. (#4468)
    • Removed the deprecated internal packages in go.opentelemetry.io/otel/exporters/otlp and its sub-packages. (#4469)
    • Dropped guaranteed support for versions of Go less than 1.20. (#4481)
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp&package-manager=go_modules&previous-version=0.40.0&new-version=0.41.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index d3a5771c23..8cda25d380 100644 --- a/go.mod +++ b/go.mod @@ -57,14 +57,14 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 - go.opentelemetry.io/otel v1.17.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 + go.opentelemetry.io/otel v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0 - go.opentelemetry.io/otel/metric v1.17.0 - go.opentelemetry.io/otel/sdk v1.17.0 - go.opentelemetry.io/otel/sdk/metric v0.40.0 - go.opentelemetry.io/otel/trace v1.17.0 + go.opentelemetry.io/otel/metric v1.18.0 + go.opentelemetry.io/otel/sdk v1.18.0 + go.opentelemetry.io/otel/sdk/metric v0.41.0 + go.opentelemetry.io/otel/trace v1.18.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 go.uber.org/zap v1.25.0 @@ -72,7 +72,7 @@ require ( golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 golang.org/x/text v0.13.0 - google.golang.org/grpc v1.57.0 + google.golang.org/grpc v1.58.0 google.golang.org/protobuf v1.31.0 ) @@ -306,14 +306,14 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.14.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.12.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect diff --git a/go.sum b/go.sum index e29fef53dc..354b14ac32 100644 --- a/go.sum +++ b/go.sum @@ -2380,12 +2380,12 @@ go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzox go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= -go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0 h1:MZbjiZeMmn5wFMORhozpouGKDxj9POHTuU5UA8msBQk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.40.0/go.mod h1:C7tOYVCJmrDTCwxNny0MuUtnDIR3032vFHYke0F2ZrU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0 h1:SZaSbubADNhH2Gxm+1GaZ/cFsGiYefZoodMMX79AOd4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.40.0/go.mod h1:N65FzQDfQH7NY7umgb0U+7ypGKVYKwwE24L6KXT4OA8= +go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= +go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 h1:k0k7hFNDd8K4iOMJXj7s8sHaC4mhTlAeppRmZXLgZ6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 h1:iV3BOgW4fry1Riw9dwypigqlIYWXvSRVT2RJmblzo40= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0/go.mod h1:7PGzqlKrxIRmbj5tlNW0nTkYZ5fHXDgk6Fy8/KjR0CI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 h1:U5GYackKpVKlPrd/5gKMlrTlP2dCESAAFU682VCpieY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0/go.mod h1:aFsJfCEnLzEu9vRRAcUiB/cpRTbVsNdF3OHSPpdjxZQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0 h1:kvWMtSUNVylLVrOE4WLUmBtgziYoCIYUNSpTYtMzVJI= @@ -2393,20 +2393,20 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0/go.mod h go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= -go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= +go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= +go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.17.0 h1:FLN2X66Ke/k5Sg3V623Q7h7nt3cHXaW1FOvKKrW0IpE= -go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= -go.opentelemetry.io/otel/sdk/metric v0.40.0 h1:qOM29YaGcxipWjL5FzpyZDpCYrDREvX0mVlmXdOjCHU= -go.opentelemetry.io/otel/sdk/metric v0.40.0/go.mod h1:dWxHtdzdJvg+ciJUKLTKwrMe5P6Dv3FyDbh8UkfgkVs= +go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY= +go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M= +go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk= +go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= -go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= -go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= +go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= +go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= @@ -2687,8 +2687,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3209,8 +3209,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o= +google.golang.org/grpc v1.58.0/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From fd90764a0e2b939036ddf5a9fdd488db6f3a1aa4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:40:18 +0000 Subject: [PATCH 0816/1008] chore(deps): Bump github.com/hashicorp/golang-lru/v2 from 2.0.5 to 2.0.6 (#2711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/hashicorp/golang-lru/v2](https://github.com/hashicorp/golang-lru) from 2.0.5 to 2.0.6.
    Release notes

    Sourced from github.com/hashicorp/golang-lru/v2's releases.

    golang-lru 2.0.6

    This release removes calling the eviction callback when the Add method is called with an item that is already in the cache (#154); it reverts PR #135 which caused issue #141.

    What's Changed

    Full Changelog: https://github.com/hashicorp/golang-lru/compare/v2.0.5...v2.0.6

    Commits
    • 8f8764f test: Add tests for eviction callback
    • 0ed35fd Revert "call evict on replacing value (#135)"
    • 1e956f5 [COMPLIANCE] Add Copyright and License Headers
    • 3afaa48 Release update arc dependencies to v2.0.5
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/hashicorp/golang-lru/v2&package-manager=go_modules&previous-version=2.0.5&new-version=2.0.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8cda25d380..74f49bec0e 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.4 - github.com/hashicorp/golang-lru/v2 v2.0.5 + github.com/hashicorp/golang-lru/v2 v2.0.6 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.12.0 github.com/ipfs/go-block-format v0.2.0 diff --git a/go.sum b/go.sum index 354b14ac32..8363974ccb 100644 --- a/go.sum +++ b/go.sum @@ -980,8 +980,8 @@ github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= -github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= +github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= From 288e9925a90c5840db440aabc343e364beee8240 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 18 Sep 2023 21:10:50 +0800 Subject: [PATCH 0817/1008] feat(share/store/cache): Split accessor cache into recent and blockstore (#2656) Adds MultiCache. Replaces eds store cache with multicache containing 2 separate caches: - recently added edses (10 accessors, added on put only) - edses requested by ipld (128 accessors, added on request to blockstore from bitswap server) --- share/eds/blockstore.go | 31 +++++++++------------ share/eds/cache/doublecache.go | 51 ++++++++++++++++++++++++++++++++++ share/eds/store.go | 16 +++++++---- share/eds/store_test.go | 4 +-- share/eds/utils.go | 16 +++++++---- 5 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 share/eds/cache/doublecache.go diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 349d6f58ba..9cbb3f4e8a 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -10,9 +10,8 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" ipld "github.com/ipfs/go-ipld-format" - - "github.com/celestiaorg/celestia-node/share/eds/cache" ) var _ bstore.Blockstore = (*blockstore)(nil) @@ -35,6 +34,13 @@ type blockstore struct { ds datastore.Batching } +func newBlockstore(store *Store, ds datastore.Batching) *blockstore { + return &blockstore{ + store: store, + ds: namespace.Wrap(ds, blockstoreCacheKey), + } +} + func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash()) if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) { @@ -146,28 +152,17 @@ func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (* return nil, fmt.Errorf("failed to find shards containing multihash: %w", err) } - // check if cache contains any of accessors + // check if either cache contains an accessor shardKey := keys[0] - if accessor, err := bs.store.cache.Get(shardKey); err == nil { + accessor, err := bs.store.cache.Get(shardKey) + if err == nil { return blockstoreCloser(accessor) } - // load accessor to the cache and use it as blockstoreCloser - accessor, err := bs.store.cache.GetOrLoad(ctx, shardKey, bs.store.getAccessor) + // load accessor to the blockstore cache and use it as blockstoreCloser + accessor, err = bs.store.cache.Second().GetOrLoad(ctx, shardKey, bs.store.getAccessor) if err != nil { return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err) } return blockstoreCloser(accessor) } - -// blockstoreCloser constructs new BlockstoreCloser from cache.Accessor -func blockstoreCloser(ac cache.Accessor) (*BlockstoreCloser, error) { - bs, err := ac.Blockstore() - if err != nil { - return nil, fmt.Errorf("eds/store: failed to get blockstore: %w", err) - } - return &BlockstoreCloser{ - ReadBlockstore: bs, - Closer: ac, - }, nil -} diff --git a/share/eds/cache/doublecache.go b/share/eds/cache/doublecache.go new file mode 100644 index 0000000000..a63eadee9e --- /dev/null +++ b/share/eds/cache/doublecache.go @@ -0,0 +1,51 @@ +package cache + +import ( + "errors" + + "github.com/filecoin-project/dagstore/shard" +) + +// DoubleCache represents a Cache that looks into multiple caches one by one. +type DoubleCache struct { + first, second Cache +} + +// NewDoubleCache creates a new DoubleCache with the provided caches. +func NewDoubleCache(first, second Cache) *DoubleCache { + return &DoubleCache{ + first: first, + second: second, + } +} + +// Get looks for an item in all the caches one by one and returns the Cache found item. +func (mc *DoubleCache) Get(key shard.Key) (Accessor, error) { + ac, err := mc.first.Get(key) + if err == nil { + return ac, nil + } + return mc.second.Get(key) +} + +// Remove removes an item from all underlying caches +func (mc *DoubleCache) Remove(key shard.Key) error { + err1 := mc.first.Remove(key) + err2 := mc.second.Remove(key) + return errors.Join(err1, err2) +} + +func (mc *DoubleCache) First() Cache { + return mc.first +} + +func (mc *DoubleCache) Second() Cache { + return mc.second +} + +func (mc *DoubleCache) EnableMetrics() error { + if err := mc.first.EnableMetrics(); err != nil { + return err + } + return mc.second.EnableMetrics() +} diff --git a/share/eds/store.go b/share/eds/store.go index e8caf4c35a..14df4a4bee 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -40,7 +40,8 @@ const ( // We don't use transient files right now, so GC is turned off by default. defaultGCInterval = 0 - defaultCacheSize = 128 + defaultRecentBlocksCacheSize = 10 + defaultBlockstoreCacheSize = 128 ) var ErrNotFound = errors.New("eds not found in store") @@ -56,7 +57,7 @@ type Store struct { mounts *mount.Registry bs *blockstore - cache cache.Cache + cache *cache.DoubleCache carIdx index.FullIndexRepo invertedIdx *simpleInvertedIndex @@ -114,11 +115,16 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to create DAGStore: %w", err) } - accessorCache, err := cache.NewAccessorCache("cache", defaultCacheSize) + recentBlocksCache, err := cache.NewAccessorCache("recent", defaultRecentBlocksCacheSize) if err != nil { return nil, fmt.Errorf("failed to create recent blocks cache: %w", err) } + blockstoreCache, err := cache.NewAccessorCache("blockstore", defaultBlockstoreCacheSize) + if err != nil { + return nil, fmt.Errorf("failed to create blockstore blocks cache: %w", err) + } + store := &Store{ basepath: basepath, dgstr: dagStore, @@ -127,7 +133,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { gcInterval: defaultGCInterval, mounts: r, shardFailures: failureChan, - cache: accessorCache, + cache: cache.NewDoubleCache(recentBlocksCache, blockstoreCache), } store.bs = newBlockstore(store, ds) return store, nil @@ -284,7 +290,7 @@ func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - _, err := s.cache.GetOrLoad(ctx, result.Key, s.getAccessor) + _, err := s.cache.First().GetOrLoad(ctx, result.Key, s.getAccessor) if err != nil { log.Warnw("unable to put accessor to recent blocks accessors cache", "err", err) return diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 0d5283e2f2..b38a25c827 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -303,7 +303,7 @@ func Test_BlockstoreCache(t *testing.T) { // store eds to the store with noopCache to allow clean cache after put swap := edsStore.cache - edsStore.cache = cache.NoopCache{} + edsStore.cache = cache.NewDoubleCache(cache.NoopCache{}, cache.NoopCache{}) eds, dah := randomEDS(t) err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) @@ -388,7 +388,7 @@ func Test_NotCachedAccessor(t *testing.T) { err = edsStore.Start(ctx) require.NoError(t, err) // replace cache with noopCache to - edsStore.cache = cache.NoopCache{} + edsStore.cache = cache.NewDoubleCache(cache.NoopCache{}, cache.NoopCache{}) eds, dah := randomEDS(t) err = edsStore.Put(ctx, dah.Hash(), eds) diff --git a/share/eds/utils.go b/share/eds/utils.go index e7b24a9aee..3417a2aa62 100644 --- a/share/eds/utils.go +++ b/share/eds/utils.go @@ -1,11 +1,10 @@ package eds import ( + "fmt" "io" "github.com/filecoin-project/dagstore" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" "github.com/celestiaorg/celestia-node/share/eds/cache" ) @@ -30,11 +29,16 @@ func newReadCloser(ac cache.Accessor) io.ReadCloser { } } -func newBlockstore(store *Store, ds datastore.Batching) *blockstore { - return &blockstore{ - store: store, - ds: namespace.Wrap(ds, blockstoreCacheKey), +// blockstoreCloser constructs new BlockstoreCloser from cache.Accessor +func blockstoreCloser(ac cache.Accessor) (*BlockstoreCloser, error) { + bs, err := ac.Blockstore() + if err != nil { + return nil, fmt.Errorf("eds/store: failed to get blockstore: %w", err) } + return &BlockstoreCloser{ + ReadBlockstore: bs, + Closer: ac, + }, nil } func closeAndLog(name string, closer io.Closer) { From eae39fd9fa4edff2b14a034acfc4c934eaac5cc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:51:55 +0000 Subject: [PATCH 0818/1008] chore(deps): Bump mathieudutour/github-tag-action from 6.0 to 6.1 (#2721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [mathieudutour/github-tag-action](https://github.com/mathieudutour/github-tag-action) from 6.0 to 6.1.
    Commits
    • fcfbdce check in prod dependencies
    • dbd80d2 version 6.1
    • a1afa11 Correctly parse create_annotated_tag (#137)
    • 3f9dffd Merge pull request #147 from JohnTitor/nodejs16
    • c4f56e7 Merge pull request #146 from JohnTitor/actions-core
    • 923acce Update @actions/core to v1.10.0
    • c5ababc Update to Node.js 16
    • e923a61 Merge pull request #142 from mathieudutour/dependabot/npm_and_yarn/actions/co...
    • 8bee2b2 Bump @​actions/core from 1.6.0 to 1.9.1
    • 92dc56e Merge pull request #124 from mathieudutour/dependabot/npm_and_yarn/node-fetch...
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mathieudutour/github-tag-action&package-manager=github_actions&previous-version=6.0&new-version=6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- .github/workflows/ci_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 6e9293cc38..7f72a24f13 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -65,7 +65,7 @@ jobs: # version_bump section, it would skip and not run, which would result # in goreleaser not running either. if: ${{ github.event_name == 'workflow_dispatch' }} - uses: mathieudutour/github-tag-action@v6.0 + uses: mathieudutour/github-tag-action@v6.1 - name: Version Release uses: celestiaorg/.github/.github/actions/version-release@v0.2.2 From c134ce74a640d54fbec20f9adf58c381cbb4804a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:59:55 +0000 Subject: [PATCH 0819/1008] chore(deps): Bump goreleaser/goreleaser-action from 4 to 5 (#2719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 4 to 5.
    Release notes

    Sourced from goreleaser/goreleaser-action's releases.

    v5.0.0

    What's Changed

    Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v4.6.0...v5.0.0

    v4.6.0

    Reverts the change to node20 runtime.

    Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v4.5.0...v4.6.0

    v4.5.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v4.4.0...v4.5.0

    v4.4.0

    What's Changed

    Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v4.3.0...v4.4.0

    v4.3.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v4.2.0...v4.3.0

    ... (truncated)

    Commits
    • 7ec5c2b chore(deps): bump @​actions/core from 1.10.0 to 1.10.1 (#434)
    • 3529a65 chore: node 20 as default runtime (#432)
    • d2f6e33 chore(deps): bump crazy-max/ghaction-import-gpg from 5 to 6 (#433)
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=goreleaser/goreleaser-action&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- .github/workflows/ci_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 7f72a24f13..cfe1bafd90 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -88,7 +88,7 @@ jobs: with: go-version: 1.21 # Generate the binaries and release - - uses: goreleaser/goreleaser-action@v4 + - uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser version: latest From 60eb760a43a203b3bb3a3df6886e35925c55ae88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 22:08:25 +0800 Subject: [PATCH 0820/1008] chore(deps): Bump celestiaorg/.github from 0.2.2 to 0.2.3 (#2720) Bumps [celestiaorg/.github](https://github.com/celestiaorg/.github) from 0.2.2 to 0.2.3.
    Release notes

    Sourced from celestiaorg/.github's releases.

    Release v0.2.3

    Fixes unknown/unknown in our ghcr.io packages

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=celestiaorg/.github&package-manager=github_actions&previous-version=0.2.2&new-version=0.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Co-authored-by: Vlad <13818348+walldiss@users.noreply.github.com> --- .github/workflows/ci_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index cfe1bafd90..271cab472f 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -25,7 +25,7 @@ on: jobs: # Dockerfile Linting hadolint: - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.2 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.3 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.2 + - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.3 markdown-lint: name: Markdown Lint @@ -68,7 +68,7 @@ jobs: uses: mathieudutour/github-tag-action@v6.1 - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@v0.2.2 + uses: celestiaorg/.github/.github/actions/version-release@v0.2.3 with: github_token: ${{ secrets.GITHUB_TOKEN }} default_bump: ${{ inputs.version }} From f6d10560b3c3a586bd1dce784f1c1b508fe0789b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:23:18 +0000 Subject: [PATCH 0821/1008] chore(deps): Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp from 1.17.0 to 1.18.0 (#2714) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.17.0 to 1.18.0.
    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp's changelog.

    [1.18.0/0.41.0/0.0.6] 2023-09-12

    This release drops the compatibility guarantee of [Go 1.19].

    Added

    • Add WithProducer option in go.opentelemetry.op/otel/exporters/prometheus to restore the ability to register producers on the prometheus exporter's manual reader. (#4473)
    • Add IgnoreValue option in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest to allow ignoring values when comparing metrics. (#4447)

    Changed

    • Use a TestingT interface instead of *testing.T struct in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest. (#4483)

    Deprecated

    • The NewMetricExporter in go.opentelemetry.io/otel/bridge/opencensus was deprecated in v0.35.0 (#3541). The deprecation notice format for the function has been corrected to trigger Go documentation and build tooling. (#4470)

    Removed

    • Removed the deprecated go.opentelemetry.io/otel/exporters/jaeger package. (#4467)
    • Removed the deprecated go.opentelemetry.io/otel/example/jaeger package. (#4467)
    • Removed the deprecated go.opentelemetry.io/otel/sdk/metric/aggregation package. (#4468)
    • Removed the deprecated internal packages in go.opentelemetry.io/otel/exporters/otlp and its sub-packages. (#4469)
    • Dropped guaranteed support for versions of Go less than 1.20. (#4481)
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp&package-manager=go_modules&previous-version=1.17.0&new-version=1.18.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 74f49bec0e..dc7aeb1676 100644 --- a/go.mod +++ b/go.mod @@ -59,8 +59,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 go.opentelemetry.io/otel v1.18.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 go.opentelemetry.io/otel/metric v1.18.0 go.opentelemetry.io/otel/sdk v1.18.0 go.opentelemetry.io/otel/sdk/metric v0.41.0 diff --git a/go.sum b/go.sum index 8363974ccb..2959221c98 100644 --- a/go.sum +++ b/go.sum @@ -2386,10 +2386,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 h1:k0k7hFNDd8K4iOMJXj go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 h1:iV3BOgW4fry1Riw9dwypigqlIYWXvSRVT2RJmblzo40= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0/go.mod h1:7PGzqlKrxIRmbj5tlNW0nTkYZ5fHXDgk6Fy8/KjR0CI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0 h1:U5GYackKpVKlPrd/5gKMlrTlP2dCESAAFU682VCpieY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.17.0/go.mod h1:aFsJfCEnLzEu9vRRAcUiB/cpRTbVsNdF3OHSPpdjxZQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0 h1:kvWMtSUNVylLVrOE4WLUmBtgziYoCIYUNSpTYtMzVJI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.17.0/go.mod h1:SExUrRYIXhDgEKG4tkiQovd2HTaELiHUsuK08s5Nqx4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0/go.mod h1:IOmXxPrxoxFMXdNy7lfDmE8MzE61YPcurbUm0SMjerI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= From f044c92591abf81c983f3152e16c88807a9dc4bf Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 19 Sep 2023 23:04:59 +0800 Subject: [PATCH 0822/1008] chore(dependabot): add otel group to dependabot (#2722) Adds dependabot rule, to reduce amount of otel dependancies PRs --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 96228eea15..26f1b4da15 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,6 +19,10 @@ updates: - renaynay labels: - kind:deps + groups: + otel: + patterns: + - "^go.opentelemetry.io/otel*" - package-ecosystem: docker directory: "/" schedule: From ebae9b909f83d9d20565270b8f0ad8e1bcfb8560 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:16:30 +0000 Subject: [PATCH 0823/1008] chore(deps): Bump go.uber.org/zap from 1.25.0 to 1.26.0 (#2731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.25.0 to 1.26.0.
    Release notes

    Sourced from go.uber.org/zap's releases.

    v1.26.0

    Enhancements:

    • #1297[]: Add Dict as a Field.
    • #1319[]: Add WithLazy method to Logger which lazily evaluates the structured context.
    • #1350[]: String encoding is much (~50%) faster now.

    Thanks to @​hhk7734, @​jquirke, @​cdvr1993 for their contributions to this release.

    #1297: uber-go/zap#1297 #1319: uber-go/zap#1319 #1350: uber-go/zap#1350

    Changelog

    Sourced from go.uber.org/zap's changelog.

    1.26.0 (14 Sep 2023)

    Enhancements:

    • #1297[]: Add Dict as a Field.
    • #1319[]: Add WithLazy method to Logger which lazily evaluates the structured context.
    • #1350[]: String encoding is much (~50%) faster now.

    Thanks to @​hhk7734, @​jquirke, and @​cdvr1993 for their contributions to this release.

    #1297: uber-go/zap#1297 #1319: uber-go/zap#1319 #1350: uber-go/zap#1350

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.uber.org/zap&package-manager=go_modules&previous-version=1.25.0&new-version=1.26.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dc7aeb1676..40b61e9ef8 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( go.opentelemetry.io/otel/trace v1.18.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 - go.uber.org/zap v1.25.0 + go.uber.org/zap v1.26.0 golang.org/x/crypto v0.13.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 diff --git a/go.sum b/go.sum index 2959221c98..baf55dcb88 100644 --- a/go.sum +++ b/go.sum @@ -2453,8 +2453,8 @@ go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= From 38c516fb7f07a05628c1a0df7f9b57b1859862f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:56:11 +0200 Subject: [PATCH 0824/1008] chore(deps): Bump go.opentelemetry.io/contrib/instrumentation/runtime from 0.43.0 to 0.44.0 (#2732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [go.opentelemetry.io/contrib/instrumentation/runtime](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.43.0 to 0.44.0.
    Release notes

    Sourced from go.opentelemetry.io/contrib/instrumentation/runtime's releases.

    Release v1.19.0/v0.44.0/v0.13.0

    Added

    • Add gcp.gce.instance.name and gcp.gce.instance.hostname resource attributes to go.opentelemetry.io/contrib/detectors/gcp. (#4263)

    Changed

    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/ec2 have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/ecs have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/eks have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/lambda have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda have been upgraded to v1.21.0. (#4265)
      • The faas.execution attribute is now faas.invocation_id.
      • The faas.id attribute is now aws.lambda.invoked_arn.
    • The semantic conventions used by go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws have been upgraded to v1.21.0. (#4265)
    • The http.request.method attribute will only allow known HTTP methods from the metrics generated by go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#4277)

    Removed

    • The high cardinality attributes net.sock.peer.addr, net.sock.peer.port, http.user_agent, enduser.id, and http.client_ip were removed from the metrics generated by go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#4277)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql module is removed. (#4295)

    New Contributors

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go-contrib/compare/v1.18.0...v1.19.0

    Release v1.18.0/v0.43.0/v0.12.0

    Added

    • Add NewMiddleware function in go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#2964)
    • The go.opentelemetry.io/contrib/exporters/autoexport package to provide configuration of trace exporters with useful defaults and environment variable support. (#2753, #4100, #4130, #4132, #4134)
    • WithRouteTag in go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp adds HTTP route attribute to metrics. (#615)
    • Add WithSpanOptions option in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc. (#3768)
    • Add testing support for Go 1.21. (#4233)

    Changed

    • Change interceptors in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to disable SENT/RECEIVED events. Use WithMessageEvents() to turn back on. (#3964)

    Fixed

    • AWS XRay Remote Sampling to cap quotaBalance to 1x quota in go.opentelemetry.io/contrib/samplers/aws/xray. (#3651, #3652)
    • Do not panic when the HTTP request has the "Expect: 100-continue" header in go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace. (#3892)

    ... (truncated)

    Changelog

    Sourced from go.opentelemetry.io/contrib/instrumentation/runtime's changelog.

    [1.19.0/0.44.0/0.13.0] - 2023-09-12

    Added

    • Add gcp.gce.instance.name and gcp.gce.instance.hostname resource attributes to go.opentelemetry.io/contrib/detectors/gcp. (#4263)

    Changed

    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/ec2 have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/ecs have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/eks have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/lambda have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda have been upgraded to v1.21.0. (#4265)
      • The faas.execution attribute is now faas.invocation_id.
      • The faas.id attribute is now aws.lambda.invoked_arn.
    • The semantic conventions used by go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws have been upgraded to v1.21.0. (#4265)
    • The http.request.method attribute will only allow known HTTP methods from the metrics generated by go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#4277)

    Removed

    • The high cardinality attributes net.sock.peer.addr, net.sock.peer.port, http.user_agent, enduser.id, and http.client_ip were removed from the metrics generated by go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#4277)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql module is removed. (#4295)
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.opentelemetry.io/contrib/instrumentation/runtime&package-manager=go_modules&previous-version=0.43.0&new-version=0.44.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 40b61e9ef8..f0192607b3 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 - go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 go.opentelemetry.io/otel v1.18.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 diff --git a/go.sum b/go.sum index baf55dcb88..667c6399c5 100644 --- a/go.sum +++ b/go.sum @@ -2374,8 +2374,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0 h1:NunhgxcK14rU7Hw2gKtV6uCSyohkXPisqneRFjnZNKQ= -go.opentelemetry.io/contrib/instrumentation/runtime v0.43.0/go.mod h1:rwb7icgpDjIhhHqv1qPGw6dDjAdAR7IKAe4PQdzBbsg= +go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 h1:TXu20nL4yYfJlQeqG/D3Ia6b0p2HZmLfJto9hqJTQ/c= +go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0/go.mod h1:tQ5gBnfjndV1su3+DiLuu6rnd9hBBzg4rkRILnjSNFg= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= From 591bc428179990f60c9bbd2a626ee82caa91322d Mon Sep 17 00:00:00 2001 From: hrt/derrandz Date: Wed, 20 Sep 2023 09:13:24 +0100 Subject: [PATCH 0825/1008] docs: Add share p2p documentation (#2008) ## Overview Self-explanatory ## How to Test Changes Launch: ``` $ godoc -http=:6060 ``` And browse the impacted packages. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --------- Co-authored-by: ramin Co-authored-by: Ryan --- blob/service.go | 3 +- share/p2p/doc.go | 18 ++++++++++++ share/p2p/peers/doc.go | 52 +++++++++++++++++++++++++++++++++++ share/p2p/shrexeds/doc.go | 51 ++++++++++++++++++++++++++++++++++ share/p2p/shrexnd/doc.go | 43 +++++++++++++++++++++++++++++ share/p2p/shrexsub/doc.go | 58 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 share/p2p/doc.go create mode 100644 share/p2p/peers/doc.go create mode 100644 share/p2p/shrexeds/doc.go create mode 100644 share/p2p/shrexnd/doc.go create mode 100644 share/p2p/shrexsub/doc.go diff --git a/blob/service.go b/blob/service.go index 05139cdfb5..2130904aa7 100644 --- a/blob/service.go +++ b/blob/service.go @@ -51,7 +51,8 @@ func NewService( } } -// SubmitOptions contains the information about fee and gasLimit price in order to configure the Submit request. +// SubmitOptions contains the information about fee and gasLimit price in order to configure the +// Submit request. type SubmitOptions struct { Fee int64 GasLimit uint64 diff --git a/share/p2p/doc.go b/share/p2p/doc.go new file mode 100644 index 0000000000..991ddf94db --- /dev/null +++ b/share/p2p/doc.go @@ -0,0 +1,18 @@ +// Package p2p provides p2p functionality that powers the share exchange protocols used by celestia-node. +// The available protocols are: +// +// - shrexsub : a floodsub-based pubsub protocol that is used to broadcast/subscribe to the event +// of new EDS in the network to peers. +// +// - shrexnd: a request/response protocol that is used to request shares by namespace or namespace data from peers. +// +// - shrexeds: a request/response protocol that is used to request extended data square shares from peers. +// This protocol exchanges the original data square in between the client and server, and it's up to the +// receiver to compute the extended data square. +// +// This package also defines a peer manager that is used to manage network peers that can be used to exchange +// shares. The peer manager is primarily responsible for providing peers to request shares from, +// and is primarily used by `getters.ShrexGetter` in share/getters/shrex.go. +// +// Find out more about each protocol in their respective sub-packages. +package p2p diff --git a/share/p2p/peers/doc.go b/share/p2p/peers/doc.go new file mode 100644 index 0000000000..bc1647eb42 --- /dev/null +++ b/share/p2p/peers/doc.go @@ -0,0 +1,52 @@ +// Package peers provides a peer manager that handles peer discovery and peer selection for the shrex getter. +// +// The peer manager is responsible for: +// - Discovering peers +// - Selecting peers for data retrieval +// - Validating peers +// - Blacklisting peers +// - Garbage collecting peers +// +// The peer manager is not responsible for: +// - Connecting to peers +// - Disconnecting from peers +// - Sending data to peers +// - Receiving data from peers +// +// The peer manager is a mechanism to store peers from shrexsub, a mechanism that +// handles "peer discovery" and "peer selection" by relying on a shrexsub subscription +// and header subscriptions, such that it listens for new headers and +// new shares and uses this information to pool peers by shares. +// +// This gives the peer manager an ability to block peers that gossip invalid shares, but also access a list of peers +// that are known to have been gossiping valid shares. +// The peers are then returned on request using a round-robin algorithm to return a different peer each time. +// If no peers are found, the peer manager will rely on full nodes retrieved from discovery. +// +// The peer manager is only concerned with recent heights, thus it retrieves peers that +// were active since `initialHeight`. +// The peer manager will also garbage collect peers such that it blacklists peers that +// have been active since `initialHeight` but have been found to be invalid. +// +// The peer manager is passed to the shrex getter and is used at request time to +// select peers for a given data hash for data retrieval. +// +// # Usage +// +// The peer manager is created using [NewManager] constructor: +// +// peerManager := peers.NewManager(headerSub, shrexSub, discovery, host, connGater, opts...) +// +// After creating the peer manager, it should be started to kick off listening and +// validation routines that enable peer selection and retrieval: +// +// err := peerManager.Start(ctx) +// +// The peer manager can be stopped at any time to stop all peer discovery and validation routines: +// +// err := peerManager.Stop(ctx) +// +// The peer manager can be used to select peers for a given datahash for shares retrieval: +// +// peer, err := peerManager.Peer(ctx, hash) +package peers diff --git a/share/p2p/shrexeds/doc.go b/share/p2p/shrexeds/doc.go new file mode 100644 index 0000000000..6bad3061f9 --- /dev/null +++ b/share/p2p/shrexeds/doc.go @@ -0,0 +1,51 @@ +// This package defines a protocol that is used to request +// extended data squares from peers in the network. +// +// This protocol is a request/response protocol that allows for sending requests for extended data squares by data root +// to the peers in the network and receiving a response containing the original data square(s), which is used +// to recompute the extended data square. +// +// The streams are established using the protocol ID: +// +// - "{networkID}/shrex/eds/v0.0.1" where networkID is the network ID of the network. (e.g. "arabica") +// +// When a peer receives a request for extended data squares, it will read +// the original data square from the EDS store by retrieving the underlying +// CARv1 file containing the full extended data square, but will limit reading +// to the original data square shares only. +// The client on the other hand will take care of computing the extended data squares from +// the original data square on receipt. +// +// # Usage +// +// To use a shrexeds client to request extended data squares from a peer, you must +// first create a new `shrexeds.Client` instance by: +// +// client, err := shrexeds.NewClient(params, host) +// +// where `params` is a `shrexeds.Parameters` instance and `host` is a `libp2p.Host` instance. +// +// To request extended data squares from a peer, you must first create a `Client.RequestEDS` instance by: +// +// eds, err := client.RequestEDS(ctx, dataHash, peer) +// +// where: +// - `ctx` is a `context.Context` instance, +// - `dataHash` is the data root of the extended data square and +// - `peer` is the peer ID of the peer to request the extended data square from. +// +// To use a shrexeds server to respond to requests for extended data squares from peers +// you must first create a new `shrexeds.Server` instance by: +// +// server, err := shrexeds.NewServer(params, host, store) +// +// where `params` is a [Parameters] instance, `host` is a libp2p.Host instance and `store` is a [eds.Store] instance. +// +// To start the server, you must call `Start` on the server: +// +// err := server.Start(ctx) +// +// To stop the server, you must call `Stop` on the server: +// +// err := server.Stop(ctx) +package shrexeds diff --git a/share/p2p/shrexnd/doc.go b/share/p2p/shrexnd/doc.go new file mode 100644 index 0000000000..74ba7397e8 --- /dev/null +++ b/share/p2p/shrexnd/doc.go @@ -0,0 +1,43 @@ +// This package defines a protocol that is used to request namespaced data from peers in the network. +// +// This protocol is a request/response protocol that sends a request for specific data that +// lives in a specific namespace ID and receives a response with the data. +// +// The streams are established using the protocol ID: +// +// - "{networkID}/shrex/nd/0.0.1" where networkID is the network ID of the network. (e.g. "arabica") +// +// The protocol uses protobuf to serialize and deserialize messages. +// +// # Usage +// +// To use a shrexnd client to request data from a peer, you must first create a new `shrexnd.Client` instance by: +// +// 1. Create a new client using `NewClient` and pass in the parameters of the protocol and the host: +// +// client, err := shrexnd.NewClient(params, host) +// +// 2. Request data from a peer by calling [Client.RequestND] on the client and +// pass in the context, the data root, the namespace ID and the peer ID: +// +// data, err := client.RequestND(ctx, dataRoot, peerID, namespaceID) +// +// where data is of type [share.NamespacedShares] +// +// To use a shrexnd server to respond to requests from peers, you must first create a new `shrexnd.Server` instance by: +// +// 1. Create a new server using `NewServer` and pass in the parameters of +// the protocol, the host, the store and store share getter: +// +// server, err := shrexnd.NewServer(params, host, store, storeShareGetter) +// +// where store is of type [share.Store] and storeShareGetter is of type [share.Getter] +// +// 2. Start the server by calling `Start` on the server: +// +// err := server.Start(ctx) +// +// 3. Stop the server by calling `Stop` on the server: +// +// err := server.Stop(ctx) +package shrexnd diff --git a/share/p2p/shrexsub/doc.go b/share/p2p/shrexsub/doc.go new file mode 100644 index 0000000000..95d08361a2 --- /dev/null +++ b/share/p2p/shrexsub/doc.go @@ -0,0 +1,58 @@ +// This package defines a protocol that is used to broadcast shares to peers over a pubsub network. +// +// This protocol runs on a rudimentary floodsub network is primarily a pubsub protocol +// that broadcasts and listens for shares over a pubsub topic. +// +// The pubsub topic used by this protocol is: +// +// "{networkID}/eds-sub/v0.1.0" +// +// where networkID is the network ID of the celestia-node that is running the protocol. (e.g. "arabica") +// +// # Usage +// +// To use this protocol, you must first create a new `shrexsub.PubSub` instance by: +// +// pubsub, err := shrexsub.NewPubSub(ctx, host, networkID) +// +// where host is the libp2p host that is running the protocol, and networkID is the network ID of the celestia-node +// that is running the protocol. +// +// After this, you can start the pubsub protocol by: +// +// err := pubsub.Start(ctx) +// +// Once you have started the `shrexsub.PubSub` instance, you can broadcast a share by: +// +// err := pubsub.Broadcast(ctx, notification) +// +// where `notification` is of type [shrexsub.Notification]. +// +// and `DataHash` is the hash of the share that you want to broadcast, and `Height` is the height of the share. +// +// You can also subscribe to the pubsub topic by: +// +// sub, err := pubsub.Subscribe(ctx) +// +// and then receive notifications by: +// +// for { +// select { +// case <-ctx.Done(): +// sub.Cancel() +// return +// case notification, err := <-sub.Next(): +// // handle notification or err +// } +// } +// +// You can also manipulate the received pubsub messages by using the [PubSub.AddValidator] method: +// +// pubsub.AddValidator(validator ValidatorFn) +// +// where `validator` is of type [shrexsub.ValidatorFn] and `Notification` is the same as above. +// +// You can also stop the pubsub protocol by: +// +// err := pubsub.Stop(ctx) +package shrexsub From 953a349f31b5320a60816ac3dc519269609bd158 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 21 Sep 2023 15:04:40 +0800 Subject: [PATCH 0826/1008] feat(share/eds/cache): Add accessor cache refs counter (#2723) Adds refs counting to accessor cache, to prevent situations when underlying accessor is closed while there are readers that uses it. --- das/options.go | 3 +- nodebuilder/blob/mocks/api.go | 3 +- share/eds/cache/accessor_cache.go | 127 +++++++++++++++++++++---- share/eds/cache/accessor_cache_test.go | 97 ++++++++++++++++++- share/eds/cache/metrics.go | 5 +- share/eds/cache/noop.go | 21 +++- share/eds/store.go | 9 +- share/eds/store_test.go | 21 ++-- 8 files changed, 249 insertions(+), 37 deletions(-) diff --git a/das/options.go b/das/options.go index e38e488c04..6af8a02174 100644 --- a/das/options.go +++ b/das/options.go @@ -52,7 +52,8 @@ func DefaultParameters() Parameters { ConcurrencyLimit: concurrencyLimit, BackgroundStoreInterval: 10 * time.Minute, SampleFrom: 1, - // SampleTimeout = approximate block time (with a bit of wiggle room) * max amount of catchup workers + // SampleTimeout = approximate block time (with a bit of wiggle room) * max amount of catchup + // workers SampleTimeout: 15 * time.Second * time.Duration(concurrencyLimit), } } diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index 6a994f4d7c..40bf299da1 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + blob "github.com/celestiaorg/celestia-node/blob" share "github.com/celestiaorg/celestia-node/share" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/share/eds/cache/accessor_cache.go b/share/eds/cache/accessor_cache.go index ff955f8c45..6f937818f8 100644 --- a/share/eds/cache/accessor_cache.go +++ b/share/eds/cache/accessor_cache.go @@ -2,15 +2,20 @@ package cache import ( "context" + "errors" "fmt" "io" "sync" + "sync/atomic" + "time" "github.com/filecoin-project/dagstore" "github.com/filecoin-project/dagstore/shard" lru "github.com/hashicorp/golang-lru/v2" ) +const defaultCloseTimeout = time.Minute + var _ Cache = (*AccessorCache)(nil) // AccessorCache implements the Cache interface using an LRU cache backend. @@ -36,6 +41,10 @@ type accessorWithBlockstore struct { // The blockstore is stored separately because each access to the blockstore over the shard // accessor reopens the underlying CAR. bs dagstore.ReadBlockstore + + done chan struct{} + refs atomic.Int32 + isClosed bool } // Blockstore implements the Blockstore of the Accessor interface. It creates the blockstore on the @@ -55,6 +64,51 @@ func (s *accessorWithBlockstore) Reader() io.Reader { return s.shardAccessor.Reader() } +func (s *accessorWithBlockstore) addRef() error { + s.Lock() + defer s.Unlock() + if s.isClosed { + // item is already closed and soon will be removed after all refs are released + return errCacheMiss + } + if s.refs.Add(1) == 1 { + // there were no refs previously and done channel was closed, reopen it by recreating + s.done = make(chan struct{}) + } + return nil +} + +func (s *accessorWithBlockstore) removeRef() { + s.Lock() + defer s.Unlock() + if s.refs.Add(-1) <= 0 { + close(s.done) + } +} + +func (s *accessorWithBlockstore) close() error { + s.Lock() + if s.isClosed { + s.Unlock() + // accessor will be closed by another goroutine + return nil + } + s.isClosed = true + done := s.done + s.Unlock() + + select { + case <-done: + case <-time.After(defaultCloseTimeout): + return fmt.Errorf("closing accessor, some readers didn't close the accessor within timeout,"+ + " amount left: %v", s.refs.Load()) + } + if err := s.shardAccessor.Close(); err != nil { + return fmt.Errorf("closing accessor: %w", err) + } + return nil +} + func NewAccessorCache(name string, cacheSize int) (*AccessorCache, error) { bc := &AccessorCache{ name: name, @@ -71,13 +125,16 @@ func NewAccessorCache(name string, cacheSize int) (*AccessorCache, error) { // evictFn will be invoked when an item is evicted from the cache. func (bc *AccessorCache) evictFn() func(shard.Key, *accessorWithBlockstore) { return func(_ shard.Key, abs *accessorWithBlockstore) { - err := abs.shardAccessor.Close() - if err != nil { - bc.metrics.observeEvicted(true) - log.Errorf("couldn't close accessor after cache eviction: %s", err) - return - } - bc.metrics.observeEvicted(false) + // we can release accessor from cache early, while it is being closed in parallel routine + go func() { + err := abs.close() + if err != nil { + bc.metrics.observeEvicted(true) + log.Errorf("couldn't close accessor after cache eviction: %s", err) + return + } + bc.metrics.observeEvicted(false) + }() } } @@ -94,7 +151,7 @@ func (bc *AccessorCache) Get(key shard.Key) (Accessor, error) { return nil, err } bc.metrics.observeGet(true) - return newCloser(accessor), nil + return newRefCloser(accessor) } func (bc *AccessorCache) get(key shard.Key) (*accessorWithBlockstore, error) { @@ -118,8 +175,12 @@ func (bc *AccessorCache) GetOrLoad( abs, err := bc.get(key) if err == nil { - bc.metrics.observeGet(true) - return newCloser(abs), nil + // return accessor, only of it is not closed yet + accessorWithRef, err := newRefCloser(abs) + if err == nil { + bc.metrics.observeGet(true) + return accessorWithRef, nil + } } // accessor not found in cache, so load new one using loader @@ -134,13 +195,27 @@ func (bc *AccessorCache) GetOrLoad( // Create a new accessor first to increment the reference count in it, so it cannot get evicted // from the inner lru cache before it is used. - ac := newCloser(abs) + accessorWithRef, err := newRefCloser(abs) + if err != nil { + return nil, err + } bc.cache.Add(key, abs) - return ac, nil + return accessorWithRef, nil } // Remove removes the Accessor for a given key from the cache. func (bc *AccessorCache) Remove(key shard.Key) error { + lk := &bc.stripedLocks[shardKeyToStriped(key)] + lk.Lock() + accessor, err := bc.get(key) + lk.Unlock() + if errors.Is(err, errCacheMiss) { + // item is not in cache + return nil + } + if err = accessor.close(); err != nil { + return err + } // The cache will call evictFn on removal, where accessor close will be called. bc.cache.Remove(key) return nil @@ -153,17 +228,31 @@ func (bc *AccessorCache) EnableMetrics() error { return err } -// accessorCloser is a temporary object before reference counting is implemented. -type accessorCloser struct { +// refCloser manages references to accessor from provided reader and removes the ref, when the +// Close is called +type refCloser struct { *accessorWithBlockstore - io.Closer + closeFn func() } -func newCloser(abs *accessorWithBlockstore) *accessorCloser { - return &accessorCloser{ - accessorWithBlockstore: abs, - Closer: io.NopCloser(nil), +// newRefCloser creates new refCloser +func newRefCloser(abs *accessorWithBlockstore) (*refCloser, error) { + if err := abs.addRef(); err != nil { + return nil, err } + + var closeOnce sync.Once + return &refCloser{ + accessorWithBlockstore: abs, + closeFn: func() { + closeOnce.Do(abs.removeRef) + }, + }, nil +} + +func (c *refCloser) Close() error { + c.closeFn() + return nil } // shardKeyToStriped returns the index of the lock to use for a given shard key. We use the last diff --git a/share/eds/cache/accessor_cache_test.go b/share/eds/cache/accessor_cache_test.go index 5e928e85cc..4f12301dee 100644 --- a/share/eds/cache/accessor_cache_test.go +++ b/share/eds/cache/accessor_cache_test.go @@ -162,7 +162,7 @@ func TestAccessorCache(t *testing.T) { require.ErrorIs(t, err, errCacheMiss) }) - t.Run("close on accessor is noop", func(t *testing.T) { + t.Run("close on accessor is not closing underlying accessor", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() cache, err := NewAccessorCache("test", 1) @@ -181,13 +181,106 @@ func TestAccessorCache(t *testing.T) { require.NoError(t, err) require.NotNil(t, accessor) - // close on returned accessor should not close inner reader + // close on returned accessor should not close inner accessor err = accessor.Close() require.NoError(t, err) // check that close was not performed on inner accessor mock.checkClosed(t, false) }) + + t.Run("close on accessor should wait all readers to finish", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key := shard.KeyFromString("key") + mock := &mockAccessor{} + accessor1, err := cache.GetOrLoad(ctx, key, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock, nil + }) + require.NoError(t, err) + + // create second readers + accessor2, err := cache.Get(key) + require.NoError(t, err) + + // initialize close + done := make(chan struct{}) + go func() { + err = cache.Remove(key) + close(done) + }() + + // close on first reader and check that it is not enough to release the inner accessor + err = accessor1.Close() + require.NoError(t, err) + mock.checkClosed(t, false) + + // second close from same reader should not release accessor either + err = accessor1.Close() + require.NoError(t, err) + mock.checkClosed(t, false) + + // reads for item that is being evicted should result in errCacheMiss + _, err = cache.Get(key) + require.ErrorIs(t, err, errCacheMiss) + + // close second reader and wait for accessor to be closed + err = accessor2.Close() + require.NoError(t, err) + // wait until close is performed on accessor + select { + case <-done: + case <-ctx.Done(): + t.Fatal("timeout reached") + } + + // item will be removed + mock.checkClosed(t, true) + }) + + t.Run("slow reader should not block eviction", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + cache, err := NewAccessorCache("test", 1) + require.NoError(t, err) + + // add accessor to the cache + key1 := shard.KeyFromString("key1") + mock1 := &mockAccessor{} + accessor1, err := cache.GetOrLoad(ctx, key1, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock1, nil + }) + require.NoError(t, err) + + // add second accessor, to trigger eviction of the first one + key2 := shard.KeyFromString("key2") + mock2 := &mockAccessor{} + accessor2, err := cache.GetOrLoad(ctx, key2, func(ctx context.Context, key shard.Key) (Accessor, error) { + return mock2, nil + }) + require.NoError(t, err) + + // first accessor should be evicted from cache + _, err = cache.Get(key1) + require.ErrorIs(t, err, errCacheMiss) + + // first accessor should not be closed before all refs are released by Close() is calls. + mock1.checkClosed(t, false) + + // after Close() is called on first accessor, it is free to get closed + err = accessor1.Close() + require.NoError(t, err) + mock1.checkClosed(t, true) + + // after Close called on second accessor, it should stay in cache (not closed) + err = accessor2.Close() + require.NoError(t, err) + mock2.checkClosed(t, false) + }) } type mockAccessor struct { diff --git a/share/eds/cache/metrics.go b/share/eds/cache/metrics.go index 21e52fec10..24056aa553 100644 --- a/share/eds/cache/metrics.go +++ b/share/eds/cache/metrics.go @@ -53,8 +53,9 @@ func (m *metrics) observeEvicted(failed bool) { if m == nil { return } - m.evictedCounter.Add(context.Background(), 1, metric.WithAttributes( - attribute.Bool(failedKey, failed))) + m.evictedCounter.Add(context.Background(), 1, + metric.WithAttributes( + attribute.Bool(failedKey, failed))) } func (m *metrics) observeGet(found bool) { diff --git a/share/eds/cache/noop.go b/share/eds/cache/noop.go index 2af94feb1b..0a1a39ec7e 100644 --- a/share/eds/cache/noop.go +++ b/share/eds/cache/noop.go @@ -2,7 +2,9 @@ package cache import ( "context" + "io" + "github.com/filecoin-project/dagstore" "github.com/filecoin-project/dagstore/shard" ) @@ -19,7 +21,7 @@ func (n NoopCache) GetOrLoad( context.Context, shard.Key, func(context.Context, shard.Key) (Accessor, error), ) (Accessor, error) { - return nil, nil + return NoopAccessor{}, nil } func (n NoopCache) Remove(shard.Key) error { @@ -29,3 +31,20 @@ func (n NoopCache) Remove(shard.Key) error { func (n NoopCache) EnableMetrics() error { return nil } + +var _ Accessor = (*NoopAccessor)(nil) + +// NoopAccessor implements noop version of Accessor interface +type NoopAccessor struct{} + +func (n NoopAccessor) Blockstore() (dagstore.ReadBlockstore, error) { + return nil, nil +} + +func (n NoopAccessor) Reader() io.Reader { + return nil +} + +func (n NoopAccessor) Close() error { + return nil +} diff --git a/share/eds/store.go b/share/eds/store.go index 14df4a4bee..c2685cabb2 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -122,7 +122,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { blockstoreCache, err := cache.NewAccessorCache("blockstore", defaultBlockstoreCacheSize) if err != nil { - return nil, fmt.Errorf("failed to create blockstore blocks cache: %w", err) + return nil, fmt.Errorf("failed to create blockstore cache: %w", err) } store := &Store{ @@ -290,11 +290,16 @@ func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - _, err := s.cache.First().GetOrLoad(ctx, result.Key, s.getAccessor) + ac, err := s.cache.First().GetOrLoad(ctx, result.Key, s.getAccessor) if err != nil { log.Warnw("unable to put accessor to recent blocks accessors cache", "err", err) return } + + // need to close returned accessor to remove the reader reference + if err := ac.Close(); err != nil { + log.Warnw("unable to close accessor after loading", "err", err) + } }() return nil diff --git a/share/eds/store_test.go b/share/eds/store_test.go index b38a25c827..7a08c737c4 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -272,6 +272,7 @@ func TestEDSStore_GC(t *testing.T) { time.Sleep(time.Millisecond * 100) // remove links to the shard from cache + time.Sleep(time.Millisecond * 100) key := shard.KeyFromString(share.DataHash(dah.Hash()).String()) err = edsStore.cache.Remove(key) require.NoError(t, err) @@ -359,20 +360,22 @@ func Test_CachedAccessor(t *testing.T) { time.Sleep(time.Millisecond * 100) // accessor should be in cache - cachedAccessor, err := edsStore.cache.Get(shard.KeyFromString(dah.String())) + _, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) require.NoError(t, err) // first read from cached accessor - firstBlock, err := io.ReadAll(cachedAccessor.Reader()) + carReader, err := edsStore.getCAR(ctx, dah.Hash()) + require.NoError(t, err) + firstBlock, err := io.ReadAll(carReader) require.NoError(t, err) - require.NoError(t, cachedAccessor.Close()) + require.NoError(t, carReader.Close()) // second read from cached accessor - cachedAccessor, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) + carReader, err = edsStore.getCAR(ctx, dah.Hash()) require.NoError(t, err) - secondBlock, err := io.ReadAll(cachedAccessor.Reader()) + secondBlock, err := io.ReadAll(carReader) require.NoError(t, err) - require.NoError(t, cachedAccessor.Close()) + require.NoError(t, carReader.Close()) require.Equal(t, firstBlock, secondBlock) } @@ -397,18 +400,18 @@ func Test_NotCachedAccessor(t *testing.T) { // accessor will be registered in cache async on put, so give it some time to settle time.Sleep(time.Millisecond * 100) - // accessor should be in cache + // accessor should not be in cache _, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) require.Error(t, err) - // first read from direct accessor + // first read from direct accessor (not from cache) carReader, err := edsStore.getCAR(ctx, dah.Hash()) require.NoError(t, err) firstBlock, err := io.ReadAll(carReader) require.NoError(t, err) require.NoError(t, carReader.Close()) - // second read from direct accessor + // second read from direct accessor (not from cache) carReader, err = edsStore.getCAR(ctx, dah.Hash()) require.NoError(t, err) secondBlock, err := io.ReadAll(carReader) From 15b8bcc8e67274c5b7ff0a7cd83a11660c360ba7 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:45:40 +0800 Subject: [PATCH 0827/1008] feat(share/eds/store): expose eds store params (#2724) Adds exported params to allow control of eds store configuration --- core/exchange_test.go | 3 +- libs/edssser/edssser.go | 3 +- nodebuilder/share/config.go | 5 ++++ nodebuilder/share/module.go | 2 +- nodebuilder/share/share_test.go | 3 +- nodebuilder/store_test.go | 10 +++---- nodebuilder/tests/fraud_test.go | 4 +-- nodebuilder/tests/swamp/swamp.go | 3 +- share/eds/store.go | 30 +++++++++----------- share/eds/store_options.go | 43 +++++++++++++++++++++++++++++ share/eds/store_test.go | 6 ++-- share/getters/getter_test.go | 11 ++++---- share/getters/shrex_test.go | 3 +- share/p2p/peers/options.go | 2 +- share/p2p/shrexeds/exchange_test.go | 4 +-- share/p2p/shrexnd/exchange_test.go | 4 +-- 16 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 share/eds/store_options.go diff --git a/core/exchange_test.go b/core/exchange_test.go index 579c69fba6..8aa62593c8 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -43,7 +43,8 @@ func createCoreFetcher(t *testing.T, cfg *testnode.Config) (*BlockFetcher, testn func createStore(t *testing.T) *eds.Store { t.Helper() - store, err := eds.NewStore(t.TempDir(), ds_sync.MutexWrap(ds.NewMapDatastore())) + storeCfg := eds.DefaultParameters() + store, err := eds.NewStore(storeCfg, t.TempDir(), ds_sync.MutexWrap(ds.NewMapDatastore())) require.NoError(t, err) return store } diff --git a/libs/edssser/edssser.go b/libs/edssser/edssser.go index fd11b47fcf..34712b785a 100644 --- a/libs/edssser/edssser.go +++ b/libs/edssser/edssser.go @@ -38,7 +38,8 @@ type EDSsser struct { } func NewEDSsser(path string, datastore datastore.Batching, cfg Config) (*EDSsser, error) { - edsstore, err := eds.NewStore(path, datastore) + storeCfg := eds.DefaultParameters() + edsstore, err := eds.NewStore(storeCfg, path, datastore) if err != nil { return nil, err } diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 7fd845a672..5d66ea7691 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -5,6 +5,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/share/availability/light" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" @@ -13,6 +14,9 @@ import ( // TODO: some params are pointers and other are not, Let's fix this. type Config struct { + // EDSStoreParams sets eds store configuration parameters + EDSStoreParams *eds.Parameters + UseShareExchange bool // ShrExEDSParams sets shrexeds client and server configuration parameters ShrExEDSParams *shrexeds.Parameters @@ -27,6 +31,7 @@ type Config struct { func DefaultConfig(tp node.Type) Config { cfg := Config{ + EDSStoreParams: eds.DefaultParameters(), Discovery: discovery.DefaultParameters(), ShrExEDSParams: shrexeds.DefaultParameters(), ShrExNDParams: shrexnd.DefaultParameters(), diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 35f8813111..15eb776551 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -113,7 +113,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option )), fx.Provide(fx.Annotate( func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { - return eds.NewStore(string(path), ds) + return eds.NewStore(cfg.EDSStoreParams, string(path), ds) }, fx.OnStart(func(ctx context.Context, store *eds.Store) error { err := store.Start(ctx) diff --git a/nodebuilder/share/share_test.go b/nodebuilder/share/share_test.go index 57119b5003..db170709db 100644 --- a/nodebuilder/share/share_test.go +++ b/nodebuilder/share/share_test.go @@ -17,9 +17,8 @@ func Test_EmptyCARExists(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - tmpDir := t.TempDir() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(tmpDir, ds) + edsStore, err := eds.NewStore(eds.DefaultParameters(), t.TempDir(), ds) require.NoError(t, err) err = edsStore.Start(ctx) require.NoError(t, err) diff --git a/nodebuilder/store_test.go b/nodebuilder/store_test.go index 8ea56a280f..bd179c1258 100644 --- a/nodebuilder/store_test.go +++ b/nodebuilder/store_test.go @@ -73,7 +73,7 @@ func BenchmarkStore(b *testing.B) { err := Init(*DefaultConfig(node.Full), dir, node.Full) require.NoError(b, err) - store := newStore(ctx, b, dir) + store := newStore(ctx, b, eds.DefaultParameters(), dir) size := 128 b.Run("enabled eds proof caching", func(b *testing.B) { b.StopTimer() @@ -128,7 +128,7 @@ func TestStoreRestart(t *testing.T) { err := Init(*DefaultConfig(node.Full), dir, node.Full) require.NoError(t, err) - store := newStore(ctx, t, dir) + store := newStore(ctx, t, eds.DefaultParameters(), dir) hashes := make([][]byte, blocks) for i := range hashes { @@ -145,7 +145,7 @@ func TestStoreRestart(t *testing.T) { // restart store store.stop(ctx, t) - store = newStore(ctx, t, dir) + store = newStore(ctx, t, eds.DefaultParameters(), dir) for _, h := range hashes { edsReader, err := store.edsStore.GetCAR(ctx, h) @@ -163,12 +163,12 @@ type store struct { edsStore *eds.Store } -func newStore(ctx context.Context, t require.TestingT, dir string) store { +func newStore(ctx context.Context, t require.TestingT, params *eds.Parameters, dir string) store { s, err := OpenStore(dir, nil) require.NoError(t, err) ds, err := s.Datastore() require.NoError(t, err) - edsStore, err := eds.NewStore(dir, ds) + edsStore, err := eds.NewStore(params, dir, ds) require.NoError(t, err) err = edsStore.Start(ctx) require.NoError(t, err) diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index cd5be80517..d708f6d454 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -55,9 +55,9 @@ func TestFraudProofHandling(t *testing.T) { set, val := sw.Validators(t) fMaker := headerfraud.NewFraudMaker(t, 10, []types.PrivValidator{val}, set) - tmpDir := t.TempDir() + storeCfg := eds.DefaultParameters() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(tmpDir, ds) + edsStore, err := eds.NewStore(storeCfg, t.TempDir(), ds) require.NoError(t, err) require.NoError(t, edsStore.Start(ctx)) t.Cleanup(func() { diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 4f07d96b51..55426cf70c 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -172,7 +172,8 @@ func (s *Swamp) setupGenesis() { // ensure core has surpassed genesis block s.WaitTillHeight(ctx, 2) - store, err := eds.NewStore(s.t.TempDir(), ds_sync.MutexWrap(ds.NewMapDatastore())) + ds := ds_sync.MutexWrap(ds.NewMapDatastore()) + store, err := eds.NewStore(eds.DefaultParameters(), s.t.TempDir(), ds) require.NoError(s.t, err) ex := core.NewExchange( diff --git a/share/eds/store.go b/share/eds/store.go index c2685cabb2..9fc90046fc 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -34,14 +34,6 @@ const ( blocksPath = "/blocks/" indexPath = "/index/" transientsPath = "/transients/" - - // GC performs DAG store garbage collection by reclaiming transient files of - // shards that are currently available but inactive, or errored. - // We don't use transient files right now, so GC is turned off by default. - defaultGCInterval = 0 - - defaultRecentBlocksCacheSize = 10 - defaultBlockstoreCacheSize = 128 ) var ErrNotFound = errors.New("eds not found in store") @@ -75,8 +67,12 @@ type Store struct { } // NewStore creates a new EDS Store under the given basepath and datastore. -func NewStore(basepath string, ds datastore.Batching) (*Store, error) { - err := setupPath(basepath) +func NewStore(params *Parameters, basePath string, ds datastore.Batching) (*Store, error) { + if err := params.Validate(); err != nil { + return nil, err + } + + err := setupPath(basePath) if err != nil { return nil, fmt.Errorf("failed to setup eds.Store directories: %w", err) } @@ -90,12 +86,12 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to register FS mount on the registry: %w", err) } - fsRepo, err := index.NewFSRepo(basepath + indexPath) + fsRepo, err := index.NewFSRepo(basePath + indexPath) if err != nil { return nil, fmt.Errorf("failed to create index repository: %w", err) } - invertedIdx, err := newSimpleInvertedIndex(basepath) + invertedIdx, err := newSimpleInvertedIndex(basePath) if err != nil { return nil, fmt.Errorf("failed to create index: %w", err) } @@ -103,7 +99,7 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { failureChan := make(chan dagstore.ShardResult) dagStore, err := dagstore.NewDAGStore( dagstore.Config{ - TransientsDir: basepath + transientsPath, + TransientsDir: basePath + transientsPath, IndexRepo: fsRepo, Datastore: ds, MountRegistry: r, @@ -115,22 +111,22 @@ func NewStore(basepath string, ds datastore.Batching) (*Store, error) { return nil, fmt.Errorf("failed to create DAGStore: %w", err) } - recentBlocksCache, err := cache.NewAccessorCache("recent", defaultRecentBlocksCacheSize) + recentBlocksCache, err := cache.NewAccessorCache("recent", params.RecentBlocksCacheSize) if err != nil { return nil, fmt.Errorf("failed to create recent blocks cache: %w", err) } - blockstoreCache, err := cache.NewAccessorCache("blockstore", defaultBlockstoreCacheSize) + blockstoreCache, err := cache.NewAccessorCache("blockstore", params.BlockstoreCacheSize) if err != nil { return nil, fmt.Errorf("failed to create blockstore cache: %w", err) } store := &Store{ - basepath: basepath, + basepath: basePath, dgstr: dagStore, carIdx: fsRepo, invertedIdx: invertedIdx, - gcInterval: defaultGCInterval, + gcInterval: params.GCInterval, mounts: r, shardFailures: failureChan, cache: cache.NewDoubleCache(recentBlocksCache, blockstoreCache), diff --git a/share/eds/store_options.go b/share/eds/store_options.go new file mode 100644 index 0000000000..e5e6ffa73d --- /dev/null +++ b/share/eds/store_options.go @@ -0,0 +1,43 @@ +package eds + +import ( + "fmt" + "time" +) + +type Parameters struct { + // GC performs DAG store garbage collection by reclaiming transient files of + // shards that are currently available but inactive, or errored. + // We don't use transient files right now, so GC is turned off by default. + GCInterval time.Duration + + // RecentBlocksCacheSize is the size of the cache for recent blocks. + RecentBlocksCacheSize int + + // BlockstoreCacheSize is the size of the cache for blockstore requested accessors. + BlockstoreCacheSize int +} + +// DefaultParameters returns the default configuration values for the EDS store parameters. +func DefaultParameters() *Parameters { + return &Parameters{ + GCInterval: 0, + RecentBlocksCacheSize: 10, + BlockstoreCacheSize: 128, + } +} + +func (p *Parameters) Validate() error { + if p.GCInterval < 0 { + return fmt.Errorf("eds: GC interval cannot be negative") + } + + if p.RecentBlocksCacheSize < 1 { + return fmt.Errorf("eds: recent blocks cache size must be positive") + } + + if p.BlockstoreCacheSize < 1 { + return fmt.Errorf("eds: blockstore cache size must be positive") + } + return nil +} diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 7a08c737c4..ce42a0e0b9 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -425,9 +425,8 @@ func BenchmarkStore(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) b.Cleanup(cancel) - tmpDir := b.TempDir() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := NewStore(tmpDir, ds) + edsStore, err := NewStore(DefaultParameters(), b.TempDir(), ds) require.NoError(b, err) err = edsStore.Start(ctx) require.NoError(b, err) @@ -469,9 +468,8 @@ func BenchmarkStore(b *testing.B) { func newStore(t *testing.T) (*Store, error) { t.Helper() - tmpDir := t.TempDir() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - return NewStore(tmpDir, ds) + return NewStore(DefaultParameters(), t.TempDir(), ds) } func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root) { diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index bee7b3a963..f8d1d0a5f0 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -26,9 +26,9 @@ func TestTeeGetter(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - tmpDir := t.TempDir() + storeCfg := eds.DefaultParameters() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(tmpDir, ds) + edsStore, err := eds.NewStore(storeCfg, t.TempDir(), ds) require.NoError(t, err) err = edsStore.Start(ctx) @@ -82,8 +82,9 @@ func TestStoreGetter(t *testing.T) { t.Cleanup(cancel) tmpDir := t.TempDir() + storeCfg := eds.DefaultParameters() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(tmpDir, ds) + edsStore, err := eds.NewStore(storeCfg, tmpDir, ds) require.NoError(t, err) err = edsStore.Start(ctx) @@ -185,9 +186,9 @@ func TestIPLDGetter(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - tmpDir := t.TempDir() + storeCfg := eds.DefaultParameters() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(tmpDir, ds) + edsStore, err := eds.NewStore(storeCfg, t.TempDir(), ds) require.NoError(t, err) err = edsStore.Start(ctx) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 38611ed0ef..9b97a4dd5a 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -190,9 +190,8 @@ func TestShrexGetter(t *testing.T) { func newStore(t *testing.T) (*eds.Store, error) { t.Helper() - tmpDir := t.TempDir() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - return eds.NewStore(tmpDir, ds) + return eds.NewStore(eds.DefaultParameters(), t.TempDir(), ds) } func generateTestEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root, share.Namespace) { diff --git a/share/p2p/peers/options.go b/share/p2p/peers/options.go index 97ec30df4a..2970dd2465 100644 --- a/share/p2p/peers/options.go +++ b/share/p2p/peers/options.go @@ -45,7 +45,7 @@ func (p *Parameters) Validate() error { return nil } -// DefaultParameters returns the default configuration values for the daser parameters +// DefaultParameters returns the default configuration values for the peer manager parameters func DefaultParameters() Parameters { return Parameters{ // PoolValidationTimeout's default value is based on the default daser sampling timeout of 1 minute. diff --git a/share/p2p/shrexeds/exchange_test.go b/share/p2p/shrexeds/exchange_test.go index e9305a96b9..9155be6dec 100644 --- a/share/p2p/shrexeds/exchange_test.go +++ b/share/p2p/shrexeds/exchange_test.go @@ -136,9 +136,9 @@ func TestExchange_RequestEDS(t *testing.T) { func newStore(t *testing.T) *eds.Store { t.Helper() - tmpDir := t.TempDir() + storeCfg := eds.DefaultParameters() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - store, err := eds.NewStore(tmpDir, ds) + store, err := eds.NewStore(storeCfg, t.TempDir(), ds) require.NoError(t, err) return store } diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 50324844b1..99a7e2f8fb 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -126,9 +126,9 @@ func (m notFoundGetter) GetSharesByNamespace( func newStore(t *testing.T) *eds.Store { t.Helper() - tmpDir := t.TempDir() + storeCfg := eds.DefaultParameters() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - store, err := eds.NewStore(tmpDir, ds) + store, err := eds.NewStore(storeCfg, t.TempDir(), ds) require.NoError(t, err) return store } From 2b610335454c02fa26512a472e005cc57cdaaaf0 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 21 Sep 2023 10:51:28 +0200 Subject: [PATCH 0828/1008] deps!: bump nmt, breaking JSON RPC return types including nmt Proofs (#2728) Related: https://github.com/celestiaorg/nmt/pull/233 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f0192607b3..0bee3a3181 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.3.1 github.com/celestiaorg/go-libp2p-messenger v0.2.0 - github.com/celestiaorg/nmt v0.19.0 + github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 github.com/cosmos/cosmos-sdk v0.46.14 github.com/cosmos/cosmos-sdk/api v0.1.0 diff --git a/go.sum b/go.sum index 667c6399c5..baae4c6cf5 100644 --- a/go.sum +++ b/go.sum @@ -377,8 +377,8 @@ github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= -github.com/celestiaorg/nmt v0.19.0 h1:9VXFeI/gt+q8h5HeCE0RjXJhOxsFzxJUjHrkvF9CMYE= -github.com/celestiaorg/nmt v0.19.0/go.mod h1:Oz15Ub6YPez9uJV0heoU4WpFctxazuIhKyUtaYNio7E= +github.com/celestiaorg/nmt v0.20.0 h1:9i7ultZ8Wv5ytt8ZRaxKQ5KOOMo4A2K2T/aPGjIlSas= +github.com/celestiaorg/nmt v0.20.0/go.mod h1:Oz15Ub6YPez9uJV0heoU4WpFctxazuIhKyUtaYNio7E= github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= github.com/celestiaorg/rsmt2d v0.11.0 h1:lcto/637WyTEZR3dLRoNvyuExfnUbxvdvKi3qz/2V4k= From b6ac8c2363706b542d83b3dd285ef4bd99d00b4f Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 21 Sep 2023 11:28:56 +0200 Subject: [PATCH 0829/1008] refactoring(full/availability): removing TeeGetter, storing via SharesAvailable (#2726) --- das/daser_test.go | 2 +- nodebuilder/share/constructors.go | 8 +- nodebuilder/tests/nd_test.go | 2 +- share/availability/cache/testing.go | 2 +- share/availability/full/availability.go | 23 ++++-- share/availability/full/availability_test.go | 19 ++++- share/availability/full/testing.go | 14 +++- share/getters/getter_test.go | 55 ------------- share/getters/store.go | 12 +-- share/getters/tee.go | 85 -------------------- share/ipld/corrupted_data_test.go | 5 +- 11 files changed, 61 insertions(+), 166 deletions(-) delete mode 100644 share/getters/tee.go diff --git a/das/daser_test.go b/das/daser_test.go index a14da325f2..6a3378cdb5 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -155,7 +155,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { ps, err := pubsub.NewGossipSub(ctx, net.Hosts()[0], pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) - avail := full.TestAvailability(getters.NewIPLDGetter(bServ)) + avail := full.TestAvailability(t, getters.NewIPLDGetter(bServ)) // 15 headers from the past and 15 future headers mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 0e8d17f208..28e0997163 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -92,7 +92,6 @@ func lightGetter( // by shrex the next time the data is retrieved (meaning shard recovery is // manual after corruption is detected). func bridgeGetter( - store *eds.Store, storeGetter *getters.StoreGetter, shrexGetter *getters.ShrexGetter, cfg Config, @@ -100,13 +99,12 @@ func bridgeGetter( var cascade []share.Getter cascade = append(cascade, storeGetter) if cfg.UseShareExchange { - cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + cascade = append(cascade, shrexGetter) } return getters.NewCascadeGetter(cascade) } func fullGetter( - store *eds.Store, storeGetter *getters.StoreGetter, shrexGetter *getters.ShrexGetter, ipldGetter *getters.IPLDGetter, @@ -115,8 +113,8 @@ func fullGetter( var cascade []share.Getter cascade = append(cascade, storeGetter) if cfg.UseShareExchange { - cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + cascade = append(cascade, shrexGetter) } - cascade = append(cascade, getters.NewTeeGetter(ipldGetter, store)) + cascade = append(cascade, ipldGetter) return getters.NewCascadeGetter(cascade) } diff --git a/nodebuilder/tests/nd_test.go b/nodebuilder/tests/nd_test.go index 64d672cddc..8149ea9fe7 100644 --- a/nodebuilder/tests/nd_test.go +++ b/nodebuilder/tests/nd_test.go @@ -204,7 +204,7 @@ func replaceShareGetter() fx.Option { ) share.Getter { cascade := make([]share.Getter, 0, 2) cascade = append(cascade, storeGetter) - cascade = append(cascade, getters.NewTeeGetter(shrexGetter, store)) + cascade = append(cascade, shrexGetter) return getters.NewCascadeGetter(cascade) }, )) diff --git a/share/availability/cache/testing.go b/share/availability/cache/testing.go index 00a1520114..989bee522c 100644 --- a/share/availability/cache/testing.go +++ b/share/availability/cache/testing.go @@ -32,7 +32,7 @@ func FullAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availabilit store := dssync.MutexWrap(ds.NewMapDatastore()) getter := getters.NewIPLDGetter(bServ) avail := NewShareAvailability( - full.TestAvailability(getter), + full.TestAvailability(t, getter), store, ) return avail, availability_test.RandFillBS(t, n, bServ) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index fb8dead839..51d9fd8518 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -3,12 +3,15 @@ package full import ( "context" "errors" + "fmt" + "github.com/filecoin-project/dagstore" logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/byzantine" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p/discovery" ) @@ -63,13 +66,15 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro } // a hack to avoid loading the whole EDS in mem if we store it already. - if fa.store != nil { - if ok, _ := fa.store.Has(ctx, root.Hash()); ok { - return nil - } + if ok, _ := fa.store.Has(ctx, root.Hash()); ok { + return nil } - _, err := fa.getter.GetEDS(ctx, root) + adder := ipld.NewProofsAdder(len(root.RowRoots)) + ctx = ipld.CtxWithProofsAdder(ctx, adder) + defer adder.Purge() + + eds, err := fa.getter.GetEDS(ctx, root) if err != nil { if errors.Is(err, context.Canceled) { return err @@ -79,8 +84,14 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro if errors.Is(err, share.ErrNotFound) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { return share.ErrNotAvailable } + return err } - return err + + err = fa.store.Put(ctx, root.Hash(), eds) + if err != nil && !errors.Is(err, dagstore.ErrShardExists) { + return fmt.Errorf("full availability: failed to store eds: %w", err) + } + return nil } func (fa *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { diff --git a/share/availability/full/availability_test.go b/share/availability/full/availability_test.go index b6d217cd6b..a769c981c4 100644 --- a/share/availability/full/availability_test.go +++ b/share/availability/full/availability_test.go @@ -35,11 +35,26 @@ func TestSharesAvailable_Full(t *testing.T) { // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it getter, dah := GetterWithRandSquare(t, 16) - avail := TestAvailability(getter) + avail := TestAvailability(t, getter) err := avail.SharesAvailable(ctx, dah) assert.NoError(t, err) } +func TestSharesAvailable_StoresToEDSStore(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it + getter, dah := GetterWithRandSquare(t, 16) + avail := TestAvailability(t, getter) + err := avail.SharesAvailable(ctx, dah) + assert.NoError(t, err) + + has, err := avail.store.Has(ctx, dah.Hash()) + assert.NoError(t, err) + assert.True(t, has) +} + func TestSharesAvailable_Full_ErrNotAvailable(t *testing.T) { ctrl := gomock.NewController(t) getter := mocks.NewMockGetter(ctrl) @@ -49,7 +64,7 @@ func TestSharesAvailable_Full_ErrNotAvailable(t *testing.T) { eds := edstest.RandEDS(t, 4) dah, err := da.NewDataAvailabilityHeader(eds) require.NoError(t, err) - avail := TestAvailability(getter) + avail := TestAvailability(t, getter) errors := []error{share.ErrNotFound, context.DeadlineExceeded} for _, getterErr := range errors { diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index 4638fde1bc..a636b26ea6 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -1,14 +1,18 @@ package full import ( + "context" "testing" "time" + "github.com/ipfs/go-datastore" routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p/discovery" @@ -32,16 +36,20 @@ func RandNode(dn *availability_test.TestDagNet, squareSize int) (*availability_t func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { nd := dn.NewTestNode() nd.Getter = getters.NewIPLDGetter(nd.BlockService) - nd.Availability = TestAvailability(nd.Getter) + nd.Availability = TestAvailability(dn.T, nd.Getter) return nd } -func TestAvailability(getter share.Getter) *ShareAvailability { +func TestAvailability(t *testing.T, getter share.Getter) *ShareAvailability { disc := discovery.NewDiscovery( nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), discovery.WithAdvertiseInterval(time.Second), discovery.WithPeersLimit(10), ) - return NewShareAvailability(nil, getter, disc) + store, err := eds.NewStore(eds.DefaultParameters(), t.TempDir(), datastore.NewMapDatastore()) + require.NoError(t, err) + err = store.Start(context.Background()) + require.NoError(t, err) + return NewShareAvailability(store, getter, disc) } diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index f8d1d0a5f0..bacb0a2c39 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -22,61 +22,6 @@ import ( "github.com/celestiaorg/celestia-node/share/sharetest" ) -func TestTeeGetter(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - storeCfg := eds.DefaultParameters() - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - edsStore, err := eds.NewStore(storeCfg, t.TempDir(), ds) - require.NoError(t, err) - - err = edsStore.Start(ctx) - require.NoError(t, err) - - bServ := ipld.NewMemBlockservice() - ig := NewIPLDGetter(bServ) - tg := NewTeeGetter(ig, edsStore) - - t.Run("TeesToEDSStore", func(t *testing.T) { - randEds, dah := randomEDS(t) - _, err := ipld.ImportShares(ctx, randEds.Flattened(), bServ) - require.NoError(t, err) - - // eds store doesn't have the EDS yet - ok, err := edsStore.Has(ctx, dah.Hash()) - assert.False(t, ok) - assert.NoError(t, err) - - retrievedEDS, err := tg.GetEDS(ctx, dah) - require.NoError(t, err) - require.True(t, randEds.Equals(retrievedEDS)) - - // eds store now has the EDS and it can be retrieved - ok, err = edsStore.Has(ctx, dah.Hash()) - assert.True(t, ok) - assert.NoError(t, err) - finalEDS, err := edsStore.Get(ctx, dah.Hash()) - assert.NoError(t, err) - require.True(t, randEds.Equals(finalEDS)) - }) - - t.Run("ShardAlreadyExistsDoesntError", func(t *testing.T) { - randEds, dah := randomEDS(t) - _, err := ipld.ImportShares(ctx, randEds.Flattened(), bServ) - require.NoError(t, err) - - retrievedEDS, err := tg.GetEDS(ctx, dah) - require.NoError(t, err) - require.True(t, randEds.Equals(retrievedEDS)) - - // no error should be returned, even though the EDS identified by the DAH already exists - retrievedEDS, err = tg.GetEDS(ctx, dah) - require.NoError(t, err) - require.True(t, randEds.Equals(retrievedEDS)) - }) -} - func TestStoreGetter(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) diff --git a/share/getters/store.go b/share/getters/store.go index 415c9f047f..ad05cf8f37 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -132,11 +132,13 @@ func (sg *StoreGetter) GetSharesByNamespace( blockGetter := eds.NewBlockGetter(bs) shares, err = collectSharesByNamespace(ctx, blockGetter, root, namespace) if errors.Is(err, ipld.ErrNodeNotFound) { - // IPLD node not found after the index pointed to this shard and the CAR blockstore has been - // opened successfully is a strong indicator of corruption. We remove the block on bridges - // and fulls and return share.ErrNotFound to ensure the data is retrieved by the next - // getter. Note that this recovery is manual and will only be restored by an RPC call to - // fetch the same datahash that was removed. + // IPLD node not found after the index pointed to this shard and the CAR + // blockstore has been opened successfully is a strong indicator of + // corruption. We remove the block on bridges and fulls and return + // share.ErrNotFound to ensure the data is retrieved by the next getter. + // Note that this recovery is manual and will only be restored by an RPC + // call to SharesAvailable that fetches the same datahash that was + // removed. err = sg.store.Remove(ctx, root.Hash()) if err != nil { log.Errorf("getter/store: failed to remove CAR after detected corruption: %w", err) diff --git a/share/getters/tee.go b/share/getters/tee.go deleted file mode 100644 index 9c89b2dec5..0000000000 --- a/share/getters/tee.go +++ /dev/null @@ -1,85 +0,0 @@ -package getters - -import ( - "context" - "errors" - "fmt" - - "github.com/filecoin-project/dagstore" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - "github.com/celestiaorg/rsmt2d" - - "github.com/celestiaorg/celestia-node/libs/utils" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/eds" - "github.com/celestiaorg/celestia-node/share/ipld" -) - -var _ share.Getter = (*TeeGetter)(nil) - -// TeeGetter is a share.Getter that wraps a getter and stores the results of GetEDS into an -// eds.Store. -type TeeGetter struct { - getter share.Getter - store *eds.Store -} - -// NewTeeGetter creates a new TeeGetter. -func NewTeeGetter(getter share.Getter, store *eds.Store) *TeeGetter { - return &TeeGetter{ - getter: getter, - store: store, - } -} - -func (tg *TeeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share share.Share, err error) { - ctx, span := tracer.Start(ctx, "tee/get-share", trace.WithAttributes( - attribute.Int("row", row), - attribute.Int("col", col), - )) - defer func() { - utils.SetStatusAndEnd(span, err) - }() - - return tg.getter.GetShare(ctx, root, row, col) -} - -func (tg *TeeGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { - ctx, span := tracer.Start(ctx, "tee/get-eds") - defer func() { - utils.SetStatusAndEnd(span, err) - }() - - adder := ipld.NewProofsAdder(len(root.RowRoots)) - ctx = ipld.CtxWithProofsAdder(ctx, adder) - defer adder.Purge() - - eds, err = tg.getter.GetEDS(ctx, root) - if err != nil { - return nil, err - } - - err = tg.store.Put(ctx, root.Hash(), eds) - if err != nil && !errors.Is(err, dagstore.ErrShardExists) { - return nil, fmt.Errorf("getter/tee: failed to store eds: %w", err) - } - - return eds, nil -} - -func (tg *TeeGetter) GetSharesByNamespace( - ctx context.Context, - root *share.Root, - namespace share.Namespace, -) (shares share.NamespacedShares, err error) { - ctx, span := tracer.Start(ctx, "tee/get-shares-by-namespace", trace.WithAttributes( - attribute.String("namespace", namespace.String()), - )) - defer func() { - utils.SetStatusAndEnd(span, err) - }() - - return tg.getter.GetSharesByNamespace(ctx, root, namespace) -} diff --git a/share/ipld/corrupted_data_test.go b/share/ipld/corrupted_data_test.go index df1d0d7888..58ddf785fb 100644 --- a/share/ipld/corrupted_data_test.go +++ b/share/ipld/corrupted_data_test.go @@ -26,7 +26,7 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { requestor := full.Node(net) provider, mockBS := availability_test.MockNode(t, net) - provider.Availability = full.TestAvailability(getters.NewIPLDGetter(provider.BlockService)) + provider.Availability = full.TestAvailability(t, getters.NewIPLDGetter(provider.BlockService)) net.ConnectAll() // before the provider starts attacking, we should be able to retrieve successfully. We pass a size @@ -38,7 +38,8 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { require.NoError(t, err) // clear the storage of the requester so that it must retrieve again, then start attacking - requestor.ClearStorage() + // we reinitialize the node to clear the eds store + requestor = full.Node(net) mockBS.Attacking = true getCtx, cancelGet = context.WithTimeout(ctx, sharesAvailableTimeout) t.Cleanup(cancelGet) From 9be8dab2af89f907c9bf5f648620a3221266fbe8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:47:43 +0000 Subject: [PATCH 0830/1008] chore(deps): Bump google.golang.org/grpc from 1.58.0 to 1.58.1 (#2740) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.58.0 to 1.58.1.
    Release notes

    Sourced from google.golang.org/grpc's releases.

    Release 1.58.1

    Bug Fixes

    • grpc: fix a bug that was decrementing active RPC count too early for streaming RPCs; leading to channel moving to IDLE even though it had open streams
    • grpc: fix a bug where transports were not being closed upon channel entering IDLE
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.58.0&new-version=1.58.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0bee3a3181..5ba14005a0 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 golang.org/x/text v0.13.0 - google.golang.org/grpc v1.58.0 + google.golang.org/grpc v1.58.1 google.golang.org/protobuf v1.31.0 ) diff --git a/go.sum b/go.sum index baae4c6cf5..1a926b5aa8 100644 --- a/go.sum +++ b/go.sum @@ -3209,8 +3209,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o= -google.golang.org/grpc v1.58.0/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= +google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 303db05be2ea00093494551d0d51b758b22bb288 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:58:41 +0000 Subject: [PATCH 0831/1008] chore(deps): Bump github.com/ipfs/boxo from 0.12.0 to 0.13.0 (#2734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/boxo](https://github.com/ipfs/boxo) from 0.12.0 to 0.13.0.
    Release notes

    Sourced from github.com/ipfs/boxo's releases.

    v0.13.0

    Added

    • ✨ The routing/http implements Delegated Peer Routing introduced in IPIP-417.

    Changed

    • 🛠 The routing/http package received the following modifications:
      • Client GetIPNSRecord and PutIPNSRecord have been renamed to GetIPNS and PutIPNS, respectively. Similarly, the required function names in the server ContentRouter have also been updated.
      • ReadBitswapProviderRecord has been renamed to BitswapRecord and marked as deprecated. From now on, please use the protocol-agnostic PeerRecord for most use cases. The new Peer Schema has been introduced in IPIP-417.

    Removed

    • 🛠 The routing/http package experienced following removals:
      • Server and client no longer support the experimental Provide method. ProvideBitswap is still usable, but marked as deprecated. A protocol-agnostic provide mechanism is being worked on in IPIP-378.
      • Server no longer exports FindProvidersPath and ProvidePath.

    Fixed

    • The normalization of DNSLink identifiers in gateway has been corrected in the edge case where the value passed to the path component of the URL is already normalized.

    What's Changed

    Full Changelog: https://github.com/ipfs/boxo/compare/v0.12.0...v0.13.0

    Changelog

    Sourced from github.com/ipfs/boxo's changelog.

    [v0.13.0]

    Added

    • ✨ The routing/http implements Delegated Peer Routing introduced in IPIP-417.

    Changed

    • 🛠 The routing/http package received the following modifications:
      • Client GetIPNSRecord and PutIPNSRecord have been renamed to GetIPNS and PutIPNS, respectively. Similarly, the required function names in the server ContentRouter have also been updated.
      • ReadBitswapProviderRecord has been renamed to BitswapRecord and marked as deprecated. From now on, please use the protocol-agnostic PeerRecord for most use cases. The new Peer Schema has been introduced in IPIP-417.

    Removed

    • 🛠 The routing/http package experienced following removals:
      • Server and client no longer support the experimental Provide method. ProvideBitswap is still usable, but marked as deprecated. A protocol-agnostic provide mechanism is being worked on in IPIP-378.
      • Server no longer exports FindProvidersPath and ProvidePath.

    Fixed

    • The normalization of DNSLink identifiers in gateway has been corrected in the edge case where the value passed to the path component of the URL is already normalized.

    Security

    Commits
    • 33ff595 Merge pull request #465 from ipfs/release-v0.13.0
    • f0ff3ce chore: bump to v0.13.0
    • 857bb53 docs: update changelog for v0.13.0
    • 68914ea fix(gateway): normalization of DNSLink inlining (#462)
    • 574df96 ci: remove obsolete protocol/cache-go-action (#460)
    • 1356946 feat(routing/http)!: delegated peer routing server and client, IPIP 417 (#422)
    • 79159c3 bitswap: mark TestSessionGetBlocks flaky
    • c135968 provider: mark TestOfflineRecordsThenOnlineRepublish flaky on windows
    • 38ecf0e bitswap: reenable tests
    • 303595b blockservice/test: passthrough blockservice options
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/boxo&package-manager=go_modules&previous-version=0.12.0&new-version=0.13.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5ba14005a0..f10e76fd5e 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/golang-lru/v2 v2.0.6 github.com/imdario/mergo v0.3.16 - github.com/ipfs/boxo v0.12.0 + github.com/ipfs/boxo v0.13.0 github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 diff --git a/go.sum b/go.sum index 1a926b5aa8..dd72331b3e 100644 --- a/go.sum +++ b/go.sum @@ -1037,8 +1037,8 @@ github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1: github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ= -github.com/ipfs/boxo v0.12.0/go.mod h1:xAnfiU6PtxWCnRqu7dcXQ10bB5/kvI1kXRotuGqGBhg= +github.com/ipfs/boxo v0.13.0 h1:uzCQekieYS4PysbYYdodNmKLuqOdLjlUziXVZ19oDeQ= +github.com/ipfs/boxo v0.13.0/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk= github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= From 3426751af3216bb7db94cfcea6b51d45fa5ff9ee Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 22 Sep 2023 14:48:10 +0200 Subject: [PATCH 0832/1008] fix(full/availability): short-circuiting SharesAvailable on empty DAH (#2746) When we stopped wrapping full availability with cache availability we forgot to add a short circuit to full avail. This will save us a datastore hit through the eds store Has call. --- share/availability/full/availability.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 51d9fd8518..be269077d7 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -57,6 +57,11 @@ func (fa *ShareAvailability) Stop(context.Context) error { // SharesAvailable reconstructs the data committed to the given Root by requesting // enough Shares from the network. func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { + // short-circuit if the given root is minimum DAH of an empty data square, to avoid datastore hit + if share.DataHash(root.Hash()).IsEmptyRoot() { + return nil + } + // we assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. if err := root.ValidateBasic(); err != nil { From 5e22259e9e541539c1f7f470f7db89532b8d72be Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 25 Sep 2023 10:37:41 +0200 Subject: [PATCH 0833/1008] fix: improve ErrNoStateAccess message (#2752) --- nodebuilder/state/stub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/state/stub.go b/nodebuilder/state/stub.go index 94326fed5e..8d17d651dd 100644 --- a/nodebuilder/state/stub.go +++ b/nodebuilder/state/stub.go @@ -10,7 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/state" ) -var ErrNoStateAccess = errors.New("node is running without state access") +var ErrNoStateAccess = errors.New("node is running without state access. run with --core.ip to resolve") // stubbedStateModule provides a stub for the state module to return // errors when state endpoints are accessed without a running connection From 4289e0975b3648b33a0333a02a3de935589c049d Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 25 Sep 2023 14:48:57 +0200 Subject: [PATCH 0834/1008] refactor(availability): merging cache.ShareAvailability and light.ShareAvailability (#2741) --- nodebuilder/share/constructors.go | 13 -- nodebuilder/share/module.go | 13 +- share/availability/cache/availability.go | 97 ------------- share/availability/cache/availability_test.go | 136 ------------------ share/availability/cache/testing.go | 39 ----- share/availability/light/availability.go | 59 +++++++- share/availability/light/availability_test.go | 66 ++++++++- share/availability/light/testing.go | 4 +- 8 files changed, 131 insertions(+), 296 deletions(-) delete mode 100644 share/availability/cache/availability.go delete mode 100644 share/availability/cache/availability_test.go delete mode 100644 share/availability/cache/testing.go diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 28e0997163..944b7f2d23 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -6,17 +6,13 @@ import ( "github.com/filecoin-project/dagstore" "github.com/ipfs/boxo/blockservice" - "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/routing" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" - "go.uber.org/fx" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/cache" - "github.com/celestiaorg/celestia-node/share/availability/light" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/ipld" @@ -37,15 +33,6 @@ func newDiscovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Disc } } -// cacheAvailability wraps light availability with a cache for result sampling. -func cacheAvailability(lc fx.Lifecycle, ds datastore.Batching, avail *light.ShareAvailability) share.Availability { - ca := cache.NewShareAvailability(avail, ds) - lc.Append(fx.Hook{ - OnStop: ca.Close, - }) - return ca -} - func newModule(getter share.Getter, avail share.Availability) Module { return &module{getter, avail} } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 15eb776551..b2e6eed4e5 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -214,10 +214,15 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return nil } }), - fx.Provide(light.NewShareAvailability), - // cacheAvailability's lifecycle continues to use a fx hook, - // since the LC requires a cacheAvailability but the constructor returns a share.Availability - fx.Provide(cacheAvailability), + fx.Provide(fx.Annotate( + light.NewShareAvailability, + fx.OnStop(func(ctx context.Context, la *light.ShareAvailability) error { + return la.Close(ctx) + }), + )), + fx.Provide(func(avail *light.ShareAvailability) share.Availability { + return avail + }), ) default: panic("invalid node type") diff --git a/share/availability/cache/availability.go b/share/availability/cache/availability.go deleted file mode 100644 index a530d94d1d..0000000000 --- a/share/availability/cache/availability.go +++ /dev/null @@ -1,97 +0,0 @@ -package cache - -import ( - "bytes" - "context" - "sync" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/autobatch" - "github.com/ipfs/go-datastore/namespace" - logging "github.com/ipfs/go-log/v2" - - "github.com/celestiaorg/celestia-node/share" -) - -var ( - log = logging.Logger("share/cache") - - cacheAvailabilityPrefix = datastore.NewKey("sampling_result") - writeBatchSize = 2048 -) - -// ShareAvailability wraps a given share.Availability (whether it's light or full) -// and stores the results of a successful sampling routine over a given Root's hash -// to disk. -type ShareAvailability struct { - avail share.Availability - - // TODO(@Wondertan): Once we come to parallelized DASer, this lock becomes a contention point - // Related to #483 - dsLk sync.RWMutex - ds *autobatch.Datastore -} - -// NewShareAvailability wraps the given share.Availability with an additional datastore -// for sampling result caching. -func NewShareAvailability( - avail share.Availability, - ds datastore.Batching, -) *ShareAvailability { - ds = namespace.Wrap(ds, cacheAvailabilityPrefix) - autoDS := autobatch.NewAutoBatching(ds, writeBatchSize) - - return &ShareAvailability{ - avail: avail, - ds: autoDS, - } -} - -// SharesAvailable will store, upon success, the hash of the given Root to disk. -func (ca *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { - // short-circuit if the given root is minimum DAH of an empty data square - if isMinRoot(root) { - return nil - } - // do not sample over Root that has already been sampled - key := rootKey(root) - - ca.dsLk.RLock() - exists, err := ca.ds.Has(ctx, key) - ca.dsLk.RUnlock() - if err != nil || exists { - return err - } - - err = ca.avail.SharesAvailable(ctx, root) - if err != nil { - return err - } - - ca.dsLk.Lock() - err = ca.ds.Put(ctx, key, []byte{}) - ca.dsLk.Unlock() - if err != nil { - log.Errorw("storing root of successful SharesAvailable request to disk", "err", err) - } - return err -} - -func (ca *ShareAvailability) ProbabilityOfAvailability(ctx context.Context) float64 { - return ca.avail.ProbabilityOfAvailability(ctx) -} - -// Close flushes all queued writes to disk. -func (ca *ShareAvailability) Close(ctx context.Context) error { - return ca.ds.Flush(ctx) -} - -func rootKey(root *share.Root) datastore.Key { - return datastore.NewKey(root.String()) -} - -// isMinRoot returns whether the given root is a minimum (empty) -// DataAvailabilityHeader (DAH). -func isMinRoot(root *share.Root) bool { - return bytes.Equal(share.EmptyRoot().Hash(), root.Hash()) -} diff --git a/share/availability/cache/availability_test.go b/share/availability/cache/availability_test.go deleted file mode 100644 index 458ce5cc95..0000000000 --- a/share/availability/cache/availability_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package cache - -import ( - "context" - "fmt" - "strconv" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-app/pkg/da" - - "github.com/celestiaorg/celestia-node/share" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/ipld" -) - -// TestCacheAvailability tests to ensure that the successful result of a -// sampling process is properly stored locally. -func TestCacheAvailability(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fullLocalServ, dah0 := FullAvailabilityWithLocalRandSquare(t, 16) - lightLocalServ, dah1 := LightAvailabilityWithLocalRandSquare(t, 16) - - var tests = []struct { - avail share.Availability - root *share.Root - }{ - { - avail: fullLocalServ, - root: dah0, - }, - { - avail: lightLocalServ, - root: dah1, - }, - } - - for i, tt := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - ca := tt.avail.(*ShareAvailability) - // ensure the dah isn't yet in the cache - exists, err := ca.ds.Has(ctx, rootKey(tt.root)) - require.NoError(t, err) - assert.False(t, exists) - err = tt.avail.SharesAvailable(ctx, tt.root) - require.NoError(t, err) - // ensure the dah was stored properly - exists, err = ca.ds.Has(ctx, rootKey(tt.root)) - require.NoError(t, err) - assert.True(t, exists) - }) - } -} - -var invalidHeader = da.DataAvailabilityHeader{ - RowRoots: [][]byte{{1, 2}}, -} - -// TestCacheAvailability_Failed tests to make sure a failed -// sampling process is not stored. -func TestCacheAvailability_Failed(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - ca := NewShareAvailability(&dummyAvailability{}, sync.MutexWrap(datastore.NewMapDatastore())) - - err := ca.SharesAvailable(ctx, &invalidHeader) - require.Error(t, err) - // ensure the dah was NOT cached - exists, err := ca.ds.Has(ctx, rootKey(&invalidHeader)) - require.NoError(t, err) - assert.False(t, exists) -} - -// TestCacheAvailability_NoDuplicateSampling tests to ensure that -// if the Root was already sampled, it does not run a sampling routine -// again. -func TestCacheAvailability_NoDuplicateSampling(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - // create root to cache - root := availability_test.RandFillBS(t, 16, ipld.NewMemBlockservice()) - // wrap dummyAvailability with a datastore - ds := sync.MutexWrap(datastore.NewMapDatastore()) - ca := NewShareAvailability(&dummyAvailability{counter: 0}, ds) - // sample the root - err := ca.SharesAvailable(ctx, root) - require.NoError(t, err) - // ensure root was cached - exists, err := ca.ds.Has(ctx, rootKey(root)) - require.NoError(t, err) - assert.True(t, exists) - // call sampling routine over same root again and ensure no error is returned - // if an error was returned, that means duplicate sampling occurred - err = ca.SharesAvailable(ctx, root) - require.NoError(t, err) -} - -// TestCacheAvailability_MinRoot tests to make sure `SharesAvailable` will -// short circuit if the given root is a minimum DataAvailabilityHeader (minRoot). -func TestCacheAvailability_MinRoot(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fullLocalAvail, _ := FullAvailabilityWithLocalRandSquare(t, 16) - err := fullLocalAvail.SharesAvailable(ctx, share.EmptyRoot()) - assert.NoError(t, err) -} - -type dummyAvailability struct { - counter int -} - -// SharesAvailable should only be called once, if called more than once, return -// error. -func (da *dummyAvailability) SharesAvailable(_ context.Context, root *share.Root) error { - if root == &invalidHeader { - return fmt.Errorf("invalid header") - } - if da.counter > 0 { - return fmt.Errorf("duplicate sampling process called") - } - da.counter++ - return nil -} - -func (da *dummyAvailability) ProbabilityOfAvailability(context.Context) float64 { - return 0 -} diff --git a/share/availability/cache/testing.go b/share/availability/cache/testing.go deleted file mode 100644 index 989bee522c..0000000000 --- a/share/availability/cache/testing.go +++ /dev/null @@ -1,39 +0,0 @@ -package cache - -import ( - "testing" - - ds "github.com/ipfs/go-datastore" - dssync "github.com/ipfs/go-datastore/sync" - - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/availability/full" - "github.com/celestiaorg/celestia-node/share/availability/light" - availability_test "github.com/celestiaorg/celestia-node/share/availability/test" - "github.com/celestiaorg/celestia-node/share/getters" - "github.com/celestiaorg/celestia-node/share/ipld" -) - -// LightAvailabilityWithLocalRandSquare wraps light.GetterWithRandSquare with cache availability -func LightAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availability, *share.Root) { - bServ := ipld.NewMemBlockservice() - store := dssync.MutexWrap(ds.NewMapDatastore()) - getter := getters.NewIPLDGetter(bServ) - avail := NewShareAvailability( - light.TestAvailability(getter), - store, - ) - return avail, availability_test.RandFillBS(t, n, bServ) -} - -// FullAvailabilityWithLocalRandSquare wraps full.GetterWithRandSquare with cache availability -func FullAvailabilityWithLocalRandSquare(t *testing.T, n int) (share.Availability, *share.Root) { - bServ := ipld.NewMemBlockservice() - store := dssync.MutexWrap(ds.NewMapDatastore()) - getter := getters.NewIPLDGetter(bServ) - avail := NewShareAvailability( - full.TestAvailability(t, getter), - store, - ) - return avail, availability_test.RandFillBS(t, n, bServ) -} diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index cc2e08129e..0fc073a2df 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -4,7 +4,11 @@ import ( "context" "errors" "math" + "sync" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/autobatch" + "github.com/ipfs/go-datastore/namespace" ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" @@ -12,7 +16,11 @@ import ( "github.com/celestiaorg/celestia-node/share/getters" ) -var log = logging.Logger("share/light") +var ( + log = logging.Logger("share/light") + cacheAvailabilityPrefix = datastore.NewKey("sampling_result") + writeBatchSize = 2048 +) // ShareAvailability implements share.Availability using Data Availability Sampling technique. // It is light because it does not require the downloading of all the data to verify @@ -21,30 +29,58 @@ var log = logging.Logger("share/light") type ShareAvailability struct { getter share.Getter params Parameters + + // TODO(@Wondertan): Once we come to parallelized DASer, this lock becomes a contention point + // Related to #483 + // TODO: Striped locks? :D + dsLk sync.RWMutex + ds *autobatch.Datastore } // NewShareAvailability creates a new light Availability. func NewShareAvailability( getter share.Getter, + ds datastore.Batching, opts ...Option, ) *ShareAvailability { params := DefaultParameters() + ds = namespace.Wrap(ds, cacheAvailabilityPrefix) + autoDS := autobatch.NewAutoBatching(ds, writeBatchSize) for _, opt := range opts { opt(¶ms) } - return &ShareAvailability{getter, params} + return &ShareAvailability{ + getter: getter, + params: params, + ds: autoDS, + } } // SharesAvailable randomly samples `params.SampleAmount` amount of Shares committed to the given // Root. This way SharesAvailable subjectively verifies that Shares are available. func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { - log.Debugw("Validate availability", "root", dah.String()) + // short-circuit if the given root is minimum DAH of an empty data square + if share.DataHash(dah.Hash()).IsEmptyRoot() { + return nil + } + + // do not sample over Root that has already been sampled + key := rootKey(dah) + + la.dsLk.RLock() + exists, err := la.ds.Has(ctx, key) + la.dsLk.RUnlock() + if err != nil || exists { + return err + } + + log.Debugw("validate availability", "root", dah.String()) // We assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. if err := dah.ValidateBasic(); err != nil { - log.Errorw("Availability validation cannot be performed on a malformed DataAvailabilityHeader", + log.Errorw("availability validation cannot be performed on a malformed DataAvailabilityHeader", "err", err) panic(err) } @@ -95,6 +131,12 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo } } + la.dsLk.Lock() + err = la.ds.Put(ctx, key, []byte{}) + la.dsLk.Unlock() + if err != nil { + log.Errorw("storing root of successful SharesAvailable request to disk", "err", err) + } return nil } @@ -106,3 +148,12 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo func (la *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { return 1 - math.Pow(0.75, float64(la.params.SampleAmount)) } + +func rootKey(root *share.Root) datastore.Key { + return datastore.NewKey(root.String()) +} + +// Close flushes all queued writes to disk. +func (la *ShareAvailability) Close(ctx context.Context) error { + return la.ds.Flush(ctx) +} diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 549ea14ce5..1e6b99cda4 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -11,9 +11,69 @@ import ( "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/sharetest" ) +func TestSharesAvailableCaches(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + getter, dah := GetterWithRandSquare(t, 16) + avail := TestAvailability(getter) + + // cache doesn't have dah yet + has, err := avail.ds.Has(ctx, rootKey(dah)) + assert.NoError(t, err) + assert.False(t, has) + + err = avail.SharesAvailable(ctx, dah) + assert.NoError(t, err) + + // is now cached + has, err = avail.ds.Has(ctx, rootKey(dah)) + assert.NoError(t, err) + assert.True(t, has) +} + +func TestSharesAvailableHitsCache(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + getter, _ := GetterWithRandSquare(t, 16) + avail := TestAvailability(getter) + + bServ := ipld.NewMemBlockservice() + dah := availability_test.RandFillBS(t, 16, bServ) + + // blockstore doesn't actually have the dah + err := avail.SharesAvailable(ctx, dah) + require.Error(t, err) + + // cache doesn't have dah yet, since it errored + has, err := avail.ds.Has(ctx, rootKey(dah)) + assert.NoError(t, err) + assert.False(t, has) + + err = avail.ds.Put(ctx, rootKey(dah), []byte{}) + require.NoError(t, err) + + // should hit cache after putting + err = avail.SharesAvailable(ctx, dah) + require.NoError(t, err) +} + +func TestSharesAvailableEmptyRoot(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + getter, _ := GetterWithRandSquare(t, 16) + avail := TestAvailability(getter) + + err := avail.SharesAvailable(ctx, share.EmptyRoot()) + assert.NoError(t, err) +} + func TestSharesAvailable(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -28,10 +88,12 @@ func TestSharesAvailableFailed(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + bServ := ipld.NewMemBlockservice() + dah := availability_test.RandFillBS(t, 16, bServ) + getter, _ := GetterWithRandSquare(t, 16) avail := TestAvailability(getter) - empty := share.EmptyRoot() - err := avail.SharesAvailable(ctx, empty) + err := avail.SharesAvailable(ctx, dah) assert.Error(t, err) } diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go index 991d143a62..e68068fc25 100644 --- a/share/availability/light/testing.go +++ b/share/availability/light/testing.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/ipfs/boxo/blockservice" + "github.com/ipfs/go-datastore" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" @@ -42,7 +43,8 @@ func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { } func TestAvailability(getter share.Getter) *ShareAvailability { - return NewShareAvailability(getter) + ds := datastore.NewMapDatastore() + return NewShareAvailability(getter, ds) } func SubNetNode(sn *availability_test.SubNet) *availability_test.TestNode { From 8602d6e51d00c018d4cc06b0a11db7a1c43b82f7 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:52:47 +0800 Subject: [PATCH 0835/1008] refactor(share/discovery): decrease default Advertisement interval (#2758) Resolves https://github.com/celestiaorg/celestia-node/issues/2704 --- share/p2p/discovery/options.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/share/p2p/discovery/options.go b/share/p2p/discovery/options.go index 8a6a162e11..9d2735c50d 100644 --- a/share/p2p/discovery/options.go +++ b/share/p2p/discovery/options.go @@ -23,9 +23,8 @@ type Option func(*Parameters) // for the Discovery module func DefaultParameters() Parameters { return Parameters{ - PeersLimit: 5, - // based on https://github.com/libp2p/go-libp2p-kad-dht/pull/793 - AdvertiseInterval: time.Hour * 22, + PeersLimit: 5, + AdvertiseInterval: time.Hour, } } From 4c931473c10720b528c22c7c4b915ca4a1b4a76a Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Mon, 25 Sep 2023 16:20:14 +0200 Subject: [PATCH 0836/1008] chore!: bump core and app (#2753) --- go.mod | 31 ++++++++++++++++---------- go.sum | 69 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index f10e76fd5e..0c85d4633d 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/celestiaorg/celestia-node -go 1.21 - -toolchain go1.21.0 +go 1.21.1 require ( cosmossdk.io/errors v1.0.0 @@ -10,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc14 + github.com/celestiaorg/celestia-app v1.0.0-rc17 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.3.1 @@ -76,6 +74,19 @@ require ( google.golang.org/protobuf v1.31.0 ) +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/bits-and-blooms/bitset v1.5.0 // indirect + github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.1 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.10.0 // indirect + github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect + github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/supranational/blst v0.3.11 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) + require ( cloud.google.com/go v0.110.6 // indirect cloud.google.com/go/compute v1.23.0 // indirect @@ -98,7 +109,6 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect - github.com/celestiaorg/quantum-gravity-bridge v1.3.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -135,7 +145,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect - github.com/ethereum/go-ethereum v1.12.2 // indirect + github.com/ethereum/go-ethereum v1.13.1 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -190,7 +200,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/holiman/uint256 v1.2.3 // indirect - github.com/huin/goupnp v1.2.0 // indirect + github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.1.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -296,8 +306,8 @@ require ( github.com/tendermint/go-amino v0.16.0 // indirect github.com/tendermint/tm-db v0.6.7 // indirect github.com/tidwall/btree v1.5.0 // indirect - github.com/tklauser/go-sysconf v0.3.10 // indirect - github.com/tklauser/numcpus v0.4.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/ulikunitz/xz v0.5.10 // indirect github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 // indirect @@ -325,7 +335,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect @@ -339,5 +348,5 @@ replace ( github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.27.0-tm-v0.34.28 ) diff --git a/go.sum b/go.sum index dd72331b3e..30a32a19f3 100644 --- a/go.sum +++ b/go.sum @@ -229,9 +229,8 @@ github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRr github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU= github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -239,8 +238,8 @@ github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETF github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -316,8 +315,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= -github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= +github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -359,10 +358,10 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc14 h1:kC3ljQzxBjQM1s8aw0dOnrn5pNbrfyfd+lS155S7QAc= -github.com/celestiaorg/celestia-app v1.0.0-rc14/go.mod h1:6aD4sFGQIe5XPYejLDKrDKsNWlDu9/P3e54pCLPywNk= -github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28 h1:2efXQaggLFknz0wQufr4nUEz5G7pSVHS1j7NuJDsvII= -github.com/celestiaorg/celestia-core v1.26.2-tm-v0.34.28/go.mod h1:++dNzzzjP9jYg+NopN9G8sg1HEZ58lv1TPtg71evZ0E= +github.com/celestiaorg/celestia-app v1.0.0-rc17 h1:ak9OIViNJCfDFklopPgxnjRqUx5oKPcctpGpmwmqJy4= +github.com/celestiaorg/celestia-app v1.0.0-rc17/go.mod h1:gDKOpD8xCWHw/PUdcTc0WFYf52Mq0etOFZ1oTkBlFkQ= +github.com/celestiaorg/celestia-core v1.27.0-tm-v0.34.28 h1:BE7JFZ1SYpwM9OfL9cPcjlO5xjIbDPgdFkJNouyl6jA= +github.com/celestiaorg/celestia-core v1.27.0-tm-v0.34.28/go.mod h1:1GT0RfdNqOXvyR3Hq4ROcNBknQNz9E6K5l3Cla9eFFk= github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14 h1:dDfoQJOlVNj4HufJ1lBLTo2k3/L/255MIiKmEQziDmw= github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14/go.mod h1:kkdiHo/zG6ar80730+bG1owdMAQXrGp4utFu7mbfADo= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJx5/hgZ+IeOLEIoLsAveJN/7/ZtQQtPSVw= @@ -379,8 +378,8 @@ github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= github.com/celestiaorg/nmt v0.20.0 h1:9i7ultZ8Wv5ytt8ZRaxKQ5KOOMo4A2K2T/aPGjIlSas= github.com/celestiaorg/nmt v0.20.0/go.mod h1:Oz15Ub6YPez9uJV0heoU4WpFctxazuIhKyUtaYNio7E= -github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0= -github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI= +github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.1 h1:xOBMoRYSh/hnsEW7OLI3bjSJXKlqILfZehjPEK/+3oI= +github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.1/go.mod h1:NziNBP12grIi+DH3TqIKUUPM2T6Hl9BTyxiK3+p8yeA= github.com/celestiaorg/rsmt2d v0.11.0 h1:lcto/637WyTEZR3dLRoNvyuExfnUbxvdvKi3qz/2V4k= github.com/celestiaorg/rsmt2d v0.11.0/go.mod h1:6Y580I3gVr0+OVFfW6m2JTwnCCmvW3WfbwSLfuT+HCA= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -430,14 +429,16 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06 h1:T+Np/xtzIjYM/P5NAw0e2Rf1FGvzDau1h54MKvx8G7w= +github.com/cockroachdb/pebble v0.0.0-20230906160148-46873a6a7a06/go.mod h1:bynZ3gvVyhlvjLI7PT6dmZ7g76xzJ7HpxfjgkzCGz6s= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= @@ -605,8 +606,8 @@ github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eE github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.12.2 h1:eGHJ4ij7oyVqUQn48LBz3B7pvQ8sV0wGJiIE6gDq/6Y= -github.com/ethereum/go-ethereum v1.12.2/go.mod h1:1cRAEV+rp/xX0zraSCBnu9Py3HQ+geRMj3HdR+k0wfI= +github.com/ethereum/go-ethereum v1.13.1 h1:UF2FaUKPIy5jeZk3X06ait3y2Q4wI+vJ1l7+UARp+60= +github.com/ethereum/go-ethereum v1.13.1/go.mod h1:xHQKzwkHSl0gnSjZK1mWa06XEdm9685AHqhRknOzqGQ= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -655,8 +656,6 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqG github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -867,6 +866,7 @@ github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0Z github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1002,8 +1002,8 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= -github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= @@ -1374,6 +1374,7 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= @@ -1784,6 +1785,7 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -2268,11 +2270,11 @@ github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= -github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= -github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -2294,8 +2296,8 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU= -github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= @@ -2850,6 +2852,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -3247,7 +3251,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= From 925961e427b5b825b2840bea024988c758113880 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:21:37 +0200 Subject: [PATCH 0837/1008] chore(deps): Bump github.com/ipfs/boxo from 0.13.0 to 0.13.1 (#2757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/boxo](https://github.com/ipfs/boxo) from 0.13.0 to 0.13.1.
    Release notes

    Sourced from github.com/ipfs/boxo's releases.

    v0.13.1

    Added

    • An option DisableHTMLErrors has been added to gateway.Config. When this option is true, pretty HTML error pages for web browsers are disabled. Instead, a text/plain page with the raw error message as the body is returned.

    What's Changed

    Full Changelog: https://github.com/ipfs/boxo/compare/v0.13.0...v0.13.1

    Changelog

    Sourced from github.com/ipfs/boxo's changelog.

    [v0.13.1]

    Added

    • An option DisableHTMLErrors has been added to gateway.Config. When this option is true, pretty HTML error pages for web browsers are disabled. Instead, a text/plain page with the raw error message as the body is returned.

    Changed

    Removed

    Fixed

    Security

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/boxo&package-manager=go_modules&previous-version=0.13.0&new-version=0.13.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0c85d4633d..99105c50d7 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/golang-lru/v2 v2.0.6 github.com/imdario/mergo v0.3.16 - github.com/ipfs/boxo v0.13.0 + github.com/ipfs/boxo v0.13.1 github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 diff --git a/go.sum b/go.sum index 30a32a19f3..9021ba7c15 100644 --- a/go.sum +++ b/go.sum @@ -1037,8 +1037,8 @@ github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1: github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.13.0 h1:uzCQekieYS4PysbYYdodNmKLuqOdLjlUziXVZ19oDeQ= -github.com/ipfs/boxo v0.13.0/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk= +github.com/ipfs/boxo v0.13.1 h1:nQ5oQzcMZR3oL41REJDcTbrvDvuZh3J9ckc9+ILeRQI= +github.com/ipfs/boxo v0.13.1/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk= github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= From cfdd9bee7677cf29d5c0393e947278c7c164e529 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:22:01 +0200 Subject: [PATCH 0838/1008] chore(deps): Bump google.golang.org/grpc from 1.58.1 to 1.58.2 (#2756) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.58.1 to 1.58.2.
    Release notes

    Sourced from google.golang.org/grpc's releases.

    Release 1.58.2

    Bug Fixes

    • balancer/weighted_round_robin: fix ticker leak on update

      A new ticker is created every time there is an update of addresses or configuration, but was not properly stopped. This change stops the ticker when it is no longer needed.

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.58.1&new-version=1.58.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 99105c50d7..7c023ce604 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 golang.org/x/text v0.13.0 - google.golang.org/grpc v1.58.1 + google.golang.org/grpc v1.58.2 google.golang.org/protobuf v1.31.0 ) diff --git a/go.sum b/go.sum index 9021ba7c15..3776182581 100644 --- a/go.sum +++ b/go.sum @@ -3213,8 +3213,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= -google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 49e6c246e5ab0246ccf473f9f316887c90bda8ce Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:28:22 +0200 Subject: [PATCH 0839/1008] fix(share/p2p/peers): Prevent making pools for nil DataHash (#2761) Not critical, but if nil DataHash is broadcasted, node could create pool for empty datahash and just keep it around until garbage collected. Best to short-circuit instead and reject message. --- share/p2p/peers/manager_test.go | 14 +++-- share/p2p/shrexsub/pubsub.go | 5 +- share/p2p/shrexsub/pubsub_test.go | 100 +++++++++++++++++++++++++----- 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index c60a737baa..1ad7e65c02 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -17,6 +17,7 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/rand" libhead "github.com/celestiaorg/go-header" @@ -348,18 +349,18 @@ func TestIntegration(t *testing.T) { time.Sleep(time.Millisecond * 100) // broadcast from BN - peerHash := share.DataHash("peer1") + randHash := rand.Bytes(32) require.NoError(t, bnPubSub.Broadcast(ctx, shrexsub.Notification{ - DataHash: peerHash, + DataHash: randHash, Height: 1, })) // FN should get message - peerID, _, err := fnPeerManager.Peer(ctx, peerHash) + gotPeer, _, err := fnPeerManager.Peer(ctx, randHash) require.NoError(t, err) - // check that peerID matched bridge node - require.Equal(t, nw.Hosts()[0].ID(), peerID) + // check that gotPeer matched bridge node + require.Equal(t, nw.Hosts()[0].ID(), gotPeer) }) t.Run("get peer from discovery", func(t *testing.T) { @@ -488,7 +489,8 @@ func stopManager(t *testing.T, m *Manager) { func testHeader() *header.ExtendedHeader { return &header.ExtendedHeader{ RawHeader: header.RawHeader{ - Height: 1, + Height: 1, + DataHash: rand.Bytes(32), }, } } diff --git a/share/p2p/shrexsub/pubsub.go b/share/p2p/shrexsub/pubsub.go index 82ed256e33..ed713b4614 100644 --- a/share/p2p/shrexsub/pubsub.go +++ b/share/p2p/shrexsub/pubsub.go @@ -102,8 +102,9 @@ func (v ValidatorFn) validate(ctx context.Context, p peer.ID, msg *pubsub.Messag DataHash: pbmsg.DataHash, Height: pbmsg.Height, } - if n.DataHash.IsEmptyRoot() { - // we don't send empty EDS data hashes, but If someone sent it to us - do hard reject + if n.Height == 0 || n.DataHash.IsEmptyRoot() || n.DataHash.Validate() != nil { + // hard reject malicious height (height 0 does not exist) and + // empty/invalid datahashes return pubsub.ValidationReject } return v(ctx, p, n) diff --git a/share/p2p/shrexsub/pubsub_test.go b/share/p2p/shrexsub/pubsub_test.go index 4198b64379..dcf85515e8 100644 --- a/share/p2p/shrexsub/pubsub_test.go +++ b/share/p2p/shrexsub/pubsub_test.go @@ -6,8 +6,10 @@ import ( "time" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/rand" pb "github.com/celestiaorg/celestia-node/share/p2p/shrexsub/pb" ) @@ -23,6 +25,13 @@ func TestPubSub(t *testing.T) { pSub2, err := NewPubSub(ctx, h.Hosts()[1], "test") require.NoError(t, err) + err = pSub2.AddValidator( + func(ctx context.Context, p peer.ID, n Notification) pubsub.ValidationResult { + // only testing shrexsub validation here + return pubsub.ValidationAccept + }, + ) + require.NoError(t, err) require.NoError(t, pSub1.Start(ctx)) require.NoError(t, pSub2.Start(ctx)) @@ -30,23 +39,86 @@ func TestPubSub(t *testing.T) { subs, err := pSub2.Subscribe() require.NoError(t, err) - notification := Notification{ - DataHash: []byte("data"), - Height: 1, + var tests = []struct { + name string + notif Notification + errExpected bool + }{ + { + name: "vaild height, valid hash", + notif: Notification{ + Height: 1, + DataHash: rand.Bytes(32), + }, + errExpected: false, + }, + { + name: "valid height, invalid hash (<32 bytes)", + notif: Notification{ + Height: 2, + DataHash: rand.Bytes(20), + }, + errExpected: true, + }, + { + name: "valid height, invalid hash (>32 bytes)", + notif: Notification{ + Height: 2, + DataHash: rand.Bytes(64), + }, + errExpected: true, + }, + { + name: "invalid height, valid hash", + notif: Notification{ + Height: 0, + DataHash: rand.Bytes(32), + }, + errExpected: true, + }, + { + name: "invalid height, nil hash", + notif: Notification{ + Height: 0, + DataHash: nil, + }, + errExpected: true, + }, + { + name: "valid height, nil hash", + notif: Notification{ + Height: 30, + DataHash: nil, + }, + errExpected: true, + }, } - msg := pb.RecentEDSNotification{ - Height: notification.Height, - DataHash: notification.DataHash, - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := pb.RecentEDSNotification{ + Height: tt.notif.Height, + DataHash: tt.notif.DataHash, + } + data, err := msg.Marshal() + require.NoError(t, err) - data, err := msg.Marshal() - require.NoError(t, err) + err = pSub1.topic.Publish(ctx, data, pubsub.WithReadiness(pubsub.MinTopicSize(1))) + require.NoError(t, err) - err = pSub1.topic.Publish(ctx, data, pubsub.WithReadiness(pubsub.MinTopicSize(1))) - require.NoError(t, err) + reqCtx, reqCtxCancel := context.WithTimeout(context.Background(), time.Millisecond*100) + defer reqCtxCancel() + + got, err := subs.Next(reqCtx) + if tt.errExpected { + require.Error(t, err) + require.ErrorIs(t, err, context.DeadlineExceeded) + return + } + require.NoError(t, err) + require.NoError(t, err) + require.Equal(t, tt.notif, got) + }) + } - got, err := subs.Next(ctx) - require.NoError(t, err) - require.Equal(t, notification, got) } From 849cb6757f60841ab3c6fd5f9564186df81c5405 Mon Sep 17 00:00:00 2001 From: ramin Date: Thu, 28 Sep 2023 11:27:51 +0100 Subject: [PATCH 0840/1008] ignore dependabot in issue-label-automation/automate-labels workflow (#2763) ## Overview - Ignores triggering the issue-label-automation workflow when a workflow is triggered by dependabot[bot]. - why?: It always fails as the "check external contributor" action doesn't find a legitimate user - this seemed simplest way vs attempting to modify the imported action to allow configuration to ignore dependabot or similar - also introduces some minor/nit formatting with a whitespace change ## Checklist - [n/a] New and updated code has appropriate documentation - [n/a] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [n/a] Visual proof for any user facing features like CLI or documentation updates - [n/a] Linked issues closed with keywords --- .github/workflows/issue-label-automation.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/issue-label-automation.yml b/.github/workflows/issue-label-automation.yml index 4b24bca99c..965dbe8e9f 100644 --- a/.github/workflows/issue-label-automation.yml +++ b/.github/workflows/issue-label-automation.yml @@ -5,12 +5,15 @@ on: types: [opened] issues: types: [opened] + jobs: automate-labels: runs-on: ubuntu-latest + if: ${{ github.actor != 'dependabot[bot]' }} permissions: issues: write pull-requests: write + steps: - name: Check for External Contributor uses: tspascoal/get-user-teams-membership@v2 From bb9658a510e16a97d0a00782e69ff807a951c047 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:29:40 +0800 Subject: [PATCH 0841/1008] test(share/eds/cache): benchmark eds cache eviction performance (#2778) Adds benchmarks to ensure cache overload performance. 1. Measures time to fully load accessor from disk to cache 2. Measures time it takes for multiple parallel ipld readers (clients) to sample eds directly from store, when there is not enough cache slots. Results shows it takes ~12s to sample for parallel 10 readers. --- share/eds/store_test.go | 58 +++++++++++++++++++++++++++++ share/getters/getter_test.go | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index ce42a0e0b9..7052533555 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -18,11 +18,13 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + dsbadger "github.com/celestiaorg/go-ds-badger4" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/cache" "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" ) func TestEDSStore(t *testing.T) { @@ -465,6 +467,62 @@ func BenchmarkStore(b *testing.B) { }) } +// BenchmarkCacheEviction benchmarks the time it takes to load a block to the cache, when the +// cache size is set to 1. This forces cache eviction on every read. +// BenchmarkCacheEviction-10/128 384 3533586 ns/op (~3ms) +func BenchmarkCacheEviction(b *testing.B) { + const ( + blocks = 4 + size = 128 + ) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + b.Cleanup(cancel) + + dir := b.TempDir() + ds, err := dsbadger.NewDatastore(dir, &dsbadger.DefaultOptions) + require.NoError(b, err) + + newStore := func(params *Parameters) *Store { + edsStore, err := NewStore(params, dir, ds) + require.NoError(b, err) + err = edsStore.Start(ctx) + require.NoError(b, err) + return edsStore + } + edsStore := newStore(DefaultParameters()) + + // generate EDSs and store them + cids := make([]cid.Cid, blocks) + for i := range cids { + eds := edstest.RandEDS(b, size) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(b, err) + + // store cids for read loop later + cids[i] = ipld.MustCidFromNamespacedSha256(dah.RowRoots[0]) + } + + // restart store to clear cache + require.NoError(b, edsStore.Stop(ctx)) + + // set BlockstoreCacheSize to 1 to force eviction on every read + params := DefaultParameters() + params.BlockstoreCacheSize = 1 + bstore := newStore(params).Blockstore() + + // start benchmark + b.ResetTimer() + for i := 0; i < b.N; i++ { + h := cids[i%blocks] + // every read will trigger eviction + _, err := bstore.Get(ctx, h) + require.NoError(b, err) + } +} + func newStore(t *testing.T) (*Store, error) { t.Helper() diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index bacb0a2c39..6c75ddf83f 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -3,6 +3,7 @@ package getters import ( "context" "os" + "sync" "testing" "time" @@ -12,7 +13,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" + dsbadger "github.com/celestiaorg/go-ds-badger4" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" @@ -217,6 +220,74 @@ func TestIPLDGetter(t *testing.T) { }) } +// BenchmarkIPLDGetterOverBusyCache benchmarks the performance of the IPLDGetter when the +// cache size of the underlying blockstore is less than the number of blocks being requested in +// parallel. This is to ensure performance doesn't degrade when the cache is being frequently +// evicted. +// BenchmarkIPLDGetterOverBusyCache-10/128 1 12460428417 ns/op (~12s) +func BenchmarkIPLDGetterOverBusyCache(b *testing.B) { + const ( + blocks = 10 + size = 128 + ) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + b.Cleanup(cancel) + + dir := b.TempDir() + ds, err := dsbadger.NewDatastore(dir, &dsbadger.DefaultOptions) + require.NoError(b, err) + + newStore := func(params *eds.Parameters) *eds.Store { + edsStore, err := eds.NewStore(params, dir, ds) + require.NoError(b, err) + err = edsStore.Start(ctx) + require.NoError(b, err) + return edsStore + } + edsStore := newStore(eds.DefaultParameters()) + + // generate EDSs and store them + hashes := make([]da.DataAvailabilityHeader, blocks) + for i := range hashes { + eds := edstest.RandEDS(b, size) + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(b, err) + err = edsStore.Put(ctx, dah.Hash(), eds) + require.NoError(b, err) + + // store cids for read loop later + hashes[i] = dah + } + + // restart store to clear cache + require.NoError(b, edsStore.Stop(ctx)) + + // set BlockstoreCacheSize to 1 to force eviction on every read + params := eds.DefaultParameters() + params.BlockstoreCacheSize = 1 + edsStore = newStore(params) + bstore := edsStore.Blockstore() + bserv := ipld.NewBlockservice(bstore, offline.Exchange(bstore)) + + // start client + getter := NewIPLDGetter(bserv) + + // request blocks in parallel + b.ResetTimer() + g := sync.WaitGroup{} + g.Add(blocks) + for _, h := range hashes { + h := h + go func() { + defer g.Done() + _, err := getter.GetEDS(ctx, &h) + require.NoError(b, err) + }() + } + g.Wait() +} + func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root) { eds := edstest.RandEDS(t, 4) dah, err := share.NewRoot(eds) From 20c7476a5fbc47d9cf1569277e293632fba9579b Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:03:16 +0800 Subject: [PATCH 0842/1008] chore(deps): fix dependabot otel grouping (#2795) Fixes dependabot regex to not use`^`, since it seems to not support anything but `*` wildcard --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 26f1b4da15..4ab88e1d32 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,7 +22,7 @@ updates: groups: otel: patterns: - - "^go.opentelemetry.io/otel*" + - "go.opentelemetry.io/otel*" - package-ecosystem: docker directory: "/" schedule: From 9e0d02169a556e7a306e96c2dadd7fd6badfccbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:17:59 +0100 Subject: [PATCH 0843/1008] chore(deps): Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace from 1.18.0 to 1.19.0 (#2784) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://github.com/open-telemetry/opentelemetry-go) from 1.18.0 to 1.19.0.
    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlptrace's changelog.

    [1.19.0/0.42.0/0.0.7] 2023-09-28

    This release contains the first stable release of the OpenTelemetry Go [metric SDK]. Our project stability guarantees now apply to the go.opentelemetry.io/otel/sdk/metric package. See our versioning policy for more information about these stability guarantees.

    Added

    • Add the "Roll the dice" getting started application example in go.opentelemetry.io/otel/example/dice. (#4539)
    • The WithWriter and WithPrettyPrint options to go.opentelemetry.io/otel/exporters/stdout/stdoutmetric to set a custom io.Writer, and allow displaying the output in human-readable JSON. (#4507)

    Changed

    • Allow '/' characters in metric instrument names. (#4501)
    • The exporter in go.opentelemetry.io/otel/exporters/stdout/stdoutmetric does not prettify its output by default anymore. (#4507)
    • Upgrade gopkg.io/yaml from v2 to v3 in go.opentelemetry.io/otel/schema. (#4535)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the Prometheus metric on every Collect if we know the scope is invalid. (#4499)

    Removed

    • Remove "go.opentelemetry.io/otel/bridge/opencensus".NewMetricExporter, which is replaced by NewMetricProducer. (#4566)

    [1.19.0-rc.1/0.42.0-rc.1] 2023-09-14

    This is a release candidate for the v1.19.0/v0.42.0 release. That release is expected to include the v1 release of the OpenTelemetry Go metric SDK and will provide stability guarantees of that SDK. See our versioning policy for more information about these stability guarantees.

    Changed

    • Allow '/' characters in metric instrument names. (#4501)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the prometheus metric on every Collect if we know the scope is invalid. (#4499)
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.opentelemetry.io/otel/exporters/otlp/otlptrace&package-manager=go_modules&previous-version=1.18.0&new-version=1.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 7c023ce604..bcd7f2aab4 100644 --- a/go.mod +++ b/go.mod @@ -55,14 +55,14 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 - go.opentelemetry.io/otel v1.18.0 + go.opentelemetry.io/otel v1.19.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 - go.opentelemetry.io/otel/metric v1.18.0 - go.opentelemetry.io/otel/sdk v1.18.0 + go.opentelemetry.io/otel/metric v1.19.0 + go.opentelemetry.io/otel/sdk v1.19.0 go.opentelemetry.io/otel/sdk/metric v0.41.0 - go.opentelemetry.io/otel/trace v1.18.0 + go.opentelemetry.io/otel/trace v1.19.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 go.uber.org/zap v1.26.0 diff --git a/go.sum b/go.sum index 3776182581..236dd50f4f 100644 --- a/go.sum +++ b/go.sum @@ -2382,33 +2382,33 @@ go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzox go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= -go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 h1:k0k7hFNDd8K4iOMJXj7s8sHaC4mhTlAeppRmZXLgZ6k= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 h1:iV3BOgW4fry1Riw9dwypigqlIYWXvSRVT2RJmblzo40= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0/go.mod h1:7PGzqlKrxIRmbj5tlNW0nTkYZ5fHXDgk6Fy8/KjR0CI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0/go.mod h1:IOmXxPrxoxFMXdNy7lfDmE8MzE61YPcurbUm0SMjerI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= -go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY= -go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk= go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= -go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= -go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= From 403a39e139a8cf3feccb666d777a368eb32ff25a Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Thu, 5 Oct 2023 07:21:24 -0500 Subject: [PATCH 0844/1008] chore: bump celestia-app, celestia-core, and the cosmos-sdk (#2801) bumps app, core, and the sdk. leaving as a draft for now since we still have to cut the official app version after we get enough approves. The review process can start for this tho --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index bcd7f2aab4..702d964619 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc17 + github.com/celestiaorg/celestia-app v1.0.0-rc18 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.3.1 @@ -77,7 +77,7 @@ require ( require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/bits-and-blooms/bitset v1.5.0 // indirect - github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.1 // indirect + github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.10.0 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect @@ -116,7 +116,7 @@ require ( github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/cometbft/cometbft-db v0.7.0 // indirect - github.com/confio/ics23/go v0.9.0 // indirect + github.com/confio/ics23/go v0.9.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect @@ -145,7 +145,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect - github.com/ethereum/go-ethereum v1.13.1 // indirect + github.com/ethereum/go-ethereum v1.13.2 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -343,10 +343,10 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.27.0-tm-v0.34.28 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 ) diff --git a/go.sum b/go.sum index 236dd50f4f..0ee3078273 100644 --- a/go.sum +++ b/go.sum @@ -358,12 +358,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc17 h1:ak9OIViNJCfDFklopPgxnjRqUx5oKPcctpGpmwmqJy4= -github.com/celestiaorg/celestia-app v1.0.0-rc17/go.mod h1:gDKOpD8xCWHw/PUdcTc0WFYf52Mq0etOFZ1oTkBlFkQ= -github.com/celestiaorg/celestia-core v1.27.0-tm-v0.34.28 h1:BE7JFZ1SYpwM9OfL9cPcjlO5xjIbDPgdFkJNouyl6jA= -github.com/celestiaorg/celestia-core v1.27.0-tm-v0.34.28/go.mod h1:1GT0RfdNqOXvyR3Hq4ROcNBknQNz9E6K5l3Cla9eFFk= -github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14 h1:dDfoQJOlVNj4HufJ1lBLTo2k3/L/255MIiKmEQziDmw= -github.com/celestiaorg/cosmos-sdk v1.18.0-sdk-v0.46.14/go.mod h1:kkdiHo/zG6ar80730+bG1owdMAQXrGp4utFu7mbfADo= +github.com/celestiaorg/celestia-app v1.0.0-rc18 h1:d2+KYPr4ri10aOtnXih3hOUd6ap94c/Hv46JFzpWzy0= +github.com/celestiaorg/celestia-app v1.0.0-rc18/go.mod h1:Q375ijriqMv8PjKmMujNs2WzMAsgxjpbeUoQt+AZre0= +github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= +github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29/go.mod h1:xrICN0PBhp3AdTaZ8q4wS5Jvi32V02HNjaC2EsWiEKk= +github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 h1:c4cMVLU2bGTesZW1ZVgeoCB++gOOJTF3OvBsqBvo6n0= +github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14/go.mod h1:D5y5Exw0bJkcDv9fvYDiZfZrDV1b6+xsFyiungxrCsU= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJx5/hgZ+IeOLEIoLsAveJN/7/ZtQQtPSVw= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= @@ -378,8 +378,8 @@ github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= github.com/celestiaorg/nmt v0.20.0 h1:9i7ultZ8Wv5ytt8ZRaxKQ5KOOMo4A2K2T/aPGjIlSas= github.com/celestiaorg/nmt v0.20.0/go.mod h1:Oz15Ub6YPez9uJV0heoU4WpFctxazuIhKyUtaYNio7E= -github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.1 h1:xOBMoRYSh/hnsEW7OLI3bjSJXKlqILfZehjPEK/+3oI= -github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.1/go.mod h1:NziNBP12grIi+DH3TqIKUUPM2T6Hl9BTyxiK3+p8yeA= +github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2 h1:Q8nr5SAtDW5gocrBwqwDJcSS/JedqU58WwQA2SP+nXw= +github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2/go.mod h1:s/LzLUw0WeYPJ6qdk4q46jKLOq7rc9Z5Mdrxtfpcigw= github.com/celestiaorg/rsmt2d v0.11.0 h1:lcto/637WyTEZR3dLRoNvyuExfnUbxvdvKi3qz/2V4k= github.com/celestiaorg/rsmt2d v0.11.0/go.mod h1:6Y580I3gVr0+OVFfW6m2JTwnCCmvW3WfbwSLfuT+HCA= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -446,8 +446,8 @@ github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZ github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= -github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= -github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= +github.com/confio/ics23/go v0.9.1 h1:3MV46eeWwO3xCauKyAtuAdJYMyPnnchW4iLr2bTw6/U= +github.com/confio/ics23/go v0.9.1/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= @@ -606,8 +606,8 @@ github.com/etclabscore/go-openrpc-reflect v0.0.37/go.mod h1:0404Ky3igAasAOpyj1eE github.com/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/ethereum/go-ethereum v1.13.1 h1:UF2FaUKPIy5jeZk3X06ait3y2Q4wI+vJ1l7+UARp+60= -github.com/ethereum/go-ethereum v1.13.1/go.mod h1:xHQKzwkHSl0gnSjZK1mWa06XEdm9685AHqhRknOzqGQ= +github.com/ethereum/go-ethereum v1.13.2 h1:g9mCpfPWqCA1OL4e6C98PeVttb0HadfBRuKTGvMnOvw= +github.com/ethereum/go-ethereum v1.13.2/go.mod h1:gkQ5Ygi64ZBh9M/4iXY1R8WqoNCx1Ey0CkYn2BD4/fw= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= From e6e840225c1b71d8dacf1150938364671bdb7858 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 4 Sep 2023 16:21:00 +0300 Subject: [PATCH 0845/1008] feat(cmd/rpc): add commands for the p2p module (#2599) --- cmd/celestia/blob.go | 58 ++--- cmd/celestia/p2p.go | 554 +++++++++++++++++++++++++++++++++++++++++++ cmd/celestia/rpc.go | 23 ++ 3 files changed, 591 insertions(+), 44 deletions(-) create mode 100644 cmd/celestia/p2p.go diff --git a/cmd/celestia/blob.go b/cmd/celestia/blob.go index 6d397525f5..9550f2c13a 100644 --- a/cmd/celestia/blob.go +++ b/cmd/celestia/blob.go @@ -2,9 +2,7 @@ package main import ( "encoding/base64" - "encoding/json" "fmt" - "os" "reflect" "strconv" @@ -85,8 +83,11 @@ var getCmd = &cobra.Command{ blob, err := client.Blob.Get(cmd.Context(), height, namespace, commitment) - printOutput(blob, err) - return nil + formatter := formatData + if base64Flag || err != nil { + formatter = nil + } + return printOutput(blob, err, formatter) }, } @@ -112,8 +113,11 @@ var getAllCmd = &cobra.Command{ blobs, err := client.Blob.GetAll(cmd.Context(), height, []share.Namespace{namespace}) - printOutput(blobs, err) - return nil + formatter := formatData + if base64Flag || err != nil { + formatter = nil + } + return printOutput(blobs, err, formatter) }, } @@ -150,9 +154,7 @@ var submitCmd = &cobra.Command{ Height: height, Commitment: parsedBlob.Commitment, } - - printOutput(response, err) - return nil + return printOutput(response, err, nil) }, } @@ -182,35 +184,10 @@ var getProofCmd = &cobra.Command{ } proof, err := client.Blob.GetProof(cmd.Context(), height, namespace, commitment) - - printOutput(proof, err) - return nil + return printOutput(proof, err, nil) }, } -func printOutput(data interface{}, err error) { - if err != nil { - data = err - } - - if !base64Flag && err == nil { - data = formatData(data) - } - - resp := struct { - Result interface{} `json:"result"` - }{ - Result: data, - } - - bytes, err := json.MarshalIndent(resp, "", " ") - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - fmt.Fprintln(os.Stdout, string(bytes)) -} - func formatData(data interface{}) interface{} { type tempBlob struct { Namespace []byte `json:"namespace"` @@ -220,11 +197,7 @@ func formatData(data interface{}) interface{} { } if reflect.TypeOf(data).Kind() == reflect.Slice { - blobs, ok := data.([]*blob.Blob) - if !ok { - return data - } - + blobs := data.([]*blob.Blob) result := make([]tempBlob, len(blobs)) for i, b := range blobs { result[i] = tempBlob{ @@ -237,10 +210,7 @@ func formatData(data interface{}) interface{} { return result } - b, ok := data.(*blob.Blob) - if !ok { - return data - } + b := data.(*blob.Blob) return tempBlob{ Namespace: b.Namespace(), Data: string(b.Data), diff --git a/cmd/celestia/p2p.go b/cmd/celestia/p2p.go new file mode 100644 index 0000000000..d0b6549f6c --- /dev/null +++ b/cmd/celestia/p2p.go @@ -0,0 +1,554 @@ +package main + +import ( + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + ma2 "github.com/multiformats/go-multiaddr" + "github.com/spf13/cobra" +) + +type peerInfo struct { + ID string `json:"id"` + PeerAddr []string `json:"peer_addr"` +} + +func init() { + p2pCmd.AddCommand(infoCmd, + peersCmd, + peerInfoCmd, + connectCmd, + closePeerCmd, + connectednessCmd, + natStatusCmd, + blockPeerCmd, + unblockPeerCmd, + blockedPeersCmd, + protectCmd, + unprotectCmd, + protectedCmd, + bandwidthStatsCmd, + peerBandwidthCmd, + bandwidthForProtocolCmd, + pubsubPeersCmd, + ) +} + +var p2pCmd = &cobra.Command{ + Use: "p2p [command]", + Short: "Allows interaction with the P2P Module via JSON-RPC", + Args: cobra.NoArgs, +} + +var infoCmd = &cobra.Command{ + Use: "info", + Short: "Gets the node's peer info (peer id and multiaddresses)", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + info, err := client.P2P.Info(cmd.Context()) + + formatter := func(data interface{}) interface{} { + peerAdd := data.(peer.AddrInfo) + ma := make([]string, len(info.Addrs)) + for i := range peerAdd.Addrs { + ma[i] = peerAdd.Addrs[i].String() + } + + return peerInfo{ + ID: peerAdd.ID.String(), + PeerAddr: ma, + } + } + return printOutput(info, err, formatter) + }, +} + +var peersCmd = &cobra.Command{ + Use: "peers", + Short: "Lists the peers we are connected to", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + result, err := client.P2P.Peers(cmd.Context()) + peers := make([]string, len(result)) + for i, peer := range result { + peers[i] = peer.String() + } + + formatter := func(data interface{}) interface{} { + conPeers := data.([]string) + return struct { + Peers []string `json:"peers"` + }{ + Peers: conPeers, + } + } + return printOutput(peers, err, formatter) + }, +} + +var peerInfoCmd = &cobra.Command{ + Use: "peer-info [param]", + Short: "Gets PeerInfo for a given peer", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + info, err := client.P2P.PeerInfo(cmd.Context(), pid) + formatter := func(data interface{}) interface{} { + peerAdd := data.(peer.AddrInfo) + ma := make([]string, len(info.Addrs)) + for i := range peerAdd.Addrs { + ma[i] = peerAdd.Addrs[i].String() + } + + return peerInfo{ + ID: peerAdd.ID.String(), + PeerAddr: ma, + } + } + return printOutput(info, err, formatter) + }, +} + +var connectCmd = &cobra.Command{ + Use: "connect [peer.ID, address]", + Short: "Establishes a connection with the given peer", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + ma, err := ma2.NewMultiaddr(args[1]) + if err != nil { + return err + } + + peerInfo := peer.AddrInfo{ + ID: pid, + Addrs: []ma2.Multiaddr{ma}, + } + + err = client.P2P.Connect(cmd.Context(), peerInfo) + if err != nil { + return printOutput(nil, err, nil) + } + return connectednessCmd.RunE(cmd, args) + }, +} + +var closePeerCmd = &cobra.Command{ + Use: "close-peer [peer.ID]", + Short: "Closes the connection with the given peer", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + err = client.P2P.ClosePeer(cmd.Context(), pid) + if err != nil { + return printOutput(nil, err, nil) + } + return connectednessCmd.RunE(cmd, args) + }, +} + +var connectednessCmd = &cobra.Command{ + Use: "connectedness [peer.ID]", + Short: "Checks the connection state between current and given peers", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + con, err := client.P2P.Connectedness(cmd.Context(), pid) + + formatter := func(data interface{}) interface{} { + conn := data.(network.Connectedness) + return struct { + ConnectionState string `json:"connection_state"` + }{ + ConnectionState: conn.String(), + } + } + return printOutput(con, err, formatter) + }, +} + +var natStatusCmd = &cobra.Command{ + Use: "nat-status", + Short: "Gets the currrent NAT status", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + r, err := client.P2P.NATStatus(cmd.Context()) + + formatter := func(data interface{}) interface{} { + rr := data.(network.Reachability) + return struct { + Reachability string `json:"reachability"` + }{ + Reachability: rr.String(), + } + } + return printOutput(r, err, formatter) + }, +} + +var blockPeerCmd = &cobra.Command{ + Use: "block-peer [peer.ID]", + Short: "Blocks the given peer", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + err = client.P2P.BlockPeer(cmd.Context(), pid) + + formatter := func(data interface{}) interface{} { + err, ok := data.(error) + blocked := false + if !ok { + blocked = true + } + return struct { + Blocked bool `json:"blocked"` + Peer string `json:"peer"` + Reason error `json:"reason,omitempty"` + }{ + Blocked: blocked, + Peer: args[0], + Reason: err, + } + } + return printOutput(err, nil, formatter) + }, +} + +var unblockPeerCmd = &cobra.Command{ + Use: "unblock-peer [peer.ID]", + Short: "Unblocks the given peer", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + err = client.P2P.UnblockPeer(cmd.Context(), pid) + + formatter := func(data interface{}) interface{} { + err, ok := data.(error) + unblocked := false + if !ok { + unblocked = true + } + + return struct { + Unblocked bool `json:"unblocked"` + Peer string `json:"peer"` + Reason error `json:"reason,omitempty"` + }{ + Unblocked: unblocked, + Peer: args[0], + Reason: err, + } + } + return printOutput(err, nil, formatter) + }, +} + +var blockedPeersCmd = &cobra.Command{ + Use: "blocked-peers", + Short: "Lists the node's blocked peers", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + list, err := client.P2P.ListBlockedPeers(cmd.Context()) + + pids := make([]string, len(list)) + for i, peer := range list { + pids[i] = peer.String() + } + + formatter := func(data interface{}) interface{} { + peers := data.([]string) + return struct { + Peers []string `json:"peers"` + }{ + Peers: peers, + } + } + return printOutput(pids, err, formatter) + }, +} + +var protectCmd = &cobra.Command{ + Use: "protect [peer.ID, tag]", + Short: "Protects the given peer from being pruned by the given tag", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + err = client.P2P.Protect(cmd.Context(), pid, args[1]) + + formatter := func(data interface{}) interface{} { + err, ok := data.(error) + protected := false + if !ok { + protected = true + } + return struct { + Protected bool `json:"protected"` + Peer string `json:"peer"` + Reason error `json:"reason,omitempty"` + }{ + Protected: protected, + Peer: args[0], + Reason: err, + } + } + return printOutput(err, nil, formatter) + }, +} + +var unprotectCmd = &cobra.Command{ + Use: "unprotect [peer.ID, tag]", + Short: "Removes a protection that may have been placed on a peer, under the specified tag." + + "The return value indicates whether the peer continues to be protected after this call, by way of a different tag", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + _, err = client.P2P.Unprotect(cmd.Context(), pid, args[1]) + + formatter := func(data interface{}) interface{} { + err, ok := data.(error) + unprotected := false + if !ok { + unprotected = true + } + return struct { + Unprotected bool `json:"unprotected"` + Peer string `json:"peer"` + Reason error `json:"reason,omitempty"` + }{ + Unprotected: unprotected, + Peer: args[0], + Reason: err, + } + } + return printOutput(err, nil, formatter) + }, +} + +var protectedCmd = &cobra.Command{ + Use: "protected [peer.ID, tag]", + Short: "Ensures that a given peer is protected under a specific tag", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + result, err := client.P2P.IsProtected(cmd.Context(), pid, args[1]) + return printOutput(result, err, nil) + }, +} + +type bandwidthStats struct { + TotalIn int64 `json:"total_in"` + TotalOut int64 `json:"total_out"` + RateIn float64 `json:"rate_in"` + RateOut float64 `json:"rate_out"` +} + +var bandwidthStatsCmd = &cobra.Command{ + Use: "bandwidth-stats", + Short: "Get stats struct with bandwidth metrics for all data sent/" + + "received by the local peer, regardless of protocol or remote peer IDs", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + result, err := client.P2P.BandwidthStats(cmd.Context()) + + formatter := func(data interface{}) interface{} { + stats := data.(metrics.Stats) + return bandwidthStats{ + TotalIn: stats.TotalIn, + TotalOut: stats.TotalOut, + RateIn: stats.RateIn, + RateOut: stats.RateOut, + } + } + return printOutput(result, err, formatter) + }, +} + +var peerBandwidthCmd = &cobra.Command{ + Use: "peer-bandwidth [peer.ID]", + Short: "Gets stats struct with bandwidth metrics associated with the given peer.ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + pid, err := peer.Decode(args[0]) + if err != nil { + return err + } + + result, err := client.P2P.BandwidthForPeer(cmd.Context(), pid) + + formatter := func(data interface{}) interface{} { + stats := data.(metrics.Stats) + return bandwidthStats{ + TotalIn: stats.TotalIn, + TotalOut: stats.TotalOut, + RateIn: stats.RateIn, + RateOut: stats.RateOut, + } + } + return printOutput(result, err, formatter) + }, +} + +var bandwidthForProtocolCmd = &cobra.Command{ + Use: "protocol-bandwidth [protocol.ID]", + Short: "Gets stats struct with bandwidth metrics associated with the given protocol.ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + result, err := client.P2P.BandwidthForProtocol(cmd.Context(), protocol.ID(args[0])) + + formatter := func(data interface{}) interface{} { + stats := data.(metrics.Stats) + return bandwidthStats{ + TotalIn: stats.TotalIn, + TotalOut: stats.TotalOut, + RateIn: stats.RateIn, + RateOut: stats.RateOut, + } + } + return printOutput(result, err, formatter) + }, +} + +var pubsubPeersCmd = &cobra.Command{ + Use: "pubsub-peers [topic]", + Short: "Lists the peers we are connected to in the given topic", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + result, err := client.P2P.PubSubPeers(cmd.Context(), args[0]) + peers := make([]string, len(result)) + + for i, peer := range result { + peers[i] = peer.String() + } + + formatter := func(data interface{}) interface{} { + conPeers := data.([]string) + return struct { + Peers []string `json:"peers"` + }{ + Peers: conPeers, + } + } + return printOutput(peers, err, formatter) + }, +} diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index c263496b26..cc5ca059f7 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -62,6 +62,7 @@ func init() { ) rpcCmd.AddCommand(logCmd, logModuleCmd) rpcCmd.AddCommand(blobCmd) + rpcCmd.AddCommand(p2pCmd) rootCmd.AddCommand(rpcCmd) } @@ -400,3 +401,25 @@ func rpcClient(ctx context.Context) (*client.Client, error) { } return client, nil } + +func printOutput(data interface{}, err error, formatData func(interface{}) interface{}) error { + switch { + case err != nil: + data = err + case formatData != nil: + data = formatData(data) + } + + resp := struct { + Result interface{} `json:"result"` + }{ + Result: data, + } + + bytes, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return err + } + fmt.Fprintln(os.Stdout, string(bytes)) + return nil +} From ae7a1d813c824d1725b888d7ca4bc7264dfe13f6 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 5 Sep 2023 12:49:38 +0300 Subject: [PATCH 0846/1008] feat(rpc): add daser cmd (#2651) --- cmd/celestia/das.go | 27 +++++++++++++++++++++++++++ cmd/celestia/rpc.go | 1 + 2 files changed, 28 insertions(+) create mode 100644 cmd/celestia/das.go diff --git a/cmd/celestia/das.go b/cmd/celestia/das.go new file mode 100644 index 0000000000..58c4a8362b --- /dev/null +++ b/cmd/celestia/das.go @@ -0,0 +1,27 @@ +package main + +import "github.com/spf13/cobra" + +func init() { + dasCmd.AddCommand(samplingStatsCmd) +} + +var dasCmd = &cobra.Command{ + Use: "das [command]", + Short: "Allows to interact with the Daser via JSON-RPC", + Args: cobra.NoArgs, +} + +var samplingStatsCmd = &cobra.Command{ + Use: "sampling-stats", + Short: "Returns the current statistics over the DA sampling process", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + stats, err := client.DAS.SamplingStats(cmd.Context()) + return printOutput(stats, err, nil) + }, +} diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index cc5ca059f7..c5d0a71343 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -63,6 +63,7 @@ func init() { rpcCmd.AddCommand(logCmd, logModuleCmd) rpcCmd.AddCommand(blobCmd) rpcCmd.AddCommand(p2pCmd) + rpcCmd.AddCommand(dasCmd) rootCmd.AddCommand(rpcCmd) } From c89ada18e986f7c9d16f2d65d817c428e870de5d Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 6 Sep 2023 16:19:53 +0300 Subject: [PATCH 0847/1008] cmd(rpc): add header cmd (#2658) --- cmd/celestia/header.go | 109 +++++++++++++++++++++++++++++++++++++++++ cmd/celestia/rpc.go | 1 + 2 files changed, 110 insertions(+) create mode 100644 cmd/celestia/header.go diff --git a/cmd/celestia/header.go b/cmd/celestia/header.go new file mode 100644 index 0000000000..1d8dc6d01b --- /dev/null +++ b/cmd/celestia/header.go @@ -0,0 +1,109 @@ +package main + +import ( + "encoding/hex" + "fmt" + "strconv" + + "github.com/spf13/cobra" +) + +func init() { + headerCmd.AddCommand( + localHeadCmd, + networkHeadCmd, + getByHashCmd, + getByHeightCmd, + syncStateCmd, + ) +} + +var headerCmd = &cobra.Command{ + Use: "header [command]", + Short: "Allows interaction with the Header Module via JSON-RPC", + Args: cobra.NoArgs, +} + +var localHeadCmd = &cobra.Command{ + Use: "local-head", + Short: "Returns the ExtendedHeader from the chain head.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + header, err := client.Header.LocalHead(cmd.Context()) + return printOutput(header, err, nil) + }, +} + +var networkHeadCmd = &cobra.Command{ + Use: "network-head", + Short: "Provides the Syncer's view of the current network head.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + header, err := client.Header.NetworkHead(cmd.Context()) + return printOutput(header, err, nil) + }, +} + +var getByHashCmd = &cobra.Command{ + Use: "get-by-hash", + Short: "Returns the header of the given hash from the node's header store.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + hash, err := hex.DecodeString(args[0]) + if err != nil { + return fmt.Errorf("error decoding a hash: expected a hex encoded string:%v", err) + } + header, err := client.Header.GetByHash(cmd.Context(), hash) + return printOutput(header, err, nil) + }, +} + +var getByHeightCmd = &cobra.Command{ + Use: "get-by-height", + Short: "Returns the ExtendedHeader at the given height if it is currently available.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + height, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a height:%v", err) + } + + header, err := client.Header.GetByHeight(cmd.Context(), height) + return printOutput(header, err, nil) + }, +} + +var syncStateCmd = &cobra.Command{ + Use: "sync-state", + Short: "Returns the current state of the header Syncer.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + header, err := client.Header.SyncState(cmd.Context()) + return printOutput(header, err, nil) + }, +} diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index c5d0a71343..4425aa9ae5 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -64,6 +64,7 @@ func init() { rpcCmd.AddCommand(blobCmd) rpcCmd.AddCommand(p2pCmd) rpcCmd.AddCommand(dasCmd) + rpcCmd.AddCommand(headerCmd) rootCmd.AddCommand(rpcCmd) } From e21654401bbb80a9a8d672f84e74b414f4474d8c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 13 Sep 2023 12:24:32 +0300 Subject: [PATCH 0848/1008] feat(rpc): add share cmd (#2664) --- cmd/celestia/rpc.go | 1 + cmd/celestia/share.go | 196 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 cmd/celestia/share.go diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 4425aa9ae5..1516211c3e 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -65,6 +65,7 @@ func init() { rpcCmd.AddCommand(p2pCmd) rpcCmd.AddCommand(dasCmd) rpcCmd.AddCommand(headerCmd) + rpcCmd.AddCommand(shareCmd) rootCmd.AddCommand(rpcCmd) } diff --git a/cmd/celestia/share.go b/cmd/celestia/share.go new file mode 100644 index 0000000000..2f84871d80 --- /dev/null +++ b/cmd/celestia/share.go @@ -0,0 +1,196 @@ +package main + +import ( + "encoding/hex" + "encoding/json" + "strconv" + + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-app/pkg/da" + + "github.com/celestiaorg/celestia-node/share" +) + +func init() { + shareCmd.AddCommand( + sharesAvailableCmd, + probabilityOfAvailabilityCmd, + getSharesByNamespaceCmd, + getShare, + getEDS, + ) +} + +var shareCmd = &cobra.Command{ + Use: "share [command]", + Short: "Allows interaction with the Share Module via JSON-RPC", + Args: cobra.NoArgs, +} + +var sharesAvailableCmd = &cobra.Command{ + Use: "available", + Short: "Subjectively validates if Shares committed to the given Root are available on the Network.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + raw, err := parseJSON(args[0]) + if err != nil { + return err + } + + root := da.MinDataAvailabilityHeader() + err = json.Unmarshal(raw, &root) + if err != nil { + return err + } + + err = client.Share.SharesAvailable(cmd.Context(), &root) + formatter := func(data interface{}) interface{} { + err, ok := data.(error) + available := false + if !ok { + available = true + } + return struct { + Available bool `json:"available"` + Hash []byte `json:"dah_hash"` + Reason error `json:"reason,omitempty"` + }{ + Available: available, + Hash: []byte(args[0]), + Reason: err, + } + } + return printOutput(err, nil, formatter) + }, +} + +var probabilityOfAvailabilityCmd = &cobra.Command{ + Use: "availability", + Short: "Calculates the probability of the data square being available based on the number of samples collected.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + prob := client.Share.ProbabilityOfAvailability(cmd.Context()) + return printOutput(prob, nil, nil) + }, +} + +var getSharesByNamespaceCmd = &cobra.Command{ + Use: "get-by-namespace [dah, namespace]", + Short: "Gets all shares from an EDS within the given namespace.", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + raw, err := parseJSON(args[0]) + if err != nil { + return err + } + + root := da.MinDataAvailabilityHeader() + err = json.Unmarshal(raw, &root) + if err != nil { + return err + } + + ns, err := parseV0Namespace(args[1]) + if err != nil { + return err + } + + shares, err := client.Share.GetSharesByNamespace(cmd.Context(), &root, ns) + return printOutput(shares, err, nil) + }, +} + +var getShare = &cobra.Command{ + Use: "get-share [dah, row, col]", + Short: "Gets a Share by coordinates in EDS.", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + raw, err := parseJSON(args[0]) + if err != nil { + return err + } + + root := da.MinDataAvailabilityHeader() + err = json.Unmarshal(raw, &root) + if err != nil { + return err + } + + row, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + + col, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return err + } + + s, err := client.Share.GetShare(cmd.Context(), &root, int(row), int(col)) + + formatter := func(data interface{}) interface{} { + sh, ok := data.(share.Share) + if !ok { + return data + } + + ns := hex.EncodeToString(share.GetNamespace(sh)) + + return struct { + Namespace string `json:"namespace"` + Data []byte `json:"data"` + }{ + Namespace: ns, + Data: share.GetData(sh), + } + } + return printOutput(s, err, formatter) + }, +} + +var getEDS = &cobra.Command{ + Use: "get-eds [dah]", + Short: "Gets the full EDS identified by the given root", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + raw, err := parseJSON(args[0]) + if err != nil { + return err + } + + root := da.MinDataAvailabilityHeader() + err = json.Unmarshal(raw, &root) + if err != nil { + return err + } + + shares, err := client.Share.GetEDS(cmd.Context(), &root) + return printOutput(shares, err, nil) + }, +} From c38fcb7e8453c79280e6267e29454a03c9fa02de Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 15 Sep 2023 13:40:54 +0300 Subject: [PATCH 0849/1008] feat(rpc): add admin cmd (#2701) --- cmd/celestia/admin.go | 108 ++++++++++++++++++++++++++++++++++++++++++ cmd/celestia/logs.go | 48 ------------------- cmd/celestia/rpc.go | 3 +- 3 files changed, 109 insertions(+), 50 deletions(-) create mode 100644 cmd/celestia/admin.go delete mode 100644 cmd/celestia/logs.go diff --git a/cmd/celestia/admin.go b/cmd/celestia/admin.go new file mode 100644 index 0000000000..a3665e0aa1 --- /dev/null +++ b/cmd/celestia/admin.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "errors" + "strings" + + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/spf13/cobra" +) + +func init() { + nodeCmd.AddCommand(nodeInfoCmd, logCmd, verifyCmd, authCmd) + rootCmd.AddCommand(nodeCmd) +} + +var nodeCmd = &cobra.Command{ + Use: "node [command]", + Short: "Allows administrating running node.", + Args: cobra.NoArgs, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + rpcClient, err := newRPCClient(cmd.Context()) + if err != nil { + return err + } + + ctx := context.WithValue(cmd.Context(), rpcClientKey{}, rpcClient) + cmd.SetContext(ctx) + return nil + }, +} + +var nodeInfoCmd = &cobra.Command{ + Use: "info", + Args: cobra.NoArgs, + Short: "Returns administrative information about the node.", + RunE: func(c *cobra.Command, args []string) error { + client, err := rpcClient(c.Context()) + if err != nil { + return err + } + info, err := client.Node.Info(c.Context()) + return printOutput(info, err, nil) + }, +} + +var logCmd = &cobra.Command{ + Use: "log-level", + Args: cobra.MinimumNArgs(1), + Short: "Allows to set log level for module to in format :" + + "`DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL and their lower-case forms`.\n" + + "To set all modules to a particular level `*:` should be passed", + RunE: func(c *cobra.Command, args []string) error { + client, err := rpcClient(c.Context()) + if err != nil { + return err + } + + for _, ll := range args { + params := strings.Split(ll, ":") + if len(params) != 2 { + return errors.New("cmd: log-level arg must be in form :," + + "e.g. pubsub:debug") + } + + if err = client.Node.LogLevelSet(c.Context(), params[0], params[1]); err != nil { + return err + } + } + return nil + }, +} + +var verifyCmd = &cobra.Command{ + Use: "permissions", + Args: cobra.ExactArgs(1), + Short: "Returns the permissions assigned to the given token.", + + RunE: func(c *cobra.Command, args []string) error { + client, err := rpcClient(c.Context()) + if err != nil { + return err + } + + perms, err := client.Node.AuthVerify(c.Context(), args[0]) + return printOutput(perms, err, nil) + }, +} + +var authCmd = &cobra.Command{ + Use: "set-permissions", + Args: cobra.MinimumNArgs(1), + Short: "Signs and returns a new token with the given permissions.", + RunE: func(c *cobra.Command, args []string) error { + client, err := rpcClient(c.Context()) + if err != nil { + return err + } + + perms := make([]auth.Permission, len(args)) + for i, p := range args { + perms[i] = (auth.Permission)(p) + } + + result, err := client.Node.AuthNew(c.Context(), perms) + return printOutput(result, err, nil) + }, +} diff --git a/cmd/celestia/logs.go b/cmd/celestia/logs.go deleted file mode 100644 index ac302ff6dd..0000000000 --- a/cmd/celestia/logs.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/spf13/cobra" - - "github.com/celestiaorg/celestia-node/cmd" -) - -var logCmd = &cobra.Command{ - Use: cmd.LogLevelFlag, - Args: cobra.ExactArgs(1), - Short: "Allows to set log level for all modules to " + - "`DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL and their lower-case forms`", - - RunE: func(c *cobra.Command, args []string) error { - client, err := rpcClient(c.Context()) - if err != nil { - return err - } - return client.Node.LogLevelSet(c.Context(), "*", args[0]) - }, -} - -var logModuleCmd = &cobra.Command{ - Use: cmd.LogLevelModuleFlag, - Args: cobra.MinimumNArgs(1), - Short: "Allows to set log level for a particular module in format :", - RunE: func(c *cobra.Command, args []string) error { - client, err := rpcClient(c.Context()) - if err != nil { - return err - } - for _, ll := range args { - params := strings.Split(ll, ":") - if len(params) != 2 { - return fmt.Errorf("cmd: %s arg must be in form :,"+ - "e.g. pubsub:debug", cmd.LogLevelModuleFlag) - } - if err = client.Node.LogLevelSet(c.Context(), params[0], params[1]); err != nil { - return err - } - } - return nil - }, -} diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 1516211c3e..a0dea6e0dd 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -23,7 +23,7 @@ import ( "github.com/celestiaorg/celestia-node/state" ) -const authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" //nolint:gosec +const authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" var requestURL string var authTokenFlag string @@ -60,7 +60,6 @@ func init() { false, "Print JSON-RPC request along with the response", ) - rpcCmd.AddCommand(logCmd, logModuleCmd) rpcCmd.AddCommand(blobCmd) rpcCmd.AddCommand(p2pCmd) rpcCmd.AddCommand(dasCmd) From cc02d71d9e10a2b747bdb6ea4796c6a0f87d4c4e Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 15 Sep 2023 13:46:41 +0300 Subject: [PATCH 0850/1008] feat(rpc): add state cmd (#2691) --- cmd/celestia/rpc.go | 1 + cmd/celestia/state.go | 424 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+) create mode 100644 cmd/celestia/state.go diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index a0dea6e0dd..e7e3c0d5be 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -65,6 +65,7 @@ func init() { rpcCmd.AddCommand(dasCmd) rpcCmd.AddCommand(headerCmd) rpcCmd.AddCommand(shareCmd) + rpcCmd.AddCommand(stateCmd) rootCmd.AddCommand(rpcCmd) } diff --git a/cmd/celestia/state.go b/cmd/celestia/state.go new file mode 100644 index 0000000000..2f3d1e138a --- /dev/null +++ b/cmd/celestia/state.go @@ -0,0 +1,424 @@ +package main + +import ( + "fmt" + "strconv" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/state" +) + +func init() { + stateCmd.AddCommand( + accountAddressCmd, + balanceCmd, + balanceForAddressCmd, + transferCmd, + submitTxCmd, + submitPFBCmd, + cancelUnbondingDelegationCmd, + beginRedelegateCmd, + undelegateCmd, + delegateCmd, + queryDelegationCmd, + queryUnbondingCmd, + queryRedelegationCmd, + ) +} + +var stateCmd = &cobra.Command{ + Use: "state [command]", + Short: "Allows interaction with the State Module via JSON-RPC", + Args: cobra.NoArgs, +} + +var accountAddressCmd = &cobra.Command{ + Use: "account-address", + Short: "Retrieves the address of the node's account/signer.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + address, err := client.State.AccountAddress(cmd.Context()) + return printOutput(address, err, nil) + }, +} + +var balanceCmd = &cobra.Command{ + Use: "balance", + Short: "Retrieves the Celestia coin balance for the node's account/signer and verifies it against " + + "the corresponding block's AppHash.", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + balance, err := client.State.Balance(cmd.Context()) + return printOutput(balance, err, nil) + }, +} + +var balanceForAddressCmd = &cobra.Command{ + Use: "balance-for-address [address]", + Short: "Retrieves the Celestia coin balance for the given address and verifies the returned balance against " + + "the corresponding block's AppHash.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + balance, err := client.State.BalanceForAddress(cmd.Context(), addr) + return printOutput(balance, err, nil) + }, +} + +var transferCmd = &cobra.Command{ + Use: "transfer [address] [amount] [fee] [gasLimit]", + Short: "Sends the given amount of coins from default wallet of the node to the given account address.", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + amount, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return fmt.Errorf("error parsing an amount:%v", err) + } + fee, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + gasLimit, err := strconv.ParseUint(args[3], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a gas limit:%v", err) + } + + txResponse, err := client.State.Transfer( + cmd.Context(), + addr.Address.(state.AccAddress), + math.NewInt(amount), + math.NewInt(fee), gasLimit, + ) + return printOutput(txResponse, err, nil) + }, +} + +var submitTxCmd = &cobra.Command{ + Use: "submit-tx [tx]", + Short: "Submits the given transaction/message to the Celestia network and blocks until the tx is included in a block.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + rawTx, err := decodeToBytes(args[0]) + if err != nil { + return fmt.Errorf("failed to decode tx: %v", err) + } + txResponse, err := client.State.SubmitTx( + cmd.Context(), + rawTx, + ) + return printOutput(txResponse, err, nil) + }, +} + +var submitPFBCmd = &cobra.Command{ + Use: "submit-pfb [namespace] [data] [fee] [gasLim]", + Short: "Allows to submit PFBs", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + namespace, err := parseV0Namespace(args[0]) + if err != nil { + return fmt.Errorf("error parsing a namespace:%v", err) + } + + fee, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + + gasLimit, err := strconv.ParseUint(args[3], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a gasLim:%v", err) + } + + parsedBlob, err := blob.NewBlobV0(namespace, []byte(args[1])) + if err != nil { + return fmt.Errorf("error creating a blob:%v", err) + } + + txResp, err := client.State.SubmitPayForBlob(cmd.Context(), types.NewInt(fee), gasLimit, []*blob.Blob{parsedBlob}) + return printOutput(txResp, err, nil) + }, +} + +var cancelUnbondingDelegationCmd = &cobra.Command{ + Use: "cancel-unbonding-delegation [address] [amount] [height] [fee] [gasLimit]", + Short: "Cancels a user's pending undelegation from a validator.", + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + amount, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return fmt.Errorf("error parsing an amount:%v", err) + } + + height, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + + fee, err := strconv.ParseInt(args[3], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + + gasLimit, err := strconv.ParseUint(args[4], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a gas limit:%v", err) + } + + txResponse, err := client.State.CancelUnbondingDelegation( + cmd.Context(), + addr.Address.(state.ValAddress), + math.NewInt(amount), + math.NewInt(height), + math.NewInt(fee), + gasLimit, + ) + return printOutput(txResponse, err, nil) + }, +} + +var beginRedelegateCmd = &cobra.Command{ + Use: "begin-redelegate [srcAddress] [dstAddress] [amount] [fee] [gasLimit]", + Short: "Sends a user's delegated tokens to a new validator for redelegation", + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + srcAddr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + dstAddr, err := parseAddressFromString(args[1]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + amount, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("error parsing an amount:%v", err) + } + + fee, err := strconv.ParseInt(args[3], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + gasLimit, err := strconv.ParseUint(args[4], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a gas limit:%v", err) + } + + txResponse, err := client.State.BeginRedelegate( + cmd.Context(), + srcAddr.Address.(state.ValAddress), + dstAddr.Address.(state.ValAddress), + math.NewInt(amount), + math.NewInt(fee), + gasLimit, + ) + return printOutput(txResponse, err, nil) + }, +} + +var undelegateCmd = &cobra.Command{ + Use: "undelegate [valAddress] [amount] [fee] [gasLimit]", + Short: "Undelegates a user's delegated tokens, unbonding them from the current validator.", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + amount, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return fmt.Errorf("error parsing an amount:%v", err) + } + fee, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + gasLimit, err := strconv.ParseUint(args[3], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a gas limit:%v", err) + } + + txResponse, err := client.State.Undelegate( + cmd.Context(), + addr.Address.(state.ValAddress), + math.NewInt(amount), + math.NewInt(fee), + gasLimit, + ) + return printOutput(txResponse, err, nil) + }, +} + +var delegateCmd = &cobra.Command{ + Use: "delegate [valAddress] [amount] [fee] [gasLimit]", + Short: "Sends a user's liquid tokens to a validator for delegation.", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + amount, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return fmt.Errorf("error parsing an amount:%v", err) + } + + fee, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a fee:%v", err) + } + + gasLimit, err := strconv.ParseUint(args[3], 10, 64) + if err != nil { + return fmt.Errorf("error parsing a gas limit:%v", err) + } + + txResponse, err := client.State.Delegate( + cmd.Context(), + addr.Address.(state.ValAddress), + math.NewInt(amount), + math.NewInt(fee), + gasLimit, + ) + return printOutput(txResponse, err, nil) + }, +} + +var queryDelegationCmd = &cobra.Command{ + Use: "get-delegation [valAddress]", + Short: "Retrieves the delegation information between a delegator and a validator.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + balance, err := client.State.QueryDelegation(cmd.Context(), addr.Address.(state.ValAddress)) + fmt.Println(balance) + fmt.Println(err) + return printOutput(balance, err, nil) + }, +} + +var queryUnbondingCmd = &cobra.Command{ + Use: "get-unbonding [valAddress]", + Short: "Retrieves the unbonding status between a delegator and a validator.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + addr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing an address:%v", err) + } + + response, err := client.State.QueryUnbonding(cmd.Context(), addr.Address.(state.ValAddress)) + return printOutput(response, err, nil) + }, +} + +var queryRedelegationCmd = &cobra.Command{ + Use: "get-redelegations [srcAddress] [dstAddress]", + Short: "Retrieves the status of the redelegations between a delegator and a validator.", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := rpcClient(cmd.Context()) + if err != nil { + return err + } + + srcAddr, err := parseAddressFromString(args[0]) + if err != nil { + return fmt.Errorf("error parsing a src address:%v", err) + } + + dstAddr, err := parseAddressFromString(args[1]) + if err != nil { + return fmt.Errorf("error parsing a dst address:%v", err) + } + + response, err := client.State.QueryRedelegations( + cmd.Context(), + srcAddr.Address.(state.ValAddress), + dstAddr.Address.(state.ValAddress), + ) + return printOutput(response, err, nil) + }, +} From 0d081eb134f4e8cc5ec2dbccc844e78bdced35e2 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 25 Sep 2023 13:43:00 +0300 Subject: [PATCH 0851/1008] refactor(cmd): extract rpc cmds (#2706) --- cmd/celestia/bridge.go | 2 +- cmd/celestia/cmd_test.go | 20 + cmd/celestia/das.go | 27 -- cmd/celestia/full.go | 2 +- cmd/celestia/light.go | 2 +- cmd/celestia/rpc.go | 452 ++---------------- cmd/celestia/util.go | 68 --- cmd/rpc.go | 58 +++ cmd/util.go | 127 +++++ cmd/{celestia/rpc_test.go => util_test.go} | 4 +- .../celestia => nodebuilder/blob/cmd}/blob.go | 52 +- nodebuilder/das/cmd/das.go | 34 ++ .../header/cmd}/header.go | 40 +- .../admin.go => nodebuilder/node/cmd/node.go | 55 +-- {cmd/celestia => nodebuilder/p2p/cmd}/p2p.go | 110 +++-- .../share/cmd}/share.go | 47 +- .../state/cmd}/state.go | 108 +++-- 17 files changed, 521 insertions(+), 687 deletions(-) delete mode 100644 cmd/celestia/das.go delete mode 100644 cmd/celestia/util.go create mode 100644 cmd/rpc.go create mode 100644 cmd/util.go rename cmd/{celestia/rpc_test.go => util_test.go} (97%) rename {cmd/celestia => nodebuilder/blob/cmd}/blob.go (77%) create mode 100644 nodebuilder/das/cmd/das.go rename {cmd/celestia => nodebuilder/header/cmd}/header.go (68%) rename cmd/celestia/admin.go => nodebuilder/node/cmd/node.go (61%) rename {cmd/celestia => nodebuilder/p2p/cmd}/p2p.go (79%) rename {cmd/celestia => nodebuilder/share/cmd}/share.go (76%) rename {cmd/celestia => nodebuilder/state/cmd}/state.go (80%) diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index fb5066e5d4..c0e2ab0d1a 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -42,6 +42,6 @@ var bridgeCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Bridge node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return persistentPreRunEnv(cmd, node.Bridge, args) + return cmdnode.PersistentPreRunEnv(cmd, node.Bridge, args) }, } diff --git a/cmd/celestia/cmd_test.go b/cmd/celestia/cmd_test.go index 503548b708..9c26489e14 100644 --- a/cmd/celestia/cmd_test.go +++ b/cmd/celestia/cmd_test.go @@ -128,3 +128,23 @@ func TestBridge(t *testing.T) { }) */ } + +func parseSignatureForHelpstring(methodSig reflect.StructField) string { + simplifiedSignature := "(" + in, out := methodSig.Type.NumIn(), methodSig.Type.NumOut() + for i := 1; i < in; i++ { + simplifiedSignature += methodSig.Type.In(i).String() + if i != in-1 { + simplifiedSignature += ", " + } + } + simplifiedSignature += ") -> (" + for i := 0; i < out-1; i++ { + simplifiedSignature += methodSig.Type.Out(i).String() + if i != out-2 { + simplifiedSignature += ", " + } + } + simplifiedSignature += ")" + return simplifiedSignature +} diff --git a/cmd/celestia/das.go b/cmd/celestia/das.go deleted file mode 100644 index 58c4a8362b..0000000000 --- a/cmd/celestia/das.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import "github.com/spf13/cobra" - -func init() { - dasCmd.AddCommand(samplingStatsCmd) -} - -var dasCmd = &cobra.Command{ - Use: "das [command]", - Short: "Allows to interact with the Daser via JSON-RPC", - Args: cobra.NoArgs, -} - -var samplingStatsCmd = &cobra.Command{ - Use: "sampling-stats", - Short: "Returns the current statistics over the DA sampling process", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) - if err != nil { - return err - } - stats, err := client.DAS.SamplingStats(cmd.Context()) - return printOutput(stats, err, nil) - }, -} diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 912de0bca8..8baff1080e 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -46,6 +46,6 @@ var fullCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Full node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return persistentPreRunEnv(cmd, node.Full, args) + return cmdnode.PersistentPreRunEnv(cmd, node.Full, args) }, } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 9c63945445..553660c5d3 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -46,6 +46,6 @@ var lightCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Light node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return persistentPreRunEnv(cmd, node.Light, args) + return cmdnode.PersistentPreRunEnv(cmd, node.Light, args) }, } diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index e7e3c0d5be..8cf51fda09 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -1,428 +1,40 @@ package main import ( - "bytes" - "context" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "os" - "reflect" - "strconv" - "strings" - - "github.com/spf13/cobra" - - "github.com/celestiaorg/celestia-node/api/rpc/client" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/state" + "github.com/celestiaorg/celestia-node/cmd" + blob "github.com/celestiaorg/celestia-node/nodebuilder/blob/cmd" + das "github.com/celestiaorg/celestia-node/nodebuilder/das/cmd" + header "github.com/celestiaorg/celestia-node/nodebuilder/header/cmd" + node "github.com/celestiaorg/celestia-node/nodebuilder/node/cmd" + p2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p/cmd" + share "github.com/celestiaorg/celestia-node/nodebuilder/share/cmd" + state "github.com/celestiaorg/celestia-node/nodebuilder/state/cmd" ) -const authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" - -var requestURL string -var authTokenFlag string -var printRequest bool - -type jsonRPCRequest struct { - ID int64 `json:"id"` - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params []interface{} `json:"params"` -} - -type outputWithRequest struct { - Request jsonRPCRequest - Response json.RawMessage -} - func init() { - rpcCmd.PersistentFlags().StringVar( - &requestURL, - "url", - "http://localhost:26658", - "Request URL", - ) - rpcCmd.PersistentFlags().StringVar( - &authTokenFlag, - "auth", - "", - "Authorization token (if not provided, the "+authEnvKey+" environment variable will be used)", + blob.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + das.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + header.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + p2p.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + share.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + state.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + node.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) + + blob.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + das.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + header.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + p2p.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + share.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + state.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + node.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + + rootCmd.AddCommand( + blob.Cmd, + das.Cmd, + header.Cmd, + p2p.Cmd, + share.Cmd, + state.Cmd, + node.Cmd, ) - rpcCmd.PersistentFlags().BoolVar( - &printRequest, - "print-request", - false, - "Print JSON-RPC request along with the response", - ) - rpcCmd.AddCommand(blobCmd) - rpcCmd.AddCommand(p2pCmd) - rpcCmd.AddCommand(dasCmd) - rpcCmd.AddCommand(headerCmd) - rpcCmd.AddCommand(shareCmd) - rpcCmd.AddCommand(stateCmd) - rootCmd.AddCommand(rpcCmd) -} - -var rpcCmd = &cobra.Command{ - Use: "rpc [namespace] [method] [params...]", - Short: "Send JSON-RPC request", - Args: cobra.MinimumNArgs(2), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - rpcClient, err := newRPCClient(cmd.Context()) - if err != nil { - return err - } - - ctx := context.WithValue(cmd.Context(), rpcClientKey{}, rpcClient) - cmd.SetContext(ctx) - return nil - }, - PersistentPostRunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) - if err != nil { - return err - } - - client.Close() - return nil - }, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - modules := client.Modules - if len(args) == 0 { - // get keys from modules (map[string]interface{}) - var keys []string - for k := range modules { - keys = append(keys, k) - } - return keys, cobra.ShellCompDirectiveNoFileComp - } else if len(args) == 1 { - // get methods from module - module := modules[args[0]] - methods := reflect.VisibleFields(reflect.TypeOf(module).Elem()) - var methodNames []string - for _, m := range methods { - methodNames = append(methodNames, m.Name+"\t"+parseSignatureForHelpstring(m)) - } - return methodNames, cobra.ShellCompDirectiveNoFileComp - } - return nil, cobra.ShellCompDirectiveNoFileComp - }, - Run: func(cmd *cobra.Command, args []string) { - namespace := args[0] - method := args[1] - params := parseParams(method, args[2:]) - - sendJSONRPCRequest(namespace, method, params) - }, -} - -func parseParams(method string, params []string) []interface{} { - parsedParams := make([]interface{}, len(params)) - validateParamsFn := func(has, want int) error { - if has != want { - return fmt.Errorf("rpc: invalid amount of params. has=%d, want=%d", has, want) - } - return nil - } - switch method { - case "GetSharesByNamespace": - if err := validateParamsFn(len(params), 2); err != nil { - panic(err) - } - // 1. Share Root - root, err := parseJSON(params[0]) - if err != nil { - panic(fmt.Errorf("couldn't parse share root as json: %v", err)) - } - parsedParams[0] = root - // 2. Namespace - namespace, err := parseV0Namespace(params[1]) - if err != nil { - panic(fmt.Sprintf("Error parsing namespace: %v", err)) - } - parsedParams[1] = namespace - return parsedParams - case "QueryDelegation", "QueryUnbonding", "BalanceForAddress": - var err error - if err = validateParamsFn(len(params), 2); err != nil { - panic(err) - } - parsedParams[0], err = parseAddressFromString(params[0]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - return parsedParams - case "QueryRedelegations": - var err error - parsedParams[0], err = parseAddressFromString(params[0]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - parsedParams[1], err = parseAddressFromString(params[1]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - return parsedParams - case "Transfer", "Delegate", "Undelegate": - // 1. Address - var err error - if err = validateParamsFn(len(params), 4); err != nil { - panic(err) - } - parsedParams[0], err = parseAddressFromString(params[0]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - // 2. Amount + Fee - parsedParams[1] = params[1] - parsedParams[2] = params[2] - // 3. GasLimit (uint64) - num, err := strconv.ParseUint(params[3], 10, 64) - if err != nil { - panic("Error parsing gas limit: uint64 could not be parsed.") - } - parsedParams[3] = num - return parsedParams - case "CancelUnbondingDelegation": - // 1. Validator Address - var err error - if err = validateParamsFn(len(params), 5); err != nil { - panic(err) - } - parsedParams[0], err = parseAddressFromString(params[0]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - // 2. Amount + Height + Fee - parsedParams[1] = params[1] - parsedParams[2] = params[2] - parsedParams[3] = params[3] - // 4. GasLimit (uint64) - num, err := strconv.ParseUint(params[4], 10, 64) - if err != nil { - panic("Error parsing gas limit: uint64 could not be parsed.") - } - parsedParams[4] = num - return parsedParams - case "BeginRedelegate": - // 1. Source Validator Address - var err error - if err = validateParamsFn(len(params), 5); err != nil { - panic(err) - } - parsedParams[0], err = parseAddressFromString(params[0]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - // 2. Destination Validator Address - parsedParams[1], err = parseAddressFromString(params[1]) - if err != nil { - panic(fmt.Errorf("error parsing address: %w", err)) - } - // 2. Amount + Fee - parsedParams[2] = params[2] - parsedParams[3] = params[3] - // 4. GasLimit (uint64) - num, err := strconv.ParseUint(params[4], 10, 64) - if err != nil { - panic("Error parsing gas limit: uint64 could not be parsed.") - } - parsedParams[4] = num - return parsedParams - default: - } - - for i, param := range params { - if param[0] == '{' || param[0] == '[' { - rawJSON, err := parseJSON(param) - if err != nil { - parsedParams[i] = param - } else { - parsedParams[i] = rawJSON - } - } else { - // try to parse arguments as numbers before adding them as strings - num, err := strconv.ParseInt(param, 10, 64) - if err == nil { - parsedParams[i] = num - continue - } - parsedParams[i] = param - } - } - return parsedParams -} - -func sendJSONRPCRequest(namespace, method string, params []interface{}) { - url := requestURL - request := jsonRPCRequest{ - ID: 1, - JSONRPC: "2.0", - Method: fmt.Sprintf("%s.%s", namespace, method), - Params: params, - } - - requestBody, err := json.Marshal(request) - if err != nil { - log.Fatalf("Error marshaling JSON-RPC request: %v", err) - } - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody)) - if err != nil { - log.Fatalf("Error creating JSON-RPC request: %v", err) - } - - req.Header.Set("Content-Type", "application/json") - - authToken := authTokenFlag - if authToken == "" { - authToken = os.Getenv(authEnvKey) - } - if authToken != "" { - req.Header.Set("Authorization", "Bearer "+authToken) - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Fatalf("Error sending JSON-RPC request: %v", err) - } - defer resp.Body.Close() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatalf("Error reading response body: %v", err) //nolint:gocritic - } - - rawResponseJSON, err := parseJSON(string(responseBody)) - if err != nil { - log.Fatalf("Error parsing JSON-RPC response: %v", err) - } - if printRequest { - output, err := json.MarshalIndent(outputWithRequest{ - Request: request, - Response: rawResponseJSON, - }, "", " ") - if err != nil { - panic(fmt.Sprintf("Error marshaling JSON-RPC response: %v", err)) - } - fmt.Println(string(output)) - return - } - - output, err := json.MarshalIndent(rawResponseJSON, "", " ") - if err != nil { - panic(fmt.Sprintf("Error marshaling JSON-RPC response: %v", err)) - } - fmt.Println(string(output)) -} - -func parseAddressFromString(addrStr string) (state.Address, error) { - var address state.Address - err := address.UnmarshalJSON([]byte(addrStr)) - if err != nil { - return address, err - } - return address, nil -} - -func parseSignatureForHelpstring(methodSig reflect.StructField) string { - simplifiedSignature := "(" - in, out := methodSig.Type.NumIn(), methodSig.Type.NumOut() - for i := 1; i < in; i++ { - simplifiedSignature += methodSig.Type.In(i).String() - if i != in-1 { - simplifiedSignature += ", " - } - } - simplifiedSignature += ") -> (" - for i := 0; i < out-1; i++ { - simplifiedSignature += methodSig.Type.Out(i).String() - if i != out-2 { - simplifiedSignature += ", " - } - } - simplifiedSignature += ")" - return simplifiedSignature -} - -// parseV0Namespace parses a namespace from a base64 or hex string. The param -// is expected to be the user-specified portion of a v0 namespace ID (i.e. the -// last 10 bytes). -func parseV0Namespace(param string) (share.Namespace, error) { - userBytes, err := decodeToBytes(param) - if err != nil { - return nil, err - } - - // if the namespace ID is <= 10 bytes, left pad it with 0s - return share.NewBlobNamespaceV0(userBytes) -} - -// decodeToBytes decodes a Base64 or hex input string into a byte slice. -func decodeToBytes(param string) ([]byte, error) { - if strings.HasPrefix(param, "0x") { - decoded, err := hex.DecodeString(param[2:]) - if err != nil { - return nil, fmt.Errorf("error decoding namespace ID: %w", err) - } - return decoded, nil - } - // otherwise, it's just a base64 string - decoded, err := base64.StdEncoding.DecodeString(param) - if err != nil { - return nil, fmt.Errorf("error decoding namespace ID: %w", err) - } - return decoded, nil -} - -func parseJSON(param string) (json.RawMessage, error) { - var raw json.RawMessage - err := json.Unmarshal([]byte(param), &raw) - return raw, err -} - -func newRPCClient(ctx context.Context) (*client.Client, error) { - if authTokenFlag == "" { - authTokenFlag = os.Getenv(authEnvKey) - } - return client.NewClient(ctx, requestURL, authTokenFlag) -} - -type rpcClientKey struct{} - -func rpcClient(ctx context.Context) (*client.Client, error) { - client, ok := ctx.Value(rpcClientKey{}).(*client.Client) - if !ok { - return nil, errors.New("rpc client was not set") - } - return client, nil -} - -func printOutput(data interface{}, err error, formatData func(interface{}) interface{}) error { - switch { - case err != nil: - data = err - case formatData != nil: - data = formatData(data) - } - - resp := struct { - Result interface{} `json:"result"` - }{ - Result: data, - } - - bytes, err := json.MarshalIndent(resp, "", " ") - if err != nil { - return err - } - fmt.Fprintln(os.Stdout, string(bytes)) - return nil } diff --git a/cmd/celestia/util.go b/cmd/celestia/util.go deleted file mode 100644 index a38860d1f7..0000000000 --- a/cmd/celestia/util.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/core" - "github.com/celestiaorg/celestia-node/nodebuilder/gateway" - "github.com/celestiaorg/celestia-node/nodebuilder/header" - "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/nodebuilder/rpc" - "github.com/celestiaorg/celestia-node/nodebuilder/state" -) - -func persistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) error { - var ( - ctx = cmd.Context() - err error - ) - - ctx = cmdnode.WithNodeType(ctx, nodeType) - - parsedNetwork, err := p2p.ParseNetwork(cmd) - if err != nil { - return err - } - ctx = cmdnode.WithNetwork(ctx, parsedNetwork) - - // loads existing config into the environment - ctx, err = cmdnode.ParseNodeFlags(ctx, cmd, cmdnode.Network(ctx)) - if err != nil { - return err - } - - cfg := cmdnode.NodeConfig(ctx) - - err = p2p.ParseFlags(cmd, &cfg.P2P) - if err != nil { - return err - } - - err = core.ParseFlags(cmd, &cfg.Core) - if err != nil { - return err - } - - if nodeType != node.Bridge { - err = header.ParseFlags(cmd, &cfg.Header) - if err != nil { - return err - } - } - - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) - if err != nil { - return err - } - - rpc.ParseFlags(cmd, &cfg.RPC) - gateway.ParseFlags(cmd, &cfg.Gateway) - state.ParseFlags(cmd, &cfg.State) - - // set config - ctx = cmdnode.WithNodeConfig(ctx, &cfg) - cmd.SetContext(ctx) - return nil -} diff --git a/cmd/rpc.go b/cmd/rpc.go new file mode 100644 index 0000000000..513967f0ed --- /dev/null +++ b/cmd/rpc.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "context" + "errors" + "os" + + "github.com/spf13/cobra" + + rpc "github.com/celestiaorg/celestia-node/api/rpc/client" +) + +const ( + // defaultRPCAddress is a default address to dial to + defaultRPCAddress = "http://localhost:26658" + authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" //nolint:gosec +) + +var ( + requestURL string + authTokenFlag string +) + +func InitURLFlag() (*string, string, string, string) { + return &requestURL, "url", defaultRPCAddress, "Request URL" +} + +func InitAuthTokenFlag() (*string, string, string, string) { + return &authTokenFlag, + "token", + "", + "Authorization token (if not provided, the " + authEnvKey + " environment variable will be used)" +} + +func InitClient(cmd *cobra.Command, _ []string) error { + if authTokenFlag == "" { + authTokenFlag = os.Getenv(authEnvKey) + } + + client, err := rpc.NewClient(cmd.Context(), requestURL, authTokenFlag) + if err != nil { + return err + } + + ctx := context.WithValue(cmd.Context(), rpcClientKey{}, client) + cmd.SetContext(ctx) + return nil +} + +type rpcClientKey struct{} + +func ParseClientFromCtx(ctx context.Context) (*rpc.Client, error) { + client, ok := ctx.Value(rpcClientKey{}).(*rpc.Client) + if !ok { + return nil, errors.New("rpc client was not set") + } + return client, nil +} diff --git a/cmd/util.go b/cmd/util.go new file mode 100644 index 0000000000..625685fe0b --- /dev/null +++ b/cmd/util.go @@ -0,0 +1,127 @@ +package cmd + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + rpc_cfg "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/state" + "github.com/celestiaorg/celestia-node/share" +) + +func PrintOutput(data interface{}, err error, formatData func(interface{}) interface{}) error { + switch { + case err != nil: + data = err + case formatData != nil: + data = formatData(data) + } + + resp := struct { + Result interface{} `json:"result"` + }{ + Result: data, + } + + bytes, err := json.MarshalIndent(resp, "", " ") + if err != nil { + return err + } + fmt.Fprintln(os.Stdout, string(bytes)) + return nil +} + +// ParseV0Namespace parses a namespace from a base64 or hex string. The param +// is expected to be the user-specified portion of a v0 namespace ID (i.e. the +// last 10 bytes). +func ParseV0Namespace(param string) (share.Namespace, error) { + userBytes, err := DecodeToBytes(param) + if err != nil { + return nil, err + } + + // if the namespace ID is <= 10 bytes, left pad it with 0s + return share.NewBlobNamespaceV0(userBytes) +} + +// DecodeToBytes decodes a Base64 or hex input string into a byte slice. +func DecodeToBytes(param string) ([]byte, error) { + if strings.HasPrefix(param, "0x") { + decoded, err := hex.DecodeString(param[2:]) + if err != nil { + return nil, fmt.Errorf("error decoding namespace ID: %w", err) + } + return decoded, nil + } + // otherwise, it's just a base64 string + decoded, err := base64.StdEncoding.DecodeString(param) + if err != nil { + return nil, fmt.Errorf("error decoding namespace ID: %w", err) + } + return decoded, nil +} + +func PersistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) error { + var ( + ctx = cmd.Context() + err error + ) + + ctx = WithNodeType(ctx, nodeType) + + parsedNetwork, err := p2p.ParseNetwork(cmd) + if err != nil { + return err + } + ctx = WithNetwork(ctx, parsedNetwork) + + // loads existing config into the environment + ctx, err = ParseNodeFlags(ctx, cmd, Network(ctx)) + if err != nil { + return err + } + + cfg := NodeConfig(ctx) + + err = p2p.ParseFlags(cmd, &cfg.P2P) + if err != nil { + return err + } + + err = core.ParseFlags(cmd, &cfg.Core) + if err != nil { + return err + } + + if nodeType != node.Bridge { + err = header.ParseFlags(cmd, &cfg.Header) + if err != nil { + return err + } + } + + ctx, err = ParseMiscFlags(ctx, cmd) + if err != nil { + return err + } + + rpc_cfg.ParseFlags(cmd, &cfg.RPC) + gateway.ParseFlags(cmd, &cfg.Gateway) + state.ParseFlags(cmd, &cfg.State) + + // set config + ctx = WithNodeConfig(ctx, &cfg) + cmd.SetContext(ctx) + return nil +} diff --git a/cmd/celestia/rpc_test.go b/cmd/util_test.go similarity index 97% rename from cmd/celestia/rpc_test.go rename to cmd/util_test.go index 53087646a7..b6e245f3e2 100644 --- a/cmd/celestia/rpc_test.go +++ b/cmd/util_test.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "testing" @@ -69,7 +69,7 @@ func Test_parseNamespaceID(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got, err := parseV0Namespace(tc.param) + got, err := ParseV0Namespace(tc.param) if tc.wantErr { assert.Error(t, err) return diff --git a/cmd/celestia/blob.go b/nodebuilder/blob/cmd/blob.go similarity index 77% rename from cmd/celestia/blob.go rename to nodebuilder/blob/cmd/blob.go index 9550f2c13a..1c5ec1ad50 100644 --- a/cmd/celestia/blob.go +++ b/nodebuilder/blob/cmd/blob.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "encoding/base64" @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/celestiaorg/celestia-node/blob" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/share" ) @@ -20,7 +21,7 @@ var ( ) func init() { - blobCmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd) + Cmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd) getCmd.PersistentFlags().BoolVar( &base64Flag, @@ -28,6 +29,7 @@ func init() { false, "printed blob's data a base64 string", ) + getAllCmd.PersistentFlags().BoolVar( &base64Flag, "base64", @@ -50,28 +52,30 @@ func init() { ) } -var blobCmd = &cobra.Command{ - Use: "blob [command]", - Short: "Allows to interact with the Blob Service via JSON-RPC", - Args: cobra.NoArgs, +var Cmd = &cobra.Command{ + Use: "blob [command]", + Short: "Allows to interact with the Blob Service via JSON-RPC", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, } var getCmd = &cobra.Command{ - Use: "get [height, namespace, commitment]", + Use: "get [height] [namespace] [commitment]", Args: cobra.ExactArgs(3), Short: "Returns the blob for the given namespace by commitment at a particular height.", RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() height, err := strconv.ParseUint(args[0], 10, 64) if err != nil { return fmt.Errorf("error parsing a height:%v", err) } - namespace, err := parseV0Namespace(args[1]) + namespace, err := cmdnode.ParseV0Namespace(args[1]) if err != nil { return fmt.Errorf("error parsing a namespace:%v", err) } @@ -87,51 +91,52 @@ var getCmd = &cobra.Command{ if base64Flag || err != nil { formatter = nil } - return printOutput(blob, err, formatter) + return cmdnode.PrintOutput(blob, err, formatter) }, } var getAllCmd = &cobra.Command{ - Use: "get-all [height, namespace]", + Use: "get-all [height] [namespace]", Args: cobra.ExactArgs(2), Short: "Returns all blobs for the given namespace at a particular height.", RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() height, err := strconv.ParseUint(args[0], 10, 64) if err != nil { return fmt.Errorf("error parsing a height:%v", err) } - namespace, err := parseV0Namespace(args[1]) + namespace, err := cmdnode.ParseV0Namespace(args[1]) if err != nil { return fmt.Errorf("error parsing a namespace:%v", err) } blobs, err := client.Blob.GetAll(cmd.Context(), height, []share.Namespace{namespace}) - formatter := formatData if base64Flag || err != nil { formatter = nil } - return printOutput(blobs, err, formatter) + return cmdnode.PrintOutput(blobs, err, formatter) }, } var submitCmd = &cobra.Command{ - Use: "submit [namespace, blobData]", + Use: "submit [namespace] [blobData]", Args: cobra.ExactArgs(2), Short: "Submit the blob at the given namespace. Note: only one blob is allowed to submit through the RPC.", RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() - namespace, err := parseV0Namespace(args[0]) + namespace, err := cmdnode.ParseV0Namespace(args[0]) if err != nil { return fmt.Errorf("error parsing a namespace:%v", err) } @@ -154,26 +159,27 @@ var submitCmd = &cobra.Command{ Height: height, Commitment: parsedBlob.Commitment, } - return printOutput(response, err, nil) + return cmdnode.PrintOutput(response, err, nil) }, } var getProofCmd = &cobra.Command{ - Use: "get-proof [height, namespace, commitment]", + Use: "get-proof [height] [namespace] [commitment]", Args: cobra.ExactArgs(3), Short: "Retrieves the blob in the given namespaces at the given height by commitment and returns its Proof.", RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() height, err := strconv.ParseUint(args[0], 10, 64) if err != nil { return fmt.Errorf("error parsing a height:%v", err) } - namespace, err := parseV0Namespace(args[1]) + namespace, err := cmdnode.ParseV0Namespace(args[1]) if err != nil { return fmt.Errorf("error parsing a namespace:%v", err) } @@ -184,7 +190,7 @@ var getProofCmd = &cobra.Command{ } proof, err := client.Blob.GetProof(cmd.Context(), height, namespace, commitment) - return printOutput(proof, err, nil) + return cmdnode.PrintOutput(proof, err, nil) }, } diff --git a/nodebuilder/das/cmd/das.go b/nodebuilder/das/cmd/das.go new file mode 100644 index 0000000000..7512861ac3 --- /dev/null +++ b/nodebuilder/das/cmd/das.go @@ -0,0 +1,34 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + cmdnode "github.com/celestiaorg/celestia-node/cmd" +) + +func init() { + Cmd.AddCommand(samplingStatsCmd) +} + +var Cmd = &cobra.Command{ + Use: "das [command]", + Short: "Allows to interact with the Daser via JSON-RPC", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, +} + +var samplingStatsCmd = &cobra.Command{ + Use: "sampling-stats", + Short: "Returns the current statistics over the DA sampling process", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) + if err != nil { + return err + } + defer client.Close() + + stats, err := client.DAS.SamplingStats(cmd.Context()) + return cmdnode.PrintOutput(stats, err, nil) + }, +} diff --git a/cmd/celestia/header.go b/nodebuilder/header/cmd/header.go similarity index 68% rename from cmd/celestia/header.go rename to nodebuilder/header/cmd/header.go index 1d8dc6d01b..b3bba1eb32 100644 --- a/cmd/celestia/header.go +++ b/nodebuilder/header/cmd/header.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "encoding/hex" @@ -6,10 +6,12 @@ import ( "strconv" "github.com/spf13/cobra" + + cmdnode "github.com/celestiaorg/celestia-node/cmd" ) func init() { - headerCmd.AddCommand( + Cmd.AddCommand( localHeadCmd, networkHeadCmd, getByHashCmd, @@ -18,10 +20,11 @@ func init() { ) } -var headerCmd = &cobra.Command{ - Use: "header [command]", - Short: "Allows interaction with the Header Module via JSON-RPC", - Args: cobra.NoArgs, +var Cmd = &cobra.Command{ + Use: "header [command]", + Short: "Allows interaction with the Header Module via JSON-RPC", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, } var localHeadCmd = &cobra.Command{ @@ -29,13 +32,14 @@ var localHeadCmd = &cobra.Command{ Short: "Returns the ExtendedHeader from the chain head.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() header, err := client.Header.LocalHead(cmd.Context()) - return printOutput(header, err, nil) + return cmdnode.PrintOutput(header, err, nil) }, } @@ -44,13 +48,14 @@ var networkHeadCmd = &cobra.Command{ Short: "Provides the Syncer's view of the current network head.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() header, err := client.Header.NetworkHead(cmd.Context()) - return printOutput(header, err, nil) + return cmdnode.PrintOutput(header, err, nil) }, } @@ -59,17 +64,18 @@ var getByHashCmd = &cobra.Command{ Short: "Returns the header of the given hash from the node's header store.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() hash, err := hex.DecodeString(args[0]) if err != nil { return fmt.Errorf("error decoding a hash: expected a hex encoded string:%v", err) } header, err := client.Header.GetByHash(cmd.Context(), hash) - return printOutput(header, err, nil) + return cmdnode.PrintOutput(header, err, nil) }, } @@ -78,10 +84,11 @@ var getByHeightCmd = &cobra.Command{ Short: "Returns the ExtendedHeader at the given height if it is currently available.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() height, err := strconv.ParseUint(args[0], 10, 64) if err != nil { @@ -89,7 +96,7 @@ var getByHeightCmd = &cobra.Command{ } header, err := client.Header.GetByHeight(cmd.Context(), height) - return printOutput(header, err, nil) + return cmdnode.PrintOutput(header, err, nil) }, } @@ -98,12 +105,13 @@ var syncStateCmd = &cobra.Command{ Short: "Returns the current state of the header Syncer.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() header, err := client.Header.SyncState(cmd.Context()) - return printOutput(header, err, nil) + return cmdnode.PrintOutput(header, err, nil) }, } diff --git a/cmd/celestia/admin.go b/nodebuilder/node/cmd/node.go similarity index 61% rename from cmd/celestia/admin.go rename to nodebuilder/node/cmd/node.go index a3665e0aa1..a65727fb03 100644 --- a/cmd/celestia/admin.go +++ b/nodebuilder/node/cmd/node.go @@ -1,33 +1,24 @@ -package main +package cmd import ( - "context" "errors" "strings" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/spf13/cobra" + + cmdnode "github.com/celestiaorg/celestia-node/cmd" ) func init() { - nodeCmd.AddCommand(nodeInfoCmd, logCmd, verifyCmd, authCmd) - rootCmd.AddCommand(nodeCmd) + Cmd.AddCommand(nodeInfoCmd, logCmd, verifyCmd, authCmd) } -var nodeCmd = &cobra.Command{ - Use: "node [command]", - Short: "Allows administrating running node.", - Args: cobra.NoArgs, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - rpcClient, err := newRPCClient(cmd.Context()) - if err != nil { - return err - } - - ctx := context.WithValue(cmd.Context(), rpcClientKey{}, rpcClient) - cmd.SetContext(ctx) - return nil - }, +var Cmd = &cobra.Command{ + Use: "node [command]", + Short: "Allows administrating running node.", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, } var nodeInfoCmd = &cobra.Command{ @@ -35,26 +26,30 @@ var nodeInfoCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Returns administrative information about the node.", RunE: func(c *cobra.Command, args []string) error { - client, err := rpcClient(c.Context()) + client, err := cmdnode.ParseClientFromCtx(c.Context()) if err != nil { return err } + defer client.Close() + info, err := client.Node.Info(c.Context()) - return printOutput(info, err, nil) + return cmdnode.PrintOutput(info, err, nil) }, } var logCmd = &cobra.Command{ - Use: "log-level", - Args: cobra.MinimumNArgs(1), - Short: "Allows to set log level for module to in format :" + + Use: "log-level", + Args: cobra.MinimumNArgs(1), + Short: "Sets log level for module.", + Long: "Allows to set log level for module to in format :" + "`DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL and their lower-case forms`.\n" + "To set all modules to a particular level `*:` should be passed", RunE: func(c *cobra.Command, args []string) error { - client, err := rpcClient(c.Context()) + client, err := cmdnode.ParseClientFromCtx(c.Context()) if err != nil { return err } + defer client.Close() for _, ll := range args { params := strings.Split(ll, ":") @@ -63,7 +58,7 @@ var logCmd = &cobra.Command{ "e.g. pubsub:debug") } - if err = client.Node.LogLevelSet(c.Context(), params[0], params[1]); err != nil { + if err := client.Node.LogLevelSet(c.Context(), params[0], params[1]); err != nil { return err } } @@ -77,13 +72,14 @@ var verifyCmd = &cobra.Command{ Short: "Returns the permissions assigned to the given token.", RunE: func(c *cobra.Command, args []string) error { - client, err := rpcClient(c.Context()) + client, err := cmdnode.ParseClientFromCtx(c.Context()) if err != nil { return err } + defer client.Close() perms, err := client.Node.AuthVerify(c.Context(), args[0]) - return printOutput(perms, err, nil) + return cmdnode.PrintOutput(perms, err, nil) }, } @@ -92,10 +88,11 @@ var authCmd = &cobra.Command{ Args: cobra.MinimumNArgs(1), Short: "Signs and returns a new token with the given permissions.", RunE: func(c *cobra.Command, args []string) error { - client, err := rpcClient(c.Context()) + client, err := cmdnode.ParseClientFromCtx(c.Context()) if err != nil { return err } + defer client.Close() perms := make([]auth.Permission, len(args)) for i, p := range args { @@ -103,6 +100,6 @@ var authCmd = &cobra.Command{ } result, err := client.Node.AuthNew(c.Context(), perms) - return printOutput(result, err, nil) + return cmdnode.PrintOutput(result, err, nil) }, } diff --git a/cmd/celestia/p2p.go b/nodebuilder/p2p/cmd/p2p.go similarity index 79% rename from cmd/celestia/p2p.go rename to nodebuilder/p2p/cmd/p2p.go index d0b6549f6c..5951595fa4 100644 --- a/cmd/celestia/p2p.go +++ b/nodebuilder/p2p/cmd/p2p.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "github.com/libp2p/go-libp2p/core/metrics" @@ -7,6 +7,8 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" ma2 "github.com/multiformats/go-multiaddr" "github.com/spf13/cobra" + + cmdnode "github.com/celestiaorg/celestia-node/cmd" ) type peerInfo struct { @@ -15,7 +17,7 @@ type peerInfo struct { } func init() { - p2pCmd.AddCommand(infoCmd, + Cmd.AddCommand(infoCmd, peersCmd, peerInfoCmd, connectCmd, @@ -35,10 +37,11 @@ func init() { ) } -var p2pCmd = &cobra.Command{ - Use: "p2p [command]", - Short: "Allows interaction with the P2P Module via JSON-RPC", - Args: cobra.NoArgs, +var Cmd = &cobra.Command{ + Use: "p2p [command]", + Short: "Allows interaction with the P2P Module via JSON-RPC", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, } var infoCmd = &cobra.Command{ @@ -46,10 +49,11 @@ var infoCmd = &cobra.Command{ Short: "Gets the node's peer info (peer id and multiaddresses)", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() info, err := client.P2P.Info(cmd.Context()) @@ -65,7 +69,7 @@ var infoCmd = &cobra.Command{ PeerAddr: ma, } } - return printOutput(info, err, formatter) + return cmdnode.PrintOutput(info, err, formatter) }, } @@ -74,10 +78,11 @@ var peersCmd = &cobra.Command{ Short: "Lists the peers we are connected to", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() result, err := client.P2P.Peers(cmd.Context()) peers := make([]string, len(result)) @@ -93,7 +98,7 @@ var peersCmd = &cobra.Command{ Peers: conPeers, } } - return printOutput(peers, err, formatter) + return cmdnode.PrintOutput(peers, err, formatter) }, } @@ -102,10 +107,11 @@ var peerInfoCmd = &cobra.Command{ Short: "Gets PeerInfo for a given peer", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -124,7 +130,7 @@ var peerInfoCmd = &cobra.Command{ PeerAddr: ma, } } - return printOutput(info, err, formatter) + return cmdnode.PrintOutput(info, err, formatter) }, } @@ -133,10 +139,11 @@ var connectCmd = &cobra.Command{ Short: "Establishes a connection with the given peer", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -155,7 +162,7 @@ var connectCmd = &cobra.Command{ err = client.P2P.Connect(cmd.Context(), peerInfo) if err != nil { - return printOutput(nil, err, nil) + return cmdnode.PrintOutput(nil, err, nil) } return connectednessCmd.RunE(cmd, args) }, @@ -166,10 +173,11 @@ var closePeerCmd = &cobra.Command{ Short: "Closes the connection with the given peer", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -178,7 +186,7 @@ var closePeerCmd = &cobra.Command{ err = client.P2P.ClosePeer(cmd.Context(), pid) if err != nil { - return printOutput(nil, err, nil) + return cmdnode.PrintOutput(nil, err, nil) } return connectednessCmd.RunE(cmd, args) }, @@ -189,10 +197,11 @@ var connectednessCmd = &cobra.Command{ Short: "Checks the connection state between current and given peers", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -209,7 +218,7 @@ var connectednessCmd = &cobra.Command{ ConnectionState: conn.String(), } } - return printOutput(con, err, formatter) + return cmdnode.PrintOutput(con, err, formatter) }, } @@ -218,10 +227,11 @@ var natStatusCmd = &cobra.Command{ Short: "Gets the currrent NAT status", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() r, err := client.P2P.NATStatus(cmd.Context()) @@ -233,7 +243,7 @@ var natStatusCmd = &cobra.Command{ Reachability: rr.String(), } } - return printOutput(r, err, formatter) + return cmdnode.PrintOutput(r, err, formatter) }, } @@ -242,10 +252,11 @@ var blockPeerCmd = &cobra.Command{ Short: "Blocks the given peer", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -270,7 +281,7 @@ var blockPeerCmd = &cobra.Command{ Reason: err, } } - return printOutput(err, nil, formatter) + return cmdnode.PrintOutput(err, nil, formatter) }, } @@ -279,10 +290,11 @@ var unblockPeerCmd = &cobra.Command{ Short: "Unblocks the given peer", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -308,7 +320,7 @@ var unblockPeerCmd = &cobra.Command{ Reason: err, } } - return printOutput(err, nil, formatter) + return cmdnode.PrintOutput(err, nil, formatter) }, } @@ -317,10 +329,11 @@ var blockedPeersCmd = &cobra.Command{ Short: "Lists the node's blocked peers", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() list, err := client.P2P.ListBlockedPeers(cmd.Context()) @@ -337,7 +350,7 @@ var blockedPeersCmd = &cobra.Command{ Peers: peers, } } - return printOutput(pids, err, formatter) + return cmdnode.PrintOutput(pids, err, formatter) }, } @@ -346,10 +359,11 @@ var protectCmd = &cobra.Command{ Short: "Protects the given peer from being pruned by the given tag", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -374,20 +388,22 @@ var protectCmd = &cobra.Command{ Reason: err, } } - return printOutput(err, nil, formatter) + return cmdnode.PrintOutput(err, nil, formatter) }, } var unprotectCmd = &cobra.Command{ - Use: "unprotect [peer.ID, tag]", - Short: "Removes a protection that may have been placed on a peer, under the specified tag." + + Use: "unprotect [peer.ID, tag]", + Short: "Removes protection from the given peer.", + Long: "Removes a protection that may have been placed on a peer, under the specified tag." + "The return value indicates whether the peer continues to be protected after this call, by way of a different tag", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -412,7 +428,7 @@ var unprotectCmd = &cobra.Command{ Reason: err, } } - return printOutput(err, nil, formatter) + return cmdnode.PrintOutput(err, nil, formatter) }, } @@ -421,10 +437,11 @@ var protectedCmd = &cobra.Command{ Short: "Ensures that a given peer is protected under a specific tag", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -432,7 +449,7 @@ var protectedCmd = &cobra.Command{ } result, err := client.P2P.IsProtected(cmd.Context(), pid, args[1]) - return printOutput(result, err, nil) + return cmdnode.PrintOutput(result, err, nil) }, } @@ -444,15 +461,17 @@ type bandwidthStats struct { } var bandwidthStatsCmd = &cobra.Command{ - Use: "bandwidth-stats", - Short: "Get stats struct with bandwidth metrics for all data sent/" + + Use: "bandwidth-stats", + Short: "Provides metrics for current peer.", + Long: "Get stats struct with bandwidth metrics for all data sent/" + "received by the local peer, regardless of protocol or remote peer IDs", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() result, err := client.P2P.BandwidthStats(cmd.Context()) @@ -465,7 +484,7 @@ var bandwidthStatsCmd = &cobra.Command{ RateOut: stats.RateOut, } } - return printOutput(result, err, formatter) + return cmdnode.PrintOutput(result, err, formatter) }, } @@ -474,10 +493,11 @@ var peerBandwidthCmd = &cobra.Command{ Short: "Gets stats struct with bandwidth metrics associated with the given peer.ID", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() pid, err := peer.Decode(args[0]) if err != nil { @@ -495,7 +515,7 @@ var peerBandwidthCmd = &cobra.Command{ RateOut: stats.RateOut, } } - return printOutput(result, err, formatter) + return cmdnode.PrintOutput(result, err, formatter) }, } @@ -504,10 +524,11 @@ var bandwidthForProtocolCmd = &cobra.Command{ Short: "Gets stats struct with bandwidth metrics associated with the given protocol.ID", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() result, err := client.P2P.BandwidthForProtocol(cmd.Context(), protocol.ID(args[0])) @@ -520,7 +541,7 @@ var bandwidthForProtocolCmd = &cobra.Command{ RateOut: stats.RateOut, } } - return printOutput(result, err, formatter) + return cmdnode.PrintOutput(result, err, formatter) }, } @@ -529,10 +550,11 @@ var pubsubPeersCmd = &cobra.Command{ Short: "Lists the peers we are connected to in the given topic", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() result, err := client.P2P.PubSubPeers(cmd.Context(), args[0]) peers := make([]string, len(result)) @@ -549,6 +571,6 @@ var pubsubPeersCmd = &cobra.Command{ Peers: conPeers, } } - return printOutput(peers, err, formatter) + return cmdnode.PrintOutput(peers, err, formatter) }, } diff --git a/cmd/celestia/share.go b/nodebuilder/share/cmd/share.go similarity index 76% rename from cmd/celestia/share.go rename to nodebuilder/share/cmd/share.go index 2f84871d80..fbf3e51db4 100644 --- a/cmd/celestia/share.go +++ b/nodebuilder/share/cmd/share.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "encoding/hex" @@ -9,11 +9,12 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/share" ) func init() { - shareCmd.AddCommand( + Cmd.AddCommand( sharesAvailableCmd, probabilityOfAvailabilityCmd, getSharesByNamespaceCmd, @@ -22,10 +23,11 @@ func init() { ) } -var shareCmd = &cobra.Command{ - Use: "share [command]", - Short: "Allows interaction with the Share Module via JSON-RPC", - Args: cobra.NoArgs, +var Cmd = &cobra.Command{ + Use: "share [command]", + Short: "Allows interaction with the Share Module via JSON-RPC", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, } var sharesAvailableCmd = &cobra.Command{ @@ -33,10 +35,11 @@ var sharesAvailableCmd = &cobra.Command{ Short: "Subjectively validates if Shares committed to the given Root are available on the Network.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() raw, err := parseJSON(args[0]) if err != nil { @@ -66,7 +69,7 @@ var sharesAvailableCmd = &cobra.Command{ Reason: err, } } - return printOutput(err, nil, formatter) + return cmdnode.PrintOutput(err, nil, formatter) }, } @@ -75,13 +78,14 @@ var probabilityOfAvailabilityCmd = &cobra.Command{ Short: "Calculates the probability of the data square being available based on the number of samples collected.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() prob := client.Share.ProbabilityOfAvailability(cmd.Context()) - return printOutput(prob, nil, nil) + return cmdnode.PrintOutput(prob, nil, nil) }, } @@ -90,10 +94,11 @@ var getSharesByNamespaceCmd = &cobra.Command{ Short: "Gets all shares from an EDS within the given namespace.", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() raw, err := parseJSON(args[0]) if err != nil { @@ -106,13 +111,13 @@ var getSharesByNamespaceCmd = &cobra.Command{ return err } - ns, err := parseV0Namespace(args[1]) + ns, err := cmdnode.ParseV0Namespace(args[1]) if err != nil { return err } shares, err := client.Share.GetSharesByNamespace(cmd.Context(), &root, ns) - return printOutput(shares, err, nil) + return cmdnode.PrintOutput(shares, err, nil) }, } @@ -121,10 +126,11 @@ var getShare = &cobra.Command{ Short: "Gets a Share by coordinates in EDS.", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() raw, err := parseJSON(args[0]) if err != nil { @@ -165,7 +171,7 @@ var getShare = &cobra.Command{ Data: share.GetData(sh), } } - return printOutput(s, err, formatter) + return cmdnode.PrintOutput(s, err, formatter) }, } @@ -174,10 +180,11 @@ var getEDS = &cobra.Command{ Short: "Gets the full EDS identified by the given root", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() raw, err := parseJSON(args[0]) if err != nil { @@ -191,6 +198,12 @@ var getEDS = &cobra.Command{ } shares, err := client.Share.GetEDS(cmd.Context(), &root) - return printOutput(shares, err, nil) + return cmdnode.PrintOutput(shares, err, nil) }, } + +func parseJSON(param string) (json.RawMessage, error) { + var raw json.RawMessage + err := json.Unmarshal([]byte(param), &raw) + return raw, err +} diff --git a/cmd/celestia/state.go b/nodebuilder/state/cmd/state.go similarity index 80% rename from cmd/celestia/state.go rename to nodebuilder/state/cmd/state.go index 2f3d1e138a..be52b6d26a 100644 --- a/cmd/celestia/state.go +++ b/nodebuilder/state/cmd/state.go @@ -1,6 +1,7 @@ -package main +package cmd import ( + "encoding/hex" "fmt" "strconv" @@ -9,11 +10,12 @@ import ( "github.com/spf13/cobra" "github.com/celestiaorg/celestia-node/blob" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/state" ) func init() { - stateCmd.AddCommand( + Cmd.AddCommand( accountAddressCmd, balanceCmd, balanceForAddressCmd, @@ -30,10 +32,11 @@ func init() { ) } -var stateCmd = &cobra.Command{ - Use: "state [command]", - Short: "Allows interaction with the State Module via JSON-RPC", - Args: cobra.NoArgs, +var Cmd = &cobra.Command{ + Use: "state [command]", + Short: "Allows interaction with the State Module via JSON-RPC", + Args: cobra.NoArgs, + PersistentPreRunE: cmdnode.InitClient, } var accountAddressCmd = &cobra.Command{ @@ -41,13 +44,14 @@ var accountAddressCmd = &cobra.Command{ Short: "Retrieves the address of the node's account/signer.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() address, err := client.State.AccountAddress(cmd.Context()) - return printOutput(address, err, nil) + return cmdnode.PrintOutput(address, err, nil) }, } @@ -57,13 +61,14 @@ var balanceCmd = &cobra.Command{ "the corresponding block's AppHash.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() balance, err := client.State.Balance(cmd.Context()) - return printOutput(balance, err, nil) + return cmdnode.PrintOutput(balance, err, nil) }, } @@ -73,10 +78,11 @@ var balanceForAddressCmd = &cobra.Command{ "the corresponding block's AppHash.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() addr, err := parseAddressFromString(args[0]) if err != nil { @@ -84,7 +90,7 @@ var balanceForAddressCmd = &cobra.Command{ } balance, err := client.State.BalanceForAddress(cmd.Context(), addr) - return printOutput(balance, err, nil) + return cmdnode.PrintOutput(balance, err, nil) }, } @@ -93,10 +99,11 @@ var transferCmd = &cobra.Command{ Short: "Sends the given amount of coins from default wallet of the node to the given account address.", Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() addr, err := parseAddressFromString(args[0]) if err != nil { @@ -122,7 +129,7 @@ var transferCmd = &cobra.Command{ math.NewInt(amount), math.NewInt(fee), gasLimit, ) - return printOutput(txResponse, err, nil) + return cmdnode.PrintOutput(txResponse, err, nil) }, } @@ -131,20 +138,21 @@ var submitTxCmd = &cobra.Command{ Short: "Submits the given transaction/message to the Celestia network and blocks until the tx is included in a block.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() - rawTx, err := decodeToBytes(args[0]) + decoded, err := hex.DecodeString(args[0]) if err != nil { return fmt.Errorf("failed to decode tx: %v", err) } txResponse, err := client.State.SubmitTx( cmd.Context(), - rawTx, + decoded, ) - return printOutput(txResponse, err, nil) + return cmdnode.PrintOutput(txResponse, err, nil) }, } @@ -153,11 +161,13 @@ var submitPFBCmd = &cobra.Command{ Short: "Allows to submit PFBs", Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } - namespace, err := parseV0Namespace(args[0]) + defer client.Close() + + namespace, err := cmdnode.ParseV0Namespace(args[0]) if err != nil { return fmt.Errorf("error parsing a namespace:%v", err) } @@ -177,8 +187,13 @@ var submitPFBCmd = &cobra.Command{ return fmt.Errorf("error creating a blob:%v", err) } - txResp, err := client.State.SubmitPayForBlob(cmd.Context(), types.NewInt(fee), gasLimit, []*blob.Blob{parsedBlob}) - return printOutput(txResp, err, nil) + txResp, err := client.State.SubmitPayForBlob( + cmd.Context(), + types.NewInt(fee), + gasLimit, + []*blob.Blob{parsedBlob}, + ) + return cmdnode.PrintOutput(txResp, err, nil) }, } @@ -187,10 +202,12 @@ var cancelUnbondingDelegationCmd = &cobra.Command{ Short: "Cancels a user's pending undelegation from a validator.", Args: cobra.ExactArgs(5), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() + addr, err := parseAddressFromString(args[0]) if err != nil { return fmt.Errorf("error parsing an address:%v", err) @@ -224,7 +241,7 @@ var cancelUnbondingDelegationCmd = &cobra.Command{ math.NewInt(fee), gasLimit, ) - return printOutput(txResponse, err, nil) + return cmdnode.PrintOutput(txResponse, err, nil) }, } @@ -233,10 +250,12 @@ var beginRedelegateCmd = &cobra.Command{ Short: "Sends a user's delegated tokens to a new validator for redelegation", Args: cobra.ExactArgs(5), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() + srcAddr, err := parseAddressFromString(args[0]) if err != nil { return fmt.Errorf("error parsing an address:%v", err) @@ -269,7 +288,7 @@ var beginRedelegateCmd = &cobra.Command{ math.NewInt(fee), gasLimit, ) - return printOutput(txResponse, err, nil) + return cmdnode.PrintOutput(txResponse, err, nil) }, } @@ -278,10 +297,12 @@ var undelegateCmd = &cobra.Command{ Short: "Undelegates a user's delegated tokens, unbonding them from the current validator.", Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() + addr, err := parseAddressFromString(args[0]) if err != nil { return fmt.Errorf("error parsing an address:%v", err) @@ -307,7 +328,7 @@ var undelegateCmd = &cobra.Command{ math.NewInt(fee), gasLimit, ) - return printOutput(txResponse, err, nil) + return cmdnode.PrintOutput(txResponse, err, nil) }, } @@ -316,10 +337,11 @@ var delegateCmd = &cobra.Command{ Short: "Sends a user's liquid tokens to a validator for delegation.", Args: cobra.ExactArgs(4), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() addr, err := parseAddressFromString(args[0]) if err != nil { @@ -348,7 +370,7 @@ var delegateCmd = &cobra.Command{ math.NewInt(fee), gasLimit, ) - return printOutput(txResponse, err, nil) + return cmdnode.PrintOutput(txResponse, err, nil) }, } @@ -357,10 +379,11 @@ var queryDelegationCmd = &cobra.Command{ Short: "Retrieves the delegation information between a delegator and a validator.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() addr, err := parseAddressFromString(args[0]) if err != nil { @@ -368,9 +391,7 @@ var queryDelegationCmd = &cobra.Command{ } balance, err := client.State.QueryDelegation(cmd.Context(), addr.Address.(state.ValAddress)) - fmt.Println(balance) - fmt.Println(err) - return printOutput(balance, err, nil) + return cmdnode.PrintOutput(balance, err, nil) }, } @@ -379,10 +400,11 @@ var queryUnbondingCmd = &cobra.Command{ Short: "Retrieves the unbonding status between a delegator and a validator.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() addr, err := parseAddressFromString(args[0]) if err != nil { @@ -390,7 +412,7 @@ var queryUnbondingCmd = &cobra.Command{ } response, err := client.State.QueryUnbonding(cmd.Context(), addr.Address.(state.ValAddress)) - return printOutput(response, err, nil) + return cmdnode.PrintOutput(response, err, nil) }, } @@ -399,10 +421,11 @@ var queryRedelegationCmd = &cobra.Command{ Short: "Retrieves the status of the redelegations between a delegator and a validator.", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - client, err := rpcClient(cmd.Context()) + client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { return err } + defer client.Close() srcAddr, err := parseAddressFromString(args[0]) if err != nil { @@ -419,6 +442,15 @@ var queryRedelegationCmd = &cobra.Command{ srcAddr.Address.(state.ValAddress), dstAddr.Address.(state.ValAddress), ) - return printOutput(response, err, nil) + return cmdnode.PrintOutput(response, err, nil) }, } + +func parseAddressFromString(addrStr string) (state.Address, error) { + var address state.Address + err := address.UnmarshalJSON([]byte(addrStr)) + if err != nil { + return address, err + } + return address, nil +} From f6a1e9d89f21e24cbba48b76f8061f680ac815b5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 26 Sep 2023 13:14:16 +0300 Subject: [PATCH 0852/1008] refactor(rpc/blob) extend docs in blob (#2755) --- nodebuilder/blob/cmd/blob.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/nodebuilder/blob/cmd/blob.go b/nodebuilder/blob/cmd/blob.go index 1c5ec1ad50..f3510edfe1 100644 --- a/nodebuilder/blob/cmd/blob.go +++ b/nodebuilder/blob/cmd/blob.go @@ -41,15 +41,19 @@ func init() { &fee, "fee", -1, - "specifies fee for blob submission", + "specifies fee (in utia) for blob submission.\n"+ + "Fee will be automatically calculated if negative value is passed [optional]", ) submitCmd.PersistentFlags().Uint64Var( &gasLimit, "gas.limit", 0, - "specifies max gas for the blob submission", + "sets the amount of gas that is consumed during blob submission [optional]", ) + + // unset the default value to avoid users confusion + submitCmd.PersistentFlags().Lookup("fee").DefValue = "0" } var Cmd = &cobra.Command{ @@ -126,9 +130,12 @@ var getAllCmd = &cobra.Command{ } var submitCmd = &cobra.Command{ - Use: "submit [namespace] [blobData]", - Args: cobra.ExactArgs(2), - Short: "Submit the blob at the given namespace. Note: only one blob is allowed to submit through the RPC.", + Use: "submit [namespace] [blobData]", + Args: cobra.ExactArgs(2), + Short: "Submit the blob at the given namespace.\n" + + "Note:\n" + + "* only one blob is allowed to submit through the RPC.\n" + + "* fee and gas.limit params will be calculated automatically if they are not provided as arguments", RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { From f596e11b6f2239dfe16e891850c911f68bc75b13 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 2 Oct 2023 11:41:04 +0300 Subject: [PATCH 0853/1008] feat(cmd/rpc): add node store flag (#2762) --- cmd/auth.go | 82 +++++++++++++++++++++++---------------------- cmd/celestia/rpc.go | 22 ++++-------- cmd/flags_node.go | 2 +- cmd/rpc.go | 57 +++++++++++++++++++++++++++---- 4 files changed, 101 insertions(+), 62 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index eb2000675e..3006526b15 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -25,59 +25,61 @@ func AuthCmd(fsets ...*flag.FlagSet) *cobra.Command { Short: "Signs and outputs a hex-encoded JWT token with the given permissions.", Long: "Signs and outputs a hex-encoded JWT token with the given permissions. NOTE: only use this command when " + "the node has already been initialized and started.", - RunE: newToken, - } + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("must specify permissions") + } + permissions, err := convertToPerms(args[0]) + if err != nil { + return err + } - for _, set := range fsets { - cmd.Flags().AddFlagSet(set) - } - return cmd -} + ks, err := newKeystore(StorePath(cmd.Context())) + if err != nil { + return err -func newToken(cmd *cobra.Command, args []string) error { - if len(args) != 1 { - return fmt.Errorf("must specify permissions") - } + } - permissions, err := convertToPerms(args[0]) - if err != nil { - return err - } + key, err := ks.Get(nodemod.SecretName) + if err != nil { + if !errors.Is(err, keystore.ErrNotFound) { + return err + } + key, err = generateNewKey(ks) + if err != nil { + return err + } + } - expanded, err := homedir.Expand(filepath.Clean(StorePath(cmd.Context()))) - if err != nil { - return err - } - ks, err := keystore.NewFSKeystore(filepath.Join(expanded, "keys"), nil) - if err != nil { - return err + token, err := buildJWTToken(key.Body, permissions) + if err != nil { + return err + } + fmt.Printf("%s", token) + return nil + }, } - var key keystore.PrivKey - key, err = ks.Get(nodemod.SecretName) - if err != nil { - if !errors.Is(err, keystore.ErrNotFound) { - return err - } - // otherwise, generate and save new priv key - key, err = generateNewKey(ks) - if err != nil { - return err - } + for _, set := range fsets { + cmd.Flags().AddFlagSet(set) } + return cmd +} - signer, err := jwt.NewHS256(key.Body) +func newKeystore(path string) (keystore.Keystore, error) { + expanded, err := homedir.Expand(filepath.Clean(path)) if err != nil { - return err + return nil, err } + return keystore.NewFSKeystore(filepath.Join(expanded, "keys"), nil) +} - token, err := authtoken.NewSignedJWT(signer, permissions) +func buildJWTToken(body []byte, permissions []auth.Permission) (string, error) { + signer, err := jwt.NewHS256(body) if err != nil { - return err + return "", err } - - fmt.Printf("%s", token) - return nil + return authtoken.NewSignedJWT(signer, permissions) } func generateNewKey(ks keystore.Keystore) (keystore.PrivKey, error) { diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go index 8cf51fda09..11e96c2e46 100644 --- a/cmd/celestia/rpc.go +++ b/cmd/celestia/rpc.go @@ -12,21 +12,13 @@ import ( ) func init() { - blob.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - das.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - header.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - p2p.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - share.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - state.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - node.Cmd.PersistentFlags().StringVar(cmd.InitURLFlag()) - - blob.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) - das.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) - header.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) - p2p.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) - share.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) - state.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) - node.Cmd.PersistentFlags().StringVar(cmd.InitAuthTokenFlag()) + blob.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) + das.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) + header.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) + p2p.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) + share.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) + state.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) + node.Cmd.PersistentFlags().AddFlagSet(cmd.RPCFlags()) rootCmd.AddCommand( blob.Cmd, diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 8c73a06169..fe4981b6c6 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -15,7 +15,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) -var ( +const ( nodeStoreFlag = "node.store" nodeConfigFlag = "node.config" ) diff --git a/cmd/rpc.go b/cmd/rpc.go index 513967f0ed..7ea0173795 100644 --- a/cmd/rpc.go +++ b/cmd/rpc.go @@ -3,11 +3,15 @@ package cmd import ( "context" "errors" + "fmt" "os" "github.com/spf13/cobra" + flag "github.com/spf13/pflag" rpc "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/api/rpc/perms" + nodemod "github.com/celestiaorg/celestia-node/nodebuilder/node" ) const ( @@ -21,15 +25,26 @@ var ( authTokenFlag string ) -func InitURLFlag() (*string, string, string, string) { - return &requestURL, "url", defaultRPCAddress, "Request URL" -} +func RPCFlags() *flag.FlagSet { + fset := &flag.FlagSet{} + + fset.StringVar( + &requestURL, + "url", + defaultRPCAddress, + "Request URL", + ) -func InitAuthTokenFlag() (*string, string, string, string) { - return &authTokenFlag, + fset.StringVar( + &authTokenFlag, "token", "", - "Authorization token (if not provided, the " + authEnvKey + " environment variable will be used)" + "Authorization token (if not provided, the "+authEnvKey+" environment variable will be used)", + ) + + storeFlag := NodeFlags().Lookup(nodeStoreFlag) + fset.AddFlag(storeFlag) + return fset } func InitClient(cmd *cobra.Command, _ []string) error { @@ -37,6 +52,18 @@ func InitClient(cmd *cobra.Command, _ []string) error { authTokenFlag = os.Getenv(authEnvKey) } + if authTokenFlag == "" { + storePath := "" + if cmd.Flag(nodeStoreFlag).Changed { + storePath = cmd.Flag(nodeStoreFlag).Value.String() + } + token, err := getToken(storePath) + if err != nil { + return fmt.Errorf("cant get the access to the auth token: %v", err) + } + authTokenFlag = token + } + client, err := rpc.NewClient(cmd.Context(), requestURL, authTokenFlag) if err != nil { return err @@ -47,6 +74,24 @@ func InitClient(cmd *cobra.Command, _ []string) error { return nil } +func getToken(path string) (string, error) { + if path == "" { + return "", errors.New("root directory was not specified") + } + + ks, err := newKeystore(path) + if err != nil { + return "", err + } + + key, err := ks.Get(nodemod.SecretName) + if err != nil { + fmt.Printf("error getting the JWT secret: %v", err) + return "", err + } + return buildJWTToken(key.Body, perms.AllPerms) +} + type rpcClientKey struct{} func ParseClientFromCtx(ctx context.Context) (*rpc.Client, error) { From 76ced90ff430cc3bb6d1372de0bb775ab9993421 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 2 Oct 2023 11:47:44 +0300 Subject: [PATCH 0854/1008] refactor(gateway): remove deprecated endpoints (#2764) --- api/gateway/das.go | 28 ---- api/gateway/endpoints.go | 23 +-- api/gateway/state.go | 209 +++------------------------- api/gateway/state_test.go | 57 -------- nodebuilder/gateway/config.go | 7 +- nodebuilder/gateway/constructors.go | 3 +- nodebuilder/gateway/flags.go | 16 +-- nodebuilder/gateway/module.go | 3 +- nodebuilder/state/cmd/state.go | 44 ------ 9 files changed, 27 insertions(+), 363 deletions(-) delete mode 100644 api/gateway/das.go delete mode 100644 api/gateway/state_test.go diff --git a/api/gateway/das.go b/api/gateway/das.go deleted file mode 100644 index 88dc97927c..0000000000 --- a/api/gateway/das.go +++ /dev/null @@ -1,28 +0,0 @@ -package gateway - -import ( - "encoding/json" - "net/http" -) - -const ( - dasStateEndpoint = "/daser/state" -) - -func (h *Handler) handleDASStateRequest(w http.ResponseWriter, r *http.Request) { - logDeprecation(dasStateEndpoint, "das.SamplingStats") - stats, err := h.das.SamplingStats(r.Context()) - if err != nil { - writeError(w, http.StatusInternalServerError, dasStateEndpoint, err) - return - } - resp, err := json.Marshal(stats) - if err != nil { - writeError(w, http.StatusInternalServerError, dasStateEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("serving request", "endpoint", dasStateEndpoint, "err", err) - } -} diff --git a/api/gateway/endpoints.go b/api/gateway/endpoints.go index 9600138909..104d01b053 100644 --- a/api/gateway/endpoints.go +++ b/api/gateway/endpoints.go @@ -5,28 +5,7 @@ import ( "net/http" ) -func (h *Handler) RegisterEndpoints(rpc *Server, deprecatedEndpointsEnabled bool) { - if deprecatedEndpointsEnabled { - log.Warn("Deprecated endpoints will be removed from the gateway in the next release. Use the RPC instead.") - // state endpoints - rpc.RegisterHandlerFunc(balanceEndpoint, h.handleBalanceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(submitPFBEndpoint, h.handleSubmitPFB, http.MethodPost) - - // staking queries - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryDelegationEndpoint, addrKey), h.handleQueryDelegation, - http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryUnbondingEndpoint, addrKey), h.handleQueryUnbonding, - http.MethodGet) - rpc.RegisterHandlerFunc(queryRedelegationsEndpoint, h.handleQueryRedelegations, - http.MethodPost) - - // DASer endpoints - // only register if DASer service is available - if h.das != nil { - rpc.RegisterHandlerFunc(dasStateEndpoint, h.handleDASStateRequest, http.MethodGet) - } - } - +func (h *Handler) RegisterEndpoints(rpc *Server) { // state endpoints rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceRequest, http.MethodGet) diff --git a/api/gateway/state.go b/api/gateway/state.go index 69900b0bfc..13cf729cc6 100644 --- a/api/gateway/state.go +++ b/api/gateway/state.go @@ -9,17 +9,12 @@ import ( "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" - "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/state" ) const ( - balanceEndpoint = "/balance" - submitTxEndpoint = "/submit_tx" - submitPFBEndpoint = "/submit_pfb" - queryDelegationEndpoint = "/query_delegation" - queryUnbondingEndpoint = "/query_unbonding" - queryRedelegationsEndpoint = "/query_redelegations" + balanceEndpoint = "/balance" + submitTxEndpoint = "/submit_tx" ) const addrKey = "address" @@ -34,21 +29,6 @@ type submitTxRequest struct { Tx string `json:"tx"` } -// submitPFBRequest represents a request to submit a PayForBlob -// transaction. -type submitPFBRequest struct { - NamespaceID string `json:"namespace_id"` - Data string `json:"data"` - Fee int64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` -} - -// queryRedelegationsRequest represents a request to query redelegations -type queryRedelegationsRequest struct { - From string `json:"from"` - To string `json:"to"` -} - func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { var ( bal *state.Balance @@ -57,24 +37,25 @@ func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { // read and parse request vars := mux.Vars(r) addrStr, exists := vars[addrKey] - if exists { - // convert address to Address type - var addr state.AccAddress - addr, err = types.AccAddressFromBech32(addrStr) + if !exists { + writeError(w, http.StatusBadRequest, balanceEndpoint, errors.New("balance endpoint requires address")) + return + } + + // convert address to Address type + var addr state.AccAddress + addr, err = types.AccAddressFromBech32(addrStr) + if err != nil { + // first check if it is a validator address and can be converted + valAddr, err := types.ValAddressFromBech32(addrStr) if err != nil { - // first check if it is a validator address and can be converted - valAddr, err := types.ValAddressFromBech32(addrStr) - if err != nil { - writeError(w, http.StatusBadRequest, balanceEndpoint, ErrInvalidAddressFormat) - return - } - addr = valAddr.Bytes() + writeError(w, http.StatusBadRequest, balanceEndpoint, ErrInvalidAddressFormat) + return } - bal, err = h.state.BalanceForAddress(r.Context(), state.Address{Address: addr}) - } else { - logDeprecation(balanceEndpoint, "state.Balance") - bal, err = h.state.Balance(r.Context()) + addr = valAddr.Bytes() } + + bal, err = h.state.BalanceForAddress(r.Context(), state.Address{Address: addr}) if err != nil { writeError(w, http.StatusInternalServerError, balanceEndpoint, err) return @@ -119,157 +100,3 @@ func (h *Handler) handleSubmitTx(w http.ResponseWriter, r *http.Request) { log.Errorw("writing response", "endpoint", submitTxEndpoint, "err", err) } } - -func (h *Handler) handleSubmitPFB(w http.ResponseWriter, r *http.Request) { - logDeprecation(submitPFBEndpoint, "blob.Submit or state.SubmitPayForBlob") - // decode request - var req submitPFBRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) - return - } - namespace, err := hex.DecodeString(req.NamespaceID) - if err != nil { - writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) - return - } - data, err := hex.DecodeString(req.Data) - if err != nil { - writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) - return - } - fee := types.NewInt(req.Fee) - - constructedBlob, err := blob.NewBlobV0(namespace, data) - if err != nil { - writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) - return - } - - // perform request - txResp, err := h.state.SubmitPayForBlob(r.Context(), fee, req.GasLimit, []*blob.Blob{constructedBlob}) - if err != nil { - if txResp == nil { - // no tx data to return - writeError(w, http.StatusBadRequest, submitPFBEndpoint, err) - return - } - // if error returned, change status from 200 to 206 - w.WriteHeader(http.StatusPartialContent) - } - - bs, err := json.Marshal(&txResp) - if err != nil { - writeError(w, http.StatusInternalServerError, submitPFBEndpoint, err) - return - } - - _, err = w.Write(bs) - if err != nil { - log.Errorw("writing response", "endpoint", submitPFBEndpoint, "err", err) - } -} - -func (h *Handler) handleQueryDelegation(w http.ResponseWriter, r *http.Request) { - logDeprecation(queryDelegationEndpoint, "state.QueryDelegation") - // read and parse request - vars := mux.Vars(r) - addrStr, exists := vars[addrKey] - if !exists { - writeError(w, http.StatusBadRequest, queryDelegationEndpoint, ErrMissingAddress) - return - } - - // convert address to Address type - addr, err := types.ValAddressFromBech32(addrStr) - if err != nil { - writeError(w, http.StatusBadRequest, queryDelegationEndpoint, err) - return - } - delegation, err := h.state.QueryDelegation(r.Context(), addr) - if err != nil { - writeError(w, http.StatusInternalServerError, queryDelegationEndpoint, err) - return - } - resp, err := json.Marshal(delegation) - if err != nil { - writeError(w, http.StatusInternalServerError, queryDelegationEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", queryDelegationEndpoint, "err", err) - } -} - -func (h *Handler) handleQueryUnbonding(w http.ResponseWriter, r *http.Request) { - logDeprecation(queryUnbondingEndpoint, "state.QueryUnbonding") - // read and parse request - vars := mux.Vars(r) - addrStr, exists := vars[addrKey] - if !exists { - writeError(w, http.StatusBadRequest, queryUnbondingEndpoint, ErrMissingAddress) - return - } - - // convert address to Address type - addr, err := types.ValAddressFromBech32(addrStr) - if err != nil { - writeError(w, http.StatusBadRequest, queryUnbondingEndpoint, err) - return - } - unbonding, err := h.state.QueryUnbonding(r.Context(), addr) - if err != nil { - writeError(w, http.StatusInternalServerError, queryUnbondingEndpoint, err) - return - } - resp, err := json.Marshal(unbonding) - if err != nil { - writeError(w, http.StatusInternalServerError, queryUnbondingEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", queryUnbondingEndpoint, "err", err) - } -} - -func (h *Handler) handleQueryRedelegations(w http.ResponseWriter, r *http.Request) { - logDeprecation(queryRedelegationsEndpoint, "state.QueryRedelegations") - var req queryRedelegationsRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - writeError(w, http.StatusBadRequest, queryRedelegationsEndpoint, err) - return - } - srcValAddr, err := types.ValAddressFromBech32(req.From) - if err != nil { - writeError(w, http.StatusBadRequest, queryRedelegationsEndpoint, err) - return - } - dstValAddr, err := types.ValAddressFromBech32(req.To) - if err != nil { - writeError(w, http.StatusBadRequest, queryRedelegationsEndpoint, err) - return - } - unbonding, err := h.state.QueryRedelegations(r.Context(), srcValAddr, dstValAddr) - if err != nil { - writeError(w, http.StatusInternalServerError, queryRedelegationsEndpoint, err) - return - } - resp, err := json.Marshal(unbonding) - if err != nil { - writeError(w, http.StatusInternalServerError, queryRedelegationsEndpoint, err) - return - } - _, err = w.Write(resp) - if err != nil { - log.Errorw("writing response", "endpoint", queryRedelegationsEndpoint, "err", err) - } -} - -func logDeprecation(endpoint string, alternative string) { - log.Warn("The " + endpoint + " endpoint is deprecated and will be removed in the next release. Please " + - "use " + alternative + " from the RPC instead.") -} diff --git a/api/gateway/state_test.go b/api/gateway/state_test.go deleted file mode 100644 index aa9196cc8d..0000000000 --- a/api/gateway/state_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package gateway - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "errors" - "net/http" - "net/http/httptest" - "testing" - - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" - - stateMock "github.com/celestiaorg/celestia-node/nodebuilder/state/mocks" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/state" -) - -func TestHandleSubmitPFB(t *testing.T) { - ctrl := gomock.NewController(t) - mock := stateMock.NewMockModule(ctrl) - handler := NewHandler(mock, nil, nil, nil) - - t.Run("partial response", func(t *testing.T) { - txResponse := state.TxResponse{ - Height: 1, - TxHash: "hash", - Codespace: "codespace", - Code: 1, - } - // simulate core-app err, since it is not exported - timedErr := errors.New("timed out waiting for tx to be included in a block") - mock.EXPECT().SubmitPayForBlob(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(&txResponse, timedErr) - - ns, err := share.NewBlobNamespaceV0([]byte("abc")) - require.NoError(t, err) - hexNs := hex.EncodeToString(ns[:]) - - bs, err := json.Marshal(submitPFBRequest{ - NamespaceID: hexNs, - Data: "DEADBEEF", - }) - require.NoError(t, err) - httpreq := httptest.NewRequest("GET", "/", bytes.NewReader(bs)) - respRec := httptest.NewRecorder() - handler.handleSubmitPFB(respRec, httpreq) - - var resp state.TxResponse - err = json.NewDecoder(respRec.Body).Decode(&resp) - require.NoError(t, err) - - require.Equal(t, http.StatusPartialContent, respRec.Code) - require.Equal(t, resp, txResponse) - }) -} diff --git a/nodebuilder/gateway/config.go b/nodebuilder/gateway/config.go index f85f207ceb..903a27489a 100644 --- a/nodebuilder/gateway/config.go +++ b/nodebuilder/gateway/config.go @@ -8,10 +8,9 @@ import ( ) type Config struct { - Address string - Port string - Enabled bool - deprecatedEndpoints bool + Address string + Port string + Enabled bool } func DefaultConfig() Config { diff --git a/nodebuilder/gateway/constructors.go b/nodebuilder/gateway/constructors.go index c771c12023..c28153b0a5 100644 --- a/nodebuilder/gateway/constructors.go +++ b/nodebuilder/gateway/constructors.go @@ -10,7 +10,6 @@ import ( // Handler constructs a new RPC Handler from the given services. func Handler( - cfg *Config, state state.Module, share share.Module, header header.Module, @@ -18,7 +17,7 @@ func Handler( serv *gateway.Server, ) { handler := gateway.NewHandler(state, share, header, daser) - handler.RegisterEndpoints(serv, cfg.deprecatedEndpoints) + handler.RegisterEndpoints(serv) handler.RegisterMiddleware(serv) } diff --git a/nodebuilder/gateway/flags.go b/nodebuilder/gateway/flags.go index 4d72a278e5..cd13e47162 100644 --- a/nodebuilder/gateway/flags.go +++ b/nodebuilder/gateway/flags.go @@ -6,10 +6,9 @@ import ( ) var ( - enabledFlag = "gateway" - addrFlag = "gateway.addr" - portFlag = "gateway.port" - deprecatedEndpoints = "gateway.deprecated-endpoints" + enabledFlag = "gateway" + addrFlag = "gateway.addr" + portFlag = "gateway.port" ) // Flags gives a set of hardcoded node/gateway package flags. @@ -21,11 +20,6 @@ func Flags() *flag.FlagSet { false, "Enables the REST gateway", ) - flags.Bool( - deprecatedEndpoints, - false, - "Enables deprecated endpoints on the gateway. These will be removed in the next release.", - ) flags.String( addrFlag, "", @@ -46,10 +40,6 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) { if cmd.Flags().Changed(enabledFlag) && err == nil { cfg.Enabled = enabled } - deprecatedEndpointsEnabled, err := cmd.Flags().GetBool(deprecatedEndpoints) - if cmd.Flags().Changed(deprecatedEndpoints) && err == nil { - cfg.deprecatedEndpoints = deprecatedEndpointsEnabled - } addr, port := cmd.Flag(addrFlag), cmd.Flag(portFlag) if !cfg.Enabled && (addr.Changed || port.Changed) { log.Warn("custom address or port provided without enabling gateway, setting config values") diff --git a/nodebuilder/gateway/module.go b/nodebuilder/gateway/module.go index b727f4c04d..4cdf325dc0 100644 --- a/nodebuilder/gateway/module.go +++ b/nodebuilder/gateway/module.go @@ -48,13 +48,12 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { "gateway", baseComponents, fx.Invoke(func( - cfg *Config, state stateServ.Module, share shareServ.Module, header headerServ.Module, serv *gateway.Server, ) { - Handler(cfg, state, share, header, nil, serv) + Handler(state, share, header, nil, serv) }), ) default: diff --git a/nodebuilder/state/cmd/state.go b/nodebuilder/state/cmd/state.go index be52b6d26a..d35c4a1b4f 100644 --- a/nodebuilder/state/cmd/state.go +++ b/nodebuilder/state/cmd/state.go @@ -6,10 +6,8 @@ import ( "strconv" "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" - "github.com/celestiaorg/celestia-node/blob" cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/state" ) @@ -21,7 +19,6 @@ func init() { balanceForAddressCmd, transferCmd, submitTxCmd, - submitPFBCmd, cancelUnbondingDelegationCmd, beginRedelegateCmd, undelegateCmd, @@ -156,47 +153,6 @@ var submitTxCmd = &cobra.Command{ }, } -var submitPFBCmd = &cobra.Command{ - Use: "submit-pfb [namespace] [data] [fee] [gasLim]", - Short: "Allows to submit PFBs", - Args: cobra.ExactArgs(4), - RunE: func(cmd *cobra.Command, args []string) error { - client, err := cmdnode.ParseClientFromCtx(cmd.Context()) - if err != nil { - return err - } - defer client.Close() - - namespace, err := cmdnode.ParseV0Namespace(args[0]) - if err != nil { - return fmt.Errorf("error parsing a namespace:%v", err) - } - - fee, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - return fmt.Errorf("error parsing a fee:%v", err) - } - - gasLimit, err := strconv.ParseUint(args[3], 10, 64) - if err != nil { - return fmt.Errorf("error parsing a gasLim:%v", err) - } - - parsedBlob, err := blob.NewBlobV0(namespace, []byte(args[1])) - if err != nil { - return fmt.Errorf("error creating a blob:%v", err) - } - - txResp, err := client.State.SubmitPayForBlob( - cmd.Context(), - types.NewInt(fee), - gasLimit, - []*blob.Blob{parsedBlob}, - ) - return cmdnode.PrintOutput(txResp, err, nil) - }, -} - var cancelUnbondingDelegationCmd = &cobra.Command{ Use: "cancel-unbonding-delegation [address] [amount] [height] [fee] [gasLimit]", Short: "Cancels a user's pending undelegation from a validator.", From 9ef7594c7827cfa53ccabb5ff454fc586eed967c Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 5 Oct 2023 12:27:57 +0300 Subject: [PATCH 0855/1008] refactoring(rpc): remove authEnvKey (#2810) ## Overview ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- cmd/rpc.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cmd/rpc.go b/cmd/rpc.go index 7ea0173795..62e9fe7923 100644 --- a/cmd/rpc.go +++ b/cmd/rpc.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "os" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -17,7 +16,6 @@ import ( const ( // defaultRPCAddress is a default address to dial to defaultRPCAddress = "http://localhost:26658" - authEnvKey = "CELESTIA_NODE_AUTH_TOKEN" //nolint:gosec ) var ( @@ -39,7 +37,7 @@ func RPCFlags() *flag.FlagSet { &authTokenFlag, "token", "", - "Authorization token (if not provided, the "+authEnvKey+" environment variable will be used)", + "Authorization token", ) storeFlag := NodeFlags().Lookup(nodeStoreFlag) @@ -48,15 +46,12 @@ func RPCFlags() *flag.FlagSet { } func InitClient(cmd *cobra.Command, _ []string) error { - if authTokenFlag == "" { - authTokenFlag = os.Getenv(authEnvKey) - } - if authTokenFlag == "" { storePath := "" - if cmd.Flag(nodeStoreFlag).Changed { - storePath = cmd.Flag(nodeStoreFlag).Value.String() + if !cmd.Flag(nodeStoreFlag).Changed { + return fmt.Errorf("cant get the access to the auth token: token/node-store flag was not specified") } + storePath = cmd.Flag(nodeStoreFlag).Value.String() token, err := getToken(storePath) if err != nil { return fmt.Errorf("cant get the access to the auth token: %v", err) From e6a69829c1883c32eab96ca8e1dd9e0aa99a4bd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:27:04 +0300 Subject: [PATCH 0856/1008] chore(deps): Bump the otel group with 2 updates (#2800) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the otel group with 2 updates: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go) and [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go). Updates `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` from 0.41.0 to 0.42.0
    Release notes

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's releases.

    Release v1.19.0/v0.42.0/v0.0.7

    This release contains the first stable release of the OpenTelemetry Go metric SDK. Our project stability guarantees now apply to the go.opentelemetry.io/otel/sdk/metric package. See our versioning policy for more information about these stability guarantees.

    Added

    • Add the "Roll the dice" getting started application example in go.opentelemetry.io/otel/example/dice. (#4539)
    • The WithWriter and WithPrettyPrint options to go.opentelemetry.io/otel/exporters/stdout/stdoutmetric to set a custom io.Writer, and allow displaying the output in human-readable JSON. (#4507)

    Changed

    • Allow '/' characters in metric instrument names. (#4501)
    • The exporter in go.opentelemetry.io/otel/exporters/stdout/stdoutmetric does not prettify its output by default anymore. (#4507)
    • Upgrade gopkg.io/yaml from v2 to v3 in go.opentelemetry.io/otel/schema. (#4535)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the Prometheus metric on every Collect if we know the scope is invalid. (#4499)

    Removed

    • Remove "go.opentelemetry.io/otel/bridge/opencensus".NewMetricExporter, which is replaced by NewMetricProducer. (#4566)

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go/compare/v1.18.0...v1.19.0

    Release v1.19.0-rc.1/v0.42.0-rc.1

    This is a release candidate for the v1.19.0/v0.42.0 release. That release is expected to include the v1 release of the OpenTelemetry Go metric SDK and will provide stability guarantees of that SDK. See our versioning policy for more information about these stability guarantees.

    Changed

    • Allow '/' characters in metric instrument names. (#4501)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the Prometheus metric on every Collect if we know the scope is invalid. (#4499)

    New Contributors

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go/compare/v1.18.0...v1.19.0-rc.1

    Release v1.18.0/v0.41.0/v0.0.6

    This release drops the compatibility guarantee of [Go 1.19].

    Added

    • Add WithProducer option in go.opentelemetry.op/otel/exporters/prometheus to restore the ability to register producers on the prometheus exporter's manual reader. (#4473)
    • Add IgnoreValue option in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest to allow ignoring values when comparing metrics. (#4447)

    ... (truncated)

    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's changelog.

    [1.19.0/0.42.0/0.0.7] 2023-09-28

    This release contains the first stable release of the OpenTelemetry Go [metric SDK]. Our project stability guarantees now apply to the go.opentelemetry.io/otel/sdk/metric package. See our versioning policy for more information about these stability guarantees.

    Added

    • Add the "Roll the dice" getting started application example in go.opentelemetry.io/otel/example/dice. (#4539)
    • The WithWriter and WithPrettyPrint options to go.opentelemetry.io/otel/exporters/stdout/stdoutmetric to set a custom io.Writer, and allow displaying the output in human-readable JSON. (#4507)

    Changed

    • Allow '/' characters in metric instrument names. (#4501)
    • The exporter in go.opentelemetry.io/otel/exporters/stdout/stdoutmetric does not prettify its output by default anymore. (#4507)
    • Upgrade gopkg.io/yaml from v2 to v3 in go.opentelemetry.io/otel/schema. (#4535)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the Prometheus metric on every Collect if we know the scope is invalid. (#4499)

    Removed

    • Remove "go.opentelemetry.io/otel/bridge/opencensus".NewMetricExporter, which is replaced by NewMetricProducer. (#4566)

    [1.19.0-rc.1/0.42.0-rc.1] 2023-09-14

    This is a release candidate for the v1.19.0/v0.42.0 release. That release is expected to include the v1 release of the OpenTelemetry Go metric SDK and will provide stability guarantees of that SDK. See our versioning policy for more information about these stability guarantees.

    Changed

    • Allow '/' characters in metric instrument names. (#4501)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the prometheus metric on every Collect if we know the scope is invalid. (#4499)
    Commits

    Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` from 1.18.0 to 1.19.0
    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp's changelog.

    [1.19.0/0.42.0/0.0.7] 2023-09-28

    This release contains the first stable release of the OpenTelemetry Go [metric SDK]. Our project stability guarantees now apply to the go.opentelemetry.io/otel/sdk/metric package. See our versioning policy for more information about these stability guarantees.

    Added

    • Add the "Roll the dice" getting started application example in go.opentelemetry.io/otel/example/dice. (#4539)
    • The WithWriter and WithPrettyPrint options to go.opentelemetry.io/otel/exporters/stdout/stdoutmetric to set a custom io.Writer, and allow displaying the output in human-readable JSON. (#4507)

    Changed

    • Allow '/' characters in metric instrument names. (#4501)
    • The exporter in go.opentelemetry.io/otel/exporters/stdout/stdoutmetric does not prettify its output by default anymore. (#4507)
    • Upgrade gopkg.io/yaml from v2 to v3 in go.opentelemetry.io/otel/schema. (#4535)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the Prometheus metric on every Collect if we know the scope is invalid. (#4499)

    Removed

    • Remove "go.opentelemetry.io/otel/bridge/opencensus".NewMetricExporter, which is replaced by NewMetricProducer. (#4566)

    [1.19.0-rc.1/0.42.0-rc.1] 2023-09-14

    This is a release candidate for the v1.19.0/v0.42.0 release. That release is expected to include the v1 release of the OpenTelemetry Go metric SDK and will provide stability guarantees of that SDK. See our versioning policy for more information about these stability guarantees.

    Changed

    • Allow '/' characters in metric instrument names. (#4501)

    Fixed

    • In go.opentelemetry.op/otel/exporters/prometheus, don't try to create the prometheus metric on every Collect if we know the scope is invalid. (#4499)
    Commits

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 702d964619..13f218440c 100644 --- a/go.mod +++ b/go.mod @@ -56,12 +56,12 @@ require ( github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 go.opentelemetry.io/otel v1.19.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 go.opentelemetry.io/otel/metric v1.19.0 go.opentelemetry.io/otel/sdk v1.19.0 - go.opentelemetry.io/otel/sdk/metric v0.41.0 + go.opentelemetry.io/otel/sdk/metric v1.19.0 go.opentelemetry.io/otel/trace v1.19.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 @@ -316,7 +316,7 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect diff --git a/go.sum b/go.sum index 0ee3078273..7c1334a343 100644 --- a/go.sum +++ b/go.sum @@ -2384,14 +2384,14 @@ go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+n go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 h1:k0k7hFNDd8K4iOMJXj7s8sHaC4mhTlAeppRmZXLgZ6k= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 h1:iV3BOgW4fry1Riw9dwypigqlIYWXvSRVT2RJmblzo40= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0/go.mod h1:7PGzqlKrxIRmbj5tlNW0nTkYZ5fHXDgk6Fy8/KjR0CI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 h1:wNMDy/LVGLj2h3p6zg4d0gypKfWKSWI14E1C4smOgl8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0/go.mod h1:YfbDdXAAkemWJK3H/DshvlrxqFB2rtW4rY6ky/3x/H0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0/go.mod h1:IOmXxPrxoxFMXdNy7lfDmE8MzE61YPcurbUm0SMjerI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= @@ -2401,8 +2401,8 @@ go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk= -go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= From 424541113fb68c97850d9e9b4795ce6102cbc2b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:30:38 +0300 Subject: [PATCH 0857/1008] chore(deps): Bump github.com/prometheus/client_golang from 1.16.0 to 1.17.0 (#2787) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.16.0 to 1.17.0.
    Release notes

    Sourced from github.com/prometheus/client_golang's releases.

    v1.17.0

    What's Changed

    • [CHANGE] Minimum required go version is now 1.19 (we also test client_golang against new 1.21 version). #1325
    • [FEATURE] Add support for Created Timestamps in Counters, Summaries and Historams. #1313
    • [ENHANCEMENT] Enable detection of a native histogram without observations. #1314

    New Contributors

    ... (truncated)

    Changelog

    Sourced from github.com/prometheus/client_golang's changelog.

    1.17.0 / 2023-09-27

    • [CHANGE] Minimum required go version is now 1.19 (we also test client_golang against new 1.21 version). #1325
    • [FEATURE] Add support for Created Timestamps in Counters, Summaries and Historams. #1313
    • [ENHANCEMENT] Enable detection of a native histogram without observations. #1314
    Commits
    • fa1408e Merge pull request #1352 from prometheus/arthursens/cut-1.17.0
    • 24a72b8 Add changelog entry for 1.17
    • 1bae6c1 Deprecated comment should begin with "Deprecated:" (#1347)
    • bbab8fe Fix typos in comments, tests, and errors (#1346)
    • df7fa49 Extend Counters, Summaries and Histograms with creation timestamp (#1313)
    • 74cc262 Add go_godebug_non_default_behavior_tlsmaxrsasize_events_total (#1348)
    • d03abf3 Cleanup golangci-lint errcheck (#1339)
    • ca6ba04 Update common Prometheus files (#1338)
    • 51d24f8 Update common Prometheus files (#1332)
    • c17edf0 Merge pull request #1304 from prometheus/dependabot/go_modules/google.golang....
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/prometheus/client_golang&package-manager=go_modules&previous-version=1.16.0&new-version=1.17.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 13f218440c..ccb71e8fc5 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.17.0 github.com/pyroscope-io/client v0.7.2 github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.7.0 @@ -278,7 +278,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect diff --git a/go.sum b/go.sum index 7c1334a343..07d9f18d02 100644 --- a/go.sum +++ b/go.sum @@ -2032,8 +2032,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -2041,8 +2041,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= From f6b7b2329115a0d079f47448e3a875453b49ec62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 10:36:32 +0000 Subject: [PATCH 0858/1008] chore(deps): Bump github.com/hashicorp/golang-lru/v2 from 2.0.6 to 2.0.7 (#2785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/hashicorp/golang-lru/v2](https://github.com/hashicorp/golang-lru) from 2.0.6 to 2.0.7.
    Release notes

    Sourced from github.com/hashicorp/golang-lru/v2's releases.

    golang-lru 2.0.7

    What's Changed

    New Contributors

    Full Changelog: https://github.com/hashicorp/golang-lru/compare/v2.0.6...v2.0.7

    Commits
    • d851586 add a Resize method to 2Q
    • d46c1d9 expirable LRU: fix so that Get/Peek cannot return an ok and empty value (#156)
    • 56a2dc0 Update arc to base package v2.0.6
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/hashicorp/golang-lru/v2&package-manager=go_modules&previous-version=2.0.6&new-version=2.0.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ccb71e8fc5..e5bec1fcf3 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.4 - github.com/hashicorp/golang-lru/v2 v2.0.6 + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.13.1 github.com/ipfs/go-block-format v0.2.0 diff --git a/go.sum b/go.sum index 07d9f18d02..6d47e155b7 100644 --- a/go.sum +++ b/go.sum @@ -980,8 +980,8 @@ github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= -github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= From fc0074676cd8eb7b46091d68db7333bf20c7adc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:39:23 +0300 Subject: [PATCH 0859/1008] chore(deps): Bump alpine from 3.18.3 to 3.18.4 (#2794) Bumps alpine from 3.18.3 to 3.18.4. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.18.3&new-version=3.18.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bebd61a644..403691d4a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ COPY . . RUN make build && make cel-key -FROM --platform=$BUILDPLATFORM docker.io/alpine:3.18.3 +FROM --platform=$BUILDPLATFORM docker.io/alpine:3.18.4 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From 95727d7ce3eedbff6fc5a3540430697af374839c Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 6 Oct 2023 11:43:41 +0100 Subject: [PATCH 0860/1008] (chore) ensure all dependency group PRs get the default label (#2798) currently only the go.mod dependabot PRs were getting the `kind:deps` label, so others that targer Docker or github actions would fail a label check eg: https://github.com/celestiaorg/celestia-node/pull/2794 this should fix that. minor tweak. --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4ab88e1d32..e2becce196 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,8 @@ updates: day: monday time: "11:00" open-pull-requests-limit: 10 + labels: + - kind:deps - package-ecosystem: gomod directory: "/" schedule: @@ -30,3 +32,5 @@ updates: day: monday time: "11:00" open-pull-requests-limit: 10 + labels: + - kind:deps From 49d962e53a772999cf1b365f72f0f73dc40d48f2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 6 Oct 2023 12:44:08 +0200 Subject: [PATCH 0861/1008] deps: bump go-header (#2813) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e5bec1fcf3..2bf4f0edf6 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v1.0.0-rc18 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.3.1 + github.com/celestiaorg/go-header v0.3.3 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 6d47e155b7..d0cae595b7 100644 --- a/go.sum +++ b/go.sum @@ -370,8 +370,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.3.1 h1:rJuFPVMoI20Du4KHWLz0IMllEIp0XgIWi9lHQLPWmq8= -github.com/celestiaorg/go-header v0.3.1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.3.3 h1:Y04hdJIJfD5hapyqK0ZQMgMTH5PQGV9YpcIf56LGc4E= +github.com/celestiaorg/go-header v0.3.3/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= From 270931bdd329359b89f436fef2b71e951a083afa Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:18:30 +0400 Subject: [PATCH 0862/1008] fix(share/peer-manager): use mutex for peer-manager pool cleanup (#2817) Resolves https://github.com/celestiaorg/celestia-node/issues/2807 --- share/p2p/peers/manager.go | 5 +---- share/p2p/peers/pool.go | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index caef242eec..4935db2774 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -7,7 +7,6 @@ import ( "sync" "sync/atomic" "time" - "unsafe" logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -518,9 +517,7 @@ func (m *Manager) markPoolAsSynced(datahash string) { p := m.getOrCreatePool(datahash) if p.isSynced.CompareAndSwap(false, true) { p.isSynced.Store(true) - old := (*unsafe.Pointer)(unsafe.Pointer(&p.pool)) - // release pointer to old pool to free up memory - atomic.StorePointer(old, unsafe.Pointer(newPool(time.Second))) + p.reset() } } diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index c43bbc963b..609d68e0b3 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -221,3 +221,12 @@ func (p *pool) len() int { defer p.m.RUnlock() return p.activeCount } + +// reset will reset the pool to its initial state. +func (p *pool) reset() { + lock := &p.m + lock.Lock() + defer lock.Lock() + // swap the pool with an empty one + *p = *newPool(time.Second) +} From 1ba92c3d50de87688453ae746960e18462953572 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:31:37 +0400 Subject: [PATCH 0863/1008] chore(deps): Bump go.opentelemetry.io/contrib/instrumentation/runtime from 0.44.0 to 0.45.0 (#2819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [go.opentelemetry.io/contrib/instrumentation/runtime](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.44.0 to 0.45.0.
    Release notes

    Sourced from go.opentelemetry.io/contrib/instrumentation/runtime's releases.

    Release v1.20.0/v0.45.0/v0.14.0

    Added

    • Set the description for the rpc.server.duration metric in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc. (#4302)
    • Add NewServerHandler and NewClientHandler that return a grpc.StatsHandler used for gRPC instrumentation in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc. (#3002)
    • Add new Prometheus bridge module in go.opentelemetry.io/contrib/bridges/prometheus. (#4227)

    Changed

    • Upgrade dependencies of OpenTelemetry Go to use the new v1.19.0/v0.42.0/v0.0.7 release.
    • Use grpc.StatsHandler for gRPC instrumentation in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example. (#4325)

    New Contributors

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go-contrib/compare/v1.19.0...v1.20.0

    Release v1.19.0/v0.44.0/v0.13.0

    Added

    • Add gcp.gce.instance.name and gcp.gce.instance.hostname resource attributes to go.opentelemetry.io/contrib/detectors/gcp. (#4263)

    Changed

    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/ec2 have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/ecs have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/eks have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/detectors/aws/lambda have been upgraded to v1.21.0. (#4265)
    • The semantic conventions used by go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda have been upgraded to v1.21.0. (#4265)
      • The faas.execution attribute is now faas.invocation_id.
      • The faas.id attribute is now aws.lambda.invoked_arn.
    • The semantic conventions used by go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws have been upgraded to v1.21.0. (#4265)
    • The http.request.method attribute will only allow known HTTP methods from the metrics generated by go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#4277)

    Removed

    • The high cardinality attributes net.sock.peer.addr, net.sock.peer.port, http.user_agent, enduser.id, and http.client_ip were removed from the metrics generated by go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp. (#4277)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache module is removed. (#4295)
    • The deprecated go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql module is removed. (#4295)

    New Contributors

    Full Changelog: https://github.com/open-telemetry/opentelemetry-go-contrib/compare/v1.18.0...v1.19.0

    Release v1.18.0/v0.43.0/v0.12.0

    ... (truncated)

    Changelog

    Sourced from go.opentelemetry.io/contrib/instrumentation/runtime's changelog.

    [1.20.0/0.45.0/0.14.0] - 2023-09-28

    Added

    • Set the description for the rpc.server.duration metric in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc. (#4302)
    • Add NewServerHandler and NewClientHandler that return a grpc.StatsHandler used for gRPC instrumentation in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc. (#3002)
    • Add new Prometheus bridge module in go.opentelemetry.io/contrib/bridges/prometheus. (#4227)

    Changed

    • Upgrade dependencies of OpenTelemetry Go to use the new v1.19.0/v0.42.0/v0.0.7 release.
    • Use grpc.StatsHandler for gRPC instrumentation in go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example. (#4325)
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.opentelemetry.io/contrib/instrumentation/runtime&package-manager=go_modules&previous-version=0.44.0&new-version=0.45.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2bf4f0edf6..599cef51f6 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 - go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0 go.opentelemetry.io/otel v1.19.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 diff --git a/go.sum b/go.sum index d0cae595b7..c907bef452 100644 --- a/go.sum +++ b/go.sum @@ -2376,8 +2376,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 h1:TXu20nL4yYfJlQeqG/D3Ia6b0p2HZmLfJto9hqJTQ/c= -go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0/go.mod h1:tQ5gBnfjndV1su3+DiLuu6rnd9hBBzg4rkRILnjSNFg= +go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0 h1:2JydY5UiDpqvj2p7sO9bgHuhTy4hgTZ0ymehdq/Ob0Q= +go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0/go.mod h1:ch3a5QxOqVWxas4CzjCFFOOQe+7HgAXC/N1oVxS9DK4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= From ec4472a50a84fdd33b346e319ee6a91693415c30 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:34:07 +0400 Subject: [PATCH 0864/1008] feat(discovery): discover peers by tag (#2730) Allow to run multiple discovery subcomponents, where each can find new peers based on preset `tag` and notify its subscribers. It could allow any sort of separate discovery subcomponents (versioned, pruned/archived, etc). Essentially it allows discovery to be abstracted away and act as single function component. Involves some minor refactoring here and there just to clean up things. Resolves https://github.com/celestiaorg/celestia-node/issues/2578 --- nodebuilder/share/config.go | 2 +- nodebuilder/share/constructors.go | 17 ++++- nodebuilder/share/module.go | 4 +- nodebuilder/tests/p2p_test.go | 2 - share/availability/full/testing.go | 10 ++- share/getters/shrex_test.go | 9 --- share/p2p/discovery/discovery.go | 64 +++++++--------- share/p2p/discovery/discovery_test.go | 102 ++++++++++++++++++++++---- share/p2p/discovery/metrics.go | 3 +- share/p2p/discovery/options.go | 47 +++++++----- share/p2p/peers/manager.go | 37 ++++------ share/p2p/peers/manager_test.go | 81 ++++++++++---------- 12 files changed, 226 insertions(+), 152 deletions(-) diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go index 5d66ea7691..1d984b6dca 100644 --- a/nodebuilder/share/config.go +++ b/nodebuilder/share/config.go @@ -26,7 +26,7 @@ type Config struct { PeerManagerParams peers.Parameters LightAvailability light.Parameters `toml:",omitempty"` - Discovery discovery.Parameters + Discovery *discovery.Parameters } func DefaultConfig(tp node.Type) Config { diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index 944b7f2d23..aa2ac5bec1 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -17,18 +17,27 @@ import ( "github.com/celestiaorg/celestia-node/share/getters" "github.com/celestiaorg/celestia-node/share/ipld" disc "github.com/celestiaorg/celestia-node/share/p2p/discovery" + "github.com/celestiaorg/celestia-node/share/p2p/peers" ) -func newDiscovery(cfg Config) func(routing.ContentRouting, host.Host) *disc.Discovery { +const ( + // fullNodesTag is the tag used to identify full nodes in the discovery service. + fullNodesTag = "full" +) + +func newDiscovery(cfg *disc.Parameters, +) func(routing.ContentRouting, host.Host, *peers.Manager) (*disc.Discovery, error) { return func( r routing.ContentRouting, h host.Host, - ) *disc.Discovery { + manager *peers.Manager, + ) (*disc.Discovery, error) { return disc.NewDiscovery( + cfg, h, routingdisc.NewRoutingDiscovery(r), - disc.WithPeersLimit(cfg.Discovery.PeersLimit), - disc.WithAdvertiseInterval(cfg.Discovery.AdvertiseInterval), + fullNodesTag, + disc.WithOnPeersUpdate(manager.UpdateFullNodePool), ) } } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index b2e6eed4e5..9fbd262a32 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -37,7 +37,7 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(newModule), fx.Invoke(func(disc *disc.Discovery) {}), fx.Provide(fx.Annotate( - newDiscovery(*cfg), + newDiscovery(cfg.Discovery), fx.OnStart(func(ctx context.Context, d *disc.Discovery) error { return d.Start(ctx) }), @@ -147,7 +147,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide( func( params peers.Parameters, - discovery *disc.Discovery, host host.Host, connGater *conngater.BasicConnectionGater, shrexSub *shrexsub.PubSub, @@ -158,7 +157,6 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option ) (*peers.Manager, error) { return peers.NewManager( params, - discovery, host, connGater, peers.WithShrexSubPools(shrexSub, headerSub), diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index d05846f40c..9fe63fb931 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -186,9 +186,7 @@ func TestRestartNodeDiscovery(t *testing.T) { sw.Disconnect(t, nodes[0], nodes[1]) // create and start one more FN with disabled discovery - fullCfg.Share.Discovery.PeersLimit = 0 disabledDiscoveryFN := sw.NewNodeWithConfig(node.Full, fullCfg, nodesConfig) - err = disabledDiscoveryFN.Start(ctx) require.NoError(t, err) // ensure that the FN with disabled discovery is discovered by both of the diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index a636b26ea6..dd21d398c2 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -41,12 +41,16 @@ func Node(dn *availability_test.TestDagNet) *availability_test.TestNode { } func TestAvailability(t *testing.T, getter share.Getter) *ShareAvailability { - disc := discovery.NewDiscovery( + params := discovery.DefaultParameters() + params.AdvertiseInterval = time.Second + params.PeersLimit = 10 + disc, err := discovery.NewDiscovery( + params, nil, routing.NewRoutingDiscovery(routinghelpers.Null{}), - discovery.WithAdvertiseInterval(time.Second), - discovery.WithPeersLimit(10), + "full", ) + require.NoError(t, err) store, err := eds.NewStore(eds.DefaultParameters(), t.TempDir(), datastore.NewMapDatastore()) require.NoError(t, err) err = store.Start(context.Background()) diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 9b97a4dd5a..85878b204a 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -9,9 +9,7 @@ import ( "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" - routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/core/host" - routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" "github.com/libp2p/go-libp2p/p2p/net/conngater" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" @@ -25,7 +23,6 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/edstest" - "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" @@ -210,18 +207,12 @@ func testManager( return nil, err } - disc := discovery.NewDiscovery(nil, - routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), - discovery.WithPeersLimit(10), - discovery.WithAdvertiseInterval(time.Second), - ) connGater, err := conngater.NewBasicConnectionGater(ds_sync.MutexWrap(datastore.NewMapDatastore())) if err != nil { return nil, err } manager, err := peers.NewManager( peers.DefaultParameters(), - disc, host, connGater, peers.WithShrexSubPools(shrexSub, headerSub), diff --git a/share/p2p/discovery/discovery.go b/share/p2p/discovery/discovery.go index f24df2c88b..0f44d42dbe 100644 --- a/share/p2p/discovery/discovery.go +++ b/share/p2p/discovery/discovery.go @@ -19,9 +19,6 @@ import ( var log = logging.Logger("share/discovery") const ( - // rendezvousPoint is the namespace where peers advertise and discover each other. - rendezvousPoint = "full" - // eventbusBufSize is the size of the buffered channel to handle // events in libp2p. We specify a larger buffer size for the channel // to avoid overflowing and blocking subscription during disconnection bursts. @@ -45,6 +42,8 @@ var discoveryRetryTimeout = retryTimeout // Discovery combines advertise and discover services and allows to store discovered nodes. // TODO: The code here gets horribly hairy, so we should refactor this at some point type Discovery struct { + // Tag is used as rondezvous point for discovery service + tag string set *limitedSet host host.Host disc discovery.Discovery @@ -58,43 +57,50 @@ type Discovery struct { cancel context.CancelFunc - params Parameters + params *Parameters } type OnUpdatedPeers func(peerID peer.ID, isAdded bool) +func (f OnUpdatedPeers) add(next OnUpdatedPeers) OnUpdatedPeers { + return func(peerID peer.ID, isAdded bool) { + f(peerID, isAdded) + next(peerID, isAdded) + } +} + // NewDiscovery constructs a new discovery. func NewDiscovery( + params *Parameters, h host.Host, d discovery.Discovery, + tag string, opts ...Option, -) *Discovery { - params := DefaultParameters() - - for _, opt := range opts { - opt(¶ms) +) (*Discovery, error) { + if err := params.Validate(); err != nil { + return nil, err } + if tag == "" { + return nil, fmt.Errorf("discovery: tag cannot be empty") + } + o := newOptions(opts...) return &Discovery{ + tag: tag, set: newLimitedSet(params.PeersLimit), host: h, disc: d, connector: newBackoffConnector(h, defaultBackoffFactory), - onUpdatedPeers: func(peer.ID, bool) {}, + onUpdatedPeers: o.onUpdatedPeers, params: params, triggerDisc: make(chan struct{}), - } + }, nil } func (d *Discovery) Start(context.Context) error { ctx, cancel := context.WithCancel(context.Background()) d.cancel = cancel - if d.params.PeersLimit == 0 { - log.Warn("peers limit is set to 0. Skipping discovery...") - return nil - } - sub, err := d.host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}, eventbus.BufSize(eventbusBufSize)) if err != nil { return fmt.Errorf("subscribing for connection events: %w", err) @@ -111,15 +117,6 @@ func (d *Discovery) Stop(context.Context) error { return nil } -// WithOnPeersUpdate chains OnPeersUpdate callbacks on every update of discovered peers list. -func (d *Discovery) WithOnPeersUpdate(f OnUpdatedPeers) { - prev := d.onUpdatedPeers - d.onUpdatedPeers = func(peerID peer.ID, isAdded bool) { - prev(peerID, isAdded) - f(peerID, isAdded) - } -} - // Peers provides a list of discovered peers in the "full" topic. // If Discovery hasn't found any peers, it blocks until at least one peer is found. func (d *Discovery) Peers(ctx context.Context) ([]peer.ID, error) { @@ -133,7 +130,7 @@ func (d *Discovery) Discard(id peer.ID) bool { return false } - d.host.ConnManager().Unprotect(id, rendezvousPoint) + d.host.ConnManager().Unprotect(id, d.tag) d.connector.Backoff(id) d.set.Remove(id) d.onUpdatedPeers(id, false) @@ -153,21 +150,16 @@ func (d *Discovery) Discard(id peer.ID) bool { // Advertise is a utility function that persistently advertises a service through an Advertiser. // TODO: Start advertising only after the reachability is confirmed by AutoNAT func (d *Discovery) Advertise(ctx context.Context) { - if d.params.AdvertiseInterval == -1 { - log.Warn("AdvertiseInterval is set to -1. Skipping advertising...") - return - } - timer := time.NewTimer(d.params.AdvertiseInterval) defer timer.Stop() for { - _, err := d.disc.Advertise(ctx, rendezvousPoint) + _, err := d.disc.Advertise(ctx, d.tag) d.metrics.observeAdvertise(ctx, err) if err != nil { if ctx.Err() != nil { return } - log.Warnw("error advertising", "rendezvous", rendezvousPoint, "err", err) + log.Warnw("error advertising", "rendezvous", d.tag, "err", err) // we don't want retry indefinitely in busy loop // internal discovery mechanism may need some time before attempts @@ -280,7 +272,7 @@ func (d *Discovery) discover(ctx context.Context) bool { findCancel() }() - peers, err := d.disc.FindPeers(findCtx, rendezvousPoint) + peers, err := d.disc.FindPeers(findCtx, d.tag) if err != nil { log.Error("unable to start discovery", "err", err) return false @@ -371,11 +363,11 @@ func (d *Discovery) handleDiscoveredPeer(ctx context.Context, peer peer.AddrInfo d.metrics.observeHandlePeer(ctx, handlePeerConnected) logger.Debug("added peer to set") - // tag to protect peer from being killed by ConnManager + // Tag to protect peer from being killed by ConnManager // NOTE: This is does not protect from remote killing the connection. // In the future, we should design a protocol that keeps bidirectional agreement on whether // connection should be kept or not, similar to mesh link in GossipSub. - d.host.ConnManager().Protect(peer.ID, rendezvousPoint) + d.host.ConnManager().Protect(peer.ID, d.tag) return true } diff --git a/share/p2p/discovery/discovery_test.go b/share/p2p/discovery/discovery_test.go index 06d88a9079..c02931e1a4 100644 --- a/share/p2p/discovery/discovery_test.go +++ b/share/p2p/discovery/discovery_test.go @@ -17,33 +17,47 @@ import ( "github.com/stretchr/testify/require" ) +const ( + fullNodesTag = "full" +) + func TestDiscovery(t *testing.T) { const nodes = 10 // higher number brings higher coverage discoveryRetryTimeout = time.Millisecond * 100 // defined in discovery.go - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) t.Cleanup(cancel) tn := newTestnet(ctx, t) - peerA := tn.discovery( - WithPeersLimit(nodes), - WithAdvertiseInterval(-1), - ) - type peerUpdate struct { peerID peer.ID isAdded bool } updateCh := make(chan peerUpdate) - peerA.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { + submit := func(peerID peer.ID, isAdded bool) { updateCh <- peerUpdate{peerID: peerID, isAdded: isAdded} - }) + } + + host, routingDisc := tn.peer() + params := DefaultParameters() + params.PeersLimit = nodes + + // start discovery listener service for peerA + peerA := tn.startNewDiscovery(params, host, routingDisc, fullNodesTag, + WithOnPeersUpdate(submit), + ) + // start discovery advertisement services for other peers + params.AdvertiseInterval = time.Millisecond * 100 discs := make([]*Discovery, nodes) for i := range discs { - discs[i] = tn.discovery(WithPeersLimit(0), WithAdvertiseInterval(time.Millisecond*100)) + host, routingDisc := tn.peer() + disc, err := NewDiscovery(params, host, routingDisc, fullNodesTag) + require.NoError(t, err) + go disc.Advertise(tn.ctx) + discs[i] = tn.startNewDiscovery(params, host, routingDisc, fullNodesTag) select { case res := <-updateCh: @@ -56,6 +70,7 @@ func TestDiscovery(t *testing.T) { assert.EqualValues(t, nodes, peerA.set.Size()) + // disconnect peerA from all peers and check that notifications are received on updateCh channel for _, disc := range discs { peerID := disc.host.ID() err := peerA.host.Network().ClosePeer(peerID) @@ -73,6 +88,51 @@ func TestDiscovery(t *testing.T) { assert.EqualValues(t, 0, peerA.set.Size()) } +func TestDiscoveryTagged(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + t.Cleanup(cancel) + + tn := newTestnet(ctx, t) + + // launch 2 peers, that advertise with different tags + adv1, routingDisc1 := tn.peer() + adv2, routingDisc2 := tn.peer() + + // sub will discover both peers, but on different tags + sub, routingDisc := tn.peer() + + params := DefaultParameters() + + // create 2 discovery services for sub, each with a different tag + done1 := make(chan struct{}) + tn.startNewDiscovery(params, sub, routingDisc, "tag1", + WithOnPeersUpdate(checkPeer(t, adv1.ID(), done1))) + + done2 := make(chan struct{}) + tn.startNewDiscovery(params, sub, routingDisc, "tag2", + WithOnPeersUpdate(checkPeer(t, adv2.ID(), done2))) + + // run discovery services for advertisers + ds1 := tn.startNewDiscovery(params, adv1, routingDisc1, "tag1") + go ds1.Advertise(tn.ctx) + + ds2 := tn.startNewDiscovery(params, adv2, routingDisc2, "tag2") + go ds2.Advertise(tn.ctx) + + // wait for discovery services to discover each other on different tags + select { + case <-done1: + case <-ctx.Done(): + t.Fatal("did not discover peer in time") + } + + select { + case <-done2: + case <-ctx.Done(): + t.Fatal("did not discover peer in time") + } +} + type testnet struct { ctx context.Context T *testing.T @@ -97,17 +157,21 @@ func newTestnet(ctx context.Context, t *testing.T) *testnet { return &testnet{ctx: ctx, T: t, bootstrapper: *host.InfoFromHost(hst)} } -func (t *testnet) discovery(opts ...Option) *Discovery { - hst, routingDisc := t.peer() - disc := NewDiscovery(hst, routingDisc, opts...) - err := disc.Start(t.ctx) +func (t *testnet) startNewDiscovery( + params *Parameters, + hst host.Host, + routingDisc discovery.Discovery, + tag string, + opts ...Option, +) *Discovery { + disc, err := NewDiscovery(params, hst, routingDisc, tag, opts...) + require.NoError(t.T, err) + err = disc.Start(t.ctx) require.NoError(t.T, err) t.T.Cleanup(func() { err := disc.Stop(t.ctx) require.NoError(t.T, err) }) - - go disc.Advertise(t.ctx) return disc } @@ -134,3 +198,11 @@ func (t *testnet) peer() (host.Host, discovery.Discovery) { return hst, routing.NewRoutingDiscovery(dht) } + +func checkPeer(t *testing.T, expected peer.ID, done chan struct{}) func(peerID peer.ID, isAdded bool) { + return func(peerID peer.ID, isAdded bool) { + defer close(done) + require.Equal(t, expected, peerID) + require.True(t, isAdded) + } +} diff --git a/share/p2p/discovery/metrics.go b/share/p2p/discovery/metrics.go index 99c9bb4548..d0be1c219d 100644 --- a/share/p2p/discovery/metrics.go +++ b/share/p2p/discovery/metrics.go @@ -15,7 +15,6 @@ const ( handlePeerResultKey = "result" handlePeerSkipSelf handlePeerResult = "skip_self" - handlePeerEmptyAddrs handlePeerResult = "skip_empty_addresses" handlePeerEnoughPeers handlePeerResult = "skip_enough_peers" handlePeerBackoff handlePeerResult = "skip_backoff" handlePeerConnected handlePeerResult = "connected" @@ -47,7 +46,7 @@ func (d *Discovery) WithMetrics() error { return fmt.Errorf("discovery: init metrics: %w", err) } d.metrics = metrics - d.WithOnPeersUpdate(metrics.observeOnPeersUpdate) + d.onUpdatedPeers = d.onUpdatedPeers.add(metrics.observeOnPeersUpdate) return nil } diff --git a/share/p2p/discovery/options.go b/share/p2p/discovery/options.go index 9d2735c50d..de4b13a7db 100644 --- a/share/p2p/discovery/options.go +++ b/share/p2p/discovery/options.go @@ -3,6 +3,8 @@ package discovery import ( "fmt" "time" + + "github.com/libp2p/go-libp2p/core/peer" ) // Parameters is the set of Parameters that must be configured for the Discovery module @@ -16,13 +18,19 @@ type Parameters struct { AdvertiseInterval time.Duration } +// options is the set of options that can be configured for the Discovery module +type options struct { + // onUpdatedPeers will be called on peer set changes + onUpdatedPeers OnUpdatedPeers +} + // Option is a function that configures Discovery Parameters -type Option func(*Parameters) +type Option func(*options) // DefaultParameters returns the default Parameters' configuration values // for the Discovery module -func DefaultParameters() Parameters { - return Parameters{ +func DefaultParameters() *Parameters { + return &Parameters{ PeersLimit: 5, AdvertiseInterval: time.Hour, } @@ -30,29 +38,30 @@ func DefaultParameters() Parameters { // Validate validates the values in Parameters func (p *Parameters) Validate() error { - if p.AdvertiseInterval <= 0 { - return fmt.Errorf( - "discovery: invalid option: value AdvertiseInterval %s, %s", - "is 0 or negative.", - "value must be positive", - ) + if p.PeersLimit <= 0 { + return fmt.Errorf("discovery: peers limit cannot be zero or negative") } + if p.AdvertiseInterval <= 0 { + return fmt.Errorf("discovery: advertise interval cannot be zero or negative") + } return nil } -// WithPeersLimit is a functional option that Discovery -// uses to set the PeersLimit configuration param -func WithPeersLimit(peersLimit uint) Option { - return func(p *Parameters) { - p.PeersLimit = peersLimit +// WithOnPeersUpdate chains OnPeersUpdate callbacks on every update of discovered peers list. +func WithOnPeersUpdate(f OnUpdatedPeers) Option { + return func(p *options) { + p.onUpdatedPeers = p.onUpdatedPeers.add(f) } } -// WithAdvertiseInterval is a functional option that Discovery -// uses to set the AdvertiseInterval configuration param -func WithAdvertiseInterval(advInterval time.Duration) Option { - return func(p *Parameters) { - p.AdvertiseInterval = advInterval +func newOptions(opts ...Option) *options { + defaults := &options{ + onUpdatedPeers: func(peer.ID, bool) {}, + } + + for _, opt := range opts { + opt(defaults) } + return defaults } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 4935db2774..e39a181150 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -21,7 +21,6 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/p2p/discovery" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -54,7 +53,6 @@ type Manager struct { // header subscription is necessary in order to Validate the inbound eds hash headerSub libhead.Subscriber[*header.ExtendedHeader] shrexSub *shrexsub.PubSub - disc *discovery.Discovery host host.Host connGater *conngater.BasicConnectionGater @@ -98,7 +96,6 @@ type syncPool struct { func NewManager( params Parameters, - discovery *discovery.Discovery, host host.Host, connGater *conngater.BasicConnectionGater, options ...Option, @@ -110,7 +107,6 @@ func NewManager( s := &Manager{ params: params, connGater: connGater, - disc: discovery, host: host, pools: make(map[string]*syncPool), blacklistedHashes: make(map[string]bool), @@ -126,23 +122,6 @@ func NewManager( } s.fullNodes = newPool(s.params.PeerCooldown) - - discovery.WithOnPeersUpdate( - func(peerID peer.ID, isAdded bool) { - if isAdded { - if s.isBlacklistedPeer(peerID) { - log.Debugw("got blacklisted peer from discovery", "peer", peerID.String()) - return - } - s.fullNodes.add(peerID) - log.Debugw("added to full nodes", "peer", peerID) - return - } - - log.Debugw("removing peer from discovered full nodes", "peer", peerID.String()) - s.fullNodes.remove(peerID) - }) - return s, nil } @@ -247,6 +226,22 @@ func (m *Manager) Peer( } } +// UpdateFullNodePool is called by discovery when new full node is discovered or removed +func (m *Manager) UpdateFullNodePool(peerID peer.ID, isAdded bool) { + if isAdded { + if m.isBlacklistedPeer(peerID) { + log.Debugw("got blacklisted peer from discovery", "peer", peerID.String()) + return + } + m.fullNodes.add(peerID) + log.Debugw("added to full nodes", "peer", peerID) + return + } + + log.Debugw("removing peer from discovered full nodes", "peer", peerID.String()) + m.fullNodes.remove(peerID) +} + func (m *Manager) newPeer( ctx context.Context, datahash share.DataHash, diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 1ad7e65c02..94ec5d5ea2 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -10,7 +10,6 @@ import ( "github.com/ipfs/go-datastore/sync" dht "github.com/libp2p/go-libp2p-kad-dht" pubsub "github.com/libp2p/go-libp2p-pubsub" - routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" @@ -364,13 +363,14 @@ func TestIntegration(t *testing.T) { }) t.Run("get peer from discovery", func(t *testing.T) { + fullNodesTag := "fullNodes" nw, err := mocknet.FullMeshConnected(3) require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) // set up bootstrapper - bsHost := nw.Hosts()[2] + bsHost := nw.Hosts()[0] bs := host.InfoFromHost(bsHost) opts := []dht.Option{ dht.Mode(dht.ModeAuto), @@ -388,62 +388,74 @@ func TestIntegration(t *testing.T) { require.NoError(t, bsRouter.Bootstrap(ctx)) // set up broadcaster node - bnHost := nw.Hosts()[0] - router1, err := dht.New(ctx, bnHost, opts...) + bnHost := nw.Hosts()[1] + bnRouter, err := dht.New(ctx, bnHost, opts...) require.NoError(t, err) - bnDisc := discovery.NewDiscovery( - nw.Hosts()[0], - routingdisc.NewRoutingDiscovery(router1), - discovery.WithPeersLimit(0), - discovery.WithAdvertiseInterval(time.Second), + + params := discovery.DefaultParameters() + params.AdvertiseInterval = time.Second + + bnDisc, err := discovery.NewDiscovery( + params, + bnHost, + routingdisc.NewRoutingDiscovery(bnRouter), + fullNodesTag, ) + require.NoError(t, err) // set up full node / receiver node - fnHost := nw.Hosts()[0] - router2, err := dht.New(ctx, fnHost, opts...) - require.NoError(t, err) - fnDisc := discovery.NewDiscovery( - nw.Hosts()[1], - routingdisc.NewRoutingDiscovery(router2), - discovery.WithPeersLimit(10), - discovery.WithAdvertiseInterval(time.Second), - ) - err = fnDisc.Start(ctx) + fnHost := nw.Hosts()[2] + fnRouter, err := dht.New(ctx, fnHost, opts...) require.NoError(t, err) - t.Cleanup(func() { - err = fnDisc.Stop(ctx) - require.NoError(t, err) - }) - // hook peer manager to discovery + // init peer manager for full node connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) fnPeerManager, err := NewManager( DefaultParameters(), - fnDisc, nil, connGater, ) require.NoError(t, err) waitCh := make(chan struct{}) - fnDisc.WithOnPeersUpdate(func(peerID peer.ID, isAdded bool) { + checkDiscoveredPeer := func(peerID peer.ID, isAdded bool) { defer close(waitCh) - // check that obtained peer id is same as BN - require.Equal(t, nw.Hosts()[0].ID(), peerID) + // check that obtained peer id is BN + require.Equal(t, bnHost.ID(), peerID) + } + + // set up discovery for full node with hook to peer manager and check discovered peer + params = discovery.DefaultParameters() + params.AdvertiseInterval = time.Second + params.PeersLimit = 10 + + fnDisc, err := discovery.NewDiscovery( + params, + fnHost, + routingdisc.NewRoutingDiscovery(fnRouter), + fullNodesTag, + discovery.WithOnPeersUpdate(fnPeerManager.UpdateFullNodePool), + discovery.WithOnPeersUpdate(checkDiscoveredPeer), + ) + require.NoError(t, fnDisc.Start(ctx)) + t.Cleanup(func() { + err = fnDisc.Stop(ctx) + require.NoError(t, err) }) - require.NoError(t, router1.Bootstrap(ctx)) - require.NoError(t, router2.Bootstrap(ctx)) + require.NoError(t, bnRouter.Bootstrap(ctx)) + require.NoError(t, fnRouter.Bootstrap(ctx)) go bnDisc.Advertise(ctx) select { case <-waitCh: - require.Contains(t, fnPeerManager.fullNodes.peersList, fnHost.ID()) + require.Contains(t, fnPeerManager.fullNodes.peersList, bnHost.ID()) case <-ctx.Done(): require.NoError(t, ctx.Err()) } + }) } @@ -457,22 +469,17 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten return nil, err } - disc := discovery.NewDiscovery(nil, - routingdisc.NewRoutingDiscovery(routinghelpers.Null{}), - discovery.WithPeersLimit(0), - discovery.WithAdvertiseInterval(time.Second), - ) connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) if err != nil { return nil, err } manager, err := NewManager( DefaultParameters(), - disc, host, connGater, WithShrexSubPools(shrexSub, headerSub), ) + if err != nil { return nil, err } From 5e5f46339cec6c74f82a45a56e086a60208bb93b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:37:09 +0000 Subject: [PATCH 0865/1008] chore(deps): Bump golang.org/x/crypto from 0.13.0 to 0.14.0 (#2821) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.13.0 to 0.14.0.
    Commits
    • e3cc52e go.mod: update golang.org/x dependencies
    • 833695f ssh: add server side support for ping@openssh.com protocol extension
    • ec07f4e chacha20: drop Go 1.10 compatibility for arm64
    • b665ba6 all: use crypto/ed25519 instead of golang.org/x/crypto/ed25519
    • a1aeb9b ssh: add test cases for compatibility with old (buggy) clients
    • 28c53ff ssh: add MultiAlgorithmSigner
    • 3f0842a sha3: have ShakeHash extend hash.Hash
    • e90f1e1 cryptobyte: add uint48 methods
    • d359caa ssh: support for marshaling keys using the OpenSSH format
    • c5370d2 ssh: check the declared public key algo against decoded one
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.13.0&new-version=0.14.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 599cef51f6..6264c79c56 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/sync v0.3.0 golang.org/x/text v0.13.0 @@ -324,8 +324,8 @@ require ( golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect diff --git a/go.sum b/go.sum index c907bef452..48286df271 100644 --- a/go.sum +++ b/go.sum @@ -2506,8 +2506,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2854,8 +2854,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2864,8 +2864,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From a2cf93d1b9096254d92148ab33e6da9d8b621160 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 9 Oct 2023 17:24:27 +0300 Subject: [PATCH 0866/1008] fix(docs): fix GetVerifiedRange doc (#2826) --- docs/adr/adr-009-public-api.md | 2 +- nodebuilder/header/header.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index 76bb1c5ab9..d2bf6ee817 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -100,7 +100,7 @@ GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) // WaitForHeight blocks until the header at the given height has been processed // by the node's header store or until context deadline is exceeded. WaitForHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) -// GetVerifiedRangeByHeight returns the given range [from:to) of ExtendedHeaders +// GetVerifiedRangeByHeight returns the given range (from:to) of ExtendedHeaders // from the node's header store and verifies that the returned headers are // adjacent to each other. GetVerifiedRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index d7eb7b5b46..9b9455da26 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -19,7 +19,7 @@ type Module interface { // GetByHash returns the header of the given hash from the node's header store. GetByHash(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) - // GetVerifiedRangeByHeight returns the given range [from:to) of ExtendedHeaders + // GetVerifiedRangeByHeight returns the given range (from:to) of ExtendedHeaders // from the node's header store and verifies that the returned headers are // adjacent to each other. GetVerifiedRangeByHeight( From 88c80e57cd9cd71daf7b28d16b69246a9040e32f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:44:31 +0400 Subject: [PATCH 0867/1008] chore(deps): Bump golang.org/x/sync from 0.3.0 to 0.4.0 (#2820) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.3.0 to 0.4.0.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/sync&package-manager=go_modules&previous-version=0.3.0&new-version=0.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6264c79c56..d39b4696fd 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.4.0 golang.org/x/text v0.13.0 google.golang.org/grpc v1.58.2 google.golang.org/protobuf v1.31.0 diff --git a/go.sum b/go.sum index 48286df271..7a986186d2 100644 --- a/go.sum +++ b/go.sum @@ -2707,8 +2707,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 321b3aaf39ddd04ecd0e0238a94c2f9d7e5ff064 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 2 Oct 2023 17:10:33 +0300 Subject: [PATCH 0868/1008] fix(tests)!: fix FraudProofHandling test (#2665) --- nodebuilder/fraud/fraud.go | 14 ++++++------ nodebuilder/fraud/mocks/api.go | 4 ++-- nodebuilder/tests/fraud_test.go | 40 ++++++++++++++++++++++++++------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 45c3863d6f..46af4af67d 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -18,7 +18,7 @@ var _ Module = (*API)(nil) //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { // Subscribe allows to subscribe on a Proof pub sub topic by its type. - Subscribe(context.Context, fraud.ProofType) (<-chan Proof, error) + Subscribe(context.Context, fraud.ProofType) (<-chan *Proof, error) // Get fetches fraud proofs from the disk by its type. Get(context.Context, fraud.ProofType) ([]Proof, error) } @@ -27,12 +27,12 @@ type Module interface { // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { Internal struct { - Subscribe func(context.Context, fraud.ProofType) (<-chan Proof, error) `perm:"public"` - Get func(context.Context, fraud.ProofType) ([]Proof, error) `perm:"public"` + Subscribe func(context.Context, fraud.ProofType) (<-chan *Proof, error) `perm:"public"` + Get func(context.Context, fraud.ProofType) ([]Proof, error) `perm:"public"` } } -func (api *API) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) { +func (api *API) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan *Proof, error) { return api.Internal.Subscribe(ctx, proofType) } @@ -49,12 +49,12 @@ type module struct { fraud.Service[*header.ExtendedHeader] } -func (s *module) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan Proof, error) { +func (s *module) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-chan *Proof, error) { subscription, err := s.Service.Subscribe(proofType) if err != nil { return nil, err } - proofs := make(chan Proof) + proofs := make(chan *Proof) go func() { defer close(proofs) defer subscription.Cancel() @@ -69,7 +69,7 @@ func (s *module) Subscribe(ctx context.Context, proofType fraud.ProofType) (<-ch select { case <-ctx.Done(): return - case proofs <- Proof{Proof: proof}: + case proofs <- &Proof{Proof: proof}: } } }() diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 399f8746e1..10111b81a8 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -53,10 +53,10 @@ func (mr *MockModuleMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { } // Subscribe mocks base method. -func (m *MockModule) Subscribe(arg0 context.Context, arg1 fraud0.ProofType) (<-chan fraud.Proof, error) { +func (m *MockModule) Subscribe(arg0 context.Context, arg1 fraud0.ProofType) (<-chan *fraud.Proof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Subscribe", arg0, arg1) - ret0, _ := ret[0].(<-chan fraud.Proof) + ret0, _ := ret[0].(<-chan *fraud.Proof) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index d708f6d454..61eca88e21 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/celestiaorg/go-fraud" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p/core/host" @@ -13,6 +14,7 @@ import ( "github.com/tendermint/tendermint/types" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/header" headerfraud "github.com/celestiaorg/celestia-node/header/headertest/fraud" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/core" @@ -98,21 +100,45 @@ func TestFraudProofHandling(t *testing.T) { select { case p := <-subscr: require.Equal(t, 10, int(p.Height())) + t.Log("Caught the proof....") subCancel() case <-ctx.Done(): subCancel() t.Fatal("full node did not receive a fraud proof in time") } + getCtx, getCancel := context.WithTimeout(ctx, time.Second) + proofs, err := fullClient.Fraud.Get(getCtx, byzantine.BadEncoding) + getCancel() + + require.NoError(t, err) + require.Len(t, proofs, 1) + require.True(t, proofs[0].Type() == byzantine.BadEncoding) // This is an obscure way to check if the Syncer was stopped. // If we cannot get a height header within a timeframe it means the syncer was stopped // FIXME: Eventually, this should be a check on service registry managing and keeping // lifecycles of each Module. // 6. - syncCtx, syncCancel := context.WithTimeout(context.Background(), blockTime*5) - _, err = fullClient.Header.WaitForHeight(syncCtx, 15) + // random height after befp.height + height := uint64(15) + // initial timeout is set to 5 sec, as we are targeting the height=15, + // blockTime=1 sec, expected befp.height=10 + timeOut := blockTime * 5 + // during befp validation the node can still receive headers and it mostly depends on + // the operating system or hardware(e.g. on macOS tests is working 100% time with a single height=15, + // and on the Linux VM sometimes the last height is 17-18). So, lets give a chance for our befp validator to check + // the fraud proof and stop the syncer. + for height < 20 { + syncCtx, syncCancel := context.WithTimeout(context.Background(), timeOut) + _, err = full.HeaderServ.WaitForHeight(syncCtx, height) + syncCancel() + if err != nil { + break + } + timeOut = blockTime + height++ + } require.ErrorIs(t, err, context.DeadlineExceeded) - syncCancel() // 7. cfg = nodebuilder.DefaultConfig(node.Light) @@ -137,11 +163,9 @@ func TestFraudProofHandling(t *testing.T) { // 9. fN := sw.NewNodeWithStore(node.Full, store) - require.Error(t, fN.Start(ctx)) - fNClient := getAdminClient(ctx, fN, t) - proofs, err := fNClient.Fraud.Get(ctx, byzantine.BadEncoding) - require.NoError(t, err) - require.NotNil(t, proofs) + err = fN.Start(ctx) + var fpExist *fraud.ErrFraudExists[*header.ExtendedHeader] + require.ErrorAs(t, err, &fpExist) sw.StopNode(ctx, bridge) sw.StopNode(ctx, full) From 97474320e53f83c0d8e9fdd78cfa0f8e88435be3 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:56:41 +0200 Subject: [PATCH 0869/1008] chore(share/availability)!: Remove `ProbabilityOfAvailability` endpoint (#2797) Resolves #2769 --------- Co-authored-by: ramin --- api/gateway/availability.go | 14 +++----------- nodebuilder/share/cmd/share.go | 17 ----------------- nodebuilder/share/mocks/api.go | 17 +---------------- nodebuilder/share/share.go | 12 ++---------- nodebuilder/tests/fraud_test.go | 3 ++- share/availability.go | 4 ---- share/availability/full/availability.go | 4 ---- share/availability/light/availability.go | 10 ---------- 8 files changed, 8 insertions(+), 73 deletions(-) diff --git a/api/gateway/availability.go b/api/gateway/availability.go index e5e53e0dba..203fd86d61 100644 --- a/api/gateway/availability.go +++ b/api/gateway/availability.go @@ -15,8 +15,7 @@ const heightAvailabilityEndpoint = "/data_available" // AvailabilityResponse represents the response to a // `/data_available` request. type AvailabilityResponse struct { - Available bool `json:"available"` - Probability string `json:"probability_of_availability"` + Available bool `json:"available"` } func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http.Request) { @@ -33,16 +32,10 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http return } - availResp := &AvailabilityResponse{ - Probability: strconv.FormatFloat( - h.share.ProbabilityOfAvailability(r.Context()), 'g', -1, 64), - } - err = h.share.SharesAvailable(r.Context(), header.DAH) switch err { case nil: - availResp.Available = true - resp, err := json.Marshal(availResp) + resp, err := json.Marshal(&AvailabilityResponse{Available: true}) if err != nil { writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) return @@ -52,8 +45,7 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http log.Errorw("serving request", "endpoint", heightAvailabilityEndpoint, "err", err) } case share.ErrNotAvailable: - availResp.Available = false - resp, err := json.Marshal(availResp) + resp, err := json.Marshal(&AvailabilityResponse{Available: false}) if err != nil { writeError(w, http.StatusInternalServerError, heightAvailabilityEndpoint, err) return diff --git a/nodebuilder/share/cmd/share.go b/nodebuilder/share/cmd/share.go index fbf3e51db4..bc22651238 100644 --- a/nodebuilder/share/cmd/share.go +++ b/nodebuilder/share/cmd/share.go @@ -16,7 +16,6 @@ import ( func init() { Cmd.AddCommand( sharesAvailableCmd, - probabilityOfAvailabilityCmd, getSharesByNamespaceCmd, getShare, getEDS, @@ -73,22 +72,6 @@ var sharesAvailableCmd = &cobra.Command{ }, } -var probabilityOfAvailabilityCmd = &cobra.Command{ - Use: "availability", - Short: "Calculates the probability of the data square being available based on the number of samples collected.", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - client, err := cmdnode.ParseClientFromCtx(cmd.Context()) - if err != nil { - return err - } - defer client.Close() - - prob := client.Share.ProbabilityOfAvailability(cmd.Context()) - return cmdnode.PrintOutput(prob, nil, nil) - }, -} - var getSharesByNamespaceCmd = &cobra.Command{ Use: "get-by-namespace [dah, namespace]", Short: "Gets all shares from an EDS within the given namespace.", diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 66baa23301..78a124a20d 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -83,20 +82,6 @@ func (mr *MockModuleMockRecorder) GetSharesByNamespace(arg0, arg1, arg2 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSharesByNamespace", reflect.TypeOf((*MockModule)(nil).GetSharesByNamespace), arg0, arg1, arg2) } -// ProbabilityOfAvailability mocks base method. -func (m *MockModule) ProbabilityOfAvailability(arg0 context.Context) float64 { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ProbabilityOfAvailability", arg0) - ret0, _ := ret[0].(float64) - return ret0 -} - -// ProbabilityOfAvailability indicates an expected call of ProbabilityOfAvailability. -func (mr *MockModuleMockRecorder) ProbabilityOfAvailability(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProbabilityOfAvailability", reflect.TypeOf((*MockModule)(nil).ProbabilityOfAvailability), arg0) -} - // SharesAvailable mocks base method. func (m *MockModule) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader) error { m.ctrl.T.Helper() diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index def3bb4d0f..37d68c1581 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -30,9 +30,6 @@ type Module interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on // the Network. SharesAvailable(context.Context, *share.Root) error - // ProbabilityOfAvailability calculates the probability of the data square - // being available based on the number of samples collected. - ProbabilityOfAvailability(context.Context) float64 // GetShare gets a Share by coordinates in EDS. GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) // GetEDS gets the full EDS identified by the given root. @@ -46,9 +43,8 @@ type Module interface { // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { Internal struct { - SharesAvailable func(context.Context, *share.Root) error `perm:"public"` - ProbabilityOfAvailability func(context.Context) float64 `perm:"public"` - GetShare func( + SharesAvailable func(context.Context, *share.Root) error `perm:"public"` + GetShare func( ctx context.Context, dah *share.Root, row, col int, @@ -69,10 +65,6 @@ func (api *API) SharesAvailable(ctx context.Context, root *share.Root) error { return api.Internal.SharesAvailable(ctx, root) } -func (api *API) ProbabilityOfAvailability(ctx context.Context) float64 { - return api.Internal.ProbabilityOfAvailability(ctx) -} - func (api *API) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { return api.Internal.GetShare(ctx, dah, row, col) } diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 61eca88e21..1927cdaf42 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/celestiaorg/go-fraud" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p/core/host" @@ -14,6 +13,8 @@ import ( "github.com/tendermint/tendermint/types" "go.uber.org/fx" + "github.com/celestiaorg/go-fraud" + "github.com/celestiaorg/celestia-node/header" headerfraud "github.com/celestiaorg/celestia-node/header/headertest/fraud" "github.com/celestiaorg/celestia-node/nodebuilder" diff --git a/share/availability.go b/share/availability.go index cd4b3a777f..587469d94b 100644 --- a/share/availability.go +++ b/share/availability.go @@ -30,8 +30,4 @@ type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on // the Network. SharesAvailable(context.Context, *Root) error - // ProbabilityOfAvailability calculates the probability of the data square - // being available based on the number of samples collected. - // TODO(@Wondertan): Merge with SharesAvailable method, eventually - ProbabilityOfAvailability(context.Context) float64 } diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index be269077d7..5fecb757fd 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -98,7 +98,3 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro } return nil } - -func (fa *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { - return 1 -} diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index 0fc073a2df..e7ad900d8d 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -3,7 +3,6 @@ package light import ( "context" "errors" - "math" "sync" "github.com/ipfs/go-datastore" @@ -140,15 +139,6 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo return nil } -// ProbabilityOfAvailability calculates the probability that the -// data square is available based on the amount of samples collected -// (params.SampleAmount). -// -// Formula: 1 - (0.75 ** amount of samples) -func (la *ShareAvailability) ProbabilityOfAvailability(context.Context) float64 { - return 1 - math.Pow(0.75, float64(la.params.SampleAmount)) -} - func rootKey(root *share.Root) datastore.Key { return datastore.NewKey(root.String()) } From 6f083b62873f214583b1f3d13ef5dc8ac3fa19e7 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:20:54 +0200 Subject: [PATCH 0870/1008] chore(api | nodebuilder)!: Elevate all api permissions to `read` and disallow `public`, remove `client.NewPublicClient` (#2771) We made a decision synchronously with @adlerjohn and @ramin to require at least `read`-level permissions to be able to access *any* endpoint on the node's API. Therefore, `NewPublicClient` is no longer necessary. --- api/rpc/client/client.go | 5 -- api/rpc_test.go | 116 ++++----------------------------- nodebuilder/fraud/fraud.go | 4 +- nodebuilder/fraud/mocks/api.go | 3 +- nodebuilder/header/header.go | 10 +-- nodebuilder/share/share.go | 8 +-- nodebuilder/state/state.go | 10 +-- 7 files changed, 31 insertions(+), 125 deletions(-) diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 7ac8a55b3d..9cd5fe08e3 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -59,11 +59,6 @@ func (c *Client) Close() { c.closer.closeAll() } -// NewPublicClient creates a new Client with one connection per namespace. -func NewPublicClient(ctx context.Context, addr string) (*Client, error) { - return newClient(ctx, addr, nil) -} - // NewClient creates a new Client with one connection per namespace with the // given token as the authorization token. func NewClient(ctx context.Context, addr string, token string) (*Client, error) { diff --git a/api/rpc_test.go b/api/rpc_test.go index 898a307389..9ff35bf1e4 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -19,7 +19,6 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/api/rpc/perms" daspkg "github.com/celestiaorg/celestia-node/das" - headerpkg "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/blob" blobMock "github.com/celestiaorg/celestia-node/nodebuilder/blob/mocks" @@ -43,16 +42,24 @@ import ( func TestRPCCallsUnderlyingNode(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - nd, server := setupNodeWithModifiedRPC(t) + + // generate dummy signer and sign admin perms token with it + signer, err := jwt.NewHS256(make([]byte, 32)) + require.NoError(t, err) + + nd, server := setupNodeWithAuthedRPC(t, signer) url := nd.RPCServer.ListenAddr() + + adminToken, err := perms.NewTokenWithPerms(signer, perms.AllPerms) + require.NoError(t, err) + // we need to run this a few times to prevent the race where the server is not yet started var ( rpcClient *client.Client - err error ) for i := 0; i < 3; i++ { time.Sleep(time.Second * 1) - rpcClient, err = client.NewPublicClient(ctx, "http://"+url) + rpcClient, err = client.NewClient(ctx, "http://"+url, string(adminToken)) if err == nil { t.Cleanup(rpcClient.Close) break @@ -154,13 +161,7 @@ func TestAuthedRPC(t *testing.T) { require.NotNil(t, rpcClient) require.NoError(t, err) - // 1. Test method with public permissions - server.Header.EXPECT().NetworkHead(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) - got, err := rpcClient.Header.NetworkHead(ctx) - require.NoError(t, err) - require.NotNil(t, got) - - // 2. Test method with read-level permissions + // 1. Test method with read-level permissions expected := daspkg.SamplingStats{ SampledChainHead: 100, CatchupHead: 100, @@ -182,7 +183,7 @@ func TestAuthedRPC(t *testing.T) { require.ErrorContains(t, err, "missing permission") } - // 3. Test method with write-level permissions + // 2. Test method with write-level permissions expectedResp := &state.TxResponse{} if tt.perm > 2 { server.State.EXPECT().SubmitTx(gomock.Any(), gomock.Any()).Return(expectedResp, nil) @@ -195,7 +196,7 @@ func TestAuthedRPC(t *testing.T) { require.ErrorContains(t, err, "missing permission") } - // 4. Test method with admin-level permissions + // 3. Test method with admin-level permissions expectedReachability := network.Reachability(3) if tt.perm > 3 { server.P2P.EXPECT().NATStatus(gomock.Any()).Return(expectedReachability, nil) @@ -213,55 +214,6 @@ func TestAuthedRPC(t *testing.T) { } } -// TestPublicClient tests that the public rpc client can only -// access public methods. -func TestPublicClient(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - // generate dummy signer and sign admin perms token with it - signer, err := jwt.NewHS256(make([]byte, 32)) - require.NoError(t, err) - - nd, server := setupNodeWithAuthedRPC(t, signer) - url := nd.RPCServer.ListenAddr() - - // we need to run this a few times to prevent the race where the server is not yet started - var rpcClient *client.Client - require.NoError(t, err) - for i := 0; i < 3; i++ { - time.Sleep(time.Second * 1) - rpcClient, err = client.NewPublicClient(ctx, "http://"+url) - if err == nil { - t.Cleanup(rpcClient.Close) - break - } - } - require.NotNil(t, rpcClient) - require.NoError(t, err) - - // 1. Test method with public permissions - server.Header.EXPECT().NetworkHead(gomock.Any()).Return(new(headerpkg.ExtendedHeader), nil) - got, err := rpcClient.Header.NetworkHead(ctx) - require.NoError(t, err) - require.NotNil(t, got) - - // 2. Test method with read-level permissions - _, err = rpcClient.DAS.SamplingStats(ctx) - require.Error(t, err) - require.ErrorContains(t, err, "missing permission") - - // 3. Test method with write-level permissions - _, err = rpcClient.State.SubmitTx(ctx, []byte{}) - require.Error(t, err) - require.ErrorContains(t, err, "missing permission") - - // 4. Test method with admin-level permissions - _, err = rpcClient.P2P.NATStatus(ctx) - require.Error(t, err) - require.ErrorContains(t, err, "missing permission") -} - func TestAllReturnValuesAreMarshalable(t *testing.T) { ra := reflect.TypeOf(new(api)).Elem() for i := 0; i < ra.NumMethod(); i++ { @@ -325,46 +277,6 @@ func implementsMarshaler(t *testing.T, typ reflect.Type) { } -func setupNodeWithModifiedRPC(t *testing.T) (*nodebuilder.Node, *mockAPI) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - ctrl := gomock.NewController(t) - - mockAPI := &mockAPI{ - stateMock.NewMockModule(ctrl), - shareMock.NewMockModule(ctrl), - fraudMock.NewMockModule(ctrl), - headerMock.NewMockModule(ctrl), - dasMock.NewMockModule(ctrl), - p2pMock.NewMockModule(ctrl), - nodeMock.NewMockModule(ctrl), - blobMock.NewMockModule(ctrl), - } - - // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root - // level module. For further information, check the documentation on fx.Invoke. - invokeRPC := fx.Invoke(func(srv *rpc.Server) { - srv.RegisterService("state", mockAPI.State) - srv.RegisterService("share", mockAPI.Share) - srv.RegisterService("fraud", mockAPI.Fraud) - srv.RegisterService("header", mockAPI.Header) - srv.RegisterService("das", mockAPI.Das) - srv.RegisterService("p2p", mockAPI.P2P) - srv.RegisterService("node", mockAPI.Node) - srv.RegisterService("blob", mockAPI.Blob) - }) - nd := nodebuilder.TestNode(t, node.Full, invokeRPC) - // start node - err := nd.Start(ctx) - require.NoError(t, err) - t.Cleanup(func() { - err = nd.Stop(ctx) - require.NoError(t, err) - }) - return nd, mockAPI -} - // setupNodeWithAuthedRPC sets up a node and overrides its JWT // signer with the given signer. func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, *mockAPI) { diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 46af4af67d..178b0527a1 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -27,8 +27,8 @@ type Module interface { // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { Internal struct { - Subscribe func(context.Context, fraud.ProofType) (<-chan *Proof, error) `perm:"public"` - Get func(context.Context, fraud.ProofType) ([]Proof, error) `perm:"public"` + Subscribe func(context.Context, fraud.ProofType) (<-chan *Proof, error) `perm:"read"` + Get func(context.Context, fraud.ProofType) ([]Proof, error) `perm:"read"` } } diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 10111b81a8..fcc7a58231 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - fraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraud0 "github.com/celestiaorg/go-fraud" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 9b9455da26..43b5646a77 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -53,18 +53,18 @@ type API struct { GetByHash func( ctx context.Context, hash libhead.Hash, - ) (*header.ExtendedHeader, error) `perm:"public"` + ) (*header.ExtendedHeader, error) `perm:"read"` GetVerifiedRangeByHeight func( context.Context, *header.ExtendedHeader, uint64, - ) ([]*header.ExtendedHeader, error) `perm:"public"` - GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"public"` + ) ([]*header.ExtendedHeader, error) `perm:"read"` + GetByHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` WaitForHeight func(context.Context, uint64) (*header.ExtendedHeader, error) `perm:"read"` SyncState func(ctx context.Context) (sync.State, error) `perm:"read"` SyncWait func(ctx context.Context) error `perm:"read"` - NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"public"` - Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"public"` + NetworkHead func(ctx context.Context) (*header.ExtendedHeader, error) `perm:"read"` + Subscribe func(ctx context.Context) (<-chan *header.ExtendedHeader, error) `perm:"read"` } } diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index 37d68c1581..da97f32a33 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -43,21 +43,21 @@ type Module interface { // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { Internal struct { - SharesAvailable func(context.Context, *share.Root) error `perm:"public"` + SharesAvailable func(context.Context, *share.Root) error `perm:"read"` GetShare func( ctx context.Context, dah *share.Root, row, col int, - ) (share.Share, error) `perm:"public"` + ) (share.Share, error) `perm:"read"` GetEDS func( ctx context.Context, root *share.Root, - ) (*rsmt2d.ExtendedDataSquare, error) `perm:"public"` + ) (*rsmt2d.ExtendedDataSquare, error) `perm:"read"` GetSharesByNamespace func( ctx context.Context, root *share.Root, namespace share.Namespace, - ) (share.NamespacedShares, error) `perm:"public"` + ) (share.NamespacedShares, error) `perm:"read"` } } diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index c66205d594..83408680da 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -100,9 +100,9 @@ type Module interface { type API struct { Internal struct { AccountAddress func(ctx context.Context) (state.Address, error) `perm:"read"` - IsStopped func(ctx context.Context) bool `perm:"public"` + IsStopped func(ctx context.Context) bool `perm:"read"` Balance func(ctx context.Context) (*state.Balance, error) `perm:"read"` - BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) `perm:"public"` + BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) `perm:"read"` Transfer func( ctx context.Context, to state.AccAddress, @@ -150,16 +150,16 @@ type API struct { QueryDelegation func( ctx context.Context, valAddr state.ValAddress, - ) (*types.QueryDelegationResponse, error) `perm:"public"` + ) (*types.QueryDelegationResponse, error) `perm:"read"` QueryUnbonding func( ctx context.Context, valAddr state.ValAddress, - ) (*types.QueryUnbondingDelegationResponse, error) `perm:"public"` + ) (*types.QueryUnbondingDelegationResponse, error) `perm:"read"` QueryRedelegations func( ctx context.Context, srcValAddr, dstValAddr state.ValAddress, - ) (*types.QueryRedelegationsResponse, error) `perm:"public"` + ) (*types.QueryRedelegationsResponse, error) `perm:"read"` } } From d69d3b23683e394c2394b78147a5113610dba298 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 6 Oct 2023 14:32:30 +0200 Subject: [PATCH 0871/1008] refactor(share)!: replacing share.Root with header.ExtendedHeader in APIS (#2773) --- api/gateway/availability.go | 2 +- api/gateway/share.go | 2 +- blob/service.go | 12 +- das/daser.go | 2 +- das/daser_test.go | 10 +- header/header.go | 17 ++- header/headertest/testing.go | 14 +- nodebuilder/node_test.go | 5 +- nodebuilder/share/cmd/share.go | 35 +++-- nodebuilder/share/module.go | 3 +- nodebuilder/share/share.go | 45 +++--- nodebuilder/tests/blob_test.go | 2 +- nodebuilder/tests/nd_test.go | 13 +- nodebuilder/tests/reconstruct_test.go | 12 +- nodebuilder/tests/sync_test.go | 8 +- share/availability.go | 6 +- share/availability/full/availability.go | 18 +-- share/availability/full/availability_test.go | 15 +- .../availability/full/reconstruction_test.go | 33 +++-- share/availability/light/availability.go | 8 +- share/availability/light/availability_test.go | 50 ++++--- share/availability/light/testing.go | 10 +- share/availability/mocks/availability.go | 19 +-- share/eds/utils.go | 104 ++++++++++++++ share/getter.go | 10 +- share/getters/cascade.go | 19 ++- share/getters/cascade_test.go | 19 +-- share/getters/getter_test.go | 134 +++++++++++------- share/getters/ipld.go | 15 +- share/getters/shrex.go | 30 ++-- share/getters/shrex_test.go | 28 ++-- share/getters/store.go | 53 ++----- share/getters/testing.go | 24 +++- share/getters/utils.go | 66 --------- share/ipld/corrupted_data_test.go | 7 +- share/ipld/utils.go | 18 +++ share/mocks/getter.go | 11 +- share/p2p/shrexnd/exchange_test.go | 34 +---- share/p2p/shrexnd/server.go | 6 +- 39 files changed, 510 insertions(+), 409 deletions(-) create mode 100644 share/ipld/utils.go diff --git a/api/gateway/availability.go b/api/gateway/availability.go index 203fd86d61..70341b34f2 100644 --- a/api/gateway/availability.go +++ b/api/gateway/availability.go @@ -32,7 +32,7 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http return } - err = h.share.SharesAvailable(r.Context(), header.DAH) + err = h.share.SharesAvailable(r.Context(), header) switch err { case nil: resp, err := json.Marshal(&AvailabilityResponse{Available: true}) diff --git a/api/gateway/share.go b/api/gateway/share.go index c9dec071f3..36c1f94a0a 100644 --- a/api/gateway/share.go +++ b/api/gateway/share.go @@ -96,7 +96,7 @@ func (h *Handler) getShares(ctx context.Context, height uint64, namespace share. return nil, err } - shares, err := h.share.GetSharesByNamespace(ctx, header.DAH, namespace) + shares, err := h.share.GetSharesByNamespace(ctx, header, namespace) if err != nil { return nil, err } diff --git a/blob/service.go b/blob/service.go index 2130904aa7..cf3daff7de 100644 --- a/blob/service.go +++ b/blob/service.go @@ -126,7 +126,7 @@ func (s *Service) GetAll(ctx context.Context, height uint64, namespaces []share. wg.Add(1) go func(i int, namespace share.Namespace) { defer wg.Done() - blobs, err := s.getBlobs(ctx, namespace, header.DAH) + blobs, err := s.getBlobs(ctx, namespace, header) if err != nil { resultErr[i] = fmt.Errorf("getting blobs for namespace(%s): %s", namespace.String(), err) return @@ -204,7 +204,7 @@ func (s *Service) getByCommitment( blobShare *shares.Share ) - namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header.DAH, namespace) + namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header, namespace) if err != nil { if errors.Is(err, share.ErrNotFound) { err = ErrBlobNotFound @@ -293,8 +293,12 @@ func (s *Service) getByCommitment( // getBlobs retrieves the DAH and fetches all shares from the requested Namespace and converts // them to Blobs. -func (s *Service) getBlobs(ctx context.Context, namespace share.Namespace, root *share.Root) ([]*Blob, error) { - namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, root, namespace) +func (s *Service) getBlobs( + ctx context.Context, + namespace share.Namespace, + header *header.ExtendedHeader, +) ([]*Blob, error) { + namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header, namespace) if err != nil { return nil, err } diff --git a/das/daser.go b/das/daser.go index 9d3e43a91b..29139d3a5f 100644 --- a/das/daser.go +++ b/das/daser.go @@ -147,7 +147,7 @@ func (d *DASer) Stop(ctx context.Context) error { } func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { - err := d.da.SharesAvailable(ctx, h.DAH) + err := d.da.SharesAvailable(ctx, h) if err != nil { var byzantineErr *byzantine.ErrByzantine if errors.As(err, &byzantineErr) { diff --git a/das/daser_test.go b/das/daser_test.go index 6a3378cdb5..ca553b0f09 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -214,7 +214,7 @@ func TestDASerSampleTimeout(t *testing.T) { avail := mocks.NewMockAvailability(gomock.NewController(t)) doneCh := make(chan struct{}) avail.EXPECT().SharesAvailable(gomock.Any(), gomock.Any()).DoAndReturn( - func(sampleCtx context.Context, h *share.Root) error { + func(sampleCtx context.Context, h *header.ExtendedHeader) error { select { case <-sampleCtx.Done(): close(doneCh) @@ -291,9 +291,7 @@ func (m *mockGetter) fillSubWithHeaders( for i := startHeight; i < endHeight; i++ { dah := availability_test.RandFillBS(t, 16, bServ) - randHeader := headertest.RandExtendedHeader(t) - randHeader.DataHash = dah.Hash() - randHeader.DAH = dah + randHeader := headertest.RandExtendedHeaderWithRoot(t, dah) randHeader.RawHeader.Height = int64(i + 1) sub.Headers[index] = randHeader @@ -319,9 +317,7 @@ func (m *mockGetter) generateHeaders(t *testing.T, bServ blockservice.BlockServi for i := startHeight; i < endHeight; i++ { dah := availability_test.RandFillBS(t, 16, bServ) - randHeader := headertest.RandExtendedHeader(t) - randHeader.DataHash = dah.Hash() - randHeader.DAH = dah + randHeader := headertest.RandExtendedHeaderWithRoot(t, dah) randHeader.RawHeader.Height = int64(i + 1) m.headers[int64(i+1)] = randHeader diff --git a/header/header.go b/header/header.go index 9778684b7d..01a84a2c0a 100644 --- a/header/header.go +++ b/header/header.go @@ -12,10 +12,9 @@ import ( core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/rsmt2d" - - "github.com/celestiaorg/celestia-node/share" ) // ConstructFn aliases a function that creates an ExtendedHeader. @@ -36,9 +35,9 @@ type RawHeader = core.Header // block headers and perform Data Availability Sampling. type ExtendedHeader struct { RawHeader `json:"header"` - Commit *core.Commit `json:"commit"` - ValidatorSet *core.ValidatorSet `json:"validator_set"` - DAH *share.Root `json:"dah"` + Commit *core.Commit `json:"commit"` + ValidatorSet *core.ValidatorSet `json:"validator_set"` + DAH *da.DataAvailabilityHeader `json:"dah"` } // MakeExtendedHeader assembles new ExtendedHeader. @@ -49,14 +48,14 @@ func MakeExtendedHeader( eds *rsmt2d.ExtendedDataSquare, ) (*ExtendedHeader, error) { var ( - dah *share.Root + dah da.DataAvailabilityHeader err error ) switch eds { case nil: - dah = share.EmptyRoot() + dah = da.MinDataAvailabilityHeader() default: - dah, err = share.NewRoot(eds) + dah, err = da.NewDataAvailabilityHeader(eds) if err != nil { return nil, err } @@ -64,7 +63,7 @@ func MakeExtendedHeader( eh := &ExtendedHeader{ RawHeader: *h, - DAH: dah, + DAH: &dah, Commit: comm, ValidatorSet: vals, } diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 0b3c6a34dc..9907fd7eb4 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -17,6 +17,7 @@ import ( "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "github.com/celestiaorg/celestia-app/pkg/da" libhead "github.com/celestiaorg/go-header" "github.com/celestiaorg/go-header/headertest" "github.com/celestiaorg/rsmt2d" @@ -202,7 +203,7 @@ func (s *TestSuite) nextProposer() *types.Validator { } // RandExtendedHeader provides an ExtendedHeader fixture. -func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { +func RandExtendedHeader(t testing.TB) *header.ExtendedHeader { dah := share.EmptyRoot() rh := RandRawHeader(t) @@ -224,6 +225,13 @@ func RandExtendedHeader(t *testing.T) *header.ExtendedHeader { } } +func RandExtendedHeaderWithRoot(t testing.TB, dah *da.DataAvailabilityHeader) *header.ExtendedHeader { + h := RandExtendedHeader(t) + h.DataHash = dah.Hash() + h.DAH = dah + return h +} + func RandValidatorSet(numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) { var ( valz = make([]*types.Validator, numValidators) @@ -256,7 +264,7 @@ func RandValidator(randPower bool, minPower int64) (*types.Validator, types.Priv } // RandRawHeader provides a RawHeader fixture. -func RandRawHeader(t *testing.T) *header.RawHeader { +func RandRawHeader(t testing.TB) *header.RawHeader { return &header.RawHeader{ Version: version.Consensus{Block: 11, App: 1}, ChainID: "test", @@ -276,7 +284,7 @@ func RandRawHeader(t *testing.T) *header.RawHeader { } // RandBlockID provides a BlockID fixture. -func RandBlockID(*testing.T) types.BlockID { +func RandBlockID(testing.TB) types.BlockID { bid := types.BlockID{ Hash: make([]byte, 32), PartSetHeader: types.PartSetHeader{ diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 3fc3f4f02a..1d0f3c4fad 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -13,6 +13,7 @@ import ( collectormetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" "google.golang.org/protobuf/proto" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/share" ) @@ -143,7 +144,9 @@ func TestEmptyBlockExists(t *testing.T) { require.NoError(t, err) // ensure an empty block exists in store - err = node.ShareServ.SharesAvailable(ctx, share.EmptyRoot()) + + eh := headertest.RandExtendedHeaderWithRoot(t, share.EmptyRoot()) + err = node.ShareServ.SharesAvailable(ctx, eh) require.NoError(t, err) err = node.Stop(ctx) diff --git a/nodebuilder/share/cmd/share.go b/nodebuilder/share/cmd/share.go index bc22651238..3d1af05289 100644 --- a/nodebuilder/share/cmd/share.go +++ b/nodebuilder/share/cmd/share.go @@ -7,9 +7,8 @@ import ( "github.com/spf13/cobra" - "github.com/celestiaorg/celestia-app/pkg/da" - cmdnode "github.com/celestiaorg/celestia-node/cmd" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" ) @@ -45,13 +44,13 @@ var sharesAvailableCmd = &cobra.Command{ return err } - root := da.MinDataAvailabilityHeader() - err = json.Unmarshal(raw, &root) + var eh *header.ExtendedHeader + err = json.Unmarshal(raw, &eh) if err != nil { return err } - err = client.Share.SharesAvailable(cmd.Context(), &root) + err = client.Share.SharesAvailable(cmd.Context(), eh) formatter := func(data interface{}) interface{} { err, ok := data.(error) available := false @@ -73,7 +72,7 @@ var sharesAvailableCmd = &cobra.Command{ } var getSharesByNamespaceCmd = &cobra.Command{ - Use: "get-by-namespace [dah, namespace]", + Use: "get-by-namespace [extended header, namespace]", Short: "Gets all shares from an EDS within the given namespace.", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -88,8 +87,8 @@ var getSharesByNamespaceCmd = &cobra.Command{ return err } - root := da.MinDataAvailabilityHeader() - err = json.Unmarshal(raw, &root) + var eh *header.ExtendedHeader + err = json.Unmarshal(raw, &eh) if err != nil { return err } @@ -99,13 +98,13 @@ var getSharesByNamespaceCmd = &cobra.Command{ return err } - shares, err := client.Share.GetSharesByNamespace(cmd.Context(), &root, ns) + shares, err := client.Share.GetSharesByNamespace(cmd.Context(), eh, ns) return cmdnode.PrintOutput(shares, err, nil) }, } var getShare = &cobra.Command{ - Use: "get-share [dah, row, col]", + Use: "get-share [extended header, row, col]", Short: "Gets a Share by coordinates in EDS.", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { @@ -120,8 +119,8 @@ var getShare = &cobra.Command{ return err } - root := da.MinDataAvailabilityHeader() - err = json.Unmarshal(raw, &root) + var eh *header.ExtendedHeader + err = json.Unmarshal(raw, &eh) if err != nil { return err } @@ -136,7 +135,7 @@ var getShare = &cobra.Command{ return err } - s, err := client.Share.GetShare(cmd.Context(), &root, int(row), int(col)) + s, err := client.Share.GetShare(cmd.Context(), eh, int(row), int(col)) formatter := func(data interface{}) interface{} { sh, ok := data.(share.Share) @@ -159,8 +158,8 @@ var getShare = &cobra.Command{ } var getEDS = &cobra.Command{ - Use: "get-eds [dah]", - Short: "Gets the full EDS identified by the given root", + Use: "get-eds [extended header]", + Short: "Gets the full EDS identified by the given extended header", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) @@ -174,13 +173,13 @@ var getEDS = &cobra.Command{ return err } - root := da.MinDataAvailabilityHeader() - err = json.Unmarshal(raw, &root) + var eh *header.ExtendedHeader + err = json.Unmarshal(raw, &eh) if err != nil { return err } - shares, err := client.Share.GetEDS(cmd.Context(), &root) + shares, err := client.Share.GetEDS(cmd.Context(), eh) return cmdnode.PrintOutput(shares, err, nil) }, } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 9fbd262a32..3fa55b2d35 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -98,11 +98,10 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option func( host host.Host, store *eds.Store, - getter *getters.StoreGetter, network modp2p.Network, ) (*shrexnd.Server, error) { cfg.ShrExNDParams.WithNetworkID(network.String()) - return shrexnd.NewServer(cfg.ShrExNDParams, host, store, getter) + return shrexnd.NewServer(cfg.ShrExNDParams, host, store) }, fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { return server.Start(ctx) diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index da97f32a33..a8e1e1c895 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -5,6 +5,7 @@ import ( "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" ) @@ -27,58 +28,60 @@ var _ Module = (*API)(nil) // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module type Module interface { - // SharesAvailable subjectively validates if Shares committed to the given Root are available on - // the Network. - SharesAvailable(context.Context, *share.Root) error + // SharesAvailable subjectively validates if Shares committed to the given + // ExtendedHeader are available on the Network. + SharesAvailable(context.Context, *header.ExtendedHeader) error // GetShare gets a Share by coordinates in EDS. - GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) - // GetEDS gets the full EDS identified by the given root. - GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) + GetShare(ctx context.Context, header *header.ExtendedHeader, row, col int) (share.Share, error) + // GetEDS gets the full EDS identified by the given extended header. + GetEDS(ctx context.Context, header *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) // GetSharesByNamespace gets all shares from an EDS within the given namespace. // Shares are returned in a row-by-row order if the namespace spans multiple rows. - GetSharesByNamespace(ctx context.Context, root *share.Root, namespace share.Namespace) (share.NamespacedShares, error) + GetSharesByNamespace( + ctx context.Context, header *header.ExtendedHeader, namespace share.Namespace, + ) (share.NamespacedShares, error) } // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. type API struct { Internal struct { - SharesAvailable func(context.Context, *share.Root) error `perm:"read"` + SharesAvailable func(context.Context, *header.ExtendedHeader) error `perm:"read"` GetShare func( ctx context.Context, - dah *share.Root, + header *header.ExtendedHeader, row, col int, ) (share.Share, error) `perm:"read"` GetEDS func( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, ) (*rsmt2d.ExtendedDataSquare, error) `perm:"read"` GetSharesByNamespace func( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, namespace share.Namespace, ) (share.NamespacedShares, error) `perm:"read"` } } -func (api *API) SharesAvailable(ctx context.Context, root *share.Root) error { - return api.Internal.SharesAvailable(ctx, root) +func (api *API) SharesAvailable(ctx context.Context, header *header.ExtendedHeader) error { + return api.Internal.SharesAvailable(ctx, header) } -func (api *API) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { - return api.Internal.GetShare(ctx, dah, row, col) +func (api *API) GetShare(ctx context.Context, header *header.ExtendedHeader, row, col int) (share.Share, error) { + return api.Internal.GetShare(ctx, header, row, col) } -func (api *API) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - return api.Internal.GetEDS(ctx, root) +func (api *API) GetEDS(ctx context.Context, header *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { + return api.Internal.GetEDS(ctx, header) } func (api *API) GetSharesByNamespace( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, namespace share.Namespace, ) (share.NamespacedShares, error) { - return api.Internal.GetSharesByNamespace(ctx, root, namespace) + return api.Internal.GetSharesByNamespace(ctx, header, namespace) } type module struct { @@ -86,6 +89,6 @@ type module struct { share.Availability } -func (m module) SharesAvailable(ctx context.Context, root *share.Root) error { - return m.Availability.SharesAvailable(ctx, root) +func (m module) SharesAvailable(ctx context.Context, header *header.ExtendedHeader) error { + return m.Availability.SharesAvailable(ctx, header) } diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index 2078fbfa74..ccbc6440cc 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -120,7 +120,7 @@ func TestBlobModule(t *testing.T) { b, err := fullClient.Blob.Get(ctx, height, newBlob.Namespace(), newBlob.Commitment) assert.Nil(t, b) require.Error(t, err) - require.ErrorIs(t, err, blob.ErrBlobNotFound) + require.ErrorContains(t, err, blob.ErrBlobNotFound.Error()) }, }, } diff --git a/nodebuilder/tests/nd_test.go b/nodebuilder/tests/nd_test.go index 8149ea9fe7..f3338ec294 100644 --- a/nodebuilder/tests/nd_test.go +++ b/nodebuilder/tests/nd_test.go @@ -65,9 +65,9 @@ func TestShrexNDFromLights(t *testing.T) { // ensure to fetch random namespace (not the reserved namespace) namespace := h.DAH.RowRoots[1][:share.NamespaceSize] - expected, err := bridgeClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) + expected, err := bridgeClient.Share.GetSharesByNamespace(reqCtx, h, namespace) require.NoError(t, err) - got, err := lightClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) + got, err := lightClient.Share.GetSharesByNamespace(reqCtx, h, namespace) require.NoError(t, err) require.True(t, len(got[0].Shares) > 0) @@ -139,18 +139,18 @@ func TestShrexNDFromLightsWithBadFulls(t *testing.T) { // ensure to fetch random namespace (not the reserved namespace) namespace := h.DAH.RowRoots[1][:share.NamespaceSize] - expected, err := bridgeClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) + expected, err := bridgeClient.Share.GetSharesByNamespace(reqCtx, h, namespace) require.NoError(t, err) require.True(t, len(expected[0].Shares) > 0) // choose a random full to test fN := fulls[len(fulls)/2] fnClient := getAdminClient(ctx, fN, t) - gotFull, err := fnClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) + gotFull, err := fnClient.Share.GetSharesByNamespace(reqCtx, h, namespace) require.NoError(t, err) require.True(t, len(gotFull[0].Shares) > 0) - gotLight, err := lightClient.Share.GetSharesByNamespace(reqCtx, h.DAH, namespace) + gotLight, err := lightClient.Share.GetSharesByNamespace(reqCtx, h, namespace) require.NoError(t, err) require.True(t, len(gotLight[0].Shares) > 0) @@ -176,11 +176,10 @@ func replaceNDServer(cfg *nodebuilder.Config, handler network.StreamHandler) fx. func( host host.Host, store *eds.Store, - getter *getters.StoreGetter, network p2p.Network, ) (*shrexnd.Server, error) { cfg.Share.ShrExNDParams.WithNetworkID(network.String()) - return shrexnd.NewServer(cfg.Share.ShrExNDParams, host, store, getter) + return shrexnd.NewServer(cfg.Share.ShrExNDParams, host, store) }, fx.OnStart(func(ctx context.Context, server *shrexnd.Server) error { // replace handler for server diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 3c2e8b1f83..89f4a0171a 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -77,7 +77,7 @@ func TestFullReconstructFromBridge(t *testing.T) { return err } - return fullClient.Share.SharesAvailable(bctx, h.DAH) + return fullClient.Share.SharesAvailable(bctx, h) }) } require.NoError(t, <-fillDn) @@ -211,10 +211,10 @@ func TestFullReconstructFromFulls(t *testing.T) { ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*30) errg, errCtx = errgroup.WithContext(ctxErr) errg.Go(func() error { - return fullClient1.Share.SharesAvailable(errCtx, h.DAH) + return fullClient1.Share.SharesAvailable(errCtx, h) }) errg.Go(func() error { - return fullClient1.Share.SharesAvailable(errCtx, h.DAH) + return fullClient1.Share.SharesAvailable(errCtx, h) }) require.Error(t, errg.Wait()) cancelErr() @@ -227,10 +227,10 @@ func TestFullReconstructFromFulls(t *testing.T) { h, err := fullClient1.Header.WaitForHeight(bctx, uint64(i)) require.NoError(t, err) errg.Go(func() error { - return fullClient1.Share.SharesAvailable(bctx, h.DAH) + return fullClient1.Share.SharesAvailable(bctx, h) }) errg.Go(func() error { - return fullClient2.Share.SharesAvailable(bctx, h.DAH) + return fullClient2.Share.SharesAvailable(bctx, h) }) } @@ -344,7 +344,7 @@ func TestFullReconstructFromLights(t *testing.T) { return err } - return fullClient.Share.SharesAvailable(bctx, h.DAH) + return fullClient.Share.SharesAvailable(bctx, h) }) } require.NoError(t, <-fillDn) diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 0bb0e1c757..8b939749d6 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -75,7 +75,7 @@ func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check that the light node has also sampled over the block at height 20 - err = lightClient.Share.SharesAvailable(ctx, h.DAH) + err = lightClient.Share.SharesAvailable(ctx, h) assert.NoError(t, err) // wait until the entire chain (up to network head) has been sampled @@ -95,7 +95,7 @@ func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check to ensure the full node can sync the 20th block's data - err = fullClient.Share.SharesAvailable(ctx, h.DAH) + err = fullClient.Share.SharesAvailable(ctx, h) assert.NoError(t, err) // wait for full node to sync up the blocks from genesis -> network head. @@ -165,7 +165,7 @@ func TestSyncAgainstBridge_EmptyChain(t *testing.T) { assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check that the light node has also sampled over the block at height 20 - err = lightClient.Share.SharesAvailable(ctx, h.DAH) + err = lightClient.Share.SharesAvailable(ctx, h) assert.NoError(t, err) // wait until the entire chain (up to network head) has been sampled @@ -185,7 +185,7 @@ func TestSyncAgainstBridge_EmptyChain(t *testing.T) { assert.EqualValues(t, h.Commit.BlockID.Hash, sw.GetCoreBlockHashByHeight(ctx, numBlocks)) // check to ensure the full node can sync the 20th block's data - err = fullClient.Share.SharesAvailable(ctx, h.DAH) + err = fullClient.Share.SharesAvailable(ctx, h) assert.NoError(t, err) // wait for full node to sync up the blocks from genesis -> network head. diff --git a/share/availability.go b/share/availability.go index 587469d94b..f3511da450 100644 --- a/share/availability.go +++ b/share/availability.go @@ -6,6 +6,8 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/header" ) // ErrNotAvailable is returned whenever DA sampling fails. @@ -26,8 +28,10 @@ func NewRoot(eds *rsmt2d.ExtendedDataSquare) (*Root, error) { } // Availability defines interface for validation of Shares' availability. +// +//go:generate mockgen -destination=availability/mocks/availability.go -package=mocks . Availability type Availability interface { // SharesAvailable subjectively validates if Shares committed to the given Root are available on // the Network. - SharesAvailable(context.Context, *Root) error + SharesAvailable(context.Context, *header.ExtendedHeader) error } diff --git a/share/availability/full/availability.go b/share/availability/full/availability.go index 5fecb757fd..4ea211cb1e 100644 --- a/share/availability/full/availability.go +++ b/share/availability/full/availability.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/dagstore" logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/byzantine" @@ -56,35 +57,36 @@ func (fa *ShareAvailability) Stop(context.Context) error { // SharesAvailable reconstructs the data committed to the given Root by requesting // enough Shares from the network. -func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Root) error { +func (fa *ShareAvailability) SharesAvailable(ctx context.Context, header *header.ExtendedHeader) error { + dah := header.DAH // short-circuit if the given root is minimum DAH of an empty data square, to avoid datastore hit - if share.DataHash(root.Hash()).IsEmptyRoot() { + if share.DataHash(dah.Hash()).IsEmptyRoot() { return nil } // we assume the caller of this method has already performed basic validation on the // given dah/root. If for some reason this has not happened, the node should panic. - if err := root.ValidateBasic(); err != nil { + if err := dah.ValidateBasic(); err != nil { log.Errorw("Availability validation cannot be performed on a malformed DataAvailabilityHeader", "err", err) panic(err) } // a hack to avoid loading the whole EDS in mem if we store it already. - if ok, _ := fa.store.Has(ctx, root.Hash()); ok { + if ok, _ := fa.store.Has(ctx, dah.Hash()); ok { return nil } - adder := ipld.NewProofsAdder(len(root.RowRoots)) + adder := ipld.NewProofsAdder(len(dah.RowRoots)) ctx = ipld.CtxWithProofsAdder(ctx, adder) defer adder.Purge() - eds, err := fa.getter.GetEDS(ctx, root) + eds, err := fa.getter.GetEDS(ctx, header) if err != nil { if errors.Is(err, context.Canceled) { return err } - log.Errorw("availability validation failed", "root", root.String(), "err", err.Error()) + log.Errorw("availability validation failed", "root", dah.String(), "err", err.Error()) var byzantineErr *byzantine.ErrByzantine if errors.Is(err, share.ErrNotFound) || errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &byzantineErr) { return share.ErrNotAvailable @@ -92,7 +94,7 @@ func (fa *ShareAvailability) SharesAvailable(ctx context.Context, root *share.Ro return err } - err = fa.store.Put(ctx, root.Hash(), eds) + err = fa.store.Put(ctx, dah.Hash(), eds) if err != nil && !errors.Is(err, dagstore.ErrShardExists) { return fmt.Errorf("full availability: failed to store eds: %w", err) } diff --git a/share/availability/full/availability_test.go b/share/availability/full/availability_test.go index a769c981c4..8ac0648a87 100644 --- a/share/availability/full/availability_test.go +++ b/share/availability/full/availability_test.go @@ -10,6 +10,7 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/eds/edstest" @@ -22,10 +23,12 @@ func TestShareAvailableOverMocknet_Full(t *testing.T) { net := availability_test.NewTestDAGNet(ctx, t) _, root := RandNode(net, 32) + + eh := headertest.RandExtendedHeaderWithRoot(t, root) nd := Node(net) net.ConnectAll() - err := nd.SharesAvailable(ctx, root) + err := nd.SharesAvailable(ctx, eh) assert.NoError(t, err) } @@ -35,8 +38,10 @@ func TestSharesAvailable_Full(t *testing.T) { // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it getter, dah := GetterWithRandSquare(t, 16) + + eh := headertest.RandExtendedHeaderWithRoot(t, dah) avail := TestAvailability(t, getter) - err := avail.SharesAvailable(ctx, dah) + err := avail.SharesAvailable(ctx, eh) assert.NoError(t, err) } @@ -46,8 +51,9 @@ func TestSharesAvailable_StoresToEDSStore(t *testing.T) { // RandServiceWithSquare creates a NewShareAvailability inside, so we can test it getter, dah := GetterWithRandSquare(t, 16) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) avail := TestAvailability(t, getter) - err := avail.SharesAvailable(ctx, dah) + err := avail.SharesAvailable(ctx, eh) assert.NoError(t, err) has, err := avail.store.Has(ctx, dah.Hash()) @@ -63,13 +69,14 @@ func TestSharesAvailable_Full_ErrNotAvailable(t *testing.T) { eds := edstest.RandEDS(t, 4) dah, err := da.NewDataAvailabilityHeader(eds) + eh := headertest.RandExtendedHeaderWithRoot(t, &dah) require.NoError(t, err) avail := TestAvailability(t, getter) errors := []error{share.ErrNotFound, context.DeadlineExceeded} for _, getterErr := range errors { getter.EXPECT().GetEDS(gomock.Any(), gomock.Any()).Return(nil, getterErr) - err := avail.SharesAvailable(ctx, &dah) + err := avail.SharesAvailable(ctx, eh) require.ErrorIs(t, err, share.ErrNotAvailable) } } diff --git a/share/availability/full/reconstruction_test.go b/share/availability/full/reconstruction_test.go index f3b6ce91bd..6ac5a3f31e 100644 --- a/share/availability/full/reconstruction_test.go +++ b/share/availability/full/reconstruction_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/light" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" @@ -37,7 +38,9 @@ func TestShareAvailable_OneFullNode(t *testing.T) { net := availability_test.NewTestDAGNet(ctx, t) source, root := RandNode(net, origSquareSize) // make a source node, a.k.a bridge - full := Node(net) // make a full availability service which reconstructs data + eh := headertest.RandExtendedHeader(t) + eh.DAH = root + full := Node(net) // make a full availability service which reconstructs data // ensure there is no connection between source and full nodes // so that full reconstructs from the light nodes only @@ -45,14 +48,14 @@ func TestShareAvailable_OneFullNode(t *testing.T) { errg, errCtx := errgroup.WithContext(ctx) errg.Go(func() error { - return full.SharesAvailable(errCtx, root) + return full.SharesAvailable(errCtx, eh) }) lights := make([]*availability_test.TestNode, lightNodes) for i := 0; i < len(lights); i++ { lights[i] = light.Node(net) go func(i int) { - err := lights[i].SharesAvailable(ctx, root) + err := lights[i].SharesAvailable(ctx, eh) if err != nil { t.Log("light errors:", err) } @@ -91,6 +94,8 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { net := availability_test.NewTestDAGNet(ctx, t) source, root := RandNode(net, origSquareSize) + eh := headertest.RandExtendedHeader(t) + eh.DAH = root // create two full nodes and ensure they are disconnected full1 := Node(net) @@ -106,10 +111,10 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { // start reconstruction for fulls errg, errCtx := errgroup.WithContext(ctx) errg.Go(func() error { - return full1.SharesAvailable(errCtx, root) + return full1.SharesAvailable(errCtx, eh) }) errg.Go(func() error { - return full2.SharesAvailable(errCtx, root) + return full2.SharesAvailable(errCtx, eh) }) // create light nodes and start sampling for them immediately @@ -119,7 +124,7 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { for i := 0; i < len(lights1); i++ { lights1[i] = light.Node(net) go func(i int) { - err := lights1[i].SharesAvailable(ctx, root) + err := lights1[i].SharesAvailable(ctx, eh) if err != nil { t.Log("light1 errors:", err) } @@ -127,7 +132,7 @@ func TestShareAvailable_ConnectedFullNodes(t *testing.T) { lights2[i] = light.Node(net) go func(i int) { - err := lights2[i].SharesAvailable(ctx, root) + err := lights2[i].SharesAvailable(ctx, eh) if err != nil { t.Log("light2 errors:", err) } @@ -189,6 +194,8 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { net := availability_test.NewTestDAGNet(ctx, t) source, root := RandNode(net, origSquareSize) + eh := headertest.RandExtendedHeader(t) + eh.DAH = root // create light nodes and start sampling for them immediately lights1, lights2 := make( @@ -201,7 +208,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { lights1[i] = light.Node(net) go func(i int) { defer wg.Done() - err := lights1[i].SharesAvailable(ctx, root) + err := lights1[i].SharesAvailable(ctx, eh) if err != nil { t.Log("light1 errors:", err) } @@ -210,7 +217,7 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { lights2[i] = light.Node(net) go func(i int) { defer wg.Done() - err := lights2[i].SharesAvailable(ctx, root) + err := lights2[i].SharesAvailable(ctx, eh) if err != nil { t.Log("light2 errors:", err) } @@ -243,10 +250,10 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { ctxErr, cancelErr := context.WithTimeout(ctx, time.Second*5) errg, errCtx := errgroup.WithContext(ctxErr) errg.Go(func() error { - return full1.SharesAvailable(errCtx, root) + return full1.SharesAvailable(errCtx, eh) }) errg.Go(func() error { - return full2.SharesAvailable(errCtx, root) + return full2.SharesAvailable(errCtx, eh) }) // check that any of the fulls cannot reconstruct on their own @@ -264,10 +271,10 @@ func TestShareAvailable_DisconnectedFullNodes(t *testing.T) { // they both should be able to reconstruct the block errg, bctx := errgroup.WithContext(ctx) errg.Go(func() error { - return full1.SharesAvailable(bctx, root) + return full1.SharesAvailable(bctx, eh) }) errg.Go(func() error { - return full2.SharesAvailable(bctx, root) + return full2.SharesAvailable(bctx, eh) }) require.NoError(t, errg.Wait()) // wait for all routines to finish before exit, in case there are any errors to log diff --git a/share/availability/light/availability.go b/share/availability/light/availability.go index e7ad900d8d..1d35542344 100644 --- a/share/availability/light/availability.go +++ b/share/availability/light/availability.go @@ -11,6 +11,7 @@ import ( ipldFormat "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/getters" ) @@ -58,8 +59,9 @@ func NewShareAvailability( } // SharesAvailable randomly samples `params.SampleAmount` amount of Shares committed to the given -// Root. This way SharesAvailable subjectively verifies that Shares are available. -func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Root) error { +// ExtendedHeader. This way SharesAvailable subjectively verifies that Shares are available. +func (la *ShareAvailability) SharesAvailable(ctx context.Context, header *header.ExtendedHeader) error { + dah := header.DAH // short-circuit if the given root is minimum DAH of an empty data square if share.DataHash(dah.Hash()).IsEmptyRoot() { return nil @@ -97,7 +99,7 @@ func (la *ShareAvailability) SharesAvailable(ctx context.Context, dah *share.Roo for _, s := range samples { go func(s Sample) { log.Debugw("fetching share", "root", dah.String(), "row", s.Row, "col", s.Col) - _, err := la.getter.GetShare(ctx, dah, s.Row, s.Col) + _, err := la.getter.GetShare(ctx, header, s.Row, s.Col) if err != nil { log.Debugw("error fetching share", "root", dah.String(), "row", s.Row, "col", s.Col) } diff --git a/share/availability/light/availability_test.go b/share/availability/light/availability_test.go index 1e6b99cda4..2ace654d50 100644 --- a/share/availability/light/availability_test.go +++ b/share/availability/light/availability_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/ipld" @@ -19,7 +20,8 @@ func TestSharesAvailableCaches(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - getter, dah := GetterWithRandSquare(t, 16) + getter, eh := GetterWithRandSquare(t, 16) + dah := eh.DAH avail := TestAvailability(getter) // cache doesn't have dah yet @@ -27,7 +29,7 @@ func TestSharesAvailableCaches(t *testing.T) { assert.NoError(t, err) assert.False(t, has) - err = avail.SharesAvailable(ctx, dah) + err = avail.SharesAvailable(ctx, eh) assert.NoError(t, err) // is now cached @@ -45,9 +47,10 @@ func TestSharesAvailableHitsCache(t *testing.T) { bServ := ipld.NewMemBlockservice() dah := availability_test.RandFillBS(t, 16, bServ) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) // blockstore doesn't actually have the dah - err := avail.SharesAvailable(ctx, dah) + err := avail.SharesAvailable(ctx, eh) require.Error(t, err) // cache doesn't have dah yet, since it errored @@ -59,7 +62,7 @@ func TestSharesAvailableHitsCache(t *testing.T) { require.NoError(t, err) // should hit cache after putting - err = avail.SharesAvailable(ctx, dah) + err = avail.SharesAvailable(ctx, eh) require.NoError(t, err) } @@ -70,7 +73,8 @@ func TestSharesAvailableEmptyRoot(t *testing.T) { getter, _ := GetterWithRandSquare(t, 16) avail := TestAvailability(getter) - err := avail.SharesAvailable(ctx, share.EmptyRoot()) + eh := headertest.RandExtendedHeaderWithRoot(t, share.EmptyRoot()) + err := avail.SharesAvailable(ctx, eh) assert.NoError(t, err) } @@ -90,10 +94,11 @@ func TestSharesAvailableFailed(t *testing.T) { bServ := ipld.NewMemBlockservice() dah := availability_test.RandFillBS(t, 16, bServ) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) getter, _ := GetterWithRandSquare(t, 16) avail := TestAvailability(getter) - err := avail.SharesAvailable(ctx, dah) + err := avail.SharesAvailable(ctx, eh) assert.Error(t, err) } @@ -103,10 +108,12 @@ func TestShareAvailableOverMocknet_Light(t *testing.T) { net := availability_test.NewTestDAGNet(ctx, t) _, root := RandNode(net, 16) + eh := headertest.RandExtendedHeader(t) + eh.DAH = root nd := Node(net) net.ConnectAll() - err := nd.SharesAvailable(ctx, root) + err := nd.SharesAvailable(ctx, eh) assert.NoError(t, err) } @@ -115,11 +122,11 @@ func TestGetShare(t *testing.T) { defer cancel() n := 16 - getter, dah := GetterWithRandSquare(t, n) + getter, eh := GetterWithRandSquare(t, n) for i := range make([]bool, n) { for j := range make([]bool, n) { - sh, err := getter.GetShare(ctx, dah, i, j) + sh, err := getter.GetShare(ctx, eh, i, j) assert.NotNil(t, sh) assert.NoError(t, err) } @@ -148,9 +155,11 @@ func TestService_GetSharesByNamespace(t *testing.T) { copy(share.GetNamespace(randShares[idx2]), share.GetNamespace(randShares[idx1])) } root := availability_test.FillBS(t, bServ, randShares) + eh := headertest.RandExtendedHeader(t) + eh.DAH = root randNamespace := share.GetNamespace(randShares[idx1]) - shares, err := getter.GetSharesByNamespace(context.Background(), root, randNamespace) + shares, err := getter.GetSharesByNamespace(context.Background(), eh, randNamespace) require.NoError(t, err) require.NoError(t, shares.Verify(root, randNamespace)) flattened := shares.Flatten() @@ -174,8 +183,10 @@ func TestService_GetSharesByNamespace(t *testing.T) { copy(share.GetNamespace(randShares[i]), lastNID) } root := availability_test.FillBS(t, bServ, randShares) + eh := headertest.RandExtendedHeader(t) + eh.DAH = root - shares, err := getter.GetSharesByNamespace(context.Background(), root, lastNID) + shares, err := getter.GetSharesByNamespace(context.Background(), eh, lastNID) require.NoError(t, err) require.NoError(t, shares.Verify(root, lastNID)) }) @@ -187,21 +198,21 @@ func TestGetShares(t *testing.T) { defer cancel() n := 16 - getter, dah := GetterWithRandSquare(t, n) + getter, eh := GetterWithRandSquare(t, n) - eds, err := getter.GetEDS(ctx, dah) + eds, err := getter.GetEDS(ctx, eh) require.NoError(t, err) gotDAH, err := share.NewRoot(eds) require.NoError(t, err) - require.True(t, dah.Equals(gotDAH)) + require.True(t, eh.DAH.Equals(gotDAH)) } func TestService_GetSharesByNamespaceNotFound(t *testing.T) { - getter, root := GetterWithRandSquare(t, 1) - root.RowRoots = nil + getter, eh := GetterWithRandSquare(t, 1) + eh.DAH.RowRoots = nil - emptyShares, err := getter.GetSharesByNamespace(context.Background(), root, sharetest.RandV0Namespace()) + emptyShares, err := getter.GetSharesByNamespace(context.Background(), eh, sharetest.RandV0Namespace()) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) } @@ -218,12 +229,13 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { for _, tt := range tests { b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { t := &testing.T{} - getter, root := GetterWithRandSquare(t, tt.amountShares) + getter, eh := GetterWithRandSquare(t, tt.amountShares) + root := eh.DAH randNamespace := root.RowRoots[(len(root.RowRoots)-1)/2][:share.NamespaceSize] root.RowRoots[(len(root.RowRoots) / 2)] = root.RowRoots[(len(root.RowRoots)-1)/2] b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := getter.GetSharesByNamespace(context.Background(), root, randNamespace) + _, err := getter.GetSharesByNamespace(context.Background(), eh, randNamespace) require.NoError(t, err) } }) diff --git a/share/availability/light/testing.go b/share/availability/light/testing.go index e68068fc25..9efc9ff14a 100644 --- a/share/availability/light/testing.go +++ b/share/availability/light/testing.go @@ -6,6 +6,8 @@ import ( "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-datastore" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" "github.com/celestiaorg/celestia-node/share/getters" @@ -14,10 +16,14 @@ import ( // GetterWithRandSquare provides a share.Getter filled with 'n' NMT trees of 'n' random shares, // essentially storing a whole square. -func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *share.Root) { +func GetterWithRandSquare(t *testing.T, n int) (share.Getter, *header.ExtendedHeader) { bServ := ipld.NewMemBlockservice() getter := getters.NewIPLDGetter(bServ) - return getter, availability_test.RandFillBS(t, n, bServ) + root := availability_test.RandFillBS(t, n, bServ) + eh := headertest.RandExtendedHeader(t) + eh.DAH = root + + return getter, eh } // EmptyGetter provides an unfilled share.Getter with corresponding blockservice.BlockService than diff --git a/share/availability/mocks/availability.go b/share/availability/mocks/availability.go index ff4b8e1328..fc68d3d2bc 100644 --- a/share/availability/mocks/availability.go +++ b/share/availability/mocks/availability.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" + header "github.com/celestiaorg/celestia-node/header" gomock "github.com/golang/mock/gomock" - - da "github.com/celestiaorg/celestia-app/pkg/da" ) // MockAvailability is a mock of Availability interface. @@ -36,22 +35,8 @@ func (m *MockAvailability) EXPECT() *MockAvailabilityMockRecorder { return m.recorder } -// ProbabilityOfAvailability mocks base method. -func (m *MockAvailability) ProbabilityOfAvailability(arg0 context.Context) float64 { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ProbabilityOfAvailability", arg0) - ret0, _ := ret[0].(float64) - return ret0 -} - -// ProbabilityOfAvailability indicates an expected call of ProbabilityOfAvailability. -func (mr *MockAvailabilityMockRecorder) ProbabilityOfAvailability(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProbabilityOfAvailability", reflect.TypeOf((*MockAvailability)(nil).ProbabilityOfAvailability), arg0) -} - // SharesAvailable mocks base method. -func (m *MockAvailability) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader) error { +func (m *MockAvailability) SharesAvailable(arg0 context.Context, arg1 *header.ExtendedHeader) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SharesAvailable", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/share/eds/utils.go b/share/eds/utils.go index 3417a2aa62..2133565b91 100644 --- a/share/eds/utils.go +++ b/share/eds/utils.go @@ -1,12 +1,21 @@ package eds import ( + "context" + "errors" "fmt" "io" "github.com/filecoin-project/dagstore" + "github.com/ipfs/boxo/blockservice" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" + "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/cache" + "github.com/celestiaorg/celestia-node/share/ipld" ) // readCloser is a helper struct, that combines io.Reader and io.Closer @@ -46,3 +55,98 @@ func closeAndLog(name string, closer io.Closer) { log.Warnw("closing "+name, "err", err) } } + +// RetrieveNamespaceFromStore gets all EDS shares in the given namespace from +// the EDS store through the corresponding CAR-level blockstore. It is extracted +// from the store getter to make it available for reuse in the shrexnd server. +func RetrieveNamespaceFromStore( + ctx context.Context, + store *Store, + dah *share.Root, + namespace share.Namespace, +) (shares share.NamespacedShares, err error) { + if err = namespace.ValidateForData(); err != nil { + return nil, err + } + + bs, err := store.CARBlockstore(ctx, dah.Hash()) + if errors.Is(err, ErrNotFound) { + // convert error to satisfy getter interface contract + err = share.ErrNotFound + } + if err != nil { + return nil, fmt.Errorf("failed to retrieve blockstore from eds store: %w", err) + } + defer func() { + if err := bs.Close(); err != nil { + log.Warnw("closing blockstore", "err", err) + } + }() + + // wrap the read-only CAR blockstore in a getter + blockGetter := NewBlockGetter(bs) + shares, err = CollectSharesByNamespace(ctx, blockGetter, dah, namespace) + if errors.Is(err, ipld.ErrNodeNotFound) { + // IPLD node not found after the index pointed to this shard and the CAR + // blockstore has been opened successfully is a strong indicator of + // corruption. We remove the block on bridges and fulls and return + // share.ErrNotFound to ensure the data is retrieved by the next getter. + // Note that this recovery is manual and will only be restored by an RPC + // call to SharesAvailable that fetches the same datahash that was + // removed. + err = store.Remove(ctx, dah.Hash()) + if err != nil { + log.Errorf("failed to remove CAR from store after detected corruption: %w", err) + } + err = share.ErrNotFound + } + if err != nil { + return nil, fmt.Errorf("failed to retrieve shares by namespace from store: %w", err) + } + + return shares, nil +} + +// CollectSharesByNamespace collects NamespaceShares within the given namespace from share.Root. +func CollectSharesByNamespace( + ctx context.Context, + bg blockservice.BlockGetter, + root *share.Root, + namespace share.Namespace, +) (shares share.NamespacedShares, err error) { + ctx, span := tracer.Start(ctx, "collect-shares-by-namespace", trace.WithAttributes( + attribute.String("namespace", namespace.String()), + )) + defer func() { + utils.SetStatusAndEnd(span, err) + }() + + rootCIDs := ipld.FilterRootByNamespace(root, namespace) + if len(rootCIDs) == 0 { + return nil, nil + } + + errGroup, ctx := errgroup.WithContext(ctx) + shares = make([]share.NamespacedRow, len(rootCIDs)) + for i, rootCID := range rootCIDs { + // shadow loop variables, to ensure correct values are captured + i, rootCID := i, rootCID + errGroup.Go(func() error { + row, proof, err := ipld.GetSharesByNamespace(ctx, bg, rootCID, namespace, len(root.RowRoots)) + shares[i] = share.NamespacedRow{ + Shares: row, + Proof: proof, + } + if err != nil { + return fmt.Errorf("retrieving shares by namespace %s for row %x: %w", namespace.String(), rootCID, err) + } + return nil + }) + } + + if err := errGroup.Wait(); err != nil { + return nil, err + } + + return shares, nil +} diff --git a/share/getter.go b/share/getter.go index 18d3873de1..3fcc93de33 100644 --- a/share/getter.go +++ b/share/getter.go @@ -8,6 +8,8 @@ import ( "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/header" ) var ( @@ -24,17 +26,17 @@ var ( //go:generate mockgen -destination=mocks/getter.go -package=mocks . Getter type Getter interface { // GetShare gets a Share by coordinates in EDS. - GetShare(ctx context.Context, root *Root, row, col int) (Share, error) + GetShare(ctx context.Context, header *header.ExtendedHeader, row, col int) (Share, error) - // GetEDS gets the full EDS identified by the given root. - GetEDS(context.Context, *Root) (*rsmt2d.ExtendedDataSquare, error) + // GetEDS gets the full EDS identified by the given extended header. + GetEDS(context.Context, *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) // GetSharesByNamespace gets all shares from an EDS within the given namespace. // Shares are returned in a row-by-row order if the namespace spans multiple rows. // Inclusion of returned data could be verified using Verify method on NamespacedShares. // If no shares are found for target namespace non-inclusion could be also verified by calling // Verify method. - GetSharesByNamespace(context.Context, *Root, Namespace) (NamespacedShares, error) + GetSharesByNamespace(context.Context, *header.ExtendedHeader, Namespace) (NamespacedShares, error) } // NamespacedShares represents all shares with proofs within a specific namespace of an EDS. diff --git a/share/getters/cascade.go b/share/getters/cascade.go index eb3e969c1c..3875127580 100644 --- a/share/getters/cascade.go +++ b/share/getters/cascade.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" @@ -32,33 +33,37 @@ func NewCascadeGetter(getters []share.Getter) *CascadeGetter { } // GetShare gets a share from any of registered share.Getters in cascading order. -func (cg *CascadeGetter) GetShare(ctx context.Context, root *share.Root, row, col int) (share.Share, error) { +func (cg *CascadeGetter) GetShare( + ctx context.Context, header *header.ExtendedHeader, row, col int, +) (share.Share, error) { ctx, span := tracer.Start(ctx, "cascade/get-share", trace.WithAttributes( attribute.Int("row", row), attribute.Int("col", col), )) defer span.End() - upperBound := len(root.RowRoots) + upperBound := len(header.DAH.RowRoots) if row >= upperBound || col >= upperBound { err := share.ErrOutOfBounds span.RecordError(err) return nil, err } get := func(ctx context.Context, get share.Getter) (share.Share, error) { - return get.GetShare(ctx, root, row, col) + return get.GetShare(ctx, header, row, col) } return cascadeGetters(ctx, cg.getters, get) } // GetEDS gets a full EDS from any of registered share.Getters in cascading order. -func (cg *CascadeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { +func (cg *CascadeGetter) GetEDS( + ctx context.Context, header *header.ExtendedHeader, +) (*rsmt2d.ExtendedDataSquare, error) { ctx, span := tracer.Start(ctx, "cascade/get-eds") defer span.End() get := func(ctx context.Context, get share.Getter) (*rsmt2d.ExtendedDataSquare, error) { - return get.GetEDS(ctx, root) + return get.GetEDS(ctx, header) } return cascadeGetters(ctx, cg.getters, get) @@ -68,7 +73,7 @@ func (cg *CascadeGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d. // order. func (cg *CascadeGetter) GetSharesByNamespace( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, namespace share.Namespace, ) (share.NamespacedShares, error) { ctx, span := tracer.Start(ctx, "cascade/get-shares-by-namespace", trace.WithAttributes( @@ -77,7 +82,7 @@ func (cg *CascadeGetter) GetSharesByNamespace( defer span.End() get := func(ctx context.Context, get share.Getter) (share.NamespacedShares, error) { - return get.GetSharesByNamespace(ctx, root, namespace) + return get.GetSharesByNamespace(ctx, header, namespace) } return cascadeGetters(ctx, cg.getters, get) diff --git a/share/getters/cascade_test.go b/share/getters/cascade_test.go index d955c50682..d2b44883a1 100644 --- a/share/getters/cascade_test.go +++ b/share/getters/cascade_test.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/mocks" ) @@ -20,24 +21,24 @@ func TestCascadeGetter(t *testing.T) { t.Cleanup(cancel) const gettersN = 3 - roots := make([]*share.Root, gettersN) + headers := make([]*header.ExtendedHeader, gettersN) getters := make([]share.Getter, gettersN) - for i := range roots { - getters[i], roots[i] = TestGetter(t) + for i := range headers { + getters[i], headers[i] = TestGetter(t) } getter := NewCascadeGetter(getters) t.Run("GetShare", func(t *testing.T) { - for _, r := range roots { - sh, err := getter.GetShare(ctx, r, 0, 0) + for _, eh := range headers { + sh, err := getter.GetShare(ctx, eh, 0, 0) assert.NoError(t, err) assert.NotEmpty(t, sh) } }) t.Run("GetEDS", func(t *testing.T) { - for _, r := range roots { - sh, err := getter.GetEDS(ctx, r) + for _, eh := range headers { + sh, err := getter.GetEDS(ctx, eh) assert.NoError(t, err) assert.NotEmpty(t, sh) } @@ -54,7 +55,7 @@ func TestCascade(t *testing.T) { successGetter := mocks.NewMockGetter(ctrl) ctxGetter := mocks.NewMockGetter(ctrl) timeoutGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + DoAndReturn(func(ctx context.Context, _ *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { return nil, context.DeadlineExceeded }).AnyTimes() immediateFailGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). @@ -62,7 +63,7 @@ func TestCascade(t *testing.T) { successGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). Return(nil, nil).AnyTimes() ctxGetter.EXPECT().GetEDS(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *share.Root) (*rsmt2d.ExtendedDataSquare, error) { + DoAndReturn(func(ctx context.Context, _ *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { return nil, ctx.Err() }).AnyTimes() diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 6c75ddf83f..77c470dae9 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -18,6 +18,8 @@ import ( dsbadger "github.com/celestiaorg/go-ds-badger4" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/edstest" @@ -41,92 +43,109 @@ func TestStoreGetter(t *testing.T) { sg := NewStoreGetter(edsStore) t.Run("GetShare", func(t *testing.T) { - randEds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, eh := randomEDS(t) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) squareSize := int(randEds.Width()) for i := 0; i < squareSize; i++ { for j := 0; j < squareSize; j++ { - share, err := sg.GetShare(ctx, dah, i, j) + share, err := sg.GetShare(ctx, eh, i, j) require.NoError(t, err) assert.Equal(t, randEds.GetCell(uint(i), uint(j)), share) } } // doesn't panic on indexes too high - _, err := sg.GetShare(ctx, dah, squareSize, squareSize) + _, err := sg.GetShare(ctx, eh, squareSize, squareSize) require.ErrorIs(t, err, share.ErrOutOfBounds) // root not found - _, dah = randomEDS(t) - _, err = sg.GetShare(ctx, dah, 0, 0) + _, eh = randomEDS(t) + _, err = sg.GetShare(ctx, eh, 0, 0) require.ErrorIs(t, err, share.ErrNotFound) }) t.Run("GetEDS", func(t *testing.T) { - randEds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, eh := randomEDS(t) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) - retrievedEDS, err := sg.GetEDS(ctx, dah) + retrievedEDS, err := sg.GetEDS(ctx, eh) require.NoError(t, err) assert.True(t, randEds.Equals(retrievedEDS)) // root not found - root := share.Root{} - _, err = sg.GetEDS(ctx, &root) + emptyRoot := da.MinDataAvailabilityHeader() + eh.DAH = &emptyRoot + _, err = sg.GetEDS(ctx, eh) require.ErrorIs(t, err, share.ErrNotFound) }) t.Run("GetSharesByNamespace", func(t *testing.T) { - randEds, namespace, dah := randomEDSWithDoubledNamespace(t, 4) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, namespace, eh := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) - shares, err := sg.GetSharesByNamespace(ctx, dah, namespace) + shares, err := sg.GetSharesByNamespace(ctx, eh, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(dah, namespace)) + require.NoError(t, shares.Verify(eh.DAH, namespace)) assert.Len(t, shares.Flatten(), 2) // namespace not found randNamespace := sharetest.RandV0Namespace() - emptyShares, err := sg.GetSharesByNamespace(ctx, dah, randNamespace) + emptyShares, err := sg.GetSharesByNamespace(ctx, eh, randNamespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) // root not found - root := share.Root{} - _, err = sg.GetSharesByNamespace(ctx, &root, namespace) + emptyRoot := da.MinDataAvailabilityHeader() + eh.DAH = &emptyRoot + _, err = sg.GetSharesByNamespace(ctx, eh, namespace) require.ErrorIs(t, err, share.ErrNotFound) }) t.Run("GetSharesFromNamespace removes corrupted shard", func(t *testing.T) { - randEds, namespace, dah := randomEDSWithDoubledNamespace(t, 4) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, namespace, eh := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) // available - shares, err := sg.GetSharesByNamespace(ctx, dah, namespace) + shares, err := sg.GetSharesByNamespace(ctx, eh, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(dah, namespace)) + require.NoError(t, shares.Verify(eh.DAH, namespace)) assert.Len(t, shares.Flatten(), 2) // 'corrupt' existing CAR by overwriting with a random EDS - f, err := os.OpenFile(tmpDir+"/blocks/"+dah.String(), os.O_WRONLY, 0644) + f, err := os.OpenFile(tmpDir+"/blocks/"+eh.DAH.String(), os.O_WRONLY, 0644) require.NoError(t, err) - edsToOverwriteWith, dah := randomEDS(t) + edsToOverwriteWith, eh := randomEDS(t) err = eds.WriteEDS(ctx, edsToOverwriteWith, f) require.NoError(t, err) - shares, err = sg.GetSharesByNamespace(ctx, dah, namespace) + shares, err = sg.GetSharesByNamespace(ctx, eh, namespace) require.ErrorIs(t, err, share.ErrNotFound) require.Nil(t, shares) // corruption detected, shard is removed - has, err := edsStore.Has(ctx, dah.Hash()) - require.False(t, has) - require.NoError(t, err) + // try every 200ms until it passes or the context ends + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + t.Fatal("context ended before successful retrieval") + case <-ticker.C: + has, err := edsStore.Has(ctx, eh.DAH.Hash()) + if err != nil { + t.Fatal(err) + } + if !has { + require.NoError(t, err) + return + } + } + } }) } @@ -150,26 +169,26 @@ func TestIPLDGetter(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - randEds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, eh := randomEDS(t) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) squareSize := int(randEds.Width()) for i := 0; i < squareSize; i++ { for j := 0; j < squareSize; j++ { - share, err := sg.GetShare(ctx, dah, i, j) + share, err := sg.GetShare(ctx, eh, i, j) require.NoError(t, err) assert.Equal(t, randEds.GetCell(uint(i), uint(j)), share) } } // doesn't panic on indexes too high - _, err := sg.GetShare(ctx, dah, squareSize+1, squareSize+1) + _, err := sg.GetShare(ctx, eh, squareSize+1, squareSize+1) require.ErrorIs(t, err, share.ErrOutOfBounds) // root not found - _, dah = randomEDS(t) - _, err = sg.GetShare(ctx, dah, 0, 0) + _, eh = randomEDS(t) + _, err = sg.GetShare(ctx, eh, 0, 0) require.ErrorIs(t, err, share.ErrNotFound) }) @@ -177,11 +196,11 @@ func TestIPLDGetter(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - randEds, dah := randomEDS(t) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, eh := randomEDS(t) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) - retrievedEDS, err := sg.GetEDS(ctx, dah) + retrievedEDS, err := sg.GetEDS(ctx, eh) require.NoError(t, err) assert.True(t, randEds.Equals(retrievedEDS)) @@ -196,25 +215,26 @@ func TestIPLDGetter(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, time.Second) t.Cleanup(cancel) - randEds, namespace, dah := randomEDSWithDoubledNamespace(t, 4) - err = edsStore.Put(ctx, dah.Hash(), randEds) + randEds, namespace, eh := randomEDSWithDoubledNamespace(t, 4) + err = edsStore.Put(ctx, eh.DAH.Hash(), randEds) require.NoError(t, err) // first check that shares are returned correctly if they exist - shares, err := sg.GetSharesByNamespace(ctx, dah, namespace) + shares, err := sg.GetSharesByNamespace(ctx, eh, namespace) require.NoError(t, err) - require.NoError(t, shares.Verify(dah, namespace)) + require.NoError(t, shares.Verify(eh.DAH, namespace)) assert.Len(t, shares.Flatten(), 2) // namespace not found randNamespace := sharetest.RandV0Namespace() - emptyShares, err := sg.GetSharesByNamespace(ctx, dah, randNamespace) + emptyShares, err := sg.GetSharesByNamespace(ctx, eh, randNamespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) // nid doesnt exist in root - root := share.Root{} - emptyShares, err = sg.GetSharesByNamespace(ctx, &root, namespace) + emptyRoot := da.MinDataAvailabilityHeader() + eh.DAH = &emptyRoot + emptyShares, err = sg.GetSharesByNamespace(ctx, eh, namespace) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) }) @@ -248,16 +268,19 @@ func BenchmarkIPLDGetterOverBusyCache(b *testing.B) { edsStore := newStore(eds.DefaultParameters()) // generate EDSs and store them - hashes := make([]da.DataAvailabilityHeader, blocks) - for i := range hashes { + headers := make([]*header.ExtendedHeader, blocks) + for i := range headers { eds := edstest.RandEDS(b, size) dah, err := da.NewDataAvailabilityHeader(eds) require.NoError(b, err) err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(b, err) + eh := headertest.RandExtendedHeader(b) + eh.DAH = &dah + // store cids for read loop later - hashes[i] = dah + headers[i] = eh } // restart store to clear cache @@ -277,27 +300,31 @@ func BenchmarkIPLDGetterOverBusyCache(b *testing.B) { b.ResetTimer() g := sync.WaitGroup{} g.Add(blocks) - for _, h := range hashes { + for _, h := range headers { h := h go func() { defer g.Done() - _, err := getter.GetEDS(ctx, &h) + _, err := getter.GetEDS(ctx, h) require.NoError(b, err) }() } g.Wait() } -func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *share.Root) { +func randomEDS(t *testing.T) (*rsmt2d.ExtendedDataSquare, *header.ExtendedHeader) { eds := edstest.RandEDS(t, 4) dah, err := share.NewRoot(eds) require.NoError(t, err) - return eds, dah + eh := headertest.RandExtendedHeaderWithRoot(t, dah) + return eds, eh } // randomEDSWithDoubledNamespace generates a random EDS and ensures that there are two shares in the // middle that share a namespace. -func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedDataSquare, []byte, *share.Root) { +func randomEDSWithDoubledNamespace( + t *testing.T, + size int, +) (*rsmt2d.ExtendedDataSquare, []byte, *header.ExtendedHeader) { n := size * size randShares := sharetest.RandShares(t, n) idx1 := (n - 1) / 2 @@ -321,6 +348,7 @@ func randomEDSWithDoubledNamespace(t *testing.T, size int) (*rsmt2d.ExtendedData require.NoError(t, err, "failure to recompute the extended data square") dah, err := share.NewRoot(eds) require.NoError(t, err) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) - return eds, share.GetNamespace(randShares[idx1]), dah + return eds, share.GetNamespace(randShares[idx1]), eh } diff --git a/share/getters/ipld.go b/share/getters/ipld.go index 4024a67f5b..e9c930248d 100644 --- a/share/getters/ipld.go +++ b/share/getters/ipld.go @@ -13,6 +13,7 @@ import ( "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" @@ -39,7 +40,7 @@ func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter { } // GetShare gets a single share at the given EDS coordinates from the bitswap network. -func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { +func (ig *IPLDGetter) GetShare(ctx context.Context, header *header.ExtendedHeader, row, col int) (share.Share, error) { var err error ctx, span := tracer.Start(ctx, "ipld/get-share", trace.WithAttributes( attribute.Int("row", row), @@ -49,6 +50,7 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in utils.SetStatusAndEnd(span, err) }() + dah := header.DAH upperBound := len(dah.RowRoots) if row >= upperBound || col >= upperBound { err := share.ErrOutOfBounds @@ -71,14 +73,17 @@ func (ig *IPLDGetter) GetShare(ctx context.Context, dah *share.Root, row, col in return s, nil } -func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d.ExtendedDataSquare, err error) { +func (ig *IPLDGetter) GetEDS( + ctx context.Context, + header *header.ExtendedHeader, +) (eds *rsmt2d.ExtendedDataSquare, err error) { ctx, span := tracer.Start(ctx, "ipld/get-eds") defer func() { utils.SetStatusAndEnd(span, err) }() // rtrv.Retrieve calls shares.GetShares until enough shares are retrieved to reconstruct the EDS - eds, err = ig.rtrv.Retrieve(ctx, root) + eds, err = ig.rtrv.Retrieve(ctx, header.DAH) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound @@ -95,7 +100,7 @@ func (ig *IPLDGetter) GetEDS(ctx context.Context, root *share.Root) (eds *rsmt2d func (ig *IPLDGetter) GetSharesByNamespace( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "ipld/get-shares-by-namespace", trace.WithAttributes( @@ -111,7 +116,7 @@ func (ig *IPLDGetter) GetSharesByNamespace( // wrap the blockservice in a session if it has been signaled in the context. blockGetter := getGetter(ctx, ig.bServ) - shares, err = collectSharesByNamespace(ctx, blockGetter, root, namespace) + shares, err = eds.CollectSharesByNamespace(ctx, blockGetter, header.DAH, namespace) if errors.Is(err, ipld.ErrNodeNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 5a2f696854..1c296a6a33 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -13,8 +13,10 @@ import ( "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" @@ -119,11 +121,11 @@ func (sg *ShrexGetter) Stop(ctx context.Context) error { return sg.peerManager.Stop(ctx) } -func (sg *ShrexGetter) GetShare(context.Context, *share.Root, int, int) (share.Share, error) { +func (sg *ShrexGetter) GetShare(context.Context, *header.ExtendedHeader, int, int) (share.Share, error) { return nil, fmt.Errorf("getter/shrex: GetShare %w", errOperationNotSupported) } -func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { +func (sg *ShrexGetter) GetEDS(ctx context.Context, header *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { var ( attempt int err error @@ -133,6 +135,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex utils.SetStatusAndEnd(span, err) }() + dah := header.DAH for { if ctx.Err() != nil { sg.metrics.recordEDSAttempt(ctx, attempt, false) @@ -140,10 +143,10 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex } attempt++ start := time.Now() - peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) + peer, setStatus, getErr := sg.peerManager.Peer(ctx, dah.Hash()) if getErr != nil { log.Debugw("eds: couldn't find peer", - "hash", root.String(), + "hash", dah.String(), "err", getErr, "finished (s)", time.Since(start)) sg.metrics.recordEDSAttempt(ctx, attempt, false) @@ -152,7 +155,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex reqStart := time.Now() reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) - eds, getErr := sg.edsClient.RequestEDS(reqCtx, root.Hash(), peer) + eds, getErr := sg.edsClient.RequestEDS(reqCtx, dah.Hash(), peer) cancel() switch { case getErr == nil: @@ -175,7 +178,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex err = errors.Join(err, getErr) } log.Debugw("eds: request failed", - "hash", root.String(), + "hash", dah.String(), "peer", peer.String(), "attempt", attempt, "err", getErr, @@ -185,7 +188,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, root *share.Root) (*rsmt2d.Ex func (sg *ShrexGetter) GetSharesByNamespace( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, namespace share.Namespace, ) (share.NamespacedShares, error) { if err := namespace.ValidateForData(); err != nil { @@ -203,7 +206,8 @@ func (sg *ShrexGetter) GetSharesByNamespace( }() // verify that the namespace could exist inside the roots before starting network requests - roots := filterRootsByNamespace(root, namespace) + dah := header.DAH + roots := ipld.FilterRootByNamespace(dah, namespace) if len(roots) == 0 { return nil, nil } @@ -215,10 +219,10 @@ func (sg *ShrexGetter) GetSharesByNamespace( } attempt++ start := time.Now() - peer, setStatus, getErr := sg.peerManager.Peer(ctx, root.Hash()) + peer, setStatus, getErr := sg.peerManager.Peer(ctx, dah.Hash()) if getErr != nil { log.Debugw("nd: couldn't find peer", - "hash", root.String(), + "hash", dah.String(), "namespace", namespace.String(), "err", getErr, "finished (s)", time.Since(start)) @@ -228,12 +232,12 @@ func (sg *ShrexGetter) GetSharesByNamespace( reqStart := time.Now() reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) - nd, getErr := sg.ndClient.RequestND(reqCtx, root, namespace, peer) + nd, getErr := sg.ndClient.RequestND(reqCtx, dah, namespace, peer) cancel() switch { case getErr == nil: // both inclusion and non-inclusion cases needs verification - if verErr := nd.Verify(root, namespace); verErr != nil { + if verErr := nd.Verify(dah, namespace); verErr != nil { getErr = verErr setStatus(peers.ResultBlacklistPeer) break @@ -257,7 +261,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( err = errors.Join(err, getErr) } log.Debugw("nd: request failed", - "hash", root.String(), + "hash", dah.String(), "namespace", namespace.String(), "peer", peer.String(), "attempt", attempt, diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index 85878b204a..b625bb4c10 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -23,6 +23,7 @@ import ( "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/p2p/peers" "github.com/celestiaorg/celestia-node/share/p2p/shrexeds" "github.com/celestiaorg/celestia-node/share/p2p/shrexnd" @@ -62,13 +63,14 @@ func TestShrexGetter(t *testing.T) { // generate test data namespace := sharetest.RandV0Namespace() randEDS, dah := edstest.RandEDSWithNamespace(t, namespace, 64) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) require.NoError(t, edsStore.Put(ctx, dah.Hash(), randEDS)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - got, err := getter.GetSharesByNamespace(ctx, dah, namespace) + got, err := getter.GetSharesByNamespace(ctx, eh, namespace) require.NoError(t, err) require.NoError(t, got.Verify(dah, namespace)) }) @@ -79,12 +81,13 @@ func TestShrexGetter(t *testing.T) { // generate test data _, dah, namespace := generateTestEDS(t) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - _, err := getter.GetSharesByNamespace(ctx, dah, namespace) + _, err := getter.GetSharesByNamespace(ctx, eh, namespace) require.ErrorIs(t, err, share.ErrNotFound) }) @@ -94,6 +97,7 @@ func TestShrexGetter(t *testing.T) { // generate test data eds, dah, maxNamespace := generateTestEDS(t) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), @@ -103,9 +107,9 @@ func TestShrexGetter(t *testing.T) { nID, err := addToNamespace(maxNamespace, -1) require.NoError(t, err) // check for namespace to be between max and min namespace in root - require.Len(t, filterRootsByNamespace(dah, nID), 1) + require.Len(t, ipld.FilterRootByNamespace(dah, nID), 1) - emptyShares, err := getter.GetSharesByNamespace(ctx, dah, nID) + emptyShares, err := getter.GetSharesByNamespace(ctx, eh, nID) require.NoError(t, err) // no shares should be returned require.Empty(t, emptyShares.Flatten()) @@ -118,6 +122,7 @@ func TestShrexGetter(t *testing.T) { // generate test data eds, dah, maxNamesapce := generateTestEDS(t) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), @@ -127,9 +132,9 @@ func TestShrexGetter(t *testing.T) { namespace, err := addToNamespace(maxNamesapce, 1) require.NoError(t, err) // check for namespace to be not in root - require.Len(t, filterRootsByNamespace(dah, namespace), 0) + require.Len(t, ipld.FilterRootByNamespace(dah, namespace), 0) - emptyShares, err := getter.GetSharesByNamespace(ctx, dah, namespace) + emptyShares, err := getter.GetSharesByNamespace(ctx, eh, namespace) require.NoError(t, err) // no shares should be returned require.Empty(t, emptyShares.Flatten()) @@ -142,13 +147,14 @@ func TestShrexGetter(t *testing.T) { // generate test data randEDS, dah, _ := generateTestEDS(t) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) require.NoError(t, edsStore.Put(ctx, dah.Hash(), randEDS)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - got, err := getter.GetEDS(ctx, dah) + got, err := getter.GetEDS(ctx, eh) require.NoError(t, err) require.Equal(t, randEDS.Flattened(), got.Flattened()) }) @@ -158,13 +164,14 @@ func TestShrexGetter(t *testing.T) { // generate test data _, dah, _ := generateTestEDS(t) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) cancel() - _, err := getter.GetEDS(ctx, dah) + _, err := getter.GetEDS(ctx, eh) require.ErrorIs(t, err, context.Canceled) }) @@ -174,12 +181,13 @@ func TestShrexGetter(t *testing.T) { // generate test data _, dah, _ := generateTestEDS(t) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ DataHash: dah.Hash(), Height: 1, }) - _, err := getter.GetEDS(ctx, dah) + _, err := getter.GetEDS(ctx, eh) require.ErrorIs(t, err, share.ErrNotFound) }) } @@ -226,7 +234,7 @@ func newNDClientServer( params := shrexnd.DefaultParameters() // create server and register handler - server, err := shrexnd.NewServer(params, srvHost, edsStore, NewStoreGetter(edsStore)) + server, err := shrexnd.NewServer(params, srvHost, edsStore) require.NoError(t, err) require.NoError(t, server.Start(ctx)) diff --git a/share/getters/store.go b/share/getters/store.go index ad05cf8f37..d66a057c56 100644 --- a/share/getters/store.go +++ b/share/getters/store.go @@ -10,6 +10,7 @@ import ( "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" @@ -33,7 +34,8 @@ func NewStoreGetter(store *eds.Store) *StoreGetter { // GetShare gets a single share at the given EDS coordinates from the eds.Store through the // corresponding CAR-level blockstore. -func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) { +func (sg *StoreGetter) GetShare(ctx context.Context, header *header.ExtendedHeader, row, col int) (share.Share, error) { + dah := header.DAH var err error ctx, span := tracer.Start(ctx, "store/get-share", trace.WithAttributes( attribute.Int("row", row), @@ -79,13 +81,15 @@ func (sg *StoreGetter) GetShare(ctx context.Context, dah *share.Root, row, col i } // GetEDS gets the EDS identified by the given root from the EDS store. -func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (data *rsmt2d.ExtendedDataSquare, err error) { +func (sg *StoreGetter) GetEDS( + ctx context.Context, header *header.ExtendedHeader, +) (data *rsmt2d.ExtendedDataSquare, err error) { ctx, span := tracer.Start(ctx, "store/get-eds") defer func() { utils.SetStatusAndEnd(span, err) }() - data, err = sg.store.Get(ctx, root.Hash()) + data, err = sg.store.Get(ctx, header.DAH.Hash()) if errors.Is(err, eds.ErrNotFound) { // convert error to satisfy getter interface contract err = share.ErrNotFound @@ -100,7 +104,7 @@ func (sg *StoreGetter) GetEDS(ctx context.Context, root *share.Root) (data *rsmt // corresponding CAR-level blockstore. func (sg *StoreGetter) GetSharesByNamespace( ctx context.Context, - root *share.Root, + header *header.ExtendedHeader, namespace share.Namespace, ) (shares share.NamespacedShares, err error) { ctx, span := tracer.Start(ctx, "store/get-shares-by-namespace", trace.WithAttributes( @@ -110,44 +114,9 @@ func (sg *StoreGetter) GetSharesByNamespace( utils.SetStatusAndEnd(span, err) }() - if err = namespace.ValidateForData(); err != nil { - return nil, err - } - - bs, err := sg.store.CARBlockstore(ctx, root.Hash()) - if errors.Is(err, eds.ErrNotFound) { - // convert error to satisfy getter interface contract - err = share.ErrNotFound - } + ns, err := eds.RetrieveNamespaceFromStore(ctx, sg.store, header.DAH, namespace) if err != nil { - return nil, fmt.Errorf("getter/store: failed to retrieve blockstore: %w", err) + return nil, fmt.Errorf("getter/store: %w", err) } - defer func() { - if err := bs.Close(); err != nil { - log.Warnw("closing blockstore", "err", err) - } - }() - - // wrap the read-only CAR blockstore in a getter - blockGetter := eds.NewBlockGetter(bs) - shares, err = collectSharesByNamespace(ctx, blockGetter, root, namespace) - if errors.Is(err, ipld.ErrNodeNotFound) { - // IPLD node not found after the index pointed to this shard and the CAR - // blockstore has been opened successfully is a strong indicator of - // corruption. We remove the block on bridges and fulls and return - // share.ErrNotFound to ensure the data is retrieved by the next getter. - // Note that this recovery is manual and will only be restored by an RPC - // call to SharesAvailable that fetches the same datahash that was - // removed. - err = sg.store.Remove(ctx, root.Hash()) - if err != nil { - log.Errorf("getter/store: failed to remove CAR after detected corruption: %w", err) - } - err = share.ErrNotFound - } - if err != nil { - return nil, fmt.Errorf("getter/store: failed to retrieve shares by namespace: %w", err) - } - - return shares, nil + return ns, nil } diff --git a/share/getters/testing.go b/share/getters/testing.go index aa0cc0e479..fafeb0541c 100644 --- a/share/getters/testing.go +++ b/share/getters/testing.go @@ -10,18 +10,21 @@ import ( "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/edstest" ) // TestGetter provides a testing SingleEDSGetter and the root of the EDS it holds. -func TestGetter(t *testing.T) (share.Getter, *share.Root) { +func TestGetter(t *testing.T) (share.Getter, *header.ExtendedHeader) { eds := edstest.RandEDS(t, 8) dah, err := share.NewRoot(eds) + eh := headertest.RandExtendedHeaderWithRoot(t, dah) require.NoError(t, err) return &SingleEDSGetter{ EDS: eds, - }, dah + }, eh } // SingleEDSGetter contains a single EDS where data is retrieved from. @@ -31,8 +34,12 @@ type SingleEDSGetter struct { } // GetShare gets a share from a kept EDS if exist and if the correct root is given. -func (seg *SingleEDSGetter) GetShare(_ context.Context, root *share.Root, row, col int) (share.Share, error) { - err := seg.checkRoot(root) +func (seg *SingleEDSGetter) GetShare( + _ context.Context, + header *header.ExtendedHeader, + row, col int, +) (share.Share, error) { + err := seg.checkRoot(header.DAH) if err != nil { return nil, err } @@ -40,8 +47,11 @@ func (seg *SingleEDSGetter) GetShare(_ context.Context, root *share.Root, row, c } // GetEDS returns a kept EDS if the correct root is given. -func (seg *SingleEDSGetter) GetEDS(_ context.Context, root *share.Root) (*rsmt2d.ExtendedDataSquare, error) { - err := seg.checkRoot(root) +func (seg *SingleEDSGetter) GetEDS( + _ context.Context, + header *header.ExtendedHeader, +) (*rsmt2d.ExtendedDataSquare, error) { + err := seg.checkRoot(header.DAH) if err != nil { return nil, err } @@ -49,7 +59,7 @@ func (seg *SingleEDSGetter) GetEDS(_ context.Context, root *share.Root) (*rsmt2d } // GetSharesByNamespace returns NamespacedShares from a kept EDS if the correct root is given. -func (seg *SingleEDSGetter) GetSharesByNamespace(context.Context, *share.Root, share.Namespace, +func (seg *SingleEDSGetter) GetSharesByNamespace(context.Context, *header.ExtendedHeader, share.Namespace, ) (share.NamespacedShares, error) { panic("SingleEDSGetter: GetSharesByNamespace is not implemented") } diff --git a/share/getters/utils.go b/share/getters/utils.go index e3f24b3857..2260183b4f 100644 --- a/share/getters/utils.go +++ b/share/getters/utils.go @@ -3,20 +3,10 @@ package getters import ( "context" "errors" - "fmt" "time" - "github.com/ipfs/boxo/blockservice" - "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" - - "github.com/celestiaorg/celestia-node/libs/utils" - "github.com/celestiaorg/celestia-node/share" - "github.com/celestiaorg/celestia-node/share/ipld" ) var ( @@ -26,62 +16,6 @@ var ( errOperationNotSupported = errors.New("operation is not supported") ) -// filterRootsByNamespace returns the row roots from the given share.Root that contain the passed -// namespace. -func filterRootsByNamespace(root *share.Root, namespace share.Namespace) []cid.Cid { - rowRootCIDs := make([]cid.Cid, 0, len(root.RowRoots)) - for _, row := range root.RowRoots { - if !namespace.IsOutsideRange(row, row) { - rowRootCIDs = append(rowRootCIDs, ipld.MustCidFromNamespacedSha256(row)) - } - } - return rowRootCIDs -} - -// collectSharesByNamespace collects NamespaceShares within the given namespace from share.Root. -func collectSharesByNamespace( - ctx context.Context, - bg blockservice.BlockGetter, - root *share.Root, - namespace share.Namespace, -) (shares share.NamespacedShares, err error) { - ctx, span := tracer.Start(ctx, "collect-shares-by-namespace", trace.WithAttributes( - attribute.String("namespace", namespace.String()), - )) - defer func() { - utils.SetStatusAndEnd(span, err) - }() - - rootCIDs := filterRootsByNamespace(root, namespace) - if len(rootCIDs) == 0 { - return nil, nil - } - - errGroup, ctx := errgroup.WithContext(ctx) - shares = make([]share.NamespacedRow, len(rootCIDs)) - for i, rootCID := range rootCIDs { - // shadow loop variables, to ensure correct values are captured - i, rootCID := i, rootCID - errGroup.Go(func() error { - row, proof, err := ipld.GetSharesByNamespace(ctx, bg, rootCID, namespace, len(root.RowRoots)) - shares[i] = share.NamespacedRow{ - Shares: row, - Proof: proof, - } - if err != nil { - return fmt.Errorf("retrieving shares by namespace %s for row %x: %w", namespace.String(), rootCID, err) - } - return nil - }) - } - - if err := errGroup.Wait(); err != nil { - return nil, err - } - - return shares, nil -} - // ctxWithSplitTimeout will split timeout stored in context by splitFactor and return the result if // it is greater than minTimeout. minTimeout == 0 will be ignored, splitFactor <= 0 will be ignored func ctxWithSplitTimeout( diff --git a/share/ipld/corrupted_data_test.go b/share/ipld/corrupted_data_test.go index 58ddf785fb..d1d6e6b4d5 100644 --- a/share/ipld/corrupted_data_test.go +++ b/share/ipld/corrupted_data_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/header/headertest" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/availability/full" availability_test "github.com/celestiaorg/celestia-node/share/availability/test" @@ -32,9 +33,11 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { // before the provider starts attacking, we should be able to retrieve successfully. We pass a size // 16 block, but this is not important to the test and any valid block size behaves the same. root := availability_test.RandFillBS(t, 16, provider.BlockService) + + eh := headertest.RandExtendedHeaderWithRoot(t, root) getCtx, cancelGet := context.WithTimeout(ctx, sharesAvailableTimeout) t.Cleanup(cancelGet) - err := requestor.SharesAvailable(getCtx, root) + err := requestor.SharesAvailable(getCtx, eh) require.NoError(t, err) // clear the storage of the requester so that it must retrieve again, then start attacking @@ -43,6 +46,6 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { mockBS.Attacking = true getCtx, cancelGet = context.WithTimeout(ctx, sharesAvailableTimeout) t.Cleanup(cancelGet) - err = requestor.SharesAvailable(getCtx, root) + err = requestor.SharesAvailable(getCtx, eh) require.ErrorIs(t, err, share.ErrNotAvailable) } diff --git a/share/ipld/utils.go b/share/ipld/utils.go new file mode 100644 index 0000000000..d3e987e7f3 --- /dev/null +++ b/share/ipld/utils.go @@ -0,0 +1,18 @@ +package ipld + +import ( + "github.com/ipfs/go-cid" + + "github.com/celestiaorg/celestia-node/share" +) + +// FilterRootByNamespace returns the row roots from the given share.Root that contain the namespace. +func FilterRootByNamespace(root *share.Root, namespace share.Namespace) []cid.Cid { + rowRootCIDs := make([]cid.Cid, 0, len(root.RowRoots)) + for _, row := range root.RowRoots { + if !namespace.IsOutsideRange(row, row) { + rowRootCIDs = append(rowRootCIDs, MustCidFromNamespacedSha256(row)) + } + } + return rowRootCIDs +} diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 2a1b84efd5..738e2b246c 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - - da "github.com/celestiaorg/celestia-app/pkg/da" + header "github.com/celestiaorg/celestia-node/header" share "github.com/celestiaorg/celestia-node/share" rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockGetter is a mock of Getter interface. @@ -39,7 +38,7 @@ func (m *MockGetter) EXPECT() *MockGetterMockRecorder { } // GetEDS mocks base method. -func (m *MockGetter) GetEDS(arg0 context.Context, arg1 *da.DataAvailabilityHeader) (*rsmt2d.ExtendedDataSquare, error) { +func (m *MockGetter) GetEDS(arg0 context.Context, arg1 *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEDS", arg0, arg1) ret0, _ := ret[0].(*rsmt2d.ExtendedDataSquare) @@ -54,7 +53,7 @@ func (mr *MockGetterMockRecorder) GetEDS(arg0, arg1 interface{}) *gomock.Call { } // GetShare mocks base method. -func (m *MockGetter) GetShare(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2, arg3 int) ([]byte, error) { +func (m *MockGetter) GetShare(arg0 context.Context, arg1 *header.ExtendedHeader, arg2, arg3 int) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetShare", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([]byte) @@ -69,7 +68,7 @@ func (mr *MockGetterMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) * } // GetSharesByNamespace mocks base method. -func (m *MockGetter) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 share.Namespace) (share.NamespacedShares, error) { +func (m *MockGetter) GetSharesByNamespace(arg0 context.Context, arg1 *header.ExtendedHeader, arg2 share.Namespace) (share.NamespacedShares, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) ret0, _ := ret[0].(share.NamespacedShares) diff --git a/share/p2p/shrexnd/exchange_test.go b/share/p2p/shrexnd/exchange_test.go index 99a7e2f8fb..cb8bbe9d74 100644 --- a/share/p2p/shrexnd/exchange_test.go +++ b/share/p2p/shrexnd/exchange_test.go @@ -13,8 +13,6 @@ import ( mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" - "github.com/celestiaorg/rsmt2d" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/eds/edstest" @@ -25,7 +23,7 @@ import ( func TestExchange_RequestND_NotFound(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - edsStore, client, server := makeExchange(t, notFoundGetter{}) + edsStore, client, server := makeExchange(t) require.NoError(t, edsStore.Start(ctx)) require.NoError(t, server.Start(ctx)) @@ -48,8 +46,8 @@ func TestExchange_RequestND_NotFound(t *testing.T) { require.NoError(t, err) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) - randNamespace := dah.RowRoots[(len(dah.RowRoots)-1)/2][:share.NamespaceSize] - emptyShares, err := client.RequestND(ctx, dah, randNamespace, server.host.ID()) + namespace := sharetest.RandV0Namespace() + emptyShares, err := client.RequestND(ctx, dah, namespace, server.host.ID()) require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) }) @@ -62,7 +60,7 @@ func TestExchange_RequestND(t *testing.T) { client, err := NewClient(DefaultParameters(), net.Hosts()[0]) require.NoError(t, err) - server, err := NewServer(DefaultParameters(), net.Hosts()[1], nil, nil) + server, err := NewServer(DefaultParameters(), net.Hosts()[1], nil) require.NoError(t, err) require.NoError(t, server.Start(context.Background())) @@ -103,26 +101,6 @@ func TestExchange_RequestND(t *testing.T) { }) } -type notFoundGetter struct{} - -func (m notFoundGetter) GetShare( - _ context.Context, _ *share.Root, _, _ int, -) (share.Share, error) { - return nil, share.ErrNotFound -} - -func (m notFoundGetter) GetEDS( - _ context.Context, _ *share.Root, -) (*rsmt2d.ExtendedDataSquare, error) { - return nil, share.ErrNotFound -} - -func (m notFoundGetter) GetSharesByNamespace( - _ context.Context, _ *share.Root, _ share.Namespace, -) (share.NamespacedShares, error) { - return nil, nil -} - func newStore(t *testing.T) *eds.Store { t.Helper() @@ -142,14 +120,14 @@ func createMocknet(t *testing.T, amount int) []libhost.Host { return net.Hosts() } -func makeExchange(t *testing.T, getter share.Getter) (*eds.Store, *Client, *Server) { +func makeExchange(t *testing.T) (*eds.Store, *Client, *Server) { t.Helper() store := newStore(t) hosts := createMocknet(t, 2) client, err := NewClient(DefaultParameters(), hosts[0]) require.NoError(t, err) - server, err := NewServer(DefaultParameters(), hosts[1], store, getter) + server, err := NewServer(DefaultParameters(), hosts[1], store) require.NoError(t, err) return store, client, server diff --git a/share/p2p/shrexnd/server.go b/share/p2p/shrexnd/server.go index 153bbbb1ba..33e61ff472 100644 --- a/share/p2p/shrexnd/server.go +++ b/share/p2p/shrexnd/server.go @@ -30,7 +30,6 @@ type Server struct { protocolID protocol.ID handler network.StreamHandler - getter share.Getter store *eds.Store params *Parameters @@ -39,13 +38,12 @@ type Server struct { } // NewServer creates new Server -func NewServer(params *Parameters, host host.Host, store *eds.Store, getter share.Getter) (*Server, error) { +func NewServer(params *Parameters, host host.Host, store *eds.Store) (*Server, error) { if err := params.Validate(); err != nil { return nil, fmt.Errorf("shrex-nd: server creation failed: %w", err) } srv := &Server{ - getter: getter, store: store, host: host, params: params, @@ -183,7 +181,7 @@ func (srv *Server) getNamespaceData(ctx context.Context, return nil, pb.StatusCode_INTERNAL, fmt.Errorf("retrieving DAH: %w", err) } - shares, err := srv.getter.GetSharesByNamespace(ctx, dah, namespace) + shares, err := eds.RetrieveNamespaceFromStore(ctx, srv.store, dah, namespace) if err != nil { return nil, pb.StatusCode_INTERNAL, fmt.Errorf("retrieving shares: %w", err) } From 00b32a808c6d800e4e820ea33a6b6c2811a0ad3a Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 9 Oct 2023 10:56:59 +0200 Subject: [PATCH 0872/1008] fix!: make API version match node version --- nodebuilder/node/admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index 03c9cf1b52..8063835f1d 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -10,7 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/authtoken" ) -const APIVersion = "v0.2.2" +const APIVersion = "v0.11.0" type module struct { tp Type From 3e56a048e5eea627d0df7d028bcc0a171f93c0e2 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 10 Oct 2023 15:01:34 +0200 Subject: [PATCH 0873/1008] chore(share/ipld): obliterate expensive ipld getter tracing (#2832) The load these traces can produce is massive. In the case of ODS size 128, and the whole block is sampled for namespace data, it will be 48896 spans (128*2-1)*128*2 - (128 * 128). Besides, there is zero value this tracing brought us so far. --- share/ipld/get.go | 15 --------------- share/ipld/get_shares.go | 6 ------ share/ipld/namespace_data.go | 18 ------------------ share/ipld/nmt.go | 4 +--- 4 files changed, 1 insertion(+), 42 deletions(-) diff --git a/share/ipld/get.go b/share/ipld/get.go index f263877dc0..adf2ffa8c5 100644 --- a/share/ipld/get.go +++ b/share/ipld/get.go @@ -10,8 +10,6 @@ import ( "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" "github.com/celestiaorg/celestia-node/share" ) @@ -100,9 +98,6 @@ func GetLeaves(ctx context.Context, maxShares int, put func(int, ipld.Node), ) { - ctx, span := tracer.Start(ctx, "get-leaves") - defer span.End() - // this buffer ensures writes to 'jobs' are never blocking (bin-tree-feat) jobs := make(chan *job, (maxShares+1)/2) // +1 for the case where 'maxShares' is 1 jobs <- &job{cid: root, ctx: ctx} @@ -120,21 +115,12 @@ func GetLeaves(ctx context.Context, // work over each job concurrently, s.t. shares do not block // processing of each other pool.Submit(func() { - ctx, span := tracer.Start(j.ctx, "process-job") - defer span.End() defer wg.Done() - span.SetAttributes( - attribute.String("cid", j.cid.String()), - attribute.Int("pos", j.sharePos), - ) - nd, err := GetNode(ctx, bGetter, j.cid) if err != nil { // we don't really care about errors here // just fetch as much as possible - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) return } // check links to know what we should do with the node @@ -142,7 +128,6 @@ func GetLeaves(ctx context.Context, if len(lnks) == 0 { // successfully fetched a share/leaf // ladies and gentlemen, we got em! - span.SetStatus(codes.Ok, "") put(j.sharePos, nd) return } diff --git a/share/ipld/get_shares.go b/share/ipld/get_shares.go index 3fda86941f..98db7012b5 100644 --- a/share/ipld/get_shares.go +++ b/share/ipld/get_shares.go @@ -32,9 +32,6 @@ func GetShare( // Does not return any error, and returns/unblocks only on success // (got all shares) or on context cancellation. func GetShares(ctx context.Context, bg blockservice.BlockGetter, root cid.Cid, shares int, put func(int, share.Share)) { - ctx, span := tracer.Start(ctx, "get-shares") - defer span.End() - putNode := func(i int, leaf format.Node) { put(i, leafToShare(leaf)) } @@ -51,9 +48,6 @@ func GetSharesByNamespace( namespace share.Namespace, maxShares int, ) ([]share.Share, *nmt.Proof, error) { - ctx, span := tracer.Start(ctx, "get-shares-by-namespace") - defer span.End() - data := NewNamespaceData(maxShares, namespace, WithLeaves(), WithProofs()) err := data.CollectLeavesByNamespace(ctx, bGetter, root) if err != nil { diff --git a/share/ipld/namespace_data.go b/share/ipld/namespace_data.go index a3f468ee47..5a6fd2abb4 100644 --- a/share/ipld/namespace_data.go +++ b/share/ipld/namespace_data.go @@ -10,8 +10,6 @@ import ( "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" "github.com/celestiaorg/nmt" @@ -193,13 +191,6 @@ func (n *NamespaceData) CollectLeavesByNamespace( return err } - ctx, span := tracer.Start(ctx, "get-leaves-by-namespace") - defer span.End() - - span.SetAttributes( - attribute.String("namespace", n.namespace.String()), - ) - // buffer the jobs to avoid blocking, we only need as many // queued as the number of shares in the second-to-last layer jobs := make(chan job, (n.maxShares+1)/2) @@ -227,15 +218,8 @@ func (n *NamespaceData) CollectLeavesByNamespace( return retrievalErr } pool.Submit(func() { - ctx, span := tracer.Start(j.ctx, "process-job") - defer span.End() defer wg.done() - span.SetAttributes( - attribute.String("cid", j.cid.String()), - attribute.Int("pos", j.sharePos), - ) - // if an error is likely to be returned or not depends on // the underlying impl of the blockservice, currently it is not a realistic probability nd, err := GetNode(ctx, bGetter, j.cid) @@ -248,7 +232,6 @@ func (n *NamespaceData) CollectLeavesByNamespace( "pos", j.sharePos, "err", err, ) - span.SetStatus(codes.Error, err.Error()) // we still need to update the bounds n.addLeaf(j.sharePos, nil) return @@ -257,7 +240,6 @@ func (n *NamespaceData) CollectLeavesByNamespace( links := nd.Links() if len(links) == 0 { // successfully fetched a leaf belonging to the namespace - span.SetStatus(codes.Ok, "") // we found a leaf, so we update the bounds n.addLeaf(j.sharePos, nd) return diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index b43d786a5f..6dba300965 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -15,7 +15,6 @@ import ( logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" mhcore "github.com/multiformats/go-multihash/core" - "go.opentelemetry.io/otel" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/da" @@ -25,8 +24,7 @@ import ( ) var ( - tracer = otel.Tracer("ipld") - log = logging.Logger("ipld") + log = logging.Logger("ipld") ) const ( From 937eb433d937e4652d54b4356d7fce1aeaf5ded1 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:27:11 +0400 Subject: [PATCH 0874/1008] fix(share/p2p/peer-manager) fix race for hasPeerCh pointer read (#2835) --- share/p2p/peers/pool.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index 609d68e0b3..d0cc45ac44 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -89,8 +89,11 @@ func (p *pool) next(ctx context.Context) <-chan peer.ID { return } + p.m.RLock() + hasPeerCh := p.hasPeerCh + p.m.RUnlock() select { - case <-p.hasPeerCh: + case <-hasPeerCh: case <-ctx.Done(): return } From 127eca71279eb5d3b93ece5f04d997ac6f9a5408 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 11 Oct 2023 11:37:52 -0500 Subject: [PATCH 0875/1008] chore: bump to the official v1.0.0 tag of celestia-app (#2831) ## Overview bumps to the official tag for celestia-app. This is a non-breaking change. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- go.mod | 8 ++++---- go.sum | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index d39b4696fd..1c76383ffa 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0-rc18 + github.com/celestiaorg/celestia-app v1.0.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.3.3 @@ -76,10 +76,10 @@ require ( require ( github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/bits-and-blooms/bitset v1.5.0 // indirect + github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2 // indirect github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.10.0 // indirect + github.com/consensys/gnark-crypto v0.12.0 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect @@ -292,7 +292,7 @@ require ( github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rs/cors v1.8.2 // indirect - github.com/rs/zerolog v1.29.1 // indirect + github.com/rs/zerolog v1.31.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/shirou/gopsutil v3.21.6+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 7a986186d2..4e31203f91 100644 --- a/go.sum +++ b/go.sum @@ -315,8 +315,8 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= -github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= +github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -358,8 +358,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0-rc18 h1:d2+KYPr4ri10aOtnXih3hOUd6ap94c/Hv46JFzpWzy0= -github.com/celestiaorg/celestia-app v1.0.0-rc18/go.mod h1:Q375ijriqMv8PjKmMujNs2WzMAsgxjpbeUoQt+AZre0= +github.com/celestiaorg/celestia-app v1.0.0 h1:CJX0y3JE1gEKzb0Hr3/qS0Z/tZgYGJyCHJzc0+SVxYM= +github.com/celestiaorg/celestia-app v1.0.0/go.mod h1:8FN8f/iZC5D6A7QYJhK86nyRKrmQrzXFucV19vLv9Es= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29/go.mod h1:xrICN0PBhp3AdTaZ8q4wS5Jvi32V02HNjaC2EsWiEKk= github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 h1:c4cMVLU2bGTesZW1ZVgeoCB++gOOJTF3OvBsqBvo6n0= @@ -454,8 +454,8 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= -github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA= -github.com/consensys/gnark-crypto v0.10.0/go.mod h1:Iq/P3HHl0ElSjsg2E1gsMwhAyxnxoKK5nVyZKd+/KhU= +github.com/consensys/gnark-crypto v0.12.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= +github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= @@ -1707,7 +1707,6 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -2120,10 +2119,10 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= -github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= -github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -2817,7 +2816,6 @@ golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2854,6 +2852,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= From 7491d53ee80b14b35ffe86542943406c3fbb55c5 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:08:16 +0400 Subject: [PATCH 0876/1008] fix(share/eds): fix races in cache tests (#2834) Fixes races in eds cache tests: - read / write isClosed flag in Cache mock - write / write for error object from 2 goroutines - read / write of cache object inside edsStore on cache swaps in tests. Resolves https://github.com/celestiaorg/celestia-node/issues/2833 --------- Co-authored-by: Wondertan --- share/eds/blockstore.go | 6 +++--- share/eds/cache/accessor_cache_test.go | 13 ++++++++++++- share/eds/metrics.go | 2 +- share/eds/store.go | 12 ++++++------ share/eds/store_test.go | 22 +++++++++++----------- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/share/eds/blockstore.go b/share/eds/blockstore.go index 9cbb3f4e8a..e44601870e 100644 --- a/share/eds/blockstore.go +++ b/share/eds/blockstore.go @@ -6,7 +6,7 @@ import ( "fmt" bstore "github.com/ipfs/boxo/blockstore" - dshelp "github.com/ipfs/boxo/datastore/dshelp" + "github.com/ipfs/boxo/datastore/dshelp" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" @@ -154,13 +154,13 @@ func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (* // check if either cache contains an accessor shardKey := keys[0] - accessor, err := bs.store.cache.Get(shardKey) + accessor, err := bs.store.cache.Load().Get(shardKey) if err == nil { return blockstoreCloser(accessor) } // load accessor to the blockstore cache and use it as blockstoreCloser - accessor, err = bs.store.cache.Second().GetOrLoad(ctx, shardKey, bs.store.getAccessor) + accessor, err = bs.store.cache.Load().Second().GetOrLoad(ctx, shardKey, bs.store.getAccessor) if err != nil { return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err) } diff --git a/share/eds/cache/accessor_cache_test.go b/share/eds/cache/accessor_cache_test.go index 4f12301dee..347b251a88 100644 --- a/share/eds/cache/accessor_cache_test.go +++ b/share/eds/cache/accessor_cache_test.go @@ -5,6 +5,7 @@ import ( "context" "errors" "io" + "sync" "testing" "time" @@ -210,7 +211,8 @@ func TestAccessorCache(t *testing.T) { // initialize close done := make(chan struct{}) go func() { - err = cache.Remove(key) + err := cache.Remove(key) + require.NoError(t, err) close(done) }() @@ -284,16 +286,21 @@ func TestAccessorCache(t *testing.T) { } type mockAccessor struct { + m sync.Mutex data []byte isClosed bool returnedBs int } func (m *mockAccessor) Reader() io.Reader { + m.m.Lock() + defer m.m.Unlock() return bytes.NewBuffer(m.data) } func (m *mockAccessor) Blockstore() (dagstore.ReadBlockstore, error) { + m.m.Lock() + defer m.m.Unlock() if m.returnedBs > 0 { return nil, errors.New("blockstore already returned") } @@ -302,6 +309,8 @@ func (m *mockAccessor) Blockstore() (dagstore.ReadBlockstore, error) { } func (m *mockAccessor) Close() error { + m.m.Lock() + defer m.m.Unlock() if m.isClosed { return errors.New("already closed") } @@ -312,6 +321,8 @@ func (m *mockAccessor) Close() error { func (m *mockAccessor) checkClosed(t *testing.T, expected bool) { // item will be removed in background, so give it some time to settle time.Sleep(time.Millisecond * 100) + m.m.Lock() + defer m.m.Unlock() require.Equal(t, expected, m.isClosed) } diff --git a/share/eds/metrics.go b/share/eds/metrics.go index 8f87643f17..cbebf8321a 100644 --- a/share/eds/metrics.go +++ b/share/eds/metrics.go @@ -124,7 +124,7 @@ func (s *Store) WithMetrics() error { return err } - if err = s.cache.EnableMetrics(); err != nil { + if err = s.cache.Load().EnableMetrics(); err != nil { return err } diff --git a/share/eds/store.go b/share/eds/store.go index 9fc90046fc..816065909e 100644 --- a/share/eds/store.go +++ b/share/eds/store.go @@ -49,7 +49,7 @@ type Store struct { mounts *mount.Registry bs *blockstore - cache *cache.DoubleCache + cache atomic.Pointer[cache.DoubleCache] carIdx index.FullIndexRepo invertedIdx *simpleInvertedIndex @@ -129,9 +129,9 @@ func NewStore(params *Parameters, basePath string, ds datastore.Batching) (*Stor gcInterval: params.GCInterval, mounts: r, shardFailures: failureChan, - cache: cache.NewDoubleCache(recentBlocksCache, blockstoreCache), } store.bs = newBlockstore(store, ds) + store.cache.Store(cache.NewDoubleCache(recentBlocksCache, blockstoreCache)) return store, nil } @@ -286,7 +286,7 @@ func (s *Store) put(ctx context.Context, root share.DataHash, square *rsmt2d.Ext go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - ac, err := s.cache.First().GetOrLoad(ctx, result.Key, s.getAccessor) + ac, err := s.cache.Load().First().GetOrLoad(ctx, result.Key, s.getAccessor) if err != nil { log.Warnw("unable to put accessor to recent blocks accessors cache", "err", err) return @@ -347,7 +347,7 @@ func (s *Store) GetCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, func (s *Store) getCAR(ctx context.Context, root share.DataHash) (io.ReadCloser, error) { key := shard.KeyFromString(root.String()) - accessor, err := s.cache.Get(key) + accessor, err := s.cache.Load().Get(key) if err == nil { return newReadCloser(accessor), nil } @@ -391,7 +391,7 @@ func (s *Store) carBlockstore( root share.DataHash, ) (*BlockstoreCloser, error) { key := shard.KeyFromString(root.String()) - accessor, err := s.cache.Get(key) + accessor, err := s.cache.Load().Get(key) if err == nil { return blockstoreCloser(accessor) } @@ -482,7 +482,7 @@ func (s *Store) Remove(ctx context.Context, root share.DataHash) error { func (s *Store) remove(ctx context.Context, root share.DataHash) (err error) { key := shard.KeyFromString(root.String()) // remove open links to accessor from cache - if err := s.cache.Remove(key); err != nil { + if err := s.cache.Load().Remove(key); err != nil { log.Warnw("remove accessor from cache", "err", err) } ch := make(chan dagstore.ShardResult, 1) diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 7052533555..09357347d0 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -158,7 +158,7 @@ func TestEDSStore(t *testing.T) { time.Sleep(time.Millisecond * 100) // remove non-failed accessor from cache - err = edsStore.cache.Remove(shard.KeyFromString(dah.String())) + err = edsStore.cache.Load().Remove(shard.KeyFromString(dah.String())) assert.NoError(t, err) _, err = edsStore.GetCAR(ctx, dah.Hash()) @@ -205,7 +205,7 @@ func TestEDSStore(t *testing.T) { // check, that the key is in the cache after put shardKey := shard.KeyFromString(dah.String()) - _, err = edsStore.cache.Get(shardKey) + _, err = edsStore.cache.Load().Get(shardKey) assert.NoError(t, err) }) @@ -276,7 +276,7 @@ func TestEDSStore_GC(t *testing.T) { // remove links to the shard from cache time.Sleep(time.Millisecond * 100) key := shard.KeyFromString(share.DataHash(dah.Hash()).String()) - err = edsStore.cache.Remove(key) + err = edsStore.cache.Load().Remove(key) require.NoError(t, err) // doesn't exist yet @@ -305,8 +305,8 @@ func Test_BlockstoreCache(t *testing.T) { require.NoError(t, err) // store eds to the store with noopCache to allow clean cache after put - swap := edsStore.cache - edsStore.cache = cache.NewDoubleCache(cache.NoopCache{}, cache.NoopCache{}) + swap := edsStore.cache.Load() + edsStore.cache.Store(cache.NewDoubleCache(cache.NoopCache{}, cache.NoopCache{})) eds, dah := randomEDS(t) err = edsStore.Put(ctx, dah.Hash(), eds) require.NoError(t, err) @@ -327,11 +327,11 @@ func Test_BlockstoreCache(t *testing.T) { } // swap back original cache - edsStore.cache = swap + edsStore.cache.Store(swap) // key shouldn't be in cache yet, check for returned errCacheMiss shardKey := shard.KeyFromString(dah.String()) - _, err = edsStore.cache.Get(shardKey) + _, err = edsStore.cache.Load().Get(shardKey) require.Error(t, err) // now get it from blockstore, to trigger storing to cache @@ -339,7 +339,7 @@ func Test_BlockstoreCache(t *testing.T) { require.NoError(t, err) // should be no errCacheMiss anymore - _, err = edsStore.cache.Get(shardKey) + _, err = edsStore.cache.Load().Get(shardKey) require.NoError(t, err) } @@ -362,7 +362,7 @@ func Test_CachedAccessor(t *testing.T) { time.Sleep(time.Millisecond * 100) // accessor should be in cache - _, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) + _, err = edsStore.cache.Load().Get(shard.KeyFromString(dah.String())) require.NoError(t, err) // first read from cached accessor @@ -393,7 +393,7 @@ func Test_NotCachedAccessor(t *testing.T) { err = edsStore.Start(ctx) require.NoError(t, err) // replace cache with noopCache to - edsStore.cache = cache.NewDoubleCache(cache.NoopCache{}, cache.NoopCache{}) + edsStore.cache.Store(cache.NewDoubleCache(cache.NoopCache{}, cache.NoopCache{})) eds, dah := randomEDS(t) err = edsStore.Put(ctx, dah.Hash(), eds) @@ -403,7 +403,7 @@ func Test_NotCachedAccessor(t *testing.T) { time.Sleep(time.Millisecond * 100) // accessor should not be in cache - _, err = edsStore.cache.Get(shard.KeyFromString(dah.String())) + _, err = edsStore.cache.Load().Get(shard.KeyFromString(dah.String())) require.Error(t, err) // first read from direct accessor (not from cache) From f1a302bba63415a8b228065dbfce5d6ceb37eff6 Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Thu, 12 Oct 2023 11:18:10 -0400 Subject: [PATCH 0877/1008] chore: Update docker-build-publish.yml (#2838) This fixes the bug that rollkit local-celestia-devent was experiencing with the docker images being overwritten. The changes to the reuseable docker action simplify the logic for easier maintenance. The changes to the node CI file are to only run the docker action on a PR or pushes to main. When the workflow was triggering on all push events, the action was running twice on PRs which was contributing to the confusion and causing images to be overwritten. This means if the team wants a docker image built for testing, they must open at least a draft PR. The PR doesn't need to be merged, but the PR trigger is what will built the image. --- .github/workflows/docker-build-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index c2589ff141..72d3f90f95 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -5,7 +5,7 @@ on: merge_group: push: branches: - - "**" + - "main" tags: - "v[0-9]+.[0-9]+.[0-9]+" - "v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+" @@ -18,6 +18,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.3 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.7 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From 17619db964bf6d854c38ea3294aef642991f5093 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 13 Oct 2023 12:11:56 +0200 Subject: [PATCH 0878/1008] fix(modp2p): temporary disable quic (#2837) Disables QUIC by default yet allows to enable it with ENV var. This is temporary and until libp2p/go-libp2p#2591 is investigated and fixed. The current solution disables QUIC programmatically while we still keep writing in the config QUIC listen addresses by default. This removes the need for users to reinit their configs once we turn QUIC back by default. This works perfectly fine on mocha. --- nodebuilder/p2p/addrs.go | 17 ++++++++++++++--- nodebuilder/p2p/host.go | 20 +++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/nodebuilder/p2p/addrs.go b/nodebuilder/p2p/addrs.go index d8f50c8144..528f58d722 100644 --- a/nodebuilder/p2p/addrs.go +++ b/nodebuilder/p2p/addrs.go @@ -2,6 +2,7 @@ package p2p import ( "fmt" + "slices" p2pconfig "github.com/libp2p/go-libp2p/config" hst "github.com/libp2p/go-libp2p/core/host" @@ -11,13 +12,23 @@ import ( // Listen returns invoke function that starts listening for inbound connections with libp2p.Host. func Listen(listen []string) func(h hst.Host) (err error) { return func(h hst.Host) (err error) { - maListen := make([]ma.Multiaddr, len(listen)) - for i, addr := range listen { - maListen[i], err = ma.NewMultiaddr(addr) + maListen := make([]ma.Multiaddr, 0, len(listen)) + for _, addr := range listen { + maddr, err := ma.NewMultiaddr(addr) if err != nil { return fmt.Errorf("failure to parse config.P2P.ListenAddresses: %s", err) } + + // TODO(@WonderTan): Remove this check when QUIC is stable + if slices.ContainsFunc(maddr.Protocols(), func(p ma.Protocol) bool { + return p.Code == ma.P_QUIC_V1 || p.Code == ma.P_WEBTRANSPORT + }) { + continue + } + + maListen = append(maListen, maddr) } + return h.Network().Listen(maListen...) } } diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index e55cb65d1f..c3943a02fa 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -3,6 +3,7 @@ package p2p import ( "context" "fmt" + "os" "github.com/libp2p/go-libp2p" p2pconfig "github.com/libp2p/go-libp2p/config" @@ -16,12 +17,22 @@ import ( "github.com/libp2p/go-libp2p/core/routing" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/conngater" + quic "github.com/libp2p/go-libp2p/p2p/transport/quic" + "github.com/libp2p/go-libp2p/p2p/transport/tcp" + webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" "github.com/prometheus/client_golang/prometheus" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) +var enableQUIC bool + +func init() { + _, ok := os.LookupEnv("CELESTIA_ENABLE_QUIC") + enableQUIC = ok +} + // routedHost constructs a wrapped Host that may fallback to address discovery, // if any top-level operation on the Host is provided with PeerID(Hash(PbK)) only. func routedHost(base HostBase, r routing.PeerRouting) hst.Host { @@ -44,8 +55,15 @@ func host(params hostParams) (HostBase, error) { libp2p.ResourceManager(params.ResourceManager), // to clearly define what defaults we rely upon libp2p.DefaultSecurity, - libp2p.DefaultTransports, libp2p.DefaultMuxers, + libp2p.Transport(tcp.NewTCPTransport), + } + + if enableQUIC { + opts = append(opts, + libp2p.Transport(quic.NewTransport), + libp2p.Transport(webtransport.New), + ) } if params.Registry != nil { From a52e2de4a76de0e25e53b04f82894d0f44888bd1 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 13 Oct 2023 12:30:26 +0200 Subject: [PATCH 0879/1008] chore: adding mainnet chain id and setting as default (#2840) :) Also removed blockspace race Part of #2823 --- nodebuilder/p2p/bootstrap.go | 6 +----- nodebuilder/p2p/genesis.go | 8 ++++---- nodebuilder/p2p/network.go | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 47b39eed31..e3971753a1 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -37,6 +37,7 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ + Mainnet: {}, Arabica: { "/dns4/da-bridge.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWM3e9MWtyc8GkP8QRt74Riu17QuhGfZMytB2vq5NwkWAu", "/dns4/da-bridge-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWKj8mcdiBGxQRe1jqhaMnh2tGoC3rPDmr5UH2q8H4WA9M", @@ -49,11 +50,6 @@ var bootstrapList = map[Network][]string{ "/dns4/da-full-1-mocha-4.celestia-mocha.com/tcp/2121/p2p/12D3KooWCUHPLqQXZzpTx1x3TAsdn3vYmTNDhzg66yG8hqoxGGN8", "/dns4/da-full-2-mocha-4.celestia-mocha.com/tcp/2121/p2p/12D3KooWR6SHsXPkkvhCRn6vp1RqSefgaT1X1nMNvrVjU2o3GoYy", }, - BlockspaceRace: { - "/dns4/bootstr-incent-3.celestia.tools/tcp/2121/p2p/12D3KooWNzdKcHagtvvr6qtjcPTAdCN6ZBiBLH8FBHbihxqu4GZx", - "/dns4/bootstr-incent-2.celestia.tools/tcp/2121/p2p/12D3KooWNJZyWeCsrKxKrxsNM1RVL2Edp77svvt7Cosa63TggC9m", - "/dns4/bootstr-incent-1.celestia.tools/tcp/2121/p2p/12D3KooWBtxdBzToQwnS4ySGpph9PtGmmjEyATkgX3PfhAo4xmf7", - }, Private: {}, } diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index 5066db5177..c88ed53ef3 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,8 +23,8 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Arabica: "5904E55478BA4B3002EE885621E007A2A6A2399662841912219AECD5D5CBE393", - Mocha: "B93BBE20A0FBFDF955811B6420F8433904664D45DB4BF51022BE4200C1A1680D", - BlockspaceRace: "1A8491A72F73929680DAA6C93E3B593579261B2E76536BFA4F5B97D6FE76E088", - Private: "", + Mainnet: "", + Arabica: "5904E55478BA4B3002EE885621E007A2A6A2399662841912219AECD5D5CBE393", + Mocha: "B93BBE20A0FBFDF955811B6420F8433904664D45DB4BF51022BE4200C1A1680D", + Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index a6197aa321..a7f9ff7236 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -10,15 +10,15 @@ import ( // NOTE: Every time we add a new long-running network, it has to be added here. const ( // DefaultNetwork is the default network of the current build. - DefaultNetwork = Mocha + DefaultNetwork = Mainnet // Arabica testnet. See: celestiaorg/networks. Arabica Network = "arabica-10" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha-4" - // BlockspaceRace testnet. See: https://docs.celestia.org/nodes/blockspace-race/. - BlockspaceRace Network = "blockspacerace-0" // Private can be used to set up any private network, including local testing setups. Private Network = "private" + // Celestia mainnet. See: celestiaorg/networks. + Mainnet Network = "celestia" // BlockTime is a network block time. // TODO @renaynay @Wondertan (#790) BlockTime = time.Second * 10 @@ -52,20 +52,20 @@ func (n Network) String() string { // networksList is a strict list of all known long-standing networks. var networksList = map[Network]struct{}{ - Arabica: {}, - Mocha: {}, - BlockspaceRace: {}, - Private: {}, + Mainnet: {}, + Arabica: {}, + Mocha: {}, + Private: {}, } // networkAliases is a strict list of all known long-standing networks // mapped from the string representation of their *alias* (rather than // their actual value) to the Network. var networkAliases = map[string]Network{ - "arabica": Arabica, - "mocha": Mocha, - "blockspacerace": BlockspaceRace, - "private": Private, + "mainnet": Mainnet, + "arabica": Arabica, + "mocha": Mocha, + "private": Private, } // listProvidedNetworks provides a string listing all known long-standing networks for things like From 34775bbf11e102f4a2457e09acdfc83c288ded73 Mon Sep 17 00:00:00 2001 From: Matthew Sevey Date: Fri, 13 Oct 2023 11:13:09 -0400 Subject: [PATCH 0880/1008] bug: fix duplicate docker build stages (#2845) Final bug fix celestiaorg/.github#82 Visual proof of action executing as expected now celestiaorg/celestia-node/actions/runs/6509222904/job/17680437495 Screen Shot 2023-10-13 at 10 13 28 AM --- .github/workflows/docker-build-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 72d3f90f95..b8322d5e17 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -18,6 +18,6 @@ jobs: permissions: contents: write packages: write - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.7 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_pipeline.yml@v0.2.8 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile From 4f2a44b4573399c61c3ad1ff857df6ebf150ee2a Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 13 Oct 2023 18:22:46 +0300 Subject: [PATCH 0881/1008] fix(blob/service): fix getByCommitment (#2828) Fixes https://github.com/celestiaorg/celestia-node/issues/2808 Co-authored-by: Hlib Kanunnikov --- blob/blob.go | 36 ++++++++++++ blob/helper.go | 27 +++------ blob/service.go | 130 ++++++++++++++++++++----------------------- blob/service_test.go | 19 +++++++ 4 files changed, 122 insertions(+), 90 deletions(-) diff --git a/blob/blob.go b/blob/blob.go index c5fd04c782..3f7e92dd20 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -3,11 +3,13 @@ package blob import ( "bytes" "encoding/json" + "errors" "fmt" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/nmt" @@ -138,3 +140,37 @@ func (b *Blob) UnmarshalJSON(data []byte) error { b.namespace = blob.Namespace return nil } + +// buildBlobsIfExist takes shares and tries building the Blobs from them. +// It will build blobs either until appShares will be empty or the first incomplete blob will appear, so in this +// specific case it will return all built blobs + remaining shares. +func buildBlobsIfExist(appShares []shares.Share) ([]*Blob, []shares.Share, error) { + if len(appShares) == 0 { + return nil, nil, errors.New("empty shares received") + } + blobs := make([]*Blob, 0, len(appShares)) + for { + length, err := appShares[0].SequenceLen() + if err != nil { + return nil, nil, err + } + + amount := shares.SparseSharesNeeded(length) + if amount > len(appShares) { + return blobs, appShares, nil + } + + b, err := parseShares(appShares[:amount]) + if err != nil { + return nil, nil, err + } + + // only 1 blob will be created bc we passed the exact amount of shares + blobs = append(blobs, b[0]) + + if amount == len(appShares) { + return blobs, nil, nil + } + appShares = appShares[amount:] + } +} diff --git a/blob/helper.go b/blob/helper.go index 5627fac998..72a56c7889 100644 --- a/blob/helper.go +++ b/blob/helper.go @@ -17,16 +17,16 @@ func SharesToBlobs(rawShares []share.Share) ([]*Blob, error) { return nil, ErrBlobNotFound } - appShares := make([]shares.Share, 0, len(rawShares)) - for _, shr := range rawShares { - bShare, err := shares.NewShare(shr) - if err != nil { - return nil, err - } - appShares = append(appShares, *bShare) + appShares, err := toAppShares(rawShares...) + if err != nil { + return nil, err } + return parseShares(appShares) +} - shareSequences, err := shares.ParseShares(appShares, true) +// parseShares takes shares and converts them to the []*Blob. +func parseShares(appShrs []shares.Share) ([]*Blob, error) { + shareSequences, err := shares.ParseShares(appShrs, true) if err != nil { return nil, err } @@ -84,14 +84,3 @@ func BlobsToShares(blobs ...*Blob) ([]share.Share, error) { } return shares.ToBytes(rawShares), nil } - -// constructAndVerifyBlob reconstruct a Blob from the passed shares and compares commitments. -func constructAndVerifyBlob(sh []share.Share, commitment Commitment) (*Blob, bool, error) { - blob, err := SharesToBlobs(sh) - if err != nil { - return nil, false, err - } - - equal := blob[0].Commitment.Equal(commitment) - return blob[0], equal, nil -} diff --git a/blob/service.go b/blob/service.go index cf3daff7de..88b6c63d3f 100644 --- a/blob/service.go +++ b/blob/service.go @@ -197,13 +197,6 @@ func (s *Service) getByCommitment( return nil, nil, err } - var ( - rawShares = make([]share.Share, 0) - proofs = make(Proof, 0) - amount int - blobShare *shares.Share - ) - namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header, namespace) if err != nil { if errors.Is(err, share.ErrNotFound) { @@ -211,84 +204,57 @@ func (s *Service) getByCommitment( } return nil, nil, err } - for _, row := range namespacedShares { - if len(row.Shares) == 0 { - break - } - - rawShares = append(rawShares, row.Shares...) - proofs = append(proofs, row.Proof) - // reconstruct the `blobShare` from the first rawShare in range - // in order to get blob's length(first share will contain this info) - if blobShare == nil { - for i, shr := range rawShares { - bShare, err := shares.NewShare(shr) - if err != nil { - return nil, nil, err - } - - // ensure that the first share is not a NamespacePaddingShare - // these shares are used to satisfy the non-interactive default rules - // and are not the part of the blob, so should be removed. - isPadding, err := bShare.IsPadding() - if err != nil { - return nil, nil, err - } - if isPadding { - continue - } - - blobShare = bShare - // save the length. - length, err := blobShare.SequenceLen() - if err != nil { - return nil, nil, err - } - amount = shares.SparseSharesNeeded(length) - rawShares = rawShares[i:] - break - } - } + var ( + rawShares = make([]shares.Share, 0) + proofs = make(Proof, 0) + // spansMultipleRows specifies whether blob is expanded into multiple rows + spansMultipleRows bool + ) - // move to the next row if the blob is incomplete. - if amount > len(rawShares) { - continue + for _, row := range namespacedShares { + appShares, err := toAppShares(row.Shares...) + if err != nil { + return nil, nil, err } + rawShares = append(rawShares, appShares...) + proofs = append(proofs, row.Proof) - blob, same, err := constructAndVerifyBlob(rawShares[:amount], commitment) + var blobs []*Blob + blobs, rawShares, err = buildBlobsIfExist(rawShares) if err != nil { return nil, nil, err } - if same { - return blob, &proofs, nil + for _, b := range blobs { + if b.Commitment.Equal(commitment) { + return b, &proofs, nil + } + // Falling under this flag means that the data from the last row + // was insufficient to create a complete blob. As a result, + // the first blob received spans two rows and includes proofs + // for both of these rows. All other blobs in the result will relate + // to the current row and have a single proof. + if spansMultipleRows { + spansMultipleRows = false + // leave proof only for the current row + proofs = proofs[len(proofs)-1:] + } } - // drop info of the checked blob - rawShares = rawShares[amount:] if len(rawShares) > 0 { - // save proof for the last row in case we have rawShares - proofs = proofs[len(proofs)-1:] - } else { - // otherwise clear proofs - proofs = nil + spansMultipleRows = true + continue } - blobShare = nil + proofs = nil } - if len(rawShares) == 0 { - return nil, nil, ErrBlobNotFound - } - - blob, same, err := constructAndVerifyBlob(rawShares, commitment) - if err != nil { - return nil, nil, err - } - if same { - return blob, &proofs, nil + err = ErrBlobNotFound + if len(rawShares) > 0 { + err = fmt.Errorf("incomplete blob with the "+ + "namespace: %s detected at %d: %w", namespace.String(), height, err) + log.Error(err) } - - return nil, nil, ErrBlobNotFound + return nil, nil, err } // getBlobs retrieves the DAH and fetches all shares from the requested Namespace and converts @@ -304,3 +270,25 @@ func (s *Service) getBlobs( } return SharesToBlobs(namespacedShares.Flatten()) } + +// toAppShares converts node's raw shares to the app shares, skipping padding +func toAppShares(shrs ...share.Share) ([]shares.Share, error) { + appShrs := make([]shares.Share, 0, len(shrs)) + for _, shr := range shrs { + bShare, err := shares.NewShare(shr) + if err != nil { + return nil, err + } + + ok, err := bShare.IsPadding() + if err != nil { + return nil, err + } + if ok { + continue + } + + appShrs = append(appShrs, *bShare) + } + return appShrs, nil +} diff --git a/blob/service_test.go b/blob/service_test.go index 64296b969c..6777084eb4 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -344,6 +344,25 @@ func TestService_GetSingleBlobWithoutPadding(t *testing.T) { assert.Equal(t, newBlob.Commitment, blobs[1].Commitment) } +func TestService_Get(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + sizes := []int{1, 6, 3, 2, 4, 6, 8, 2, 15, 17} + + appBlobs, err := blobtest.GenerateV0Blobs(sizes, true) + require.NoError(t, err) + blobs, err := convertBlobs(appBlobs...) + require.NoError(t, err) + + service := createService(ctx, t, blobs) + for _, blob := range blobs { + b, err := service.Get(ctx, 1, blob.Namespace(), blob.Commitment) + require.NoError(t, err) + assert.Equal(t, b.Commitment, blob.Commitment) + } +} + func TestService_GetAllWithoutPadding(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) From dbf7dbff39bab55359171e9973cd781ad9b9968f Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 13 Oct 2023 17:25:17 +0200 Subject: [PATCH 0882/1008] fix: removing chainid from default store name for mainnet (#2841) Also part of #2823 Stacked on #2840 --- cmd/flags_node.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/flags_node.go b/cmd/flags_node.go index fe4981b6c6..ef5af26580 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -87,6 +87,10 @@ func DefaultNodeStorePath(tp string, network string) (string, error) { return "", err } } + if network == p2p.Mainnet.String() { + return fmt.Sprintf("%s/.celestia-%s", home, strings.ToLower(tp)), nil + } + // only include network name in path for testnets and custom networks return fmt.Sprintf( "%s/.celestia-%s-%s", home, From 422dea262a2b32bd1b026d760eaf1c7ed78e141b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:27:25 +0200 Subject: [PATCH 0883/1008] deps!: Bumps go-header (#2844) Bumps go-header - includes breaks to Getter interface + metrics instantiation for header pkg Co-authored-by: Hlib Kanunnikov --- core/exchange.go | 45 +++++++++++++++--------------- core/exchange_test.go | 23 +++++++++++++-- core/listener_test.go | 14 +++++++--- das/daser_test.go | 6 +--- docs/adr/adr-009-public-api.md | 4 +-- go.mod | 2 +- go.sum | 4 +-- nodebuilder/header/config.go | 3 ++ nodebuilder/header/constructors.go | 27 ++++++++++++++---- nodebuilder/header/header.go | 10 +++---- nodebuilder/header/mocks/api.go | 15 +++++----- nodebuilder/header/module.go | 18 +++++++++--- nodebuilder/header/opts.go | 28 ------------------- nodebuilder/header/service.go | 4 +-- nodebuilder/settings.go | 7 +++-- 15 files changed, 117 insertions(+), 93 deletions(-) delete mode 100644 nodebuilder/header/opts.go diff --git a/core/exchange.go b/core/exchange.go index f8e1606a3e..06f648edad 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -42,7 +42,29 @@ func (ce *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.Ext return ce.getExtendedHeaderByHeight(ctx, &intHeight) } -func (ce *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { +func (ce *Exchange) GetRangeByHeight( + ctx context.Context, + from *header.ExtendedHeader, + to uint64, +) ([]*header.ExtendedHeader, error) { + amount := to - (from.Height() + 1) + headers, err := ce.getRangeByHeight(ctx, from.Height()+1, amount) + if err != nil { + return nil, err + } + + for _, h := range headers { + err := from.Verify(h) + if err != nil { + return nil, fmt.Errorf("verifying next header against last verified height: %d: %w", + from.Height(), err) + } + from = h + } + return headers, nil +} + +func (ce *Exchange) getRangeByHeight(ctx context.Context, from, amount uint64) ([]*header.ExtendedHeader, error) { if amount == 0 { return nil, nil } @@ -73,27 +95,6 @@ func (ce *Exchange) GetRangeByHeight(ctx context.Context, from, amount uint64) ( return headers, nil } -func (ce *Exchange) GetVerifiedRange( - ctx context.Context, - from *header.ExtendedHeader, - amount uint64, -) ([]*header.ExtendedHeader, error) { - headers, err := ce.GetRangeByHeight(ctx, from.Height()+1, amount) - if err != nil { - return nil, err - } - - for _, h := range headers { - err := from.Verify(h) - if err != nil { - return nil, fmt.Errorf("verifying next header against last verified height: %d: %w", - from.Height(), err) - } - from = h - } - return headers, nil -} - func (ce *Exchange) Get(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) { log.Debugw("requesting header", "hash", hash.String()) block, err := ce.fetcher.GetBlockByHash(ctx, hash) diff --git a/core/exchange_test.go b/core/exchange_test.go index 8aa62593c8..853b5a8dc6 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -17,6 +17,9 @@ import ( ) func TestCoreExchange_RequestHeaders(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + fetcher, _ := createCoreFetcher(t, DefaultTestConfig()) // generate 10 blocks @@ -25,10 +28,26 @@ func TestCoreExchange_RequestHeaders(t *testing.T) { store := createStore(t) ce := NewExchange(fetcher, store, header.MakeExtendedHeader) - headers, err := ce.GetRangeByHeight(context.Background(), 1, 10) + + // initialize store with genesis block + genHeight := int64(1) + genBlock, err := fetcher.GetBlock(ctx, &genHeight) + require.NoError(t, err) + genHeader, err := ce.Get(ctx, genBlock.Header.Hash().Bytes()) + require.NoError(t, err) + + to := uint64(10) + expectedFirstHeightInRange := genHeader.Height() + 1 + expectedLastHeightInRange := to - 1 + expectedLenHeaders := to - expectedFirstHeightInRange + + // request headers from height 1 to 10 [2:10) + headers, err := ce.GetRangeByHeight(context.Background(), genHeader, to) require.NoError(t, err) - assert.Equal(t, 10, len(headers)) + assert.Len(t, headers, int(expectedLenHeaders)) + assert.Equal(t, expectedFirstHeightInRange, headers[0].Height()) + assert.Equal(t, expectedLastHeightInRange, headers[len(headers)-1].Height()) } func createCoreFetcher(t *testing.T, cfg *testnode.Config) (*BlockFetcher, testnode.Context) { diff --git a/core/listener_test.go b/core/listener_test.go index 90bdefb72f..eab634a330 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -31,8 +31,13 @@ func TestListener(t *testing.T) { // create mocknet with two pubsub endpoints ps0, ps1 := createMocknetWithTwoPubsubEndpoints(ctx, t) - subscriber := p2p.NewSubscriber[*header.ExtendedHeader](ps1, header.MsgID, networkID) - err := subscriber.SetVerifier(func(context.Context, *header.ExtendedHeader) error { + subscriber, err := p2p.NewSubscriber[*header.ExtendedHeader]( + ps1, + header.MsgID, + p2p.WithSubscriberNetworkID(networkID), + ) + require.NoError(t, err) + err = subscriber.SetVerifier(func(context.Context, *header.ExtendedHeader) error { return nil }) require.NoError(t, err) @@ -162,8 +167,9 @@ func createListener( edsSub *shrexsub.PubSub, store *eds.Store, ) *Listener { - p2pSub := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, networkID) - err := p2pSub.Start(ctx) + p2pSub, err := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, p2p.WithSubscriberNetworkID(networkID)) + require.NoError(t, err) + err = p2pSub.Start(ctx) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, p2pSub.Stop(ctx)) diff --git a/das/daser_test.go b/das/daser_test.go index ca553b0f09..1ec160e224 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -384,11 +384,7 @@ func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.Exten DAH: &share.Root{RowRoots: make([][]byte, 0)}}, nil } -func (m getterStub) GetRangeByHeight(context.Context, uint64, uint64) ([]*header.ExtendedHeader, error) { - return nil, nil -} - -func (m getterStub) GetVerifiedRange( +func (m getterStub) GetRangeByHeight( context.Context, *header.ExtendedHeader, uint64, diff --git a/docs/adr/adr-009-public-api.md b/docs/adr/adr-009-public-api.md index d2bf6ee817..be9e8827eb 100644 --- a/docs/adr/adr-009-public-api.md +++ b/docs/adr/adr-009-public-api.md @@ -100,10 +100,10 @@ GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) // WaitForHeight blocks until the header at the given height has been processed // by the node's header store or until context deadline is exceeded. WaitForHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) -// GetVerifiedRangeByHeight returns the given range (from:to) of ExtendedHeaders +// GetRangeByHeight returns the given range (from:to) of ExtendedHeaders // from the node's header store and verifies that the returned headers are // adjacent to each other. -GetVerifiedRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) +GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) // Subscribe creates long-living Subscription for newly validated // ExtendedHeaders. Multiple Subscriptions can be created. Subscribe(context.Context) (<-chan *header.ExtendedHeader, error) diff --git a/go.mod b/go.mod index 1c76383ffa..dfae47fb29 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v1.0.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.3.3 + github.com/celestiaorg/go-header v0.4.0 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 4e31203f91..0c46be987a 100644 --- a/go.sum +++ b/go.sum @@ -370,8 +370,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.3.3 h1:Y04hdJIJfD5hapyqK0ZQMgMTH5PQGV9YpcIf56LGc4E= -github.com/celestiaorg/go-header v0.3.3/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.4.0 h1:Ine/xpvFx8o9p6fXW+h2RSPp68rn7VUxTkW1okJxcEY= +github.com/celestiaorg/go-header v0.4.0/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index 238fb39afd..d7265373ce 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -16,6 +16,9 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" ) +// MetricsEnabled will be set during runtime if metrics are enabled on the node. +var MetricsEnabled = false + // Config contains configuration parameters for header retrieval and management. type Config struct { // TrustedHash is the Block/Header hash that Nodes use as starting point for header synchronization. diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index be4bcbd427..15d2da09b1 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -40,12 +40,18 @@ func newP2PExchange[H libhead.Header[H]]( ids[index] = peer.ID host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) } - exchange, err := p2p.NewExchange[H](host, ids, conngater, + + opts := []p2p.Option[p2p.ClientParameters]{ p2p.WithParams(cfg.Client), p2p.WithNetworkID[p2p.ClientParameters](network.String()), p2p.WithChainID(network.String()), p2p.WithPeerIDStore[p2p.ClientParameters](pidstore), - ) + } + if MetricsEnabled { + opts = append(opts, p2p.WithMetrics[p2p.ClientParameters]()) + } + + exchange, err := p2p.NewExchange[H](host, ids, conngater, opts...) if err != nil { return nil, err } @@ -68,10 +74,12 @@ func newSyncer[H libhead.Header[H]]( sub libhead.Subscriber[H], cfg Config, ) (*sync.Syncer[H], *modfraud.ServiceBreaker[*sync.Syncer[H], H], error) { - syncer, err := sync.NewSyncer[H](ex, store, sub, - sync.WithParams(cfg.Syncer), - sync.WithBlockTime(modp2p.BlockTime), - ) + opts := []sync.Option{sync.WithParams(cfg.Syncer), sync.WithBlockTime(modp2p.BlockTime)} + if MetricsEnabled { + opts = append(opts, sync.WithMetrics()) + } + + syncer, err := sync.NewSyncer[H](ex, store, sub, opts...) if err != nil { return nil, nil, err } @@ -96,6 +104,13 @@ func newInitStore[H libhead.Header[H]]( return nil, err } + if MetricsEnabled { + err = libhead.WithMetrics[H](s) + if err != nil { + return nil, err + } + } + trustedHash, err := cfg.trustedHash(net) if err != nil { return nil, err diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go index 43b5646a77..f807796eb6 100644 --- a/nodebuilder/header/header.go +++ b/nodebuilder/header/header.go @@ -19,10 +19,10 @@ type Module interface { // GetByHash returns the header of the given hash from the node's header store. GetByHash(ctx context.Context, hash libhead.Hash) (*header.ExtendedHeader, error) - // GetVerifiedRangeByHeight returns the given range (from:to) of ExtendedHeaders + // GetRangeByHeight returns the given range (from:to) of ExtendedHeaders // from the node's header store and verifies that the returned headers are // adjacent to each other. - GetVerifiedRangeByHeight( + GetRangeByHeight( ctx context.Context, from *header.ExtendedHeader, to uint64, @@ -54,7 +54,7 @@ type API struct { ctx context.Context, hash libhead.Hash, ) (*header.ExtendedHeader, error) `perm:"read"` - GetVerifiedRangeByHeight func( + GetRangeByHeight func( context.Context, *header.ExtendedHeader, uint64, @@ -72,12 +72,12 @@ func (api *API) GetByHash(ctx context.Context, hash libhead.Hash) (*header.Exten return api.Internal.GetByHash(ctx, hash) } -func (api *API) GetVerifiedRangeByHeight( +func (api *API) GetRangeByHeight( ctx context.Context, from *header.ExtendedHeader, to uint64, ) ([]*header.ExtendedHeader, error) { - return api.Internal.GetVerifiedRangeByHeight(ctx, from, to) + return api.Internal.GetRangeByHeight(ctx, from, to) } func (api *API) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 7d6661ff5d..b0d2b961d9 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -68,19 +67,19 @@ func (mr *MockModuleMockRecorder) GetByHeight(arg0, arg1 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByHeight", reflect.TypeOf((*MockModule)(nil).GetByHeight), arg0, arg1) } -// GetVerifiedRangeByHeight mocks base method. -func (m *MockModule) GetVerifiedRangeByHeight(arg0 context.Context, arg1 *header.ExtendedHeader, arg2 uint64) ([]*header.ExtendedHeader, error) { +// GetRangeByHeight mocks base method. +func (m *MockModule) GetRangeByHeight(arg0 context.Context, arg1 *header.ExtendedHeader, arg2 uint64) ([]*header.ExtendedHeader, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVerifiedRangeByHeight", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "GetRangeByHeight", arg0, arg1, arg2) ret0, _ := ret[0].([]*header.ExtendedHeader) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetVerifiedRangeByHeight indicates an expected call of GetVerifiedRangeByHeight. -func (mr *MockModuleMockRecorder) GetVerifiedRangeByHeight(arg0, arg1, arg2 interface{}) *gomock.Call { +// GetRangeByHeight indicates an expected call of GetRangeByHeight. +func (mr *MockModuleMockRecorder) GetRangeByHeight(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerifiedRangeByHeight", reflect.TypeOf((*MockModule)(nil).GetVerifiedRangeByHeight), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeByHeight", reflect.TypeOf((*MockModule)(nil).GetRangeByHeight), arg0, arg1, arg2) } // LocalHead mocks base method. diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 557cbcfab7..4be25f7125 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -50,8 +50,12 @@ func ConstructModule[H libhead.Header[H]](tp node.Type, cfg *Config) fx.Option { }), )), fx.Provide(fx.Annotate( - func(ps *pubsub.PubSub, network modp2p.Network) *p2p.Subscriber[H] { - return p2p.NewSubscriber[H](ps, header.MsgID, network.String()) + func(ps *pubsub.PubSub, network modp2p.Network) (*p2p.Subscriber[H], error) { + opts := []p2p.SubscriberOption{p2p.WithSubscriberNetworkID(network.String())} + if MetricsEnabled { + opts = append(opts, p2p.WithSubscriberMetrics()) + } + return p2p.NewSubscriber[H](ps, header.MsgID, opts...) }, fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber[H]) error { return sub.Start(ctx) @@ -62,14 +66,20 @@ func ConstructModule[H libhead.Header[H]](tp node.Type, cfg *Config) fx.Option { )), fx.Provide(fx.Annotate( func( + cfg Config, host host.Host, store libhead.Store[H], network modp2p.Network, ) (*p2p.ExchangeServer[H], error) { - return p2p.NewExchangeServer[H](host, store, + opts := []p2p.Option[p2p.ServerParameters]{ p2p.WithParams(cfg.Server), p2p.WithNetworkID[p2p.ServerParameters](network.String()), - ) + } + if MetricsEnabled { + opts = append(opts, p2p.WithMetrics[p2p.ServerParameters]()) + } + + return p2p.NewExchangeServer[H](host, store, opts...) }, fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer[H]) error { return server.Start(ctx) diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go deleted file mode 100644 index decedcad85..0000000000 --- a/nodebuilder/header/opts.go +++ /dev/null @@ -1,28 +0,0 @@ -package header - -import ( - libhead "github.com/celestiaorg/go-header" - "github.com/celestiaorg/go-header/p2p" - "github.com/celestiaorg/go-header/sync" - - "github.com/celestiaorg/celestia-node/header" -) - -// WithMetrics provides sets `MetricsEnabled` to true on ClientParameters for the header exchange -func WithMetrics( - store libhead.Store[*header.ExtendedHeader], - ex libhead.Exchange[*header.ExtendedHeader], - sync *sync.Syncer[*header.ExtendedHeader], -) error { - if p2pex, ok := ex.(*p2p.Exchange[*header.ExtendedHeader]); ok { - if err := p2pex.InitMetrics(); err != nil { - return err - } - } - - if err := sync.InitMetrics(); err != nil { - return err - } - - return libhead.WithMetrics[*header.ExtendedHeader](store) -} diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go index 2b208cb88d..072ef070c6 100644 --- a/nodebuilder/header/service.go +++ b/nodebuilder/header/service.go @@ -52,12 +52,12 @@ func (s *Service) GetByHash(ctx context.Context, hash libhead.Hash) (*header.Ext return s.store.Get(ctx, hash) } -func (s *Service) GetVerifiedRangeByHeight( +func (s *Service) GetRangeByHeight( ctx context.Context, from *header.ExtendedHeader, to uint64, ) ([]*header.ExtendedHeader, error) { - return s.store.GetVerifiedRange(ctx, from, to) + return s.store.GetRangeByHeight(ctx, from, to) } func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index d56125209c..9767081971 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -24,7 +24,7 @@ import ( "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/das" - modheader "github.com/celestiaorg/celestia-node/nodebuilder/header" + modhead "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/share" @@ -72,6 +72,10 @@ func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { // WithMetrics enables metrics exporting for the node. func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Option { + // TODO @renaynay: this will be refactored when there is more granular + // control over which module to enable metrics for + modhead.MetricsEnabled = true + baseComponents := fx.Options( fx.Supply(metricOpts), fx.Invoke(initializeMetrics), @@ -83,7 +87,6 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti }), fx.Invoke(fraud.WithMetrics[*header.ExtendedHeader]), fx.Invoke(node.WithMetrics), - fx.Invoke(modheader.WithMetrics), fx.Invoke(share.WithDiscoveryMetrics), ) From 9f2b74accfed10c447b972f8b86d71666df53f8f Mon Sep 17 00:00:00 2001 From: Borys Semerenko Date: Mon, 16 Oct 2023 13:06:59 +0300 Subject: [PATCH 0884/1008] misc(blob): Add debug logs for the GetAll method (#2749) Co-authored-by: Ryan Co-authored-by: ramin --- blob/service.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/blob/service.go b/blob/service.go index 88b6c63d3f..49bcee6af3 100644 --- a/blob/service.go +++ b/blob/service.go @@ -121,6 +121,10 @@ func (s *Service) GetAll(ctx context.Context, height uint64, namespaces []share. resultErr = make([]error, len(namespaces)) ) + for _, ns := range namespaces { + log.Debugw("performing GetAll request", "namespace", ns.String(), "height", height) + } + wg := sync.WaitGroup{} for i, namespace := range namespaces { wg.Add(1) @@ -131,6 +135,8 @@ func (s *Service) GetAll(ctx context.Context, height uint64, namespaces []share. resultErr[i] = fmt.Errorf("getting blobs for namespace(%s): %s", namespace.String(), err) return } + + log.Debugw("receiving blobs", "height", height, "total", len(blobs)) resultBlobs[i] = blobs }(i, namespace) } From b54f2b72c2d3d6b6dd251f446c6ab3259e0c68e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:52:31 +0000 Subject: [PATCH 0885/1008] chore(deps): Bump tspascoal/get-user-teams-membership from 2 to 3 (#2827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tspascoal/get-user-teams-membership](https://github.com/tspascoal/get-user-teams-membership) from 2 to 3.
    Release notes

    Sourced from tspascoal/get-user-teams-membership's releases.

    3.0.0

    What's Changed

    Full Changelog: https://github.com/tspascoal/get-user-teams-membership/compare/v2...v3.0.0

    v2.1.0

    What's Changed

    Features

    Dependency Updates

    New Contributors

    Full Changelog: https://github.com/tspascoal/get-user-teams-membership/compare/v2...v2.1.0

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tspascoal/get-user-teams-membership&package-manager=github_actions&previous-version=2&new-version=3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-label-automation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-label-automation.yml b/.github/workflows/issue-label-automation.yml index 965dbe8e9f..0d2d37ca49 100644 --- a/.github/workflows/issue-label-automation.yml +++ b/.github/workflows/issue-label-automation.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Check for External Contributor - uses: tspascoal/get-user-teams-membership@v2 + uses: tspascoal/get-user-teams-membership@v3 id: teamCheck with: username: ${{ github.actor }} From d16c35526fde51efa6e7bf5076fef4f8902c62ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:24:46 +0000 Subject: [PATCH 0886/1008] chore(deps): Bump github.com/celestiaorg/celestia-app from 1.0.0 to 1.1.0 (#2850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/celestiaorg/celestia-app](https://github.com/celestiaorg/celestia-app) from 1.0.0 to 1.1.0.
    Release notes

    Sourced from github.com/celestiaorg/celestia-app's releases.

    v1.1.0

    Upgrade Notice

    celestia-app v1.1.0 is compatible with celestia-app 1.0.0. Consensus nodes that are already running v1.0.0 don't need to upgrade to v1.1.0 with urgency. This release contains a fix so that Ledger hardware wallets with firmware 2.2.2 can sign transactions.

    What's Changed

    Full Changelog: https://github.com/celestiaorg/celestia-app/compare/v1.0.0...v1.1.0

    Commits
    • 2b8cc9e fix: restore ability to sign transaction with ledger firmware 2.2.2 (backport...
    • dafc6d5 chore: decrease SlashFractionDoubleSign to 2% (backport #2630) (#2666)
    • f2e5bce chore: increase MinDeposit from 1K to 10K TIA (backport #2631) (#2660)
    • 21c45aa chore: remove unnecessary legacy handler from qgb module (backport #2635) (#2...
    • a861911 ci: generate pre-built binaries for darwin and arm64 (backport #2654) (#2662)
    • 01dad84 fix: LatestAttestationNonce constant name typo (backport #2646) (#2653)
    • 3fa42b5 fix: specs for MaxDepositPeriod (backport #2626) (#2649)
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/celestiaorg/celestia-app&package-manager=go_modules&previous-version=1.0.0&new-version=1.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 30 ++++++++++++++--------------- go.sum | 60 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index dfae47fb29..fcd3c27a0d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.0.0 + github.com/celestiaorg/celestia-app v1.1.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.4.0 @@ -67,10 +67,10 @@ require ( go.uber.org/fx v1.20.0 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sync v0.4.0 golang.org/x/text v0.13.0 - google.golang.org/grpc v1.58.2 + google.golang.org/grpc v1.58.3 google.golang.org/protobuf v1.31.0 ) @@ -88,10 +88,10 @@ require ( ) require ( - cloud.google.com/go v0.110.6 // indirect + cloud.google.com/go v0.110.8 // indirect cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.1 // indirect + cloud.google.com/go/iam v1.1.2 // indirect cloud.google.com/go/storage v1.30.1 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -126,7 +126,7 @@ require ( github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.6 // indirect github.com/cosmos/ibc-go/v6 v6.2.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.1 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -178,8 +178,8 @@ require ( github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.1 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -313,7 +313,7 @@ require ( github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/zondax/hid v0.9.1 // indirect - github.com/zondax/ledger-go v0.14.1 // indirect + github.com/zondax/ledger-go v0.14.2 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect @@ -322,18 +322,18 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/tools v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/api v0.126.0 // indirect + google.golang.org/api v0.128.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect + google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 0c46be987a..1db8ac6d65 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q= -cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -116,8 +116,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= +cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -358,8 +358,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.0.0 h1:CJX0y3JE1gEKzb0Hr3/qS0Z/tZgYGJyCHJzc0+SVxYM= -github.com/celestiaorg/celestia-app v1.0.0/go.mod h1:8FN8f/iZC5D6A7QYJhK86nyRKrmQrzXFucV19vLv9Es= +github.com/celestiaorg/celestia-app v1.1.0 h1:8TM/8xnZ+xf7NfEjYZ9XBEGuVG1QT8hPD3uh05RYEsE= +github.com/celestiaorg/celestia-app v1.1.0/go.mod h1:1uAaesXbMkV6EwfBfULXBKYhjHP8DGSVpA3BfKM4QUw= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29/go.mod h1:xrICN0PBhp3AdTaZ8q4wS5Jvi32V02HNjaC2EsWiEKk= github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 h1:c4cMVLU2bGTesZW1ZVgeoCB++gOOJTF3OvBsqBvo6n0= @@ -494,8 +494,8 @@ github.com/cosmos/iavl v0.19.6 h1:XY78yEeNPrEYyNCKlqr9chrwoeSDJ0bV2VjocTk//OU= github.com/cosmos/iavl v0.19.6/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-go/v6 v6.2.0 h1:HKS5WNxQrlmjowHb73J9LqlNJfvTnvkbhXZ9QzNTU7Q= github.com/cosmos/ibc-go/v6 v6.2.0/go.mod h1:+S3sxcNwOhgraYDJAhIFDg5ipXHaUnJrg7tOQqGyWlc= -github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= -github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= +github.com/cosmos/ledger-cosmos-go v0.13.1 h1:12ac9+GwBb9BjP7X5ygpFk09Itwzjzfmg6A2CWFjoVs= +github.com/cosmos/ledger-cosmos-go v0.13.1/go.mod h1:5tv2RVJEd2+Y38TIQN4CRjJeQGyqOEiKJDfqhk5UjqE= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -877,8 +877,8 @@ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4= +github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -890,8 +890,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -2351,8 +2351,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= -github.com/zondax/ledger-go v0.14.1/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= +github.com/zondax/ledger-go v0.14.2 h1:NDaba434N7JUVKg4P4nFv6TOjSkUosYb9sdiw3c61Zk= +github.com/zondax/ledger-go v0.14.2/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= @@ -2529,8 +2529,8 @@ golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2659,8 +2659,8 @@ golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2965,8 +2965,8 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -3036,8 +3036,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3161,12 +3161,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -3212,8 +3212,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= -google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 0b854485d4ee7d84ffdaf8c2e8bd1fcedf940680 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 16 Oct 2023 15:46:01 +0200 Subject: [PATCH 0887/1008] fix(share/getters): short-circuit on empty root in ShrexGetter (#2846) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Should fix issue where GetEDS hangs on empty data root To test run: `celestia share get-eds “$(celestia header get-by-height 500 --node.store ~/.celestia-light-mocha-4 | jq .result)” --node.store ~/.celestia-light-mocha-4` with and without the PR --- share/getters/shrex.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 1c296a6a33..9576da252b 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -136,6 +136,10 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, header *header.ExtendedHeader }() dah := header.DAH + // short circuit if the data root is empty + if dah.Equals(share.EmptyRoot()) { + return share.EmptyExtendedDataSquare(), nil + } for { if ctx.Err() != nil { sg.metrics.recordEDSAttempt(ctx, attempt, false) From a7a17e4bb3209456867b099a9ca061e513b283e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Zdyba=C5=82?= Date: Tue, 17 Oct 2023 15:23:03 +0200 Subject: [PATCH 0888/1008] fix(blob): properly wrap error in GetAll method (#2853) Error returned by GetAll is not formatted correctly. It's especially important, because users of the method will most probably try to detect ErrBlobNotFound (to differentiate from other errors). --- blob/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blob/service.go b/blob/service.go index 49bcee6af3..67fcae349a 100644 --- a/blob/service.go +++ b/blob/service.go @@ -132,7 +132,7 @@ func (s *Service) GetAll(ctx context.Context, height uint64, namespaces []share. defer wg.Done() blobs, err := s.getBlobs(ctx, namespace, header) if err != nil { - resultErr[i] = fmt.Errorf("getting blobs for namespace(%s): %s", namespace.String(), err) + resultErr[i] = fmt.Errorf("getting blobs for namespace(%s): %w", namespace.String(), err) return } From dd53c3b318be3d9c51f9acd62db75c2a2bc288cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:09:44 +0000 Subject: [PATCH 0889/1008] chore(deps): Bump celestiaorg/.github from 0.2.3 to 0.2.8 (#2851) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hlib Kanunnikov --- .github/workflows/ci_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 271cab472f..6d641edc73 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -25,7 +25,7 @@ on: jobs: # Dockerfile Linting hadolint: - uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.3 # yamllint disable-line rule:line-length + uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.8 # yamllint disable-line rule:line-length with: dockerfile: Dockerfile @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.3 + - uses: celestiaorg/.github/.github/actions/yamllint@v0.2.8 markdown-lint: name: Markdown Lint @@ -68,7 +68,7 @@ jobs: uses: mathieudutour/github-tag-action@v6.1 - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@v0.2.3 + uses: celestiaorg/.github/.github/actions/version-release@v0.2.8 with: github_token: ${{ secrets.GITHUB_TOKEN }} default_bump: ${{ inputs.version }} From 40eea9084d7230538a9af33991af796a057a308d Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Wed, 18 Oct 2023 19:15:25 +0300 Subject: [PATCH 0890/1008] fix(p2p): Start listening to QUIC when CELESTIA_ENABLE_QUIC is set (#2857) --- nodebuilder/p2p/addrs.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nodebuilder/p2p/addrs.go b/nodebuilder/p2p/addrs.go index 528f58d722..27fbd244a3 100644 --- a/nodebuilder/p2p/addrs.go +++ b/nodebuilder/p2p/addrs.go @@ -19,11 +19,13 @@ func Listen(listen []string) func(h hst.Host) (err error) { return fmt.Errorf("failure to parse config.P2P.ListenAddresses: %s", err) } - // TODO(@WonderTan): Remove this check when QUIC is stable - if slices.ContainsFunc(maddr.Protocols(), func(p ma.Protocol) bool { - return p.Code == ma.P_QUIC_V1 || p.Code == ma.P_WEBTRANSPORT - }) { - continue + if !enableQUIC { + // TODO(@WonderTan): Remove this check when QUIC is stable + if slices.ContainsFunc(maddr.Protocols(), func(p ma.Protocol) bool { + return p.Code == ma.P_QUIC_V1 || p.Code == ma.P_WEBTRANSPORT + }) { + continue + } } maListen = append(maListen, maddr) From 2c5eef712ded6f5b383990d55df73da5d143aada Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 18 Oct 2023 18:16:14 +0200 Subject: [PATCH 0891/1008] fix(core): Increase listener timeout to 3x block time (#2852) Block time can vary quite a bit so do not trigger re-subscription unless there's a significant lag (3x blocktime). This issue was found by bridge node runners on mocha-4 where block time sometimes exceeded 20s --- core/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/listener.go b/core/listener.go index 1c79fbbe71..de0d84ed35 100644 --- a/core/listener.go +++ b/core/listener.go @@ -57,7 +57,7 @@ func NewListener( hashBroadcaster: hashBroadcaster, construct: construct, store: store, - listenerTimeout: 2 * blocktime, + listenerTimeout: 5 * blocktime, } } From db16322eef01538ef40a103d657dbeafaa27bd8f Mon Sep 17 00:00:00 2001 From: Nguyen Nhu Viet Date: Thu, 19 Oct 2023 16:35:47 +0200 Subject: [PATCH 0892/1008] chore: fix docker builds for arm64 (#2862) ## Overview Now, this is finally fixed Ref: https://github.com/rollkit/local-celestia-devnet/pull/63 Closes https://github.com/celestiaorg/devops/issues/527 ## Checklist - [ ] New and updated code has appropriate documentation - [ ] New and updated code has new and/or updated testing - [ ] Required CI checks are passing - [ ] Visual proof for any user facing features like CLI or documentation updates - [ ] Linked issues closed with keywords --- Dockerfile | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 403691d4a7..a3ac41f7aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,15 @@ FROM --platform=$BUILDPLATFORM docker.io/golang:1.21-alpine3.18 as builder +ARG TARGETPLATFORM +ARG BUILDPLATFORM +ARG TARGETOS +ARG TARGETARCH + +ENV CGO_ENABLED=0 +ENV GO111MODULE=on + # hadolint ignore=DL3018 -RUN apk update && apk add --no-cache \ +RUN uname -a && apk update && apk add --no-cache \ bash \ gcc \ git \ @@ -13,9 +21,11 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . -RUN make build && make cel-key +RUN uname -a &&\ + CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + make build && make cel-key -FROM --platform=$BUILDPLATFORM docker.io/alpine:3.18.4 +FROM docker.io/alpine:3.18.4 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 @@ -28,7 +38,8 @@ ENV NODE_TYPE bridge ENV P2P_NETWORK mocha # hadolint ignore=DL3018 -RUN apk update && apk add --no-cache \ +RUN uname -a &&\ + apk update && apk add --no-cache \ bash \ curl \ jq \ From 39b5c77faaaef8fd77baf0291e5330ad821bc350 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 23 Oct 2023 08:05:09 -0400 Subject: [PATCH 0893/1008] override organization pull_request_template.md (#2805) As discussed, overrides the organization wide `.github/pull_request_template.md` with the checklist everyone ignores, instead favoring just a comment when opening the PR and a request to add a reference to an issue for a PR check we'll add shortly, opening a PR should just look like this now: pr-message --- .github/pull_request_template.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..61c286f936 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ + From 1a5d63cee99d562d2fd50717205928fa5a8f75b1 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 23 Oct 2023 09:24:12 -0400 Subject: [PATCH 0894/1008] (ci) matrix tests for different OS (#2855) Refs https://github.com/celestiaorg/celestia-node/issues/1584 Lets run our test suite on multi configurations as they sometimes pass one not other (as reported by @vgonkivs) - Task: run tests on > 1 OS (previously ubuntu-latest only) now `ubuntu-latest` and `macos-latest` - currently ONLY for the unit tests (do we want to change this for all the tests?) - also makes a small unrequested tweak to make the execution in go-ci run AFTER the mod and lint checks, so it'll pause if those two fail and fail the entire pipeline faster. Save some wasted compute time, which will be at least x2 worse once we matrix these example run: https://github.com/celestiaorg/celestia-node/actions/runs/6559066381/job/17813974060 --- .github/workflows/go-ci.yml | 12 +++++++++++- blob/service_test.go | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 288a9cfb2f..c12fd409d3 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -44,8 +44,14 @@ jobs: run: git diff --exit-code test_coverage: + needs: [lint, go_mod_tidy_check] name: Unit Tests Coverage - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + env: + OS: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -61,10 +67,13 @@ jobs: - name: upload coverage uses: codecov/codecov-action@v3.1.4 with: + env_vars: OS token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.txt + name: coverage-${{ matrix.os }} unit_race_test: + needs: [lint, go_mod_tidy_check] name: Run Unit Tests with Race Detector runs-on: ubuntu-latest @@ -80,6 +89,7 @@ jobs: run: make test-unit-race integration_test: + needs: [lint, go_mod_tidy_check] name: Run Integration Tests runs-on: ubuntu-latest diff --git a/blob/service_test.go b/blob/service_test.go index 6777084eb4..152058bed0 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -1,3 +1,5 @@ +//go:build linux + package blob import ( From e8eaf4a9401a0c1207396b9e942712c807ff2dbc Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 23 Oct 2023 09:35:27 -0400 Subject: [PATCH 0895/1008] update golangci-lint (#2868) - updates `golangci-lint` to 1.55 - removes "double cache" when setting up golang, thus removing linter noise (`Cannot open file...file exists`) so easier to see actual lint errors closes https://github.com/celestiaorg/celestia-node/issues/2590 --- .github/workflows/go-ci.yml | 4 +++- share/availability/test/corrupt_data.go | 4 ++-- share/eds/cache/metrics.go | 8 +++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index c12fd409d3..9d80281c6c 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -25,7 +25,9 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.54.2 + version: v1.55 + skip-pkg-cache: true + skip-build-cache: true go_mod_tidy_check: name: Go Mod Tidy Check diff --git a/share/availability/test/corrupt_data.go b/share/availability/test/corrupt_data.go index f0bd8fbbc5..1ff553f8b3 100644 --- a/share/availability/test/corrupt_data.go +++ b/share/availability/test/corrupt_data.go @@ -63,7 +63,7 @@ func (fb FraudulentBlockstore) Has(context.Context, cid.Cid) (bool, error) { func (fb FraudulentBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { key := cid.String() if fb.Attacking { - key = "corrupt" + key + key = "corrupt_get" + key } data, err := fb.Datastore.Get(ctx, ds.NewKey(key)) @@ -76,7 +76,7 @@ func (fb FraudulentBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Blo func (fb FraudulentBlockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { key := cid.String() if fb.Attacking { - key = "corrupt" + key + key = "corrupt_size" + key } return fb.Datastore.GetSize(ctx, ds.NewKey(key)) diff --git a/share/eds/cache/metrics.go b/share/eds/cache/metrics.go index 24056aa553..b2e3bec8d8 100644 --- a/share/eds/cache/metrics.go +++ b/share/eds/cache/metrics.go @@ -18,19 +18,21 @@ type metrics struct { } func newMetrics(bc *AccessorCache) (*metrics, error) { - evictedCounter, err := meter.Int64Counter("eds_blockstore_cache_"+bc.name+"_evicted_counter", + metricsPrefix := "eds_blockstore_cache_" + bc.name + + evictedCounter, err := meter.Int64Counter(metricsPrefix+"_evicted_counter", metric.WithDescription("eds blockstore cache evicted event counter")) if err != nil { return nil, err } - getCounter, err := meter.Int64Counter("eds_blockstore_cache_"+bc.name+"_get_counter", + getCounter, err := meter.Int64Counter(metricsPrefix+"_get_counter", metric.WithDescription("eds blockstore cache evicted event counter")) if err != nil { return nil, err } - cacheSize, err := meter.Int64ObservableGauge("eds_blockstore_cache_"+bc.name+"_size", + cacheSize, err := meter.Int64ObservableGauge(metricsPrefix+"_size", metric.WithDescription("total amount of items in blockstore cache"), ) if err != nil { From 7ac72876afa0b6ead00651c00c7fec9d7142e1c9 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 24 Oct 2023 12:49:44 +0300 Subject: [PATCH 0896/1008] fix(docgen): fix das naming for the openrpc spec (#2870) --- nodebuilder/default_services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/default_services.go b/nodebuilder/default_services.go index 0373e44244..430f6ba66b 100644 --- a/nodebuilder/default_services.go +++ b/nodebuilder/default_services.go @@ -18,7 +18,7 @@ var PackageToAPI = map[string]interface{}{ "state": &state.API{}, "share": &share.API{}, "header": &header.API{}, - "daser": &das.API{}, + "das": &das.API{}, "p2p": &p2p.API{}, "blob": &blob.API{}, "node": &node.API{}, From 43e6bbf592f47786bf0b8523e39aa256fe368105 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 24 Oct 2023 14:08:35 +0300 Subject: [PATCH 0897/1008] chore(nodebuilder/das): change daser module name to das (#2871) --- nodebuilder/das/module.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodebuilder/das/module.go b/nodebuilder/das/module.go index d9f7e700e2..0545fffc27 100644 --- a/nodebuilder/das/module.go +++ b/nodebuilder/das/module.go @@ -38,7 +38,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { switch tp { case node.Light, node.Full: return fx.Module( - "daser", + "das", baseComponents, fx.Provide(fx.Annotate( newDASer, @@ -56,7 +56,7 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { ) case node.Bridge: return fx.Module( - "daser", + "das", baseComponents, fx.Provide(newDaserStub), ) From 565691f713f5e7abc233b9ada95f53a2ec34f216 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 25 Oct 2023 23:06:50 +0300 Subject: [PATCH 0898/1008] tests(share/byzantine): extend befp tests (#2864) --- share/eds/byzantine/bad_encoding.go | 48 +++++-- share/eds/byzantine/bad_encoding_test.go | 153 +++++++++++++++++++++-- 2 files changed, 181 insertions(+), 20 deletions(-) diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index e3a862e38a..fbb6b592ea 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -102,15 +102,31 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { return nil } +var ( + errHeightMismatch = errors.New("height reported in proof does not match with the header's height") + errIncorrectIndex = errors.New("row/col index is more then the roots amount") + errIncorrectAmountOfShares = errors.New("incorrect amount of shares") + errIncorrectShare = errors.New("incorrect share received") + errNMTTreeRootsMatch = errors.New("recomputed root matches the DAH root") +) + +var ( + invalidProofPrefix = fmt.Sprintf("invalid %s proof", BadEncoding) +) + // Validate ensures that fraud proof is correct. // Validate checks that provided Merkle Proofs correspond to the shares, // rebuilds bad row or col from received shares, computes Merkle Root // and compares it with block's Merkle Root. func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { if hdr.Height() != p.BlockHeight { - return fmt.Errorf("incorrect block height during BEFP validation: expected %d, got %d", - p.BlockHeight, hdr.Height(), + log.Debugf("%s: %s. expected block's height: %d, got: %d", + invalidProofPrefix, + errHeightMismatch, + hdr.Height(), + p.BlockHeight, ) + return errHeightMismatch } if len(hdr.DAH.RowRoots) != len(hdr.DAH.ColumnRoots) { @@ -132,9 +148,10 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { } if int(p.Index) >= len(merkleRoots) { - return fmt.Errorf("invalid %s proof: index out of bounds (%d >= %d)", - BadEncoding, int(p.Index), len(merkleRoots), + log.Debugf("%s:%s (%d >= %d)", + invalidProofPrefix, errIncorrectIndex, int(p.Index), len(merkleRoots), ) + return errIncorrectIndex } if len(p.Shares) != len(merkleRoots) { @@ -142,9 +159,10 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { // column, it should exactly match the number of row roots. In this // context, the number of row roots is the width of the extended data // square. - return fmt.Errorf("invalid %s proof: incorrect number of shares %d != %d", - BadEncoding, len(p.Shares), len(merkleRoots), + log.Infof("%s: %s (%d >= %d)", + invalidProofPrefix, errIncorrectAmountOfShares, int(p.Index), len(merkleRoots), ) + return errIncorrectAmountOfShares } odsWidth := uint64(len(merkleRoots) / 2) @@ -160,7 +178,9 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { } if amount < odsWidth { - return errors.New("fraud: invalid proof: not enough shares provided to reconstruct row/col") + log.Debugf("%s: %s. not enough shares provided to reconstruct row/col", + invalidProofPrefix, errIncorrectAmountOfShares) + return errIncorrectAmountOfShares } // verify that Merkle proofs correspond to particular shares. @@ -171,7 +191,8 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { } // validate inclusion of the share into one of the DAHeader roots if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(merkleRoots[index])); !ok { - return fmt.Errorf("invalid %s proof: incorrect share received at index %d", BadEncoding, index) + log.Debugf("%s: %s at index %d", invalidProofPrefix, errIncorrectShare, index) + return errIncorrectShare } // NMTree commits the additional namespace while rsmt2d does not know about, so we trim it // this is ugliness from NMTWrapper that we have to embrace ¯\_(ツ)_/¯ @@ -184,7 +205,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { // the row/col can't be reconstructed, or the building of NMTree fails. rebuiltShares, err := codec.Decode(shares) if err != nil { - log.Infow("failed to decode shares at height", + log.Debugw("failed to decode shares at height", "height", hdr.Height(), "err", err, ) return nil @@ -192,7 +213,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0:odsWidth]) if err != nil { - log.Infow("failed to encode shares at height", + log.Debugw("failed to encode shares at height", "height", hdr.Height(), "err", err, ) return nil @@ -203,7 +224,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { for _, share := range rebuiltShares { err = tree.Push(share) if err != nil { - log.Infow("failed to build a tree from the reconstructed shares at height", + log.Debugw("failed to build a tree from the reconstructed shares at height", "height", hdr.Height(), "err", err, ) return nil @@ -212,7 +233,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { expectedRoot, err := tree.Root() if err != nil { - log.Infow("failed to build a tree root at height", + log.Debugw("failed to build a tree root at height", "height", hdr.Height(), "err", err, ) return nil @@ -226,7 +247,8 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error { // comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block. if bytes.Equal(expectedRoot, root) { - return fmt.Errorf("invalid %s proof: recomputed Merkle root matches the DAH's row/column root", BadEncoding) + log.Debugf("invalid %s proof:%s", BadEncoding, errNMTTreeRootsMatch) + return errNMTTreeRootsMatch } return nil } diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 3e245c1ab3..e7032107ca 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -5,11 +5,12 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" core "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/test/util/malicious" + "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" @@ -19,8 +20,8 @@ import ( "github.com/celestiaorg/celestia-node/share/sharetest" ) -func TestBadEncodingFraudProof(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) +func TestBEFP_Validate(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer t.Cleanup(cancel) bServ := ipld.NewMemBlockservice() @@ -37,10 +38,115 @@ func TestBadEncodingFraudProof(t *testing.T) { errByz := NewErrByzantine(ctx, bServ, &dah, errRsmt2d) befp := CreateBadEncodingProof([]byte("hash"), 0, errByz) - err = befp.Validate(&header.ExtendedHeader{ - DAH: &dah, - }) - assert.NoError(t, err) + + var test = []struct { + name string + prepareFn func() error + expectedResult func(error) + }{ + { + name: "valid BEFP", + prepareFn: func() error { + return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + }, + expectedResult: func(err error) { + require.NoError(t, err) + }, + }, + { + name: "invalid BEFP for valid header", + prepareFn: func() error { + validSquare := edstest.RandEDS(t, 2) + validDah, err := da.NewDataAvailabilityHeader(validSquare) + require.NoError(t, err) + err = ipld.ImportEDS(ctx, validSquare, bServ) + require.NoError(t, err) + validShares := validSquare.Flattened() + errInvalidByz := NewErrByzantine(ctx, bServ, &validDah, + &rsmt2d.ErrByzantineData{ + Axis: rsmt2d.Row, + Index: 0, + Shares: validShares[0:4], + }, + ) + invalidBefp := CreateBadEncodingProof([]byte("hash"), 0, errInvalidByz) + return invalidBefp.Validate(&header.ExtendedHeader{DAH: &validDah}) + }, + expectedResult: func(err error) { + require.ErrorIs(t, err, errNMTTreeRootsMatch) + }, + }, + { + name: "incorrect share with Proof", + prepareFn: func() error { + befp, ok := befp.(*BadEncodingProof) + require.True(t, ok) + befp.Shares[0].Share = befp.Shares[1].Share + return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + }, + expectedResult: func(err error) { + require.ErrorIs(t, err, errIncorrectShare) + }, + }, + { + name: "invalid amount of shares", + prepareFn: func() error { + befp, ok := befp.(*BadEncodingProof) + require.True(t, ok) + befp.Shares = befp.Shares[0 : len(befp.Shares)/2] + return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + }, + expectedResult: func(err error) { + require.ErrorIs(t, err, errIncorrectAmountOfShares) + }, + }, + { + name: "not enough shares to recompute the root", + prepareFn: func() error { + befp, ok := befp.(*BadEncodingProof) + require.True(t, ok) + befp.Shares[0] = nil + return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + }, + expectedResult: func(err error) { + require.ErrorIs(t, err, errIncorrectAmountOfShares) + }, + }, + { + name: "index out of bounds", + prepareFn: func() error { + befp, ok := befp.(*BadEncodingProof) + require.True(t, ok) + befpCopy := *befp + befpCopy.Index = 100 + return befpCopy.Validate(&header.ExtendedHeader{DAH: &dah}) + }, + expectedResult: func(err error) { + require.ErrorIs(t, err, errIncorrectIndex) + }, + }, + { + name: "heights mismatch", + prepareFn: func() error { + return befp.Validate(&header.ExtendedHeader{ + RawHeader: core.Header{ + Height: 42, + }, + DAH: &dah, + }) + }, + expectedResult: func(err error) { + require.ErrorIs(t, err, errHeightMismatch) + }, + }, + } + + for _, tt := range test { + t.Run(tt.name, func(t *testing.T) { + err = tt.prepareFn() + tt.expectedResult(err) + }) + } } // TestIncorrectBadEncodingFraudProof asserts that BEFP is not generated for the correct data @@ -90,3 +196,36 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { err = proof.Validate(h) require.Error(t, err) } + +func TestBEFP_ValidateOutOfOrderShares(t *testing.T) { + // skipping it for now because `malicious` package has a small issue: Constructor does not apply + // passed options, so it's not possible to store shares and thus get proofs for them. + // should be ok once app team will fix it. + t.Skip() + eds := edstest.RandEDS(t, 16) + shares := eds.Flattened() + shares[0], shares[1] = shares[1], shares[0] // corrupting eds + bServ := ipld.NewMemBlockservice() + batchAddr := ipld.NewNmtNodeAdder(context.Background(), bServ, ipld.MaxSizeBatchOption(16*2)) + eds, err := rsmt2d.ImportExtendedDataSquare(shares, + share.DefaultRSMT2DCodec(), + malicious.NewConstructor(16, nmt.NodeVisitor(batchAddr.Visit)), + ) + require.NoError(t, err, "failure to recompute the extended data square") + + err = batchAddr.Commit() + require.NoError(t, err) + + dah, err := da.NewDataAvailabilityHeader(eds) + require.NoError(t, err) + + var errRsmt2d *rsmt2d.ErrByzantineData + err = eds.Repair(dah.RowRoots, dah.ColumnRoots) + require.ErrorAs(t, err, &errRsmt2d) + + errByz := NewErrByzantine(context.Background(), bServ, &dah, errRsmt2d) + + befp := CreateBadEncodingProof([]byte("hash"), 0, errByz) + err = befp.Validate(&header.ExtendedHeader{DAH: &dah}) + require.Error(t, err) +} From 7d545457e2d533863450baba2066101bb5ec8e7d Mon Sep 17 00:00:00 2001 From: ramin Date: Wed, 25 Oct 2023 22:49:31 -0400 Subject: [PATCH 0899/1008] signed binaries for goreleaser (#2869) mimics the signed binaries by goreleaser as implemented in `celestia-app` so node can also generate signed binaries @MSevey confirmed that we have an org level secret for `GPG_SIGNING_KEY` and `GPG_PASSPHRASE` so should require no repo setup implementation for app: https://github.com/celestiaorg/celestia-app/commit/934fdeda062229f5c150e9dce74f05b0557ea123 Will follow up with some work to ensure this workflow actually gets triggered (currently the steps before do not pass and generate binaries) closes: https://github.com/celestiaorg/celestia-node/issues/2679 --- .github/workflows/ci_release.yml | 7 +++++++ .goreleaser.yaml | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 6d641edc73..f795ca4a86 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -87,6 +87,12 @@ jobs: - uses: actions/setup-go@v4 with: go-version: 1.21 + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v4 + with: + gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} # Generate the binaries and release - uses: goreleaser/goreleaser-action@v5 with: @@ -95,6 +101,7 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} # TODO: permission issue, but not worth fixing as this should be refactored # into the celestiaorg/.github repo, at which point any permission issues will diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 70336dabc0..b229b4c348 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -45,6 +45,18 @@ archives: {{- if .Arm }}v{{ .Arm }}{{ end }} checksum: name_template: "checksums.txt" +signs: + - artifacts: checksum + args: + [ + "--batch", + "-u", + "{{ .Env.GPG_FINGERPRINT }}", + "--output", + "${signature}", + "--detach-sign", + "${artifact}", + ] snapshot: name_template: "{{ incpatch .Version }}-next" changelog: From aa6e0d8e2bc171f7937d90036154e31ed6c88898 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 26 Oct 2023 15:55:25 +0200 Subject: [PATCH 0900/1008] chore: bump go-header (#2877) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fcd3c27a0d..5a48d30251 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v1.1.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.4.0 + github.com/celestiaorg/go-header v0.4.1 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 1db8ac6d65..922f91385f 100644 --- a/go.sum +++ b/go.sum @@ -370,8 +370,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.4.0 h1:Ine/xpvFx8o9p6fXW+h2RSPp68rn7VUxTkW1okJxcEY= -github.com/celestiaorg/go-header v0.4.0/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.4.1 h1:bjbUcKDnhrJJ9EoE7vtPpgleNLVjc2S+cB4/qe8nQmo= +github.com/celestiaorg/go-header v0.4.1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= From 6a1e8044c892fa55a923256a8b25eb773460fb0c Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Thu, 26 Oct 2023 22:45:12 +0800 Subject: [PATCH 0901/1008] fix(core/listener): make listener to retry subscribing on errors (#2876) Resolves https://github.com/celestiaorg/celestia-node/issues/2875 --- core/listener.go | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/core/listener.go b/core/listener.go index de0d84ed35..1754cb62b3 100644 --- a/core/listener.go +++ b/core/listener.go @@ -20,7 +20,10 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) -var tracer = otel.Tracer("core/listener") +var ( + tracer = otel.Tracer("core/listener") + retrySubscriptionDelay = 5 * time.Second +) // Listener is responsible for listening to Core for // new block events and converting new Core blocks into @@ -94,18 +97,34 @@ func (cl *Listener) runSubscriber(ctx context.Context, sub <-chan types.EventDat // listener stopped because external context was canceled return } - log.Warnw("listener: subscriber error, resubscribing...", "err", err) - err = cl.fetcher.UnsubscribeNewBlockEvent(ctx) - if err != nil { - log.Errorw("listener: unsubscribe error", "err", err) + log.Warnw("listener: subscriber error, resubscribing...", "err", err) + sub = cl.resubscribe(ctx) + if sub == nil { return } + } +} - sub, err = cl.fetcher.SubscribeNewBlockEvent(ctx) - if err != nil { - log.Errorw("listener: resubscribe error", "err", err) - return +func (cl *Listener) resubscribe(ctx context.Context) <-chan types.EventDataSignedBlock { + err := cl.fetcher.UnsubscribeNewBlockEvent(ctx) + if err != nil { + log.Warnw("listener: unsubscribe", "err", err) + } + + ticker := time.NewTicker(retrySubscriptionDelay) + defer ticker.Stop() + for { + sub, err := cl.fetcher.SubscribeNewBlockEvent(ctx) + if err == nil { + return sub + } + log.Errorw("listener: resubscribe", "err", err) + + select { + case <-ctx.Done(): + return nil + case <-ticker.C: } } } From 8e2aaa40003d5639d131a863ae4507436e93d5be Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:51:25 +0200 Subject: [PATCH 0902/1008] chore(deps): Bumps app to v1.3.0 (#2881) --- core/listener_test.go | 5 +++++ go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/core/listener_test.go b/core/listener_test.go index eab634a330..9537860d78 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -169,8 +169,13 @@ func createListener( ) *Listener { p2pSub, err := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, p2p.WithSubscriberNetworkID(networkID)) require.NoError(t, err) + err = p2pSub.Start(ctx) require.NoError(t, err) + err = p2pSub.SetVerifier(func(ctx context.Context, msg *header.ExtendedHeader) error { + return nil + }) + require.NoError(t, err) t.Cleanup(func() { require.NoError(t, p2pSub.Stop(ctx)) }) diff --git a/go.mod b/go.mod index 5a48d30251..432ba0710d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.1.0 + github.com/celestiaorg/celestia-app v1.3.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.4.1 @@ -79,7 +79,7 @@ require ( github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2 // indirect github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.0 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect @@ -126,7 +126,7 @@ require ( github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.6 // indirect github.com/cosmos/ibc-go/v6 v6.2.0 // indirect - github.com/cosmos/ledger-cosmos-go v0.13.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.13.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -312,8 +312,8 @@ require ( github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20230818171029-f91ae536ca25 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect - github.com/zondax/hid v0.9.1 // indirect - github.com/zondax/ledger-go v0.14.2 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect diff --git a/go.sum b/go.sum index 922f91385f..4d555d3915 100644 --- a/go.sum +++ b/go.sum @@ -358,8 +358,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.1.0 h1:8TM/8xnZ+xf7NfEjYZ9XBEGuVG1QT8hPD3uh05RYEsE= -github.com/celestiaorg/celestia-app v1.1.0/go.mod h1:1uAaesXbMkV6EwfBfULXBKYhjHP8DGSVpA3BfKM4QUw= +github.com/celestiaorg/celestia-app v1.3.0 h1:vW1zMc1tQ216utzDOYSC3wGO023sKVt8+zujVXnXlOc= +github.com/celestiaorg/celestia-app v1.3.0/go.mod h1:zhdQIFGFZRRxrDVtFE4OFIT7/12RE8DRyfvNZdW8ceM= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29/go.mod h1:xrICN0PBhp3AdTaZ8q4wS5Jvi32V02HNjaC2EsWiEKk= github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 h1:c4cMVLU2bGTesZW1ZVgeoCB++gOOJTF3OvBsqBvo6n0= @@ -454,8 +454,8 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= -github.com/consensys/gnark-crypto v0.12.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= -github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= @@ -494,8 +494,8 @@ github.com/cosmos/iavl v0.19.6 h1:XY78yEeNPrEYyNCKlqr9chrwoeSDJ0bV2VjocTk//OU= github.com/cosmos/iavl v0.19.6/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-go/v6 v6.2.0 h1:HKS5WNxQrlmjowHb73J9LqlNJfvTnvkbhXZ9QzNTU7Q= github.com/cosmos/ibc-go/v6 v6.2.0/go.mod h1:+S3sxcNwOhgraYDJAhIFDg5ipXHaUnJrg7tOQqGyWlc= -github.com/cosmos/ledger-cosmos-go v0.13.1 h1:12ac9+GwBb9BjP7X5ygpFk09Itwzjzfmg6A2CWFjoVs= -github.com/cosmos/ledger-cosmos-go v0.13.1/go.mod h1:5tv2RVJEd2+Y38TIQN4CRjJeQGyqOEiKJDfqhk5UjqE= +github.com/cosmos/ledger-cosmos-go v0.13.2 h1:aY0KZSmUwNKbBm9OvbIjvf7Ozz2YzzpAbgvN2C8x2T0= +github.com/cosmos/ledger-cosmos-go v0.13.2/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -2349,10 +2349,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= -github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/ledger-go v0.14.2 h1:NDaba434N7JUVKg4P4nFv6TOjSkUosYb9sdiw3c61Zk= -github.com/zondax/ledger-go v0.14.2/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc= gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= From 1552e9e2c837ebabb49591202dd785e089f19728 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 30 Oct 2023 09:34:28 -0400 Subject: [PATCH 0903/1008] (ci) update github actions ci to use a single GO_VERSION variable (#2806) This sets a single variable (`GO_VERSION`) in the main `ci.yml` workflow, which will be the single place we set the current go version. We then run a `setup` job that sets an required output for subsequent jobs to use, adding: ``` with: go-version: ${{ needs.setup.outputs.go-version }} ``` Where we had previously either: 1) duplicated the GO_VERSION env var in that workflow 2) passed in `1.21` or whatever go version explicitly When the rpc openrpc.json workflow check changes are merged, this will also prevent another duplication of the go version appearing. refs #2698 --- .github/workflows/ci_release.yml | 20 ++++++++++++++++++-- .github/workflows/go-ci.yml | 18 ++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index f795ca4a86..71baf03050 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -9,7 +9,6 @@ on: - "v*" pull_request: workflow_dispatch: - # Inputs the workflow accepts. inputs: version: # Friendly description to be shown in the UI instead of 'name' @@ -23,6 +22,20 @@ on: - major jobs: + # set up go version for use through pipelines, setting + # variable one time and setting outputs to access passing it + # to other jobs + setup: + runs-on: ubuntu-latest + env: + # upgrade go version throughout pipeline here + GO_VERSION: "1.21" + outputs: + go-version: ${{ steps.set-vars.outputs.go-version }} + steps: + - id: set-vars + run: echo "go-version=${{env.GO_VERSION}}" >> "$GITHUB_OUTPUT" + # Dockerfile Linting hadolint: uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.8 # yamllint disable-line rule:line-length @@ -48,7 +61,10 @@ jobs: markdownlint --config .markdownlint.yaml '**/*.md' go-ci: + needs: setup uses: ./.github/workflows/go-ci.yml + with: + go-version: ${{ needs.setup.outputs.go-version }} # If this was a workflow dispatch event, we need to generate and push a tag # for goreleaser to grab @@ -86,7 +102,7 @@ jobs: - run: git fetch --force --tags - uses: actions/setup-go@v4 with: - go-version: 1.21 + go-version: ${{ needs.setup.outputs.go-version }} - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v4 diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 9d80281c6c..39405704eb 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -2,9 +2,11 @@ name: Go CI on: workflow_call: - -env: - GO_VERSION: '1.21' + inputs: + go-version: + description: 'Go version' + required: true + type: string concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -20,7 +22,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ inputs.go-version }} - name: golangci-lint uses: golangci/golangci-lint-action@v3.7.0 @@ -38,7 +40,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ inputs.go-version }} - run: go mod tidy @@ -61,7 +63,7 @@ jobs: - name: set up go uses: actions/setup-go@v4 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ inputs.go-version }} - name: run unit tests run: make test-unit @@ -85,7 +87,7 @@ jobs: - name: set up go uses: actions/setup-go@v4 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ inputs.go-version }} - name: execute test run run: make test-unit-race @@ -101,7 +103,7 @@ jobs: - name: set up go uses: actions/setup-go@v4 with: - go-version: ${{ env.GO_VERSION }} + go-version: ${{ inputs.go-version }} - name: Swamp Tests run: make test-swamp From f6a649e59c20c64b798a5d2314c3179033c3cc33 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 30 Oct 2023 14:56:33 +0100 Subject: [PATCH 0904/1008] chore(nodebuilder): bind OTLP error logging (#2861) A minor slight improvement. It was easy to miss errors in the log wall with the default logger. Here, I use our logging system. I tested it and it looks good --- nodebuilder/settings.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 9767081971..29019c7e77 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p/core/peer" "github.com/pyroscope-io/client/pyroscope" otelpyroscope "github.com/pyroscope-io/otel-profiling-go" @@ -208,5 +209,14 @@ func initializeMetrics( }, }) otel.SetMeterProvider(provider) + otel.SetErrorHandler(&loggingErrorHandler{}) return nil } + +var metricsLogger = logging.Logger("otlp") + +type loggingErrorHandler struct{} + +func (loggingErrorHandler) Handle(err error) { + metricsLogger.Error(err) +} From 62e1769a6c012d4d1aa2fd5ef654b3ddcaa95899 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 30 Oct 2023 13:03:16 -0400 Subject: [PATCH 0905/1008] (ci) mark flakey test suites as "continue-on-error: true" (#2874) in the interest of having a full pipeline for release 0.12.0, have marked the swamp and unit w/ race tests to `continue-on-error: true` so though the job will still fail in ci, the pipeline and dependent steps after those tests (namely version_bump + goreleaser) will still proceed as though they were successful, and we can get signed binaries with the tagged release --- .github/workflows/ci_release.yml | 18 ++++++++++++++---- .github/workflows/go-ci.yml | 3 +++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 71baf03050..2a45e8337c 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -32,10 +32,17 @@ jobs: GO_VERSION: "1.21" outputs: go-version: ${{ steps.set-vars.outputs.go-version }} + branch: ${{ steps.trim_ref.outputs.branch }} steps: - - id: set-vars + - name: Set go version + id: set-vars run: echo "go-version=${{env.GO_VERSION}}" >> "$GITHUB_OUTPUT" + - name: Trim branch name + id: trim_ref + run: | + echo "branch=$(${${{ github.ref }}:11})" >> $GITHUB_OUTPUT + # Dockerfile Linting hadolint: uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.8 # yamllint disable-line rule:line-length @@ -74,6 +81,7 @@ jobs: permissions: "write-all" steps: - uses: actions/checkout@v4 + - name: Bump version and push tag # Placing the if condition here is a workaround for needing to block # on this step during workflow dispatch events but the step not @@ -82,12 +90,10 @@ jobs: # in goreleaser not running either. if: ${{ github.event_name == 'workflow_dispatch' }} uses: mathieudutour/github-tag-action@v6.1 - - - name: Version Release - uses: celestiaorg/.github/.github/actions/version-release@v0.2.8 with: github_token: ${{ secrets.GITHUB_TOKEN }} default_bump: ${{ inputs.version }} + release_branches: ${{ needs.setup.outputs.branch }} # Generate the release with goreleaser to include pre-built binaries goreleaser: @@ -99,16 +105,20 @@ jobs: permissions: "write-all" steps: - uses: actions/checkout@v4 + - run: git fetch --force --tags + - uses: actions/setup-go@v4 with: go-version: ${{ needs.setup.outputs.go-version }} + - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v4 with: gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} + # Generate the binaries and release - uses: goreleaser/goreleaser-action@v5 with: diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 39405704eb..9aeef0d4da 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -91,6 +91,7 @@ jobs: - name: execute test run run: make test-unit-race + continue-on-error: true integration_test: needs: [lint, go_mod_tidy_check] @@ -107,6 +108,8 @@ jobs: - name: Swamp Tests run: make test-swamp + continue-on-error: true - name: Swamp Tests with Race Detector run: make test-swamp-race + continue-on-error: true From 20664789d8d5119ec197de304cb9ee103f80c4c1 Mon Sep 17 00:00:00 2001 From: Bao Pham <145053932+bao1029p@users.noreply.github.com> Date: Tue, 31 Oct 2023 01:48:48 +0700 Subject: [PATCH 0906/1008] refactor(nodebuilder/share/cmd)!: Take block height instead of `ExtendedHeader` as argument for share cmds (#2872) Co-authored-by: Bao Co-authored-by: heh im a handsome boi Replace extended headers with block height as an argument in shares cmd **Issues close** Close [#2848 ](https://github.com/celestiaorg/celestia-node/issues/2848) **Changes made** - Replace all uses of `extended header` as an argument with block height in `nodebuilder/share/cmd/share.go.` - Add logic to fetch header first with `block height` for --- nodebuilder/share/cmd/share.go | 54 +++++++++++++--------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/nodebuilder/share/cmd/share.go b/nodebuilder/share/cmd/share.go index 3d1af05289..b890f2d4c0 100644 --- a/nodebuilder/share/cmd/share.go +++ b/nodebuilder/share/cmd/share.go @@ -1,12 +1,14 @@ package cmd import ( + "context" "encoding/hex" - "encoding/json" + "fmt" "strconv" "github.com/spf13/cobra" + rpc "github.com/celestiaorg/celestia-node/api/rpc/client" cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" @@ -39,13 +41,8 @@ var sharesAvailableCmd = &cobra.Command{ } defer client.Close() - raw, err := parseJSON(args[0]) - if err != nil { - return err - } + eh, err := getExtendedHeaderFromCmdArg(cmd.Context(), client, args[0]) - var eh *header.ExtendedHeader - err = json.Unmarshal(raw, &eh) if err != nil { return err } @@ -72,7 +69,7 @@ var sharesAvailableCmd = &cobra.Command{ } var getSharesByNamespaceCmd = &cobra.Command{ - Use: "get-by-namespace [extended header, namespace]", + Use: "get-by-namespace (height | hash) namespace", Short: "Gets all shares from an EDS within the given namespace.", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -82,13 +79,8 @@ var getSharesByNamespaceCmd = &cobra.Command{ } defer client.Close() - raw, err := parseJSON(args[0]) - if err != nil { - return err - } + eh, err := getExtendedHeaderFromCmdArg(cmd.Context(), client, args[0]) - var eh *header.ExtendedHeader - err = json.Unmarshal(raw, &eh) if err != nil { return err } @@ -104,7 +96,7 @@ var getSharesByNamespaceCmd = &cobra.Command{ } var getShare = &cobra.Command{ - Use: "get-share [extended header, row, col]", + Use: "get-share (height | hash) row col", Short: "Gets a Share by coordinates in EDS.", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { @@ -114,13 +106,8 @@ var getShare = &cobra.Command{ } defer client.Close() - raw, err := parseJSON(args[0]) - if err != nil { - return err - } + eh, err := getExtendedHeaderFromCmdArg(cmd.Context(), client, args[0]) - var eh *header.ExtendedHeader - err = json.Unmarshal(raw, &eh) if err != nil { return err } @@ -158,8 +145,8 @@ var getShare = &cobra.Command{ } var getEDS = &cobra.Command{ - Use: "get-eds [extended header]", - Short: "Gets the full EDS identified by the given extended header", + Use: "get-eds (height | hash)", + Short: "Gets the full EDS identified by the given block height", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) @@ -168,13 +155,8 @@ var getEDS = &cobra.Command{ } defer client.Close() - raw, err := parseJSON(args[0]) - if err != nil { - return err - } + eh, err := getExtendedHeaderFromCmdArg(cmd.Context(), client, args[0]) - var eh *header.ExtendedHeader - err = json.Unmarshal(raw, &eh) if err != nil { return err } @@ -184,8 +166,14 @@ var getEDS = &cobra.Command{ }, } -func parseJSON(param string) (json.RawMessage, error) { - var raw json.RawMessage - err := json.Unmarshal([]byte(param), &raw) - return raw, err +func getExtendedHeaderFromCmdArg(ctx context.Context, client *rpc.Client, arg string) (*header.ExtendedHeader, error) { + hash, err := hex.DecodeString(arg) + if err == nil { + return client.Header.GetByHash(ctx, hash) + } + height, err := strconv.ParseUint(arg, 10, 64) + if err != nil { + return nil, fmt.Errorf("can't parse the height/hash argument: %w", err) + } + return client.Header.GetByHeight(ctx, height) } From 08f394d694a8bd2555af971783a39796af28b5a1 Mon Sep 17 00:00:00 2001 From: smuu <18609909+smuu@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:39:01 +0100 Subject: [PATCH 0907/1008] feat: celestia mainnet bootstrappers (#2888) --------- Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> --- nodebuilder/p2p/bootstrap.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index e3971753a1..3e9da1d77d 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -37,7 +37,17 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ - Mainnet: {}, + Mainnet: { + "/dns4/da-bridge-1.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWSqZaLcn5Guypo2mrHr297YPJnV8KMEMXNjs3qAS8msw8", + "/dns4/da-bridge-2.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWQpuTFELgsUypqp9N4a1rKBccmrmQVY8Em9yhqppTJcXf", + "/dns4/da-bridge-3.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWSGa4huD6ts816navn7KFYiStBiy5LrBQH1HuEahk4TzQ", + "/dns4/da-bridge-4.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWHBXCmXaUNat6ooynXG837JXPsZpSTeSzZx6DpgNatMmR", + "/dns4/da-bridge-5.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWDGTBK1a2Ru1qmnnRwP6Dmc44Zpsxi3xbgFk7ATEPfmEU", + "/dns4/da-bridge-6.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWLTUFyf3QEGqYkHWQS2yCtuUcL78vnKBdXU5gABM1YDeH", + "/dns4/da-full-1.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWKZCMcwGCYbL18iuw3YVpAZoyb1VBGbx9Kapsjw3soZgr", + "/dns4/da-full-2.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWE3fmRtHgfk9DCuQFfY3H3JYEnTU3xZozv1Xmo8KWrWbK", + "/dns4/da-full-3.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWK6Ftsd4XsWCsQZgZPNhTrE5urwmkoo5P61tGvnKmNVyv", + }, Arabica: { "/dns4/da-bridge.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWM3e9MWtyc8GkP8QRt74Riu17QuhGfZMytB2vq5NwkWAu", "/dns4/da-bridge-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWKj8mcdiBGxQRe1jqhaMnh2tGoC3rPDmr5UH2q8H4WA9M", From 7d9c59639c737409e6aaaf2bfc3ce1c5fe3d7bd2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:05:06 +0100 Subject: [PATCH 0908/1008] feat(nodebuilder/p2p): Add mainnet genesis hash (#2889) ![IMG_A965E35247A9-1](https://github.com/celestiaorg/celestia-node/assets/41963722/f2ff92df-567f-44fe-9378-0179aa14df6f) --- nodebuilder/p2p/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index c88ed53ef3..dcc19dfa49 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -23,7 +23,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ - Mainnet: "", + Mainnet: "6BE39EFD10BA412A9DB5288488303F5DD32CF386707A5BEF33617F4C43301872", Arabica: "5904E55478BA4B3002EE885621E007A2A6A2399662841912219AECD5D5CBE393", Mocha: "B93BBE20A0FBFDF955811B6420F8433904664D45DB4BF51022BE4200C1A1680D", Private: "", From ddd3d6a9d0fea7119b7f1effb3a8c8dd0570d2d7 Mon Sep 17 00:00:00 2001 From: ramin Date: Thu, 2 Nov 2023 18:46:32 +0100 Subject: [PATCH 0909/1008] chore(cmd): add a newline after printing token so it doesn't run into shell prompt (#2887) minor annoyance where running `auth admin` always has the token output run right into the next shell prompt. --- cmd/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/auth.go b/cmd/auth.go index 3006526b15..a637373242 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -55,7 +55,7 @@ func AuthCmd(fsets ...*flag.FlagSet) *cobra.Command { if err != nil { return err } - fmt.Printf("%s", token) + fmt.Printf("%s\n", token) return nil }, } From 33f5cd306502ffa39d941e042e624d80c786c89b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 12:20:06 +0000 Subject: [PATCH 0910/1008] chore(deps): Bump actions/setup-node from 3 to 4 (#2886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
    Release notes

    Sourced from actions/setup-node's releases.

    v4.0.0

    What's Changed

    In scope of this release we changed version of node runtime for action from node16 to node20 and updated dependencies in actions/setup-node#866

    Besides, release contains such changes as:

    New Contributors

    Full Changelog: https://github.com/actions/setup-node/compare/v3...v4.0.0

    v3.8.2

    What's Changed

    Full Changelog: https://github.com/actions/setup-node/compare/v3...v3.8.2

    v3.8.1

    What's Changed

    In scope of this release, the filter was removed within the cache-save step by @​dmitry-shibanov in actions/setup-node#831. It is filtered and checked in the toolkit/cache library.

    Full Changelog: https://github.com/actions/setup-node/compare/v3...v3.8.1

    v3.8.0

    What's Changed

    Bug fixes:

    Feature implementations:

    Documentation changes:

    Update dependencies:

    ... (truncated)

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 2a45e8337c..0e7cb044fa 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 - run: | From 38ef88387688f62a02e03220e9e1f5ec1b7fc3a6 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 9 Nov 2023 14:38:55 +0200 Subject: [PATCH 0911/1008] chore: increase lint timeout (#2917) Increasing timeout helps to avoid the `timeout exceeded` error. --- .github/workflows/go-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 9aeef0d4da..f2767618b4 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -27,6 +27,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: + args: --timeout 10m version: v1.55 skip-pkg-cache: true skip-build-cache: true From 806274b036d5f1a128ffb6cc570c2ec18ecbc761 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 9 Nov 2023 14:51:50 +0200 Subject: [PATCH 0912/1008] fix(test/daser): stop the eds store before finishing the test (#2918) --- share/availability/full/testing.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/share/availability/full/testing.go b/share/availability/full/testing.go index dd21d398c2..46e97581f2 100644 --- a/share/availability/full/testing.go +++ b/share/availability/full/testing.go @@ -55,5 +55,10 @@ func TestAvailability(t *testing.T, getter share.Getter) *ShareAvailability { require.NoError(t, err) err = store.Start(context.Background()) require.NoError(t, err) + + t.Cleanup(func() { + err = store.Stop(context.Background()) + require.NoError(t, err) + }) return NewShareAvailability(store, getter, disc) } From f96a46197b078b5bb06d13f202a8110cd877e4b8 Mon Sep 17 00:00:00 2001 From: Josh Stein <46639943+jcstein@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:56:22 +0100 Subject: [PATCH 0913/1008] chore: update link to node rpc docs (#2916) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89cb67dc15..3a552495ad 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ For more information on setting up a node and the hardware requirements needed, ## API docs -The celestia-node public API is documented [here](https://docs.celestia.org/category/node-api/). +The celestia-node public API is documented [here](https://node-rpc-docs.celestia.org/). ## Node types From 0c78183aa533edbb3924aba697aadeacf11d149a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:37:59 +0000 Subject: [PATCH 0914/1008] chore(deps): Bump crazy-max/ghaction-import-gpg from 4 to 6 (#2885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 4 to 6.
    Release notes

    Sourced from crazy-max/ghaction-import-gpg's releases.

    v6.0.0

    Full Changelog: https://github.com/crazy-max/ghaction-import-gpg/compare/v5.4.0...v6.0.0

    v5.4.0

    Full Changelog: https://github.com/crazy-max/ghaction-import-gpg/compare/v5.3.0...v5.4.0

    v5.3.0

    Full Changelog: https://github.com/crazy-max/ghaction-import-gpg/compare/v5.2.0...v5.3.0

    v5.2.0

    • Remove setOutput workaround by @​crazy-max (#152)
    • Bump @​actions/core from 1.9.0 to 1.10.0 (#147 #151)
    • Bump openpgp from 5.3.1 to 5.5.0 (#149)

    Full Changelog: https://github.com/crazy-max/ghaction-import-gpg/compare/v5.1.0...v5.2.0

    v5.1.0

    • Bump openpgp from 5.2.1 to 5.3.1 (#145)
    • Bump @​actions/core from 1.6.0 to 1.9.0 (#143)

    v5.0.0

    • Node 16 as default runtime (#136)
      • This requires a minimum Actions Runner version of v2.285.0, which is by default available in GHES 3.4 or later.

    v4.4.0

    • Use eslint prettier plugin (#132)
    • Bump openpgp from 5.2.0 to 5.2.1 (#131)
    • Bump minimist from 1.2.5 to 1.2.6 (#134)
    • Bump @​actions/exec from 1.1.0 to 1.1.1 (#133)

    v4.3.0

    • Update dev dependencies and workflow (#130)
    • Fix error deleting keys with short key id (#129)
    • Bump openpgp from 5.1.0 to 5.2.0 (#128)

    ... (truncated)

    Commits
    • 82a020f Merge pull request #182 from crazy-max/dependabot/github_actions/actions/chec...
    • 7ad3b9b Merge pull request #183 from crazy-max/update-node20
    • 40ca14f chore: node 20 as default runtime
    • 418bb95 chore: update generated content
    • ecf7766 chore: update dev dependencies
    • d3591c7 chore: update to node 20
    • 255def4 chore: update yarn to 3.6.3
    • 3a06279 codecov: update config
    • c0d4620 codecov: update config
    • f30daee Bump actions/checkout from 3 to 4
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crazy-max/ghaction-import-gpg&package-manager=github_actions&previous-version=4&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan --- .github/workflows/ci_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 0e7cb044fa..e4dfdf1be4 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -114,7 +114,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} From 62733627e0b25492999c34a556984b2bf91a2c93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:50:46 +0000 Subject: [PATCH 0915/1008] chore(deps): Bump golang.org/x/sync from 0.4.0 to 0.5.0 (#2900) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.4.0 to 0.5.0.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/sync&package-manager=go_modules&previous-version=0.4.0&new-version=0.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 432ba0710d..1174b79ade 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/sync v0.4.0 + golang.org/x/sync v0.5.0 golang.org/x/text v0.13.0 google.golang.org/grpc v1.58.3 google.golang.org/protobuf v1.31.0 diff --git a/go.sum b/go.sum index 4d555d3915..ffe39ebc70 100644 --- a/go.sum +++ b/go.sum @@ -2706,8 +2706,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 541cdaf87296d6778ae2f8d76aefc04f3cee5887 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:18:16 +0000 Subject: [PATCH 0916/1008] chore(deps): Bump github.com/gorilla/mux from 1.8.0 to 1.8.1 (#2899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.8.0 to 1.8.1.
    Release notes

    Sourced from github.com/gorilla/mux's releases.

    Release v1.8.1

    What's Changed

    New Contributors

    Full Changelog: https://github.com/gorilla/mux/compare/v1.8.0...v1.8.1

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/gorilla/mux&package-manager=go_modules&previous-version=1.8.0&new-version=1.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 1174b79ade..db0e3d2ddb 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 - github.com/gorilla/mux v1.8.0 + github.com/gorilla/mux v1.8.1 github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/imdario/mergo v0.3.16 diff --git a/go.sum b/go.sum index ffe39ebc70..f27ce66336 100644 --- a/go.sum +++ b/go.sum @@ -902,8 +902,9 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= From 5756ad10a44482c0858bff737f92782a7f1c187d Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 10 Nov 2023 09:46:41 +0000 Subject: [PATCH 0917/1008] chore(ci): disable fail-fast on matrix tests (#2919) Disable "fail-fast" so if the `ubuntu-latest` matrix unit test fails then it wont cancel the run of `macos-latest`, this will help with easing ability to merge as we've reduced the requirement for both to merge --- .github/workflows/go-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index f2767618b4..30e7a1294d 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -52,6 +52,7 @@ jobs: needs: [lint, go_mod_tidy_check] name: Unit Tests Coverage strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} From 36cdfb35a4d0ac4fb0c86161894bd2cc67818e41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:25:53 +0000 Subject: [PATCH 0918/1008] chore(deps): Bump golang.org/x/text from 0.13.0 to 0.14.0 (#2901) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.13.0 to 0.14.0.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/text&package-manager=go_modules&previous-version=0.13.0&new-version=0.14.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db0e3d2ddb..d6266d0314 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sync v0.5.0 - golang.org/x/text v0.13.0 + golang.org/x/text v0.14.0 google.golang.org/grpc v1.58.3 google.golang.org/protobuf v1.31.0 ) diff --git a/go.sum b/go.sum index f27ce66336..6e59b38907 100644 --- a/go.sum +++ b/go.sum @@ -2880,8 +2880,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 1ae18c9bae15cb390255644ff62e261e713fa0ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:38:19 +0000 Subject: [PATCH 0919/1008] chore(deps): Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#2902) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.7.0 to 1.8.0.
    Release notes

    Sourced from github.com/spf13/cobra's releases.

    v1.8.0

    ✨ Features

    🐛 Bug fixes

    🔧 Maintenance

    🧪 Testing & CI/CD

    ✏️ Documentation


    Thank you everyone who contributed to this release and all your hard work! Cobra and this community would never be possible without all of you!!!! 🐍

    Full Changelog: https://github.com/spf13/cobra/compare/v1.7.0...v1.8.0

    Commits
    • a0a6ae0 Improve API to get flag completion function (#2063)
    • 890302a Support usage as plugin for tools like kubectl (#2018)
    • 48cea5c build(deps): bump actions/checkout from 3 to 4 (#2028)
    • 22953d8 Replace all non-alphanumerics in active help env var program prefix (#1940)
    • 00b68a1 Add tests for flag completion registration (#2053)
    • b711e87 Don't complete --help flag when flag parsing disabled (#2061)
    • 8b1eba4 Fix linter errors (#2052)
    • 4cafa37 Allow running persistent run hooks of all parents (#2044)
    • 5c962a2 build(deps): bump github.com/cpuguy83/go-md2man/v2 from 2.0.2 to 2.0.3 (#2047)
    • efe8fa3 build(deps): bump actions/setup-go from 3 to 4 (#1934)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/spf13/cobra&package-manager=go_modules&previous-version=1.7.0&new-version=1.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d6266d0314..a3d1a8e694 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/pyroscope-io/client v0.7.2 github.com/pyroscope-io/otel-profiling-go v0.4.0 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 diff --git a/go.sum b/go.sum index 6e59b38907..a7ffc8dbff 100644 --- a/go.sum +++ b/go.sum @@ -500,8 +500,8 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= @@ -2199,8 +2199,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= From 1823aeedf9645eb2196827e1099088e0f720d053 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 10 Nov 2023 12:53:52 +0200 Subject: [PATCH 0920/1008] fix(blob/service): handle absence proof (#2911) Fixes #2860 --- blob/service.go | 7 +++++++ blob/service_test.go | 2 -- share/eds/utils.go | 2 +- share/getters/shrex.go | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/blob/service.go b/blob/service.go index 67fcae349a..79e7dd7937 100644 --- a/blob/service.go +++ b/blob/service.go @@ -219,6 +219,13 @@ func (s *Service) getByCommitment( ) for _, row := range namespacedShares { + if len(row.Shares) == 0 { + // the above condition means that we've faced with an Absence Proof. + // This Proof proves that the namespace was not found in the DAH, so + // we can return `ErrBlobNotFound`. + return nil, nil, ErrBlobNotFound + } + appShares, err := toAppShares(row.Shares...) if err != nil { return nil, nil, err diff --git a/blob/service_test.go b/blob/service_test.go index 152058bed0..6777084eb4 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -1,5 +1,3 @@ -//go:build linux - package blob import ( diff --git a/share/eds/utils.go b/share/eds/utils.go index 2133565b91..b897dd14b5 100644 --- a/share/eds/utils.go +++ b/share/eds/utils.go @@ -123,7 +123,7 @@ func CollectSharesByNamespace( rootCIDs := ipld.FilterRootByNamespace(root, namespace) if len(rootCIDs) == 0 { - return nil, nil + return []share.NamespacedRow{}, nil } errGroup, ctx := errgroup.WithContext(ctx) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 9576da252b..0586826e22 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -213,7 +213,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( dah := header.DAH roots := ipld.FilterRootByNamespace(dah, namespace) if len(roots) == 0 { - return nil, nil + return []share.NamespacedRow{}, nil } for { From 65f0f53dcd7bf25c009e19026aaf775204ac4fad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:03:54 +0000 Subject: [PATCH 0921/1008] chore(deps): Bump go.uber.org/fx from 1.20.0 to 1.20.1 (#2879) Bumps [go.uber.org/fx](https://github.com/uber-go/fx) from 1.20.0 to 1.20.1.
    Release notes

    Sourced from go.uber.org/fx's releases.

    v1.20.1

    Added

    • Provided, Decorated, Supplied, and Replaced events now include a trace of module locations through which the option was given to the App.
    • wasi support.
    Changelog

    Sourced from go.uber.org/fx's changelog.

    1.20.1 - 2023-10-17

    Added

    • Provided, Decorated, Supplied, and Replaced events now include a trace of module locations through which the option was given to the App.
    • wasi support.
    Commits
    • 9636854 Prepare release v1.20.1 (#1126)
    • 1a5cc04 Include Module Traces in Events (#1122)
    • bbb3783 chore(deps): Bump golang.org/x/net from 0.16.0 to 0.17.0 in /tools (#1124)
    • 4b3089c chore(deps): Bump golang.org/x/tools from 0.13.0 to 0.14.0 in /tools (#1123)
    • f594306 adding support for wasi (#1121)
    • 580de58 chore(deps): bump actions/checkout from 3 to 4 (#1115)
    • e03bc58 chore(deps): bump honnef.co/go/tools from 0.4.5 to 0.4.6 in /tools (#1119)
    • fe6a7db chore(deps): bump golang.org/x/tools from 0.12.0 to 0.13.0 in /tools (#1118)
    • 00970bd chore(deps): bump honnef.co/go/tools from 0.4.3 to 0.4.5 in /tools (#1112)
    • 391edcd Use Go 1.21 (#1111)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=go.uber.org/fx&package-manager=go_modules&previous-version=1.20.0&new-version=1.20.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a3d1a8e694..77ec4d5bfa 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.19.0 go.opentelemetry.io/otel/trace v1.19.0 go.opentelemetry.io/proto/otlp v1.0.0 - go.uber.org/fx v1.20.0 + go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 diff --git a/go.sum b/go.sum index a7ffc8dbff..ba656c61ff 100644 --- a/go.sum +++ b/go.sum @@ -2425,8 +2425,8 @@ go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= -go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= -go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= From 70d7a3992bc6f0473a1ffff8b91302971c49ae03 Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 10 Nov 2023 12:34:19 +0000 Subject: [PATCH 0922/1008] (ci) enable optional verbose logging on unit tests and upload artifacts (#2865) - Optionally enabled verbose logging for unit tests - adds ability to verbose log to a file, while sending "normal" quiet output to stdout - updates CI to upload the test.log artifact and store for 5 days for later debugging - local workflow and test runs will stay the same - only enabled for `make test-unit` for now as a proposal for how we achieve this, can expand to rest if we like it @renaynay and @vgonkivs If everyone likes, i can expand to all test cases fixes https://github.com/celestiaorg/celestia-node/issues/1211 --- .github/workflows/ci_release.yml | 10 ++++++++++ .github/workflows/go-ci.yml | 28 +++++++++++++++++++++++++++- Makefile | 9 ++++++++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index e4dfdf1be4..25970fdb11 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -33,6 +33,7 @@ jobs: outputs: go-version: ${{ steps.set-vars.outputs.go-version }} branch: ${{ steps.trim_ref.outputs.branch }} + debug: ${{ steps.debug.outputs.debug }} steps: - name: Set go version id: set-vars @@ -43,6 +44,15 @@ jobs: run: | echo "branch=$(${${{ github.ref }}:11})" >> $GITHUB_OUTPUT + - name: Set debug output + id: debug + run: | + if [[ "${{ runner.debug }}" == "true" ]]; then + echo "debug=true" >> $GITHUB_ENV + else + echo "debug=false" >> $GITHUB_ENV + fi + # Dockerfile Linting hadolint: uses: celestiaorg/.github/.github/workflows/reusable_dockerfile_lint.yml@v0.2.8 # yamllint disable-line rule:line-length diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 30e7a1294d..d27e08e516 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -13,7 +13,22 @@ concurrency: cancel-in-progress: true jobs: + setup: + runs-on: ubuntu-latest + outputs: + debug: ${{ steps.debug.outputs.debug }} + steps: + - name: Set debug output + id: debug + run: | + if [[ "${{ runner.debug }}" == "true" ]]; then + echo "debug=true" >> $GITHUB_ENV + else + echo "debug=false" >> $GITHUB_ENV + fi + lint: + needs: [setup] name: Lint runs-on: ubuntu-latest @@ -33,6 +48,7 @@ jobs: skip-build-cache: true go_mod_tidy_check: + needs: [setup] name: Go Mod Tidy Check runs-on: ubuntu-latest @@ -68,7 +84,17 @@ jobs: go-version: ${{ inputs.go-version }} - name: run unit tests - run: make test-unit + run: make test-unit ENABLE_VERBOSE=${{ needs.setup.outputs.debug }} + + - name: Upload unit test output + uses: actions/upload-artifact@v3 + if: always() && needs.setup.outputs.debug == 'true' + with: + name: unit-test-output-${{ matrix.os }} + path: | + debug.log + coverage.txt + retention-days: 5 - name: upload coverage uses: codecov/codecov-action@v3.1.4 diff --git a/Makefile b/Makefile index d90075431a..1133db0533 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,13 @@ LDFLAGS=-ldflags="-X '$(versioningPath).buildTime=$(shell date)' -X '$(versionin ifeq (${PREFIX},) PREFIX := /usr/local endif +ifeq ($(ENABLE_VERBOSE),true) + LOG_AND_FILTER = | tee debug.log + VERBOSE = -v +else + VERBOSE = + LOG_AND_FILTER = +endif ## help: Get more info on make commands. help: Makefile @echo " Choose a command run in "$(PROJECTNAME)":" @@ -99,7 +106,7 @@ lint: lint-imports ## test-unit: Running unit tests test-unit: @echo "--> Running unit tests" - @go test -covermode=atomic -coverprofile=coverage.txt `go list ./... | grep -v nodebuilder/tests` + @go test $(VERBOSE) -covermode=atomic -coverprofile=coverage.txt `go list ./... | grep -v nodebuilder/tests` $(LOG_AND_FILTER) .PHONY: test-unit ## test-unit-race: Running unit tests with data race detector From 48352ebfb033ff10cf76b7f85e9fad4addbdbd91 Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 10 Nov 2023 12:48:34 +0000 Subject: [PATCH 0923/1008] chore(ci): remove 'continue-on-error' configuration from flakey tests (#2913) Added these in the run up to cutting 0.12.0 so hopefully the tag/release pipeline would generate signed binaries and not halt on any of these three flakey test runs. Setting these back to fail explicitly so we can see them go red and feel bad, and now start to fix them --- .github/workflows/go-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index d27e08e516..056e4816bf 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -119,7 +119,6 @@ jobs: - name: execute test run run: make test-unit-race - continue-on-error: true integration_test: needs: [lint, go_mod_tidy_check] @@ -136,8 +135,6 @@ jobs: - name: Swamp Tests run: make test-swamp - continue-on-error: true - name: Swamp Tests with Race Detector run: make test-swamp-race - continue-on-error: true From db4490c530f8c73e24366f1f563bbd89b7ef1edc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:07:33 +0000 Subject: [PATCH 0924/1008] chore(deps): Bump github.com/multiformats/go-multiaddr from 0.11.0 to 0.12.0 (#2847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/multiformats/go-multiaddr](https://github.com/multiformats/go-multiaddr) from 0.11.0 to 0.12.0.
    Release notes

    Sourced from github.com/multiformats/go-multiaddr's releases.

    v0.12.0

    What's Changed

    Full Changelog: https://github.com/multiformats/go-multiaddr/compare/v0.11.0...v0.12.0

    Commits
    • 5dd793c release v0.12.0 (#223)
    • f9a66bc net: consider /dns/localhost as private address (#221)
    • a124954 net: consider dns addresses as public (#220)
    • dfafc59 chore: bump go.mod to Go 1.20 and run go fix (#217)
    • 92a246d Merge pull request #216 from multiformats/uci/copy-templates
    • db92edd chore: add or force update .github/workflows/tagpush.yml
    • 438fa23 chore: add or force update .github/workflows/release-check.yml
    • 841bae1 chore: add or force update .github/workflows/releaser.yml
    • 9bfab28 chore: add or force update .github/workflows/go-check.yml
    • aeaad91 chore: add or force update .github/workflows/go-test.yml
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/multiformats/go-multiaddr&package-manager=go_modules&previous-version=0.11.0&new-version=0.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 77ec4d5bfa..febfb03a1b 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.7.3 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.11.0 + github.com/multiformats/go-multiaddr v0.12.0 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 diff --git a/go.sum b/go.sum index ba656c61ff..5f13e39e08 100644 --- a/go.sum +++ b/go.sum @@ -1827,8 +1827,8 @@ github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= -github.com/multiformats/go-multiaddr v0.11.0 h1:XqGyJ8ufbCE0HmTDwx2kPdsrQ36AGPZNZX6s6xfJH10= -github.com/multiformats/go-multiaddr v0.11.0/go.mod h1:gWUm0QLR4thQ6+ZF6SXUw8YjtwQSPapICM+NmCkxHSM= +github.com/multiformats/go-multiaddr v0.12.0 h1:1QlibTFkoXJuDjjYsMHhE73TnzJQl8FSWatk/0gxGzE= +github.com/multiformats/go-multiaddr v0.12.0/go.mod h1:WmZXgObOQOYp9r3cslLlppkrz1FYSHmE834dfz/lWu8= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= From 0847919876fe6211b523c79eeb4ac07b7583321d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:34:13 +0000 Subject: [PATCH 0925/1008] chore(deps): Bump google.golang.org/grpc from 1.58.3 to 1.59.0 (#2866) [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.58.3&new-version=1.59.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index febfb03a1b..113c9a02ae 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 - google.golang.org/grpc v1.58.3 + google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.31.0 ) @@ -166,7 +166,7 @@ require ( github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/gateway v1.1.0 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect @@ -323,7 +323,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect diff --git a/go.sum b/go.sum index 5f13e39e08..1de7057386 100644 --- a/go.sum +++ b/go.sum @@ -756,8 +756,8 @@ github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -2689,8 +2689,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3213,8 +3213,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 3fde8c9acb90bb9d4d1abbdc2c5917d7f3693388 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:51:59 +0000 Subject: [PATCH 0926/1008] chore(deps): Bump github.com/ipfs/boxo from 0.13.1 to 0.15.0 (#2903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/boxo](https://github.com/ipfs/boxo) from 0.13.1 to 0.15.0.
    Release notes

    Sourced from github.com/ipfs/boxo's releases.

    v0.15.0

    Changed

    What's Changed

    Full Changelog: https://github.com/ipfs/boxo/compare/v0.14.0...v0.15.0

    v0.14.0

    Added

    • boxo/gateway:
      • A new WithResolver(...) option can be used with NewBlocksBackend(...) allowing the user to pass their custom Resolver implementation.
      • The gateway now sets a Cache-Control header for requests under the /ipns/ namespace if the TTL for the corresponding IPNS Records or DNSLink entities is known.
    • boxo/bitswap/client:
      • A new WithoutDuplicatedBlockStats() option can be used with bitswap.New and bsclient.New. This disable accounting for duplicated blocks, which requires a blockstore.Has() lookup for every received block and thus, can impact performance.
    • ✨ Migrated repositories into Boxo

    Changed

    • boxo/gateway
      • 🛠 The IPFSBackend interface was updated to make the responses of the Head method more explicit. It now returns a HeadResponse instead of a files.Node.
    • boxo/routing/http/client.Client is now exported. This means you can now pass it around functions, or add it to a struct if you want.
    • 🛠 The path package has been massively refactored. With this refactor, we have condensed the different path-related and/or Kubo-specific packages under a single generic one. Therefore, there are many breaking changes. Please consult the documentation for more details on how to use the new package.
      • Note: content paths created with boxo/path are automatically normalized:
        • Replace multiple slashes with a single slash.
        • Eliminate each . path name element (the current directory).
        • Eliminate each inner .. path name element (the parent directory) along with the non-.. element that precedes it.
        • Eliminate .. elements that begin a rooted path: that is, replace "/.." by "/" at the beginning of a path.
    • 🛠 The signature of CoreAPI.ResolvePath in coreiface has changed to now return the remainder segments as a second return value, matching the signature of resolver.ResolveToLastNode.
    • 🛠 routing/http/client.FindPeers now returns iter.ResultIter[types.PeerRecord] instead of iter.ResultIter[types.Record]. The specification indicates that records for this method will always be Peer Records.
    • 🛠 The namesys package has been refactored. The following are the largest modifications:
      • The options in coreiface/options/namesys have been moved to namesys and their names have been made more consistent.
      • Many of the exported structs and functions have been renamed in order to be consistent with the remaining packages.
      • namesys.Resolver.Resolve now returns a TTL, in addition to the resolved path. If the TTL is unknown, 0 is returned. IPNSResolver is able to resolve a TTL, while DNSResolver is not.
      • namesys/resolver.ResolveIPNS has been moved to namesys.ResolveIPNS and now returns a TTL in addition to the resolved path.
    • boxo/ipns record defaults follow recommendations from IPNS Record Specification:
      • DefaultRecordTTL is now set to 1h
      • DefaultRecordLifetime follows the increased expiration window of Amino DHT (go-libp2p-kad-dht#793) and is set to 48h
    • 🛠 The gateway's IPFSBackend.ResolveMutable is now expected to return a TTL in addition to the resolved path. If the TTL is unknown, 0 should be returned.

    Removed

    ... (truncated)

    Changelog

    Sourced from github.com/ipfs/boxo's changelog.

    [v0.15.0]

    Changed

    [v0.14.0]

    Added

    • boxo/gateway:
      • A new WithResolver(...) option can be used with NewBlocksBackend(...) allowing the user to pass their custom Resolver implementation.
      • The gateway now sets a Cache-Control header for requests under the /ipns/ namespace if the TTL for the corresponding IPNS Records or DNSLink entities is known.
    • boxo/bitswap/client:
      • A new WithoutDuplicatedBlockStats() option can be used with bitswap.New and bsclient.New. This disable accounting for duplicated blocks, which requires a blockstore.Has() lookup for every received block and thus, can impact performance.
    • ✨ Migrated repositories into Boxo

    Changed

    • boxo/gateway
      • 🛠 The IPFSBackend interface was updated to make the responses of the Head method more explicit. It now returns a HeadResponse instead of a files.Node.
    • boxo/routing/http/client.Client is now exported. This means you can now pass it around functions, or add it to a struct if you want.
    • 🛠 The path package has been massively refactored. With this refactor, we have condensed the different path-related and/or Kubo-specific packages under a single generic one. Therefore, there are many breaking changes. Please consult the documentation for more details on how to use the new package.
      • Note: content paths created with boxo/path are automatically normalized:
        • Replace multiple slashes with a single slash.
        • Eliminate each . path name element (the current directory).
        • Eliminate each inner .. path name element (the parent directory) along with the non-.. element that precedes it.
        • Eliminate .. elements that begin a rooted path: that is, replace "/.." by "/" at the beginning of a path.
    • 🛠 The signature of CoreAPI.ResolvePath in coreiface has changed to now return the remainder segments as a second return value, matching the signature of resolver.ResolveToLastNode.
    • 🛠 routing/http/client.FindPeers now returns iter.ResultIter[types.PeerRecord] instead of iter.ResultIter[types.Record]. The specification indicates that records for this method will always be Peer Records.
    • 🛠 The namesys package has been refactored. The following are the largest modifications:
      • The options in coreiface/options/namesys have been moved to namesys and their names have been made more consistent.
      • Many of the exported structs and functions have been renamed in order to be consistent with the remaining packages.
      • namesys.Resolver.Resolve now returns a TTL, in addition to the resolved path. If the TTL is unknown, 0 is returned. IPNSResolver is able to resolve a TTL, while DNSResolver is not.
      • namesys/resolver.ResolveIPNS has been moved to namesys.ResolveIPNS and now returns a TTL

    ... (truncated)

    Commits
    • 521f8d4 Merge pull request #505 from ipfs/release-v0.15.0
    • 5af54b2 chore: release 0.15.0
    • 75ca06e docs: release notes for 0.15.0
    • 3982cdc chore: bump to go-libp2p@0.32.0
    • 9c22812 Merge pull request #501 from ipfs/release
    • 07c5719 Merge pull request #500 from ipfs/release-v0.14.0
    • 50f0e2c chore: bump version to 0.14.0
    • 66c76e5 docs: update changelog for 0.14.0
    • 08b11e5 Merge pull request #474 from ipfs/chore/migrate-bootstrap
    • 45426db Update bootstrap/bootstrap.go
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/boxo&package-manager=go_modules&previous-version=0.13.1&new-version=0.15.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 33 ++++++++++++++-------------- go.sum | 68 +++++++++++++++++++++++++++++++--------------------------- 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index 113c9a02ae..04dd804cc4 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/imdario/mergo v0.3.16 - github.com/ipfs/boxo v0.13.1 + github.com/ipfs/boxo v0.15.0 github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 @@ -36,9 +36,9 @@ require ( github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipld/go-car v0.6.2 - github.com/libp2p/go-libp2p v0.31.0 + github.com/libp2p/go-libp2p v0.32.0 github.com/libp2p/go-libp2p-kad-dht v0.25.1 - github.com/libp2p/go-libp2p-pubsub v0.9.3 + github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.7.3 github.com/mitchellh/go-homedir v1.1.0 @@ -67,7 +67,7 @@ require ( go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 google.golang.org/grpc v1.59.0 @@ -84,6 +84,7 @@ require ( github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/supranational/blst v0.3.11 // indirect + go.uber.org/mock v0.3.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) @@ -175,7 +176,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect - github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect + github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect @@ -229,7 +230,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/koron/go-ssdp v0.0.4 // indirect @@ -249,9 +250,9 @@ require ( github.com/manifoldco/promptui v0.9.0 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.55 // indirect + github.com/miekg/dns v1.1.56 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect @@ -265,9 +266,9 @@ require ( github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multicodec v0.9.0 // indirect - github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.11.0 // indirect + github.com/onsi/ginkgo/v2 v2.13.0 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -283,9 +284,9 @@ require ( github.com/prometheus/procfs v0.11.1 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.3 // indirect - github.com/quic-go/quic-go v0.38.1 // indirect - github.com/quic-go/webtransport-go v0.5.3 // indirect + github.com/quic-go/qtls-go1-20 v0.3.4 // indirect + github.com/quic-go/quic-go v0.39.3 // indirect + github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect @@ -319,14 +320,14 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect - go.uber.org/dig v1.17.0 // indirect + go.uber.org/dig v1.17.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/api v0.128.0 // indirect diff --git a/go.sum b/go.sum index 1de7057386..0458580b74 100644 --- a/go.sum +++ b/go.sum @@ -861,8 +861,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= -github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= +github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= @@ -1038,8 +1038,8 @@ github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1: github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.13.1 h1:nQ5oQzcMZR3oL41REJDcTbrvDvuZh3J9ckc9+ILeRQI= -github.com/ipfs/boxo v0.13.1/go.mod h1:btrtHy0lmO1ODMECbbEY1pxNtrLilvKSYLoGQt1yYCk= +github.com/ipfs/boxo v0.15.0 h1:BriLydj2nlK1nKeJQHxcKSuG5ZXcoutzhBklOtxC5pk= +github.com/ipfs/boxo v0.15.0/go.mod h1:X5ulcbR5Nh7sm3Db8+08AApUo6FsGC5mb23QDKAoB/M= github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= @@ -1332,8 +1332,8 @@ github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -1414,8 +1414,8 @@ github.com/libp2p/go-libp2p v0.22.0/go.mod h1:UDolmweypBSjQb2f7xutPnwZ/fxioLbMBx github.com/libp2p/go-libp2p v0.23.4/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= github.com/libp2p/go-libp2p v0.25.0/go.mod h1:vXHmFpcfl+xIGN4qW58Bw3a0/SKGAesr5/T4IuJHE3o= github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= -github.com/libp2p/go-libp2p v0.31.0 h1:LFShhP8F6xthWiBBq3euxbKjZsoRajVEyBS9snfHxYg= -github.com/libp2p/go-libp2p v0.31.0/go.mod h1:W/FEK1c/t04PbRH3fA9i5oucu5YcgrG0JVoBWT1B7Eg= +github.com/libp2p/go-libp2p v0.32.0 h1:86I4B7nBUPIyTgw3+5Ibq6K7DdKRCuZw8URCfPc1hQM= +github.com/libp2p/go-libp2p v0.32.0/go.mod h1:hXXC3kXPlBZ1eu8Q2hptGrMB4mZ3048JUoS4EKaHW5c= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= @@ -1506,8 +1506,8 @@ github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuD github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0= github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= -github.com/libp2p/go-libp2p-pubsub v0.9.3 h1:ihcz9oIBMaCK9kcx+yHWm3mLAFBMAUsM4ux42aikDxo= -github.com/libp2p/go-libp2p-pubsub v0.9.3/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc= +github.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA= +github.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc= github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= @@ -1720,8 +1720,9 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1744,8 +1745,8 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1882,8 +1883,9 @@ github.com/multiformats/go-multistream v0.3.0/go.mod h1:ODRoqamLUsETKS9BNcII4gcR github.com/multiformats/go-multistream v0.3.1/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-multistream v0.4.0/go.mod h1:BS6ZSYcA4NwYEaIMeCtpJydp2Dc+fNRA6uJMSu/m8+4= -github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= +github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -1934,8 +1936,8 @@ github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7 github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1949,8 +1951,8 @@ github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeR github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= @@ -2084,14 +2086,14 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1 github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= -github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg= +github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= -github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE= -github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4= +github.com/quic-go/quic-go v0.39.3 h1:o3YB6t2SR+HU/pgwF29kJ6g4jJIJEwEZ8CKia1h1TKg= +github.com/quic-go/quic-go v0.39.3/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q= github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= -github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= -github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= +github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/raulk/clock v1.1.0/go.mod h1:3MpVxdZ/ODBQDxbN+kzshf5OSZwPjtMDx6BBXBmOeY0= @@ -2422,8 +2424,8 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= -go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= +go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= @@ -2434,6 +2436,8 @@ go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -2530,8 +2534,8 @@ golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2567,8 +2571,8 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2966,8 +2970,8 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From a4ef6d135605c700928700662103332615c293b3 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:09:54 +0100 Subject: [PATCH 0927/1008] feat(core): Enable metrics for core package (#2863) While we do plan to remove the `bridge` node type eventually, it's useful to have some visibility into the core package in the short term. --- core/exchange.go | 28 ++++++++++- core/exchange_metrics.go | 49 +++++++++++++++++++ core/exchange_test.go | 3 +- core/listener.go | 29 ++++++++++-- core/listener_metrics.go | 81 ++++++++++++++++++++++++++++++++ core/listener_test.go | 4 +- core/option.go | 15 ++++++ nodebuilder/core/config.go | 2 + nodebuilder/core/module.go | 24 ++++++++-- nodebuilder/settings.go | 2 + nodebuilder/tests/swamp/swamp.go | 3 +- 11 files changed, 229 insertions(+), 11 deletions(-) create mode 100644 core/exchange_metrics.go create mode 100644 core/listener_metrics.go create mode 100644 core/option.go diff --git a/core/exchange.go b/core/exchange.go index 06f648edad..79f3d6337a 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -22,18 +22,38 @@ type Exchange struct { fetcher *BlockFetcher store *eds.Store construct header.ConstructFn + + metrics *exchangeMetrics } func NewExchange( fetcher *BlockFetcher, store *eds.Store, construct header.ConstructFn, -) *Exchange { + opts ...Option, +) (*Exchange, error) { + p := new(params) + for _, opt := range opts { + opt(p) + } + + var ( + metrics *exchangeMetrics + err error + ) + if p.metrics { + metrics, err = newExchangeMetrics() + if err != nil { + return nil, err + } + } + return &Exchange{ fetcher: fetcher, store: store, construct: construct, - } + metrics: metrics, + }, nil } func (ce *Exchange) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { @@ -47,12 +67,16 @@ func (ce *Exchange) GetRangeByHeight( from *header.ExtendedHeader, to uint64, ) ([]*header.ExtendedHeader, error) { + start := time.Now() + amount := to - (from.Height() + 1) headers, err := ce.getRangeByHeight(ctx, from.Height()+1, amount) if err != nil { return nil, err } + ce.metrics.requestDurationPerHeader(ctx, time.Since(start), amount) + for _, h := range headers { err := from.Verify(h) if err != nil { diff --git a/core/exchange_metrics.go b/core/exchange_metrics.go new file mode 100644 index 0000000000..0b454a6e4d --- /dev/null +++ b/core/exchange_metrics.go @@ -0,0 +1,49 @@ +package core + +import ( + "context" + "time" + + "go.opentelemetry.io/otel/metric" +) + +type exchangeMetrics struct { + getByHeightDuration metric.Float64Histogram +} + +func newExchangeMetrics() (*exchangeMetrics, error) { + m := new(exchangeMetrics) + + var err error + m.getByHeightDuration, err = meter.Float64Histogram( + "core_ex_get_by_height_request_time", + metric.WithDescription("core exchange client getByHeight request time in seconds (per single height)"), + ) + if err != nil { + return nil, err + } + + return m, nil +} + +func (m *exchangeMetrics) observe(ctx context.Context, observeFn func(ctx context.Context)) { + if m == nil { + return + } + + if ctx.Err() != nil { + ctx = context.Background() + } + + observeFn(ctx) +} + +func (m *exchangeMetrics) requestDurationPerHeader(ctx context.Context, duration time.Duration, amount uint64) { + m.observe(ctx, func(ctx context.Context) { + if amount == 0 { + return + } + durationPerHeader := duration.Seconds() / float64(amount) + m.getByHeightDuration.Record(ctx, durationPerHeader) + }) +} diff --git a/core/exchange_test.go b/core/exchange_test.go index 853b5a8dc6..c43084c57d 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -27,7 +27,8 @@ func TestCoreExchange_RequestHeaders(t *testing.T) { store := createStore(t) - ce := NewExchange(fetcher, store, header.MakeExtendedHeader) + ce, err := NewExchange(fetcher, store, header.MakeExtendedHeader) + require.NoError(t, err) // initialize store with genesis block genHeight := int64(1) diff --git a/core/listener.go b/core/listener.go index 1754cb62b3..8447733506 100644 --- a/core/listener.go +++ b/core/listener.go @@ -43,6 +43,8 @@ type Listener struct { listenerTimeout time.Duration + metrics *listenerMetrics + cancel context.CancelFunc } @@ -53,7 +55,24 @@ func NewListener( construct header.ConstructFn, store *eds.Store, blocktime time.Duration, -) *Listener { + opts ...Option, +) (*Listener, error) { + p := new(params) + for _, opt := range opts { + opt(p) + } + + var ( + metrics *listenerMetrics + err error + ) + if p.metrics { + metrics, err = newListenerMetrics() + if err != nil { + return nil, err + } + } + return &Listener{ fetcher: fetcher, headerBroadcaster: bcast, @@ -61,7 +80,8 @@ func NewListener( construct: construct, store: store, listenerTimeout: 5 * blocktime, - } + metrics: metrics, + }, nil } // Start kicks off the Listener listener loop. @@ -85,7 +105,7 @@ func (cl *Listener) Start(context.Context) error { func (cl *Listener) Stop(context.Context) error { cl.cancel() cl.cancel = nil - return nil + return cl.metrics.Close() } // runSubscriber runs a subscriber to receive event data of new signed blocks. It will attempt to @@ -144,6 +164,7 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSigned } log.Debugw("listener: new block from core", "height", b.Header.Height) + err := cl.handleNewSignedBlock(ctx, b) if err != nil { log.Errorw("listener: handling new block msg", @@ -157,6 +178,7 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSigned } timeout.Reset(cl.listenerTimeout) case <-timeout.C: + cl.metrics.subscriptionStuck(ctx) return errors.New("underlying subscription is stuck") case <-ctx.Done(): return ctx.Err() @@ -178,6 +200,7 @@ func (cl *Listener) handleNewSignedBlock(ctx context.Context, b types.EventDataS if err != nil { return fmt.Errorf("extending block data: %w", err) } + // generate extended header eh, err := cl.construct(&b.Header, &b.Commit, &b.ValidatorSet, eds) if err != nil { diff --git a/core/listener_metrics.go b/core/listener_metrics.go new file mode 100644 index 0000000000..a325149b5d --- /dev/null +++ b/core/listener_metrics.go @@ -0,0 +1,81 @@ +package core + +import ( + "context" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +var meter = otel.Meter("core") + +type listenerMetrics struct { + lastTimeSubscriptionStuck time.Time + lastTimeSubscriptionStuckInst metric.Int64ObservableGauge + lastTimeSubscriptionStuckReg metric.Registration + + subscriptionStuckInst metric.Int64Counter +} + +func newListenerMetrics() (*listenerMetrics, error) { + m := new(listenerMetrics) + + var err error + m.subscriptionStuckInst, err = meter.Int64Counter( + "core_listener_subscription_stuck_count", + metric.WithDescription("number of times core listener block subscription has been stuck/retried"), + ) + if err != nil { + return nil, err + } + + m.lastTimeSubscriptionStuckInst, err = meter.Int64ObservableGauge( + "core_listener_last_time_subscription_stuck_timestamp", + metric.WithDescription("last time the listener subscription was stuck"), + ) + if err != nil { + return nil, err + } + m.lastTimeSubscriptionStuckReg, err = meter.RegisterCallback( + m.observeLastTimeStuckCallback, + m.lastTimeSubscriptionStuckInst, + ) + if err != nil { + return nil, err + } + + return m, nil +} + +func (m *listenerMetrics) observe(ctx context.Context, observeFn func(ctx context.Context)) { + if m == nil { + return + } + + if ctx.Err() != nil { + ctx = context.Background() + } + + observeFn(ctx) +} + +func (m *listenerMetrics) subscriptionStuck(ctx context.Context) { + m.observe(ctx, func(ctx context.Context) { + m.subscriptionStuckInst.Add(ctx, 1) + m.lastTimeSubscriptionStuck = time.Now() + }) +} + +func (m *listenerMetrics) observeLastTimeStuckCallback(_ context.Context, obs metric.Observer) error { + obs.ObserveInt64(m.lastTimeSubscriptionStuckInst, m.lastTimeSubscriptionStuck.Unix()) + return nil +} + +func (m *listenerMetrics) Close() error { + if m == nil { + return nil + } + + return m.lastTimeSubscriptionStuckReg.Unregister() +} diff --git a/core/listener_test.go b/core/listener_test.go index 9537860d78..bf84c07b41 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -180,7 +180,9 @@ func createListener( require.NoError(t, p2pSub.Stop(ctx)) }) - return NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, store, nodep2p.BlockTime) + listener, err := NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, store, nodep2p.BlockTime) + require.NoError(t, err) + return listener } func createEdsPubSub(ctx context.Context, t *testing.T) *shrexsub.PubSub { diff --git a/core/option.go b/core/option.go new file mode 100644 index 0000000000..6e06fade48 --- /dev/null +++ b/core/option.go @@ -0,0 +1,15 @@ +package core + +type Option func(*params) + +type params struct { + metrics bool +} + +// WithMetrics is a functional option that enables metrics +// inside the core package. +func WithMetrics() Option { + return func(p *params) { + p.metrics = true + } +} diff --git a/nodebuilder/core/config.go b/nodebuilder/core/config.go index 4affcd3087..bb5eea5b83 100644 --- a/nodebuilder/core/config.go +++ b/nodebuilder/core/config.go @@ -7,6 +7,8 @@ import ( "github.com/celestiaorg/celestia-node/libs/utils" ) +var MetricsEnabled bool + // Config combines all configuration fields for managing the relationship with a Core node. type Config struct { IP string diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index 02863eae7e..fec7c14b1b 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -35,7 +35,20 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return fx.Module("core", baseComponents, fx.Provide(core.NewBlockFetcher), - fxutil.ProvideAs(core.NewExchange, new(libhead.Exchange[*header.ExtendedHeader])), + fxutil.ProvideAs( + func( + fetcher *core.BlockFetcher, + store *eds.Store, + construct header.ConstructFn, + ) (*core.Exchange, error) { + var opts []core.Option + if MetricsEnabled { + opts = append(opts, core.WithMetrics()) + } + + return core.NewExchange(fetcher, store, construct, opts...) + }, + new(libhead.Exchange[*header.ExtendedHeader])), fx.Invoke(fx.Annotate( func( bcast libhead.Broadcaster[*header.ExtendedHeader], @@ -43,8 +56,13 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option pubsub *shrexsub.PubSub, construct header.ConstructFn, store *eds.Store, - ) *core.Listener { - return core.NewListener(bcast, fetcher, pubsub.Broadcast, construct, store, p2p.BlockTime) + ) (*core.Listener, error) { + var opts []core.Option + if MetricsEnabled { + opts = append(opts, core.WithMetrics()) + } + + return core.NewListener(bcast, fetcher, pubsub.Broadcast, construct, store, p2p.BlockTime, opts...) }, fx.OnStart(func(ctx context.Context, listener *core.Listener) error { return listener.Start(ctx) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 29019c7e77..2d924af000 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -24,6 +24,7 @@ import ( "github.com/celestiaorg/go-fraud" "github.com/celestiaorg/celestia-node/header" + modcore "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/das" modhead "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -76,6 +77,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti // TODO @renaynay: this will be refactored when there is more granular // control over which module to enable metrics for modhead.MetricsEnabled = true + modcore.MetricsEnabled = true baseComponents := fx.Options( fx.Supply(metricOpts), diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 55426cf70c..e3ac3ad4f2 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -176,11 +176,12 @@ func (s *Swamp) setupGenesis() { store, err := eds.NewStore(eds.DefaultParameters(), s.t.TempDir(), ds) require.NoError(s.t, err) - ex := core.NewExchange( + ex, err := core.NewExchange( core.NewBlockFetcher(s.ClientContext.Client), store, header.MakeExtendedHeader, ) + require.NoError(s.t, err) h, err := ex.GetByHeight(ctx, 1) require.NoError(s.t, err) From d1883d6ddb9f34b2a8aa37d0a4d74f11721152f5 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:11:10 -0600 Subject: [PATCH 0928/1008] chore: bump celestia-app to v1.4.0 (#2948) This PR bumps to v1.4.0 of celestia-app, which should be a non-breaking version and has been tested by syncing from scratch on mainnet. We still need to test on robusta before moving onto arabica-11 and mocha-4. --- go.mod | 4 ++-- go.sum | 8 ++++---- state/core_access_test.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 04dd804cc4..c648c01889 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 - github.com/celestiaorg/celestia-app v1.3.0 + github.com/celestiaorg/celestia-app v1.4.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.4.1 @@ -344,7 +344,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.18.3-sdk-v0.46.14 github.com/filecoin-project/dagstore => github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 // broken goleveldb needs to be replaced for the cosmos-sdk and celestia-app diff --git a/go.sum b/go.sum index 0458580b74..d45b9f0c06 100644 --- a/go.sum +++ b/go.sum @@ -358,12 +358,12 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v1.3.0 h1:vW1zMc1tQ216utzDOYSC3wGO023sKVt8+zujVXnXlOc= -github.com/celestiaorg/celestia-app v1.3.0/go.mod h1:zhdQIFGFZRRxrDVtFE4OFIT7/12RE8DRyfvNZdW8ceM= +github.com/celestiaorg/celestia-app v1.4.0 h1:hTId3xL8GssN5sHSHPP7svHi/iWp+XVxphStiR7ADiY= +github.com/celestiaorg/celestia-app v1.4.0/go.mod h1:zhdQIFGFZRRxrDVtFE4OFIT7/12RE8DRyfvNZdW8ceM= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29/go.mod h1:xrICN0PBhp3AdTaZ8q4wS5Jvi32V02HNjaC2EsWiEKk= -github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14 h1:c4cMVLU2bGTesZW1ZVgeoCB++gOOJTF3OvBsqBvo6n0= -github.com/celestiaorg/cosmos-sdk v1.18.1-sdk-v0.46.14/go.mod h1:D5y5Exw0bJkcDv9fvYDiZfZrDV1b6+xsFyiungxrCsU= +github.com/celestiaorg/cosmos-sdk v1.18.3-sdk-v0.46.14 h1:+Te28r5Zp4Vp69f82kcON9/BIF8f1BNXb0go2+achuc= +github.com/celestiaorg/cosmos-sdk v1.18.3-sdk-v0.46.14/go.mod h1:Og5KKgoBEiQlI6u56lDLG191pfknIdXctFn3COWLQP8= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJx5/hgZ+IeOLEIoLsAveJN/7/ZtQQtPSVw= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= diff --git a/state/core_access_test.go b/state/core_access_test.go index 69e9f251c0..68d05a955a 100644 --- a/state/core_access_test.go +++ b/state/core_access_test.go @@ -27,7 +27,7 @@ func TestSubmitPayForBlob(t *testing.T) { tmCfg.Consensus.TimeoutCommit = time.Millisecond * 1 appConf := testnode.DefaultAppConfig() appConf.API.Enable = true - appConf.MinGasPrices = fmt.Sprintf("0.1%s", app.BondDenom) + appConf.MinGasPrices = fmt.Sprintf("0.002%s", app.BondDenom) config := testnode.DefaultConfig().WithTendermintConfig(tmCfg).WithAppConfig(appConf).WithAccounts(accounts) cctx, rpcAddr, grpcAddr := testnode.NewNetwork(t, config) From dd3b8e1411dee3acaad1b4dd21833893f5daedba Mon Sep 17 00:00:00 2001 From: Javed Khan Date: Thu, 23 Nov 2023 08:52:36 -0800 Subject: [PATCH 0929/1008] feat(cmd): pass Start options; add WithFlagSet option (#2950) Fixes #2936 This PR updates the `Start` command to accept a variadic number of `funcs` which can be used to customise the command. This allows the caller to hook into and modify the command, for example to add a `PreRun` hook, as required for `celestia-da`. With this PR in place, the `celestia-da` requirement, which needs to add some required flags and add a `PreRun` hook can be satisfied as: ```go bridgeCmd := bridge.NewCommand(WithPreRun()) lightCmd := light.NewCommand(WithPreRun()) fullCmd := full.NewCommand(WithPreRun()) ``` Ideally this can be applied for other commands `Init`, `Auth` as well, but it's not required for the current requirement. --- cmd/celestia/bridge.go | 47 ---------------------- cmd/celestia/full.go | 51 ------------------------ cmd/celestia/light.go | 51 ------------------------ cmd/celestia/main.go | 19 +++++++++ cmd/node.go | 88 ++++++++++++++++++++++++++++++++++++++++++ cmd/start.go | 8 ++-- cmd/util.go | 10 +++++ 7 files changed, 121 insertions(+), 153 deletions(-) delete mode 100644 cmd/celestia/bridge.go delete mode 100644 cmd/celestia/full.go delete mode 100644 cmd/celestia/light.go create mode 100644 cmd/node.go diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go deleted file mode 100644 index c0e2ab0d1a..0000000000 --- a/cmd/celestia/bridge.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/core" - "github.com/celestiaorg/celestia-node/nodebuilder/gateway" - "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/nodebuilder/rpc" - "github.com/celestiaorg/celestia-node/nodebuilder/state" -) - -// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the -// PersistentPreRun func on parent command. - -func init() { - flags := []*pflag.FlagSet{ - cmdnode.NodeFlags(), - p2p.Flags(), - core.Flags(), - cmdnode.MiscFlags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - } - - bridgeCmd.AddCommand( - cmdnode.Init(flags...), - cmdnode.Start(flags...), - cmdnode.AuthCmd(flags...), - cmdnode.ResetStore(flags...), - cmdnode.RemoveConfigCmd(flags...), - cmdnode.UpdateConfigCmd(flags...), - ) -} - -var bridgeCmd = &cobra.Command{ - Use: "bridge [subcommand]", - Args: cobra.NoArgs, - Short: "Manage your Bridge node", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return cmdnode.PersistentPreRunEnv(cmd, node.Bridge, args) - }, -} diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go deleted file mode 100644 index 8baff1080e..0000000000 --- a/cmd/celestia/full.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/core" - "github.com/celestiaorg/celestia-node/nodebuilder/gateway" - "github.com/celestiaorg/celestia-node/nodebuilder/header" - "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/nodebuilder/rpc" - "github.com/celestiaorg/celestia-node/nodebuilder/state" -) - -// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the -// PersistentPreRun func on parent command. - -func init() { - flags := []*pflag.FlagSet{ - cmdnode.NodeFlags(), - p2p.Flags(), - header.Flags(), - cmdnode.MiscFlags(), - // NOTE: for now, state-related queries can only be accessed - // over an RPC connection with a celestia-core node. - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - } - - fullCmd.AddCommand( - cmdnode.Init(flags...), - cmdnode.Start(flags...), - cmdnode.AuthCmd(flags...), - cmdnode.ResetStore(flags...), - cmdnode.RemoveConfigCmd(flags...), - cmdnode.UpdateConfigCmd(flags...), - ) -} - -var fullCmd = &cobra.Command{ - Use: "full [subcommand]", - Args: cobra.NoArgs, - Short: "Manage your Full node", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return cmdnode.PersistentPreRunEnv(cmd, node.Full, args) - }, -} diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go deleted file mode 100644 index 553660c5d3..0000000000 --- a/cmd/celestia/light.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/nodebuilder/core" - "github.com/celestiaorg/celestia-node/nodebuilder/gateway" - "github.com/celestiaorg/celestia-node/nodebuilder/header" - "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/nodebuilder/rpc" - "github.com/celestiaorg/celestia-node/nodebuilder/state" -) - -// NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the -// PersistentPreRun func on parent command. - -func init() { - flags := []*pflag.FlagSet{ - cmdnode.NodeFlags(), - p2p.Flags(), - header.Flags(), - cmdnode.MiscFlags(), - // NOTE: for now, state-related queries can only be accessed - // over an RPC connection with a celestia-core node. - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - } - - lightCmd.AddCommand( - cmdnode.Init(flags...), - cmdnode.Start(flags...), - cmdnode.AuthCmd(flags...), - cmdnode.ResetStore(flags...), - cmdnode.RemoveConfigCmd(flags...), - cmdnode.UpdateConfigCmd(flags...), - ) -} - -var lightCmd = &cobra.Command{ - Use: "light [subcommand]", - Args: cobra.NoArgs, - Short: "Manage your Light node", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return cmdnode.PersistentPreRunEnv(cmd, node.Light, args) - }, -} diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index 24c9423100..76287d998f 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -5,9 +5,28 @@ import ( "os" "github.com/spf13/cobra" + "github.com/spf13/pflag" + + cmdnode "github.com/celestiaorg/celestia-node/cmd" ) +func WithSubcommands() func(*cobra.Command, []*pflag.FlagSet) { + return func(c *cobra.Command, flags []*pflag.FlagSet) { + c.AddCommand( + cmdnode.Init(flags...), + cmdnode.Start(cmdnode.WithFlagSet(flags)), + cmdnode.AuthCmd(flags...), + cmdnode.ResetStore(flags...), + cmdnode.RemoveConfigCmd(flags...), + cmdnode.UpdateConfigCmd(flags...), + ) + } +} + func init() { + bridgeCmd := cmdnode.NewBridge(WithSubcommands()) + lightCmd := cmdnode.NewLight(WithSubcommands()) + fullCmd := cmdnode.NewFull(WithSubcommands()) rootCmd.AddCommand( bridgeCmd, lightCmd, diff --git a/cmd/node.go b/cmd/node.go new file mode 100644 index 0000000000..3cdcb29c05 --- /dev/null +++ b/cmd/node.go @@ -0,0 +1,88 @@ +package cmd + +import ( + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/gateway" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/state" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func NewBridge(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { + flags := []*pflag.FlagSet{ + NodeFlags(), + p2p.Flags(), + MiscFlags(), + core.Flags(), + rpc.Flags(), + gateway.Flags(), + state.Flags(), + } + cmd := &cobra.Command{ + Use: "bridge [subcommand]", + Args: cobra.NoArgs, + Short: "Manage your Bridge node", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return PersistentPreRunEnv(cmd, node.Bridge, args) + }, + } + for _, option := range options { + option(cmd, flags) + } + return cmd +} + +func NewLight(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { + flags := []*pflag.FlagSet{ + NodeFlags(), + p2p.Flags(), + header.Flags(), + MiscFlags(), + core.Flags(), + rpc.Flags(), + gateway.Flags(), + state.Flags(), + } + cmd := &cobra.Command{ + Use: "light [subcommand]", + Args: cobra.NoArgs, + Short: "Manage your Light node", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return PersistentPreRunEnv(cmd, node.Light, args) + }, + } + for _, option := range options { + option(cmd, flags) + } + return cmd +} + +func NewFull(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { + flags := []*pflag.FlagSet{ + NodeFlags(), + p2p.Flags(), + header.Flags(), + MiscFlags(), + core.Flags(), + rpc.Flags(), + gateway.Flags(), + state.Flags(), + } + cmd := &cobra.Command{ + Use: "full [subcommand]", + Args: cobra.NoArgs, + Short: "Manage your Full node", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return PersistentPreRunEnv(cmd, node.Full, args) + }, + } + for _, option := range options { + option(cmd, flags) + } + return cmd +} diff --git a/cmd/start.go b/cmd/start.go index d46b553e20..281dfcc0e4 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/spf13/cobra" - flag "github.com/spf13/pflag" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" @@ -18,7 +17,7 @@ import ( ) // Start constructs a CLI command to start Celestia Node daemon of any type with the given flags. -func Start(fsets ...*flag.FlagSet) *cobra.Command { +func Start(options ...func(*cobra.Command)) *cobra.Command { cmd := &cobra.Command{ Use: "start", Short: `Starts Node daemon. First stopping signal gracefully stops the Node and second terminates it. @@ -72,8 +71,9 @@ Options passed on start override configuration options only on start and are not return nd.Stop(ctx) }, } - for _, set := range fsets { - cmd.Flags().AddFlagSet(set) + // Apply each passed option to the command + for _, option := range options { + option(cmd) } return cmd } diff --git a/cmd/util.go b/cmd/util.go index 625685fe0b..020699449a 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/spf13/cobra" + flag "github.com/spf13/pflag" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/gateway" @@ -125,3 +126,12 @@ func PersistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) err cmd.SetContext(ctx) return nil } + +// WithFlagSet adds the given flagset to the command. +func WithFlagSet(fset []*flag.FlagSet) func(*cobra.Command) { + return func(c *cobra.Command) { + for _, set := range fset { + c.Flags().AddFlagSet(set) + } + } +} From 7fe691e58381e3ea8870322fd7f83d7b6241f1d5 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 29 Nov 2023 17:15:54 +0100 Subject: [PATCH 0930/1008] chore(node): tweak pyroscope options (#2974) * Increases UploadRate to 15 secs. * Send goroutines profiles additionally Extracted from #2960 --- nodebuilder/settings.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 2d924af000..298976fda4 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -52,6 +52,7 @@ func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { return fx.Options( fx.Invoke(func(peerID peer.ID) error { _, err := pyroscope.Start(pyroscope.Config{ + UploadRate: 15 * time.Second, ApplicationName: "celestia.da-node", ServerAddress: endpoint, Tags: map[string]string{ @@ -65,6 +66,7 @@ func WithPyroscope(endpoint string, nodeType node.Type) fx.Option { pyroscope.ProfileAllocSpace, pyroscope.ProfileInuseObjects, pyroscope.ProfileInuseSpace, + pyroscope.ProfileGoroutines, }, }) return err From 24b333dff31d958fcd73f4967501ec08255b7af3 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 29 Nov 2023 18:20:19 +0100 Subject: [PATCH 0931/1008] chore(modheader): lower store cache size for LN (#2975) Part of an effort to decrease the memory footprint for LNs. Headers are pretty beefy. Extracted from #2960 --- nodebuilder/header/config.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nodebuilder/header/config.go b/nodebuilder/header/config.go index d7265373ce..3fb7162ae0 100644 --- a/nodebuilder/header/config.go +++ b/nodebuilder/header/config.go @@ -43,13 +43,18 @@ func DefaultConfig(tp node.Type) Config { Store: store.DefaultParameters(), Syncer: sync.DefaultParameters(), Server: p2p_exchange.DefaultServerParameters(), + Client: p2p_exchange.DefaultClientParameters(), } switch tp { case node.Bridge: return cfg - case node.Light, node.Full: - cfg.Client = p2p_exchange.DefaultClientParameters() + case node.Full: + return cfg + case node.Light: + cfg.Store.StoreCacheSize = 512 + cfg.Store.IndexCacheSize = 2048 + cfg.Store.WriteBatchSize = 512 return cfg default: panic("header: invalid node type") From 72ace5dedf83672460672bd23b190a6f10c89bf2 Mon Sep 17 00:00:00 2001 From: ramin Date: Thu, 30 Nov 2023 11:24:06 +0000 Subject: [PATCH 0932/1008] misc(config): add @ramin to codeowners (#2973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 👀 por favor --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e7e610edf8..f9ac165e7a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,6 +7,6 @@ # global owners are only requested if there isn't a more specific # codeowner specified below. For this reason, the global codeowners # are often repeated in package-level definitions. -* @renaynay @Wondertan @vgonkivs @distractedm1nd @walldiss +* @renaynay @Wondertan @vgonkivs @distractedm1nd @walldiss @ramin docs/adr @adlerjohn @liamsi From fa6a1378a39522a46e3a6b93a96443b9263ebd9b Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 30 Nov 2023 17:22:35 +0100 Subject: [PATCH 0933/1008] fix(node/store): substantial memory usage improvements (#2960) * Fixes memory leak #2905 * Reduces memory consumption (from up-to 3gib to up-to 300mib) (TODO: proofs in pyroscope) * Improves cache hit rate on Badger * The warning log with Badger's low cache hit rate has disappeared. NOTE: More details in code comments --- Makefile | 33 +++++++++++ blob/blob.go | 4 +- cmd/node.go | 6 +- go.mod | 6 +- go.sum | 4 +- nodebuilder/fraud/mocks/api.go | 3 +- nodebuilder/header/mocks/api.go | 3 +- nodebuilder/share/mocks/api.go | 3 +- nodebuilder/store.go | 73 ++++++++++++++++++++++-- nodebuilder/tests/fraud_test.go | 6 +- share/availability/mocks/availability.go | 3 +- share/eds/inverted_index.go | 18 +++++- share/mocks/getter.go | 3 +- 13 files changed, 141 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 1133db0533..9e1635308b 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,12 @@ build: @go build -o build/ ${LDFLAGS} ./cmd/celestia .PHONY: build +## build-jemalloc: Build celestia-node binary with jemalloc allocator for BadgerDB instead of Go's native one +build-jemalloc: jemalloc + @echo "--> Building Celestia with jemalloc" + @go build -o build/ ${LDFLAGS} -tags jemalloc ./cmd/celestia +.PHONY: build-jemalloc + ## clean: Clean up celestia-node binary. clean: @echo "--> Cleaning up ./build" @@ -213,3 +219,30 @@ goreleaser-build: goreleaser-release: goreleaser release --clean --fail-fast --skip-publish .PHONY: goreleaser-release + +# Copied from https://github.com/dgraph-io/badger/blob/main/Makefile + +USER_ID = $(shell id -u) +HAS_JEMALLOC = $(shell test -f /usr/local/lib/libjemalloc.a && echo "jemalloc") +JEMALLOC_URL = "https://github.com/jemalloc/jemalloc/releases/download/5.2.1/jemalloc-5.2.1.tar.bz2" + +## jemalloc installs jemalloc allocator +jemalloc: + @if [ -z "$(HAS_JEMALLOC)" ] ; then \ + mkdir -p /tmp/jemalloc-temp && cd /tmp/jemalloc-temp ; \ + echo "Downloading jemalloc..." ; \ + curl -s -L ${JEMALLOC_URL} -o jemalloc.tar.bz2 ; \ + tar xjf ./jemalloc.tar.bz2 ; \ + cd jemalloc-5.2.1 ; \ + ./configure --with-jemalloc-prefix='je_' --with-malloc-conf='background_thread:true,metadata_thp:auto'; \ + make ; \ + if [ "$(USER_ID)" -eq "0" ]; then \ + make install ; \ + else \ + echo "==== Need sudo access to install jemalloc" ; \ + sudo make install ; \ + fi ; \ + cd /tmp ; \ + rm -rf /tmp/jemalloc-temp ; \ + fi +.PHONY: jemalloc diff --git a/blob/blob.go b/blob/blob.go index 3f7e92dd20..945ffdf587 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -142,8 +142,8 @@ func (b *Blob) UnmarshalJSON(data []byte) error { } // buildBlobsIfExist takes shares and tries building the Blobs from them. -// It will build blobs either until appShares will be empty or the first incomplete blob will appear, so in this -// specific case it will return all built blobs + remaining shares. +// It will build blobs either until appShares will be empty or the first incomplete blob will +// appear, so in this specific case it will return all built blobs + remaining shares. func buildBlobsIfExist(appShares []shares.Share) ([]*Blob, []shares.Share, error) { if len(appShares) == 0 { return nil, nil, errors.New("empty shares received") diff --git a/cmd/node.go b/cmd/node.go index 3cdcb29c05..51ac4a6d2e 100644 --- a/cmd/node.go +++ b/cmd/node.go @@ -1,6 +1,9 @@ package cmd import ( + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/nodebuilder/gateway" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -8,9 +11,6 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/state" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" ) func NewBridge(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { diff --git a/go.mod b/go.mod index c648c01889..b4441c6f6b 100644 --- a/go.mod +++ b/go.mod @@ -82,6 +82,7 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/ethereum/c-kzg-4844 v0.3.1 // indirect + github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/supranational/blst v0.3.11 // indirect go.uber.org/mock v0.3.0 // indirect @@ -138,7 +139,7 @@ require ( github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect - github.com/dgraph-io/badger/v4 v4.1.0 // indirect + github.com/dgraph-io/badger/v4 v4.2.1-0.20231013074411-fb1b00959581 github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -197,7 +198,6 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/holiman/uint256 v1.2.3 // indirect @@ -351,3 +351,5 @@ replace ( github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 ) + +replace github.com/dgraph-io/badger/v4 => github.com/celestiaorg/badger/v4 v4.0.0-20231125230536-2b9e13346f75 diff --git a/go.sum b/go.sum index d45b9f0c06..962e46067b 100644 --- a/go.sum +++ b/go.sum @@ -358,6 +358,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/celestiaorg/badger/v4 v4.0.0-20231125230536-2b9e13346f75 h1:dfArr6rKfcB46+/sR/ZZuiBbcB1ZQcpZQT4E4HHcIuE= +github.com/celestiaorg/badger/v4 v4.0.0-20231125230536-2b9e13346f75/go.mod h1:T/uWAYxrXdaXw64ihI++9RMbKTCpKd/yE9+saARew7k= github.com/celestiaorg/celestia-app v1.4.0 h1:hTId3xL8GssN5sHSHPP7svHi/iWp+XVxphStiR7ADiY= github.com/celestiaorg/celestia-app v1.4.0/go.mod h1:zhdQIFGFZRRxrDVtFE4OFIT7/12RE8DRyfvNZdW8ceM= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= @@ -549,8 +551,6 @@ github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= -github.com/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4APOQ= -github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index fcc7a58231..10111b81a8 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,9 +8,10 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + fraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraud0 "github.com/celestiaorg/go-fraud" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index b0d2b961d9..9b15f2242e 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 78a124a20d..87f98e4f73 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + da "github.com/celestiaorg/celestia-app/pkg/da" share "github.com/celestiaorg/celestia-node/share" rsmt2d "github.com/celestiaorg/rsmt2d" - gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/store.go b/nodebuilder/store.go index 6d313893b1..f2238c8c3f 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -4,10 +4,12 @@ import ( "errors" "fmt" "path/filepath" + "runtime" "sync" "time" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/dgraph-io/badger/v4/options" "github.com/ipfs/go-datastore" "github.com/mitchellh/go-homedir" @@ -15,6 +17,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/keystore" + "github.com/celestiaorg/celestia-node/share" ) var ( @@ -118,10 +121,8 @@ func (f *fsStore) Datastore() (datastore.Batching, error) { return f.data, nil } - opts := dsbadger.DefaultOptions // this should be copied - opts.GcInterval = time.Minute * 10 - - ds, err := dsbadger.NewDatastore(dataPath(f.path), &opts) + cfg := constraintBadgerConfig() + ds, err := dsbadger.NewDatastore(dataPath(f.path), cfg) if err != nil { return nil, fmt.Errorf("node: can't open Badger Datastore: %w", err) } @@ -182,3 +183,67 @@ func indexPath(base string) string { func dataPath(base string) string { return filepath.Join(base, "data") } + +// constraintBadgerConfig returns BadgerDB configuration optimized for low memory usage and more frequent +// compaction which prevents memory spikes. +// This is particularly important for LNs with restricted memory resources. +// +// With the following configuration, a LN uses up to 300iB of RAM during initial sync/sampling +// and up to 200MiB during normal operation. (on 4 core CPU, 8GiB RAM droplet) +// +// With the following configuration and "-tags=jemalloc", a LN uses no more than 180MiB during initial +// sync/sampling and up to 100MiB during normal operation. (same hardware spec) +// NOTE: To enable jemalloc, build celestia-node with "-tags=jemalloc" flag, which configures Badger to +// use jemalloc instead of Go's default allocator. +// +// TODO(@Wondertan): Consider alternative less constraint configuration for FN/BN +// TODO(@Wondertan): Consider dynamic memory allocation based on available RAM +func constraintBadgerConfig() *dsbadger.Options { + opts := dsbadger.DefaultOptions // this must be copied + // ValueLog: + // 2mib default => share.Size - makes sure headers and samples are stored in value log + // This *tremendously* reduces the amount of memory used by the node, up to 10 times less during + // compaction + opts.ValueThreshold = share.Size + // make sure we don't have any limits for stored headers + opts.ValueLogMaxEntries = 100000000 + // run value log GC more often to spread the work over time + opts.GcInterval = time.Minute * 1 + // default 0.5 => 0.125 - makes sure value log GC is more aggressive on reclaiming disk space + opts.GcDiscardRatio = 0.125 + + // badger stores checksum for every value, but doesn't verify it by default + // enabling this option may allow us to see detect corrupted data + opts.ChecksumVerificationMode = options.OnBlockRead + opts.VerifyValueChecksum = true + // default 64mib => 0 - disable block cache + // most of our component maintain their own caches, so this is not needed + opts.BlockCacheSize = 0 + // not much gain as it compresses the LSM only as well compression requires block cache + opts.Compression = options.None + + // MemTables: + // default 64mib => 16mib - decreases memory usage and makes compaction more often + opts.MemTableSize = 16 << 20 + // default 5 => 3 + opts.NumMemtables = 3 + // default 5 => 3 + opts.NumLevelZeroTables = 3 + // default 15 => 5 - this prevents memory growth on CPU constraint systems by blocking all writers + opts.NumLevelZeroTablesStall = 5 + + // Compaction: + // Dynamic compactor allocation + compactors := runtime.NumCPU() / 2 + if compactors < 2 { + compactors = 2 // can't be less than 2 + } + if compactors > opts.MaxLevels { // ensure there is no more compactors than db table levels + compactors = opts.MaxLevels + } + opts.NumCompactors = compactors + // makes sure badger is always compacted on shutdown + opts.CompactL0OnClose = true + + return &opts +} diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index 1927cdaf42..cb07dbb73a 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -126,9 +126,9 @@ func TestFraudProofHandling(t *testing.T) { // blockTime=1 sec, expected befp.height=10 timeOut := blockTime * 5 // during befp validation the node can still receive headers and it mostly depends on - // the operating system or hardware(e.g. on macOS tests is working 100% time with a single height=15, - // and on the Linux VM sometimes the last height is 17-18). So, lets give a chance for our befp validator to check - // the fraud proof and stop the syncer. + // the operating system or hardware(e.g. on macOS tests is working 100% time with a single + // height=15, and on the Linux VM sometimes the last height is 17-18). So, lets give a chance for + // our befp validator to check the fraud proof and stop the syncer. for height < 20 { syncCtx, syncCancel := context.WithTimeout(context.Background(), timeOut) _, err = full.HeaderServ.WaitForHeight(syncCtx, height) diff --git a/share/availability/mocks/availability.go b/share/availability/mocks/availability.go index fc68d3d2bc..bb1c01bf85 100644 --- a/share/availability/mocks/availability.go +++ b/share/availability/mocks/availability.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - header "github.com/celestiaorg/celestia-node/header" gomock "github.com/golang/mock/gomock" + + header "github.com/celestiaorg/celestia-node/header" ) // MockAvailability is a mock of Availability interface. diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index 8b9dcb5d95..dc33c70447 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "runtime" + "github.com/dgraph-io/badger/v4/options" "github.com/filecoin-project/dagstore/index" "github.com/filecoin-project/dagstore/shard" ds "github.com/ipfs/go-datastore" @@ -29,14 +31,24 @@ type simpleInvertedIndex struct { // don't care which shard is used to serve a cid. func newSimpleInvertedIndex(storePath string) (*simpleInvertedIndex, error) { opts := dsbadger.DefaultOptions // this should be copied - // turn off value log GC + // turn off value log GC as we don't use value log opts.GcInterval = 0 - // 20 compactors show to have no hangups on put operation up to 40k blocks with eds size 128. - opts.NumCompactors = 20 // use minimum amount of NumLevelZeroTables to trigger L0 compaction faster opts.NumLevelZeroTables = 1 // MaxLevels = 8 will allow the db to grow to ~11.1 TiB opts.MaxLevels = 8 + // inverted index stores unique hash keys, so we don't need to detect conflicts + opts.DetectConflicts = false + // we don't need compression for inverted index as it just hashes + opts.Compression = options.None + compactors := runtime.NumCPU() + if compactors < 2 { + compactors = 2 + } + if compactors > opts.MaxLevels { // ensure there is no more compactors than db table levels + compactors = opts.MaxLevels + } + opts.NumCompactors = compactors ds, err := dsbadger.NewDatastore(storePath+invertedIndexPath, &opts) if err != nil { diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 738e2b246c..2adfa50cfe 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -8,10 +8,11 @@ import ( context "context" reflect "reflect" + gomock "github.com/golang/mock/gomock" + header "github.com/celestiaorg/celestia-node/header" share "github.com/celestiaorg/celestia-node/share" rsmt2d "github.com/celestiaorg/rsmt2d" - gomock "github.com/golang/mock/gomock" ) // MockGetter is a mock of Getter interface. From 31f0e25a89e229620d14b15f9a22f4b8ec2a20f8 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 30 Nov 2023 17:53:16 +0100 Subject: [PATCH 0934/1008] refactor(modp2p): decouple Bitswap (#2962) Decouples Bitswap into Client and Server. This is necessary to provide new custom options to the Client. In particular, this brings client.WithoutDuplicatedBlockStats() option to the table as nice little optimization for LNs. It disables one metrics we don't care much about atm, but avoids expensive Has call to DB as profiles show. --- nodebuilder/p2p/bitswap.go | 54 +++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go index 19f98609fc..014435071a 100644 --- a/nodebuilder/p2p/bitswap.go +++ b/nodebuilder/p2p/bitswap.go @@ -2,10 +2,12 @@ package p2p import ( "context" + "errors" "fmt" - "github.com/ipfs/boxo/bitswap" + "github.com/ipfs/boxo/bitswap/client" "github.com/ipfs/boxo/bitswap/network" + "github.com/ipfs/boxo/bitswap/server" "github.com/ipfs/boxo/blockstore" "github.com/ipfs/boxo/exchange" "github.com/ipfs/go-datastore" @@ -28,17 +30,38 @@ const ( // dataExchange provides a constructor for IPFS block's DataExchange over BitSwap. func dataExchange(params bitSwapParams) exchange.Interface { - prefix := protocol.ID(fmt.Sprintf("/celestia/%s", params.Net)) - return bitswap.New( + prefix := protocolID(params.Net) + net := network.NewFromIpfsHost(params.Host, &routinghelpers.Null{}, network.Prefix(prefix)) + srvr := server.New( params.Ctx, - network.NewFromIpfsHost(params.Host, &routinghelpers.Null{}, network.Prefix(prefix)), + net, params.Bs, - bitswap.ProvideEnabled(false), - // NOTE: These below ar required for our protocol to work reliably. - // See https://github.com/celestiaorg/celestia-node/issues/732 - bitswap.SetSendDontHaves(false), - bitswap.SetSimulateDontHavesOnTimeout(false), + server.ProvideEnabled(false), // we don't provide blocks over DHT + // NOTE: These below are required for our protocol to work reliably. + // // See https://github.com/celestiaorg/celestia-node/issues/732 + server.SetSendDontHaves(false), ) + + clnt := client.New( + params.Ctx, + net, + params.Bs, + client.WithBlockReceivedNotifier(srvr), + client.SetSimulateDontHavesOnTimeout(false), + client.WithoutDuplicatedBlockStats(), + ) + net.Start(srvr, clnt) // starting with hook does not work + + params.Lifecycle.Append(fx.Hook{ + OnStop: func(ctx context.Context) (err error) { + err = errors.Join(err, clnt.Close()) + err = errors.Join(err, srvr.Close()) + net.Stop() + return err + }, + }) + + return clnt } func blockstoreFromDatastore(ctx context.Context, ds datastore.Batching) (blockstore.Blockstore, error) { @@ -66,8 +89,13 @@ func blockstoreFromEDSStore(ctx context.Context, store *eds.Store) (blockstore.B type bitSwapParams struct { fx.In - Ctx context.Context - Net Network - Host hst.Host - Bs blockstore.Blockstore + Lifecycle fx.Lifecycle + Ctx context.Context + Net Network + Host hst.Host + Bs blockstore.Blockstore +} + +func protocolID(network Network) protocol.ID { + return protocol.ID(fmt.Sprintf("/celestia/%s", network)) } From f2d62f8c15a71fcfb658da5d00e96aea6d859c2f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 4 Dec 2023 13:15:12 +0200 Subject: [PATCH 0935/1008] test(blob): add submitting equal blobs test case (#2914) --- nodebuilder/tests/blob_test.go | 72 +++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index ccbc6440cc..627dd850d0 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -73,6 +73,8 @@ func TestBlobModule(t *testing.T) { { name: "Get", doFn: func(t *testing.T) { + // https://github.com/celestiaorg/celestia-node/issues/2915 + time.Sleep(time.Second) blob1, err := fullClient.Blob.Get(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) require.NoError(t, err) require.Equal(t, blobs[0], blob1) @@ -81,6 +83,8 @@ func TestBlobModule(t *testing.T) { { name: "GetAll", doFn: func(t *testing.T) { + // https://github.com/celestiaorg/celestia-node/issues/2915 + time.Sleep(time.Second) newBlobs, err := fullClient.Blob.GetAll(ctx, height, []share.Namespace{blobs[0].Namespace()}) require.NoError(t, err) require.Len(t, newBlobs, len(appBlobs0)) @@ -91,6 +95,8 @@ func TestBlobModule(t *testing.T) { { name: "Included", doFn: func(t *testing.T) { + // https://github.com/celestiaorg/celestia-node/issues/2915 + time.Sleep(time.Second) proof, err := fullClient.Blob.GetProof(ctx, height, blobs[0].Namespace(), blobs[0].Commitment) require.NoError(t, err) @@ -123,12 +129,76 @@ func TestBlobModule(t *testing.T) { require.ErrorContains(t, err, blob.ErrBlobNotFound.Error()) }, }, + { + name: "Submit equal blobs", + doFn: func(t *testing.T) { + appBlob, err := blobtest.GenerateV0Blobs([]int{8, 4}, true) + require.NoError(t, err) + b, err := blob.NewBlob( + appBlob[0].ShareVersion, + append([]byte{appBlob[0].NamespaceVersion}, appBlob[0].NamespaceID...), + appBlob[0].Data, + ) + require.NoError(t, err) + + height, err := fullClient.Blob.Submit(ctx, []*blob.Blob{b, b}, nil) + require.NoError(t, err) + + _, err = fullClient.Header.WaitForHeight(ctx, height) + require.NoError(t, err) + + b0, err := fullClient.Blob.Get(ctx, height, b.Namespace(), b.Commitment) + require.NoError(t, err) + require.Equal(t, b, b0) + + // give some time to store the data, + // otherwise the test will hang on the IPLD level. + // https://github.com/celestiaorg/celestia-node/issues/2915 + time.Sleep(time.Second) + + proof, err := fullClient.Blob.GetProof(ctx, height, b.Namespace(), b.Commitment) + require.NoError(t, err) + + included, err := fullClient.Blob.Included(ctx, height, b.Namespace(), proof, b.Commitment) + require.NoError(t, err) + require.True(t, included) + }, + }, + { + // This test allows to check that the blob won't be + // deduplicated if it will be sent multiple times in + // different pfbs. + name: "Submit the same blob in different pfb", + doFn: func(t *testing.T) { + h, err := fullClient.Blob.Submit(ctx, []*blob.Blob{blobs[0]}, nil) + require.NoError(t, err) + + _, err = fullClient.Header.WaitForHeight(ctx, h) + require.NoError(t, err) + + b0, err := fullClient.Blob.Get(ctx, h, blobs[0].Namespace(), blobs[0].Commitment) + require.NoError(t, err) + require.Equal(t, blobs[0], b0) + + // give some time to store the data, + // otherwise the test will hang on the IPLD level. + // https://github.com/celestiaorg/celestia-node/issues/2915 + time.Sleep(time.Second) + + proof, err := fullClient.Blob.GetProof(ctx, h, blobs[0].Namespace(), blobs[0].Commitment) + require.NoError(t, err) + + included, err := fullClient.Blob.Included(ctx, h, blobs[0].Namespace(), proof, blobs[0].Commitment) + require.NoError(t, err) + require.True(t, included) + + }, + }, } for _, tt := range test { tt := tt t.Run(tt.name, func(t *testing.T) { - t.Parallel() tt.doFn(t) }) } From d84bc9eb283b9848658619934176e1af27b1439b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:14:28 +0200 Subject: [PATCH 0936/1008] chore(deps): Bump alpine from 3.18.4 to 3.18.5 (#2987) Bumps alpine from 3.18.4 to 3.18.5. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.18.4&new-version=3.18.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a3ac41f7aa..bc01f24149 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN uname -a &&\ CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ make build && make cel-key -FROM docker.io/alpine:3.18.4 +FROM docker.io/alpine:3.18.5 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From b7b6350f8921d4454b8a38c8485d74f908fb6a61 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Tue, 5 Dec 2023 06:59:50 -0500 Subject: [PATCH 0937/1008] ci: configure prerelease-suffix (#2990) --- .goreleaser.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b229b4c348..9a3991ecae 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -65,3 +65,5 @@ changelog: exclude: - "^docs:" - "^test:" +git: + prerelease_suffix: "-" From dc1bd000272e25b880f312cc5f4532727824f339 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:56:20 +0100 Subject: [PATCH 0938/1008] pruner: Implement skeleton for pruner package (#2972) This PR implements a skeleton for the pruner package and integrates the pruner's AvailabilityWindow determination into the DASer sampling routine. Namings are up for discussion. --------- Co-authored-by: New Author Name --- das/daser.go | 15 +++++++++++ das/daser_test.go | 3 ++- das/options.go | 15 ++++++++++- libs/utils/fs.go | 4 ++- libs/utils/square.go | 4 ++- logs/logs.go | 4 ++- nodebuilder/das/constructors.go | 5 ++++ nodebuilder/module.go | 2 ++ nodebuilder/prune/module.go | 47 +++++++++++++++++++++++++++++++++ nodebuilder/state/config.go | 4 ++- pruner/archival/pruner.go | 20 ++++++++++++++ pruner/archival/window.go | 5 ++++ pruner/light/pruner.go | 17 ++++++++++++ pruner/light/window.go | 9 +++++++ pruner/pruner.go | 13 +++++++++ pruner/service.go | 25 ++++++++++++++++++ pruner/window.go | 7 +++++ 17 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 nodebuilder/prune/module.go create mode 100644 pruner/archival/pruner.go create mode 100644 pruner/archival/window.go create mode 100644 pruner/light/pruner.go create mode 100644 pruner/light/window.go create mode 100644 pruner/pruner.go create mode 100644 pruner/service.go create mode 100644 pruner/window.go diff --git a/das/daser.go b/das/daser.go index 29139d3a5f..40eee3d316 100644 --- a/das/daser.go +++ b/das/daser.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sync/atomic" + "time" "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -147,6 +148,12 @@ func (d *DASer) Stop(ctx context.Context) error { } func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { + // short-circuit if pruning is enabled and the header is outside the + // availability window + if !d.isWithinSamplingWindow(h) { + return nil + } + err := d.da.SharesAvailable(ctx, h) if err != nil { var byzantineErr *byzantine.ErrByzantine @@ -162,6 +169,14 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { return nil } +func (d *DASer) isWithinSamplingWindow(eh *header.ExtendedHeader) bool { + // if sampling window is not set, then all headers are within the window + if d.params.SamplingWindow == 0 { + return true + } + return time.Since(eh.Time()) <= d.params.SamplingWindow +} + // SamplingStats returns the current statistics over the DA sampling process. func (d *DASer) SamplingStats(ctx context.Context) (SamplingStats, error) { return d.sampler.stats(ctx) diff --git a/das/daser_test.go b/das/daser_test.go index 1ec160e224..fd1eb39f7d 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -230,7 +230,8 @@ func TestDASerSampleTimeout(t *testing.T) { fserv := &fraudtest.DummyService[*header.ExtendedHeader]{} // create and start DASer - daser, err := NewDASer(avail, sub, getter, ds, fserv, newBroadcastMock(1), WithSampleTimeout(1)) + daser, err := NewDASer(avail, sub, getter, ds, fserv, newBroadcastMock(1), + WithSampleTimeout(1)) require.NoError(t, err) require.NoError(t, daser.Start(ctx)) diff --git a/das/options.go b/das/options.go index 6af8a02174..c2f2b2fedb 100644 --- a/das/options.go +++ b/das/options.go @@ -40,6 +40,11 @@ type Parameters struct { // divided between parallel workers. SampleTimeout should be adjusted proportionally to // ConcurrencyLimit. SampleTimeout time.Duration + + // SamplingWindow determines the time window that headers should fall into + // in order to be sampled. If set to 0, the sampling window will include + // all headers. + SamplingWindow time.Duration } // DefaultParameters returns the default configuration values for the daser parameters @@ -148,10 +153,18 @@ func WithSampleFrom(sampleFrom uint64) Option { } } -// WithSampleFrom is a functional option to configure the daser's `SampleTimeout` parameter +// WithSampleTimeout is a functional option to configure the daser's `SampleTimeout` parameter // Refer to WithSamplingRange documentation to see an example of how to use this func WithSampleTimeout(sampleTimeout time.Duration) Option { return func(d *DASer) { d.params.SampleTimeout = sampleTimeout } } + +// WithSamplingWindow is a functional option to configure the DASer's +// `SamplingWindow` parameter. +func WithSamplingWindow(samplingWindow time.Duration) Option { + return func(d *DASer) { + d.params.SamplingWindow = samplingWindow + } +} diff --git a/libs/utils/fs.go b/libs/utils/fs.go index d67e9a1eaa..4ad8b6443e 100644 --- a/libs/utils/fs.go +++ b/libs/utils/fs.go @@ -1,6 +1,8 @@ package utils -import "os" +import ( + "os" +) // Exists checks whether file or directory exists under the given 'path' on the system. func Exists(path string) bool { diff --git a/libs/utils/square.go b/libs/utils/square.go index ce2663fd81..68d7fc5ce7 100644 --- a/libs/utils/square.go +++ b/libs/utils/square.go @@ -1,6 +1,8 @@ package utils -import "math" +import ( + "math" +) // SquareSize returns the size of the square based on the given amount of shares. func SquareSize(lenShares int) uint64 { diff --git a/logs/logs.go b/logs/logs.go index 23d0683996..5cb9ed16c6 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -1,6 +1,8 @@ package logs -import logging "github.com/ipfs/go-log/v2" +import ( + logging "github.com/ipfs/go-log/v2" +) func SetAllLoggers(level logging.LogLevel) { logging.SetAllLoggers(level) diff --git a/nodebuilder/das/constructors.go b/nodebuilder/das/constructors.go index 7c6b5bed4f..973aca5679 100644 --- a/nodebuilder/das/constructors.go +++ b/nodebuilder/das/constructors.go @@ -3,6 +3,7 @@ package das import ( "context" "fmt" + "time" "github.com/ipfs/go-datastore" @@ -12,6 +13,7 @@ import ( "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" modfraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/pruner" "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds/byzantine" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" @@ -44,8 +46,11 @@ func newDASer( batching datastore.Batching, fraudServ fraud.Service[*header.ExtendedHeader], bFn shrexsub.BroadcastFn, + availWindow pruner.AvailabilityWindow, options ...das.Option, ) (*das.DASer, *modfraud.ServiceBreaker[*das.DASer, *header.ExtendedHeader], error) { + options = append(options, das.WithSamplingWindow(time.Duration(availWindow))) + ds, err := das.NewDASer(da, hsub, store, batching, fraudServ, bFn, options...) if err != nil { return nil, nil, err diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 3068113102..26dc1d9e33 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -15,6 +15,7 @@ import ( modhead "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/prune" "github.com/celestiaorg/celestia-node/nodebuilder/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/share" "github.com/celestiaorg/celestia-node/nodebuilder/state" @@ -56,6 +57,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store fraud.ConstructModule(tp), blob.ConstructModule(), node.ConstructModule(tp), + prune.ConstructModule(tp), ) return fx.Module( diff --git a/nodebuilder/prune/module.go b/nodebuilder/prune/module.go new file mode 100644 index 0000000000..330ef21cdc --- /dev/null +++ b/nodebuilder/prune/module.go @@ -0,0 +1,47 @@ +package prune + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/pruner" + "github.com/celestiaorg/celestia-node/pruner/archival" + "github.com/celestiaorg/celestia-node/pruner/light" +) + +func ConstructModule(tp node.Type) fx.Option { + baseComponents := fx.Options( + fx.Provide(fx.Annotate( + pruner.NewService, + fx.OnStart(func(ctx context.Context, p *pruner.Service) error { + return p.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, p *pruner.Service) error { + return p.Stop(ctx) + }), + )), + ) + + switch tp { + case node.Full, node.Bridge: + return fx.Module("prune", + baseComponents, + fx.Provide(func() pruner.Pruner { + return archival.NewPruner() + }), + fx.Supply(archival.Window), + ) + case node.Light: + return fx.Module("prune", + baseComponents, + fx.Provide(func() pruner.Pruner { + return light.NewPruner() + }), + fx.Supply(archival.Window), // TODO @renaynay: turn this into light.Window in following PR + ) + default: + panic("unknown node type") + } +} diff --git a/nodebuilder/state/config.go b/nodebuilder/state/config.go index e6db813e06..f42e646b76 100644 --- a/nodebuilder/state/config.go +++ b/nodebuilder/state/config.go @@ -1,6 +1,8 @@ package state -import "github.com/cosmos/cosmos-sdk/crypto/keyring" +import ( + "github.com/cosmos/cosmos-sdk/crypto/keyring" +) var defaultKeyringBackend = keyring.BackendTest diff --git a/pruner/archival/pruner.go b/pruner/archival/pruner.go new file mode 100644 index 0000000000..7b1cb935f3 --- /dev/null +++ b/pruner/archival/pruner.go @@ -0,0 +1,20 @@ +package archival + +import ( + "context" + + "github.com/celestiaorg/celestia-node/header" +) + +// Pruner is a noop implementation of the pruner.Factory interface +// that allows archival nodes to sync and retain historical data +// that is out of the availability window. +type Pruner struct{} + +func NewPruner() *Pruner { + return &Pruner{} +} + +func (p *Pruner) Prune(context.Context, ...*header.ExtendedHeader) error { + return nil +} diff --git a/pruner/archival/window.go b/pruner/archival/window.go new file mode 100644 index 0000000000..b89a779816 --- /dev/null +++ b/pruner/archival/window.go @@ -0,0 +1,5 @@ +package archival + +import "github.com/celestiaorg/celestia-node/pruner" + +const Window = pruner.AvailabilityWindow(0) diff --git a/pruner/light/pruner.go b/pruner/light/pruner.go new file mode 100644 index 0000000000..513bfa2b66 --- /dev/null +++ b/pruner/light/pruner.go @@ -0,0 +1,17 @@ +package light + +import ( + "context" + + "github.com/celestiaorg/celestia-node/header" +) + +type Pruner struct{} + +func NewPruner() *Pruner { + return &Pruner{} +} + +func (p *Pruner) Prune(context.Context, ...*header.ExtendedHeader) error { + return nil +} diff --git a/pruner/light/window.go b/pruner/light/window.go new file mode 100644 index 0000000000..53bfe4a163 --- /dev/null +++ b/pruner/light/window.go @@ -0,0 +1,9 @@ +package light + +import ( + "time" + + "github.com/celestiaorg/celestia-node/pruner" +) + +const Window = pruner.AvailabilityWindow(time.Second * 86400 * 30) diff --git a/pruner/pruner.go b/pruner/pruner.go new file mode 100644 index 0000000000..fae60e483c --- /dev/null +++ b/pruner/pruner.go @@ -0,0 +1,13 @@ +package pruner + +import ( + "context" + + "github.com/celestiaorg/celestia-node/header" +) + +// Pruner contains methods necessary to prune data +// from the node's datastore. +type Pruner interface { + Prune(context.Context, ...*header.ExtendedHeader) error +} diff --git a/pruner/service.go b/pruner/service.go new file mode 100644 index 0000000000..f67265977a --- /dev/null +++ b/pruner/service.go @@ -0,0 +1,25 @@ +package pruner + +import ( + "context" +) + +// Service handles the pruning routine for the node using the +// prune Pruner. +type Service struct { + pruner Pruner +} + +func NewService(p Pruner) *Service { + return &Service{ + pruner: p, + } +} + +func (s *Service) Start(context.Context) error { + return nil +} + +func (s *Service) Stop(context.Context) error { + return nil +} diff --git a/pruner/window.go b/pruner/window.go new file mode 100644 index 0000000000..0a86c535ce --- /dev/null +++ b/pruner/window.go @@ -0,0 +1,7 @@ +package pruner + +import ( + "time" +) + +type AvailabilityWindow time.Duration From b709b4eb85803fa86310d4f235ce1988efbf3c19 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 8 Dec 2023 13:20:34 +0200 Subject: [PATCH 0939/1008] fix(cmd): explicitly set a string representation of the error (#2997) --- cmd/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/util.go b/cmd/util.go index 020699449a..08fa02155b 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -24,7 +24,7 @@ import ( func PrintOutput(data interface{}, err error, formatData func(interface{}) interface{}) error { switch { case err != nil: - data = err + data = err.Error() case formatData != nil: data = formatData(data) } From 327291292a87b4f7fadfe27b271e28d0f84dd59e Mon Sep 17 00:00:00 2001 From: Bork Bork <107079055+BorkBorked@users.noreply.github.com> Date: Tue, 12 Dec 2023 22:26:15 +0100 Subject: [PATCH 0940/1008] share: typos fix (#2993) --- share/getters/getter_test.go | 2 +- share/p2p/params.go | 2 +- share/p2p/shrexsub/pubsub_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 77c470dae9..8264f6e822 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -231,7 +231,7 @@ func TestIPLDGetter(t *testing.T) { require.NoError(t, err) require.Empty(t, emptyShares.Flatten()) - // nid doesnt exist in root + // nid doesn't exist in root emptyRoot := da.MinDataAvailabilityHeader() eh.DAH = &emptyRoot emptyShares, err = sg.GetSharesByNamespace(ctx, eh, namespace) diff --git a/share/p2p/params.go b/share/p2p/params.go index f11179293d..6636e38fc5 100644 --- a/share/p2p/params.go +++ b/share/p2p/params.go @@ -45,7 +45,7 @@ func (p *Parameters) Validate() error { return fmt.Errorf("invalid write timeout: %v, %s", p.ServerWriteTimeout, errSuffix) } if p.HandleRequestTimeout <= 0 { - return fmt.Errorf("invalid hadnle request timeout: %v, %s", p.HandleRequestTimeout, errSuffix) + return fmt.Errorf("invalid handle request timeout: %v, %s", p.HandleRequestTimeout, errSuffix) } if p.ConcurrencyLimit <= 0 { return fmt.Errorf("invalid concurrency limit: %s", errSuffix) diff --git a/share/p2p/shrexsub/pubsub_test.go b/share/p2p/shrexsub/pubsub_test.go index dcf85515e8..85b16c055b 100644 --- a/share/p2p/shrexsub/pubsub_test.go +++ b/share/p2p/shrexsub/pubsub_test.go @@ -45,7 +45,7 @@ func TestPubSub(t *testing.T) { errExpected bool }{ { - name: "vaild height, valid hash", + name: "valid height, valid hash", notif: Notification{ Height: 1, DataHash: rand.Bytes(32), From 31e06606820bddf40111b8daeae7acaa189ca761 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 13 Dec 2023 16:21:21 +0100 Subject: [PATCH 0941/1008] Revert "fix(modp2p): temporary disable quic (#2837)" (#2963) Reverts disabled quic. We weren't able to reproduce [the issue](https://github.com/libp2p/go-libp2p/issues/2591) since we first observed it, as well as quic released changes to address this. Even if the issue is there, I think it's more or less safe as we always have TCP as fallback --- nodebuilder/p2p/addrs.go | 19 +++---------------- nodebuilder/p2p/host.go | 20 +------------------- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/nodebuilder/p2p/addrs.go b/nodebuilder/p2p/addrs.go index 27fbd244a3..d8f50c8144 100644 --- a/nodebuilder/p2p/addrs.go +++ b/nodebuilder/p2p/addrs.go @@ -2,7 +2,6 @@ package p2p import ( "fmt" - "slices" p2pconfig "github.com/libp2p/go-libp2p/config" hst "github.com/libp2p/go-libp2p/core/host" @@ -12,25 +11,13 @@ import ( // Listen returns invoke function that starts listening for inbound connections with libp2p.Host. func Listen(listen []string) func(h hst.Host) (err error) { return func(h hst.Host) (err error) { - maListen := make([]ma.Multiaddr, 0, len(listen)) - for _, addr := range listen { - maddr, err := ma.NewMultiaddr(addr) + maListen := make([]ma.Multiaddr, len(listen)) + for i, addr := range listen { + maListen[i], err = ma.NewMultiaddr(addr) if err != nil { return fmt.Errorf("failure to parse config.P2P.ListenAddresses: %s", err) } - - if !enableQUIC { - // TODO(@WonderTan): Remove this check when QUIC is stable - if slices.ContainsFunc(maddr.Protocols(), func(p ma.Protocol) bool { - return p.Code == ma.P_QUIC_V1 || p.Code == ma.P_WEBTRANSPORT - }) { - continue - } - } - - maListen = append(maListen, maddr) } - return h.Network().Listen(maListen...) } } diff --git a/nodebuilder/p2p/host.go b/nodebuilder/p2p/host.go index c3943a02fa..e55cb65d1f 100644 --- a/nodebuilder/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -3,7 +3,6 @@ package p2p import ( "context" "fmt" - "os" "github.com/libp2p/go-libp2p" p2pconfig "github.com/libp2p/go-libp2p/config" @@ -17,22 +16,12 @@ import ( "github.com/libp2p/go-libp2p/core/routing" routedhost "github.com/libp2p/go-libp2p/p2p/host/routed" "github.com/libp2p/go-libp2p/p2p/net/conngater" - quic "github.com/libp2p/go-libp2p/p2p/transport/quic" - "github.com/libp2p/go-libp2p/p2p/transport/tcp" - webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" "github.com/prometheus/client_golang/prometheus" "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -var enableQUIC bool - -func init() { - _, ok := os.LookupEnv("CELESTIA_ENABLE_QUIC") - enableQUIC = ok -} - // routedHost constructs a wrapped Host that may fallback to address discovery, // if any top-level operation on the Host is provided with PeerID(Hash(PbK)) only. func routedHost(base HostBase, r routing.PeerRouting) hst.Host { @@ -55,15 +44,8 @@ func host(params hostParams) (HostBase, error) { libp2p.ResourceManager(params.ResourceManager), // to clearly define what defaults we rely upon libp2p.DefaultSecurity, + libp2p.DefaultTransports, libp2p.DefaultMuxers, - libp2p.Transport(tcp.NewTCPTransport), - } - - if enableQUIC { - opts = append(opts, - libp2p.Transport(quic.NewTransport), - libp2p.Transport(webtransport.New), - ) } if params.Registry != nil { From 1403f1d7faa8a5420af7a5bffde138e839405a88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 10:59:39 +0000 Subject: [PATCH 0942/1008] chore(deps): Bump alpine from 3.18.5 to 3.19.0 (#3002) Bumps alpine from 3.18.5 to 3.19.0. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.18.5&new-version=3.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bc01f24149..e15d7d4f1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN uname -a &&\ CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ make build && make cel-key -FROM docker.io/alpine:3.18.5 +FROM docker.io/alpine:3.19.0 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From 791f53f236c6263c078c5a080787cc8a549d3670 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:17:42 +0000 Subject: [PATCH 0943/1008] chore(deps): Bump actions/stale from 8 to 9 (#3001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9.
    Release notes

    Sourced from actions/stale's releases.

    v9.0.0

    Breaking Changes

    1. Action is now stateful: If the action ends because of operations-per-run then the next run will start from the first unprocessed issue skipping the issues processed during the previous run(s). The state is reset when all the issues are processed. This should be considered for scheduling workflow runs.
    2. Version 9 of this action updated the runtime to Node.js 20. All scripts are now run with Node.js 20 instead of Node.js 16 and are affected by any breaking changes between Node.js 16 and 20.

    What Else Changed

    1. Performance optimization that removes unnecessary API calls by @​dsame #1033 fixes #792
    2. Logs displaying current github API rate limit by @​dsame #1032 addresses #1029

    For more information, please read the action documentation and its section about statefulness

    New Contributors

    Full Changelog: https://github.com/actions/stale/compare/v8...v9.0.0

    Changelog

    Sourced from actions/stale's changelog.

    Changelog

    [7.0.0]

    :warning: Breaking change :warning:

    [6.0.1]

    Update @​actions/core to v1.10.0 (#839)

    [6.0.0]

    :warning: Breaking change :warning:

    Issues/PRs default close-issue-reason is now not_planned(#789)

    [5.1.0]

    Don't process stale issues right after they're marked stale [Add close-issue-reason option]#764#772 Various dependabot/dependency updates

    4.1.0 (2021-07-14)

    Features

    4.0.0 (2021-07-14)

    Features

    Bug Fixes

    • dry-run: forbid mutations in dry-run (#500) (f1017f3), closes #499
    • logs: coloured logs (#465) (5fbbfba)
    • operations: fail fast the current batch to respect the operations limit (#474) (5f6f311), closes #466
    • label comparison: make label comparison case insensitive #517, closes #516
    • filtering comments by actor could have strange behavior: "stale" comments are now detected based on if the message is the stale message not who made the comment(#519), fixes #441, #509, #518

    Breaking Changes

    ... (truncated)

    Commits
    • 28ca103 Upgrade Node to v20 (#1110)
    • b69b346 build(deps-dev): bump @​types/node from 18.16.18 to 20.5.1 (#1079)
    • 88a6f4f build(deps-dev): bump typescript from 5.1.3 to 5.2.2 (#1083)
    • 796531a Merge pull request #1080 from akv-platform/fix-delete-cache
    • 8986f62 Don not try to delete cache if it does not exists
    • cab99b3 fix typo proceeded/processed
    • 184e7af Merge pull request #1064 from actions/dependabot/npm_and_yarn/typescript-esli...
    • 523885c chore: update eslint-plugin, parser and eslint-plugin-jest
    • 2487a1d build(deps-dev): bump @​typescript-eslint/eslint-plugin
    • 60c722e Merge pull request #1063 from actions/dependabot/npm_and_yarn/jest-29.6.2
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/stale&package-manager=github_actions&previous-version=8&new-version=9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d1088e5f33..595effed1f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 60 From cde1065deb8d1407b0cf855aab3d573bd87d5c7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 13:22:04 +0000 Subject: [PATCH 0944/1008] chore(deps): Bump actions/setup-go from 4 to 5 (#3000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
    Release notes

    Sourced from actions/setup-go's releases.

    v5.0.0

    What's Changed

    In scope of this release, we change Nodejs runtime from node16 to node20 (actions/setup-go#421). Moreover, we update some dependencies to the latest versions (actions/setup-go#445).

    Besides, this release contains such changes as:

    New Contributors

    Full Changelog: https://github.com/actions/setup-go/compare/v4...v5.0.0

    v4.1.0

    What's Changed

    In scope of this release, slow installation on Windows was fixed by @​dsame in actions/setup-go#393 and OS version was added to primaryKey for Ubuntu runners to avoid conflicts (actions/setup-go#383)

    This release also includes the following changes:

    New Contributors

    Full Changelog: https://github.com/actions/setup-go/compare/v4...v4.1.0

    v4.0.1

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/setup-go/compare/v4...v4.0.1

    Commits
    • 0c52d54 Update dependencies for node20 (#445)
    • bfd2fb3 Merge pull request #421 from chenrui333/node20-runtime
    • 3d65fa5 feat: bump to use actions/checkout@v4
    • 8a505c9 feat: bump to use node20 runtime
    • 883490d Merge pull request #417 from artemgavrilov/main
    • d45ebba Rephrase sentence
    • 317c661 Replace wildcards term with globs.
    • f90673a Merge pull request #1 from artemgavrilov/caching-docs-improvement
    • 8018234 Improve documentation regarding dependencies cachin
    • d085b4f Merge pull request #411 from galargh/fix/windows-hostedtoolcache
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-go&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci_release.yml | 2 +- .github/workflows/go-ci.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 25970fdb11..ad15a083bc 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -118,7 +118,7 @@ jobs: - run: git fetch --force --tags - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: ${{ needs.setup.outputs.go-version }} diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 056e4816bf..c14692cd58 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -35,7 +35,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} @@ -55,7 +55,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} @@ -79,7 +79,7 @@ jobs: - uses: actions/checkout@v4 - name: set up go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} @@ -113,7 +113,7 @@ jobs: - uses: actions/checkout@v4 - name: set up go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@v4 - name: set up go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ inputs.go-version }} From fae3024a92396fa4217aa21b46fedb2505cb65b4 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 18 Dec 2023 10:39:12 +0000 Subject: [PATCH 0945/1008] feat(rpc): default binding to localhost vs 0.0.0.0 open to world (#2955) fixes #2824 which requests we bind to localhost by default (previously was world accessible) - sets defaults to bind to localhost properly (previously flags SAID the defaults were localhost but were actually 0.0.0.0) - removes an unused `DefaultConfig` defined in `api/gateway/package` - moves defaults (bind address and port) to vars so we can use them to set in flag usage messages so they don't drift out of sync with reality - adds tests for the defaults - adds test cases for flag definitions - adds test cases for flag parsing Other skips suggestion of binding vhosts, we don't do any kind of middleware checking in rpc (i believe in geth this would be a way to bind to 0.0.0.0 then limit to allowed remote IPs making requests). I'd suggest this is a more advanced configuration use case and belongs outside of the node itself to be managed by the infra operator where it'd be handled by a ingress gateway or routing rule to the machine/instance/orchestrator where the node software is running --- api/gateway/config.go | 8 --- nodebuilder/config_test.go | 4 +- nodebuilder/gateway/config.go | 4 +- nodebuilder/gateway/config_test.go | 18 ++++++ nodebuilder/gateway/defaults.go | 6 ++ nodebuilder/gateway/defaults_test.go | 12 ++++ nodebuilder/gateway/flags.go | 6 +- nodebuilder/gateway/flags_test.go | 95 ++++++++++++++++++++++++++++ nodebuilder/rpc/config.go | 4 +- nodebuilder/rpc/config_test.go | 59 +++++++++++++++++ nodebuilder/rpc/defaults.go | 6 ++ nodebuilder/rpc/defaults_test.go | 12 ++++ nodebuilder/rpc/flags.go | 6 +- nodebuilder/rpc/flags_test.go | 95 ++++++++++++++++++++++++++++ 14 files changed, 317 insertions(+), 18 deletions(-) create mode 100644 nodebuilder/gateway/config_test.go create mode 100644 nodebuilder/gateway/defaults.go create mode 100644 nodebuilder/gateway/defaults_test.go create mode 100644 nodebuilder/gateway/flags_test.go create mode 100644 nodebuilder/rpc/config_test.go create mode 100644 nodebuilder/rpc/defaults.go create mode 100644 nodebuilder/rpc/defaults_test.go create mode 100644 nodebuilder/rpc/flags_test.go diff --git a/api/gateway/config.go b/api/gateway/config.go index f7d8bb44b1..0485da486e 100644 --- a/api/gateway/config.go +++ b/api/gateway/config.go @@ -11,14 +11,6 @@ type Config struct { Port string } -func DefaultConfig() Config { - return Config{ - Address: "0.0.0.0", - // do NOT expose the same port as celestia-core by default so that both can run on the same machine - Port: "26658", - } -} - func (cfg *Config) Validate() error { if ip := net.ParseIP(cfg.Address); ip == nil { return fmt.Errorf("service/gateway: invalid listen address format: %s", cfg.Address) diff --git a/nodebuilder/config_test.go b/nodebuilder/config_test.go index db9af3a64d..e7b64b0aed 100644 --- a/nodebuilder/config_test.go +++ b/nodebuilder/config_test.go @@ -69,7 +69,7 @@ var outdatedConfig = ` KeyringBackend = "test" [P2P] - ListenAddresses = ["/ip4/0.0.0.0/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", "/ip4/0.0.0.0/tcp/2121", + ListenAddresses = ["/ip4/0.0.0.0/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", "/ip4/0.0.0.0/tcp/2121", "/ip6/::/tcp/2121"] AnnounceAddresses = [] NoAnnounceAddresses = ["/ip4/0.0.0.0/udp/2121/quic-v1", "/ip4/127.0.0.1/udp/2121/quic-v1", "/ip6/::/udp/2121/quic-v1", @@ -91,7 +91,7 @@ var outdatedConfig = ` [Gateway] Address = "0.0.0.0" Port = "26659" - Enabled = true + Enabled = true [Share] PeersLimit = 5 diff --git a/nodebuilder/gateway/config.go b/nodebuilder/gateway/config.go index 903a27489a..c49a4749a3 100644 --- a/nodebuilder/gateway/config.go +++ b/nodebuilder/gateway/config.go @@ -15,9 +15,9 @@ type Config struct { func DefaultConfig() Config { return Config{ - Address: "0.0.0.0", + Address: defaultBindAddress, // do NOT expose the same port as celestia-core by default so that both can run on the same machine - Port: "26659", + Port: defaultPort, Enabled: false, } } diff --git a/nodebuilder/gateway/config_test.go b/nodebuilder/gateway/config_test.go new file mode 100644 index 0000000000..9ef3f1e310 --- /dev/null +++ b/nodebuilder/gateway/config_test.go @@ -0,0 +1,18 @@ +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestDefaultConfig tests that the default gateway config is correct. +func TestDefaultConfig(t *testing.T) { + expected := Config{ + Address: defaultBindAddress, + Port: defaultPort, + Enabled: false, + } + + assert.Equal(t, expected, DefaultConfig()) +} diff --git a/nodebuilder/gateway/defaults.go b/nodebuilder/gateway/defaults.go new file mode 100644 index 0000000000..e6c48d5d4e --- /dev/null +++ b/nodebuilder/gateway/defaults.go @@ -0,0 +1,6 @@ +package gateway + +const ( + defaultBindAddress = "localhost" + defaultPort = "26659" +) diff --git a/nodebuilder/gateway/defaults_test.go b/nodebuilder/gateway/defaults_test.go new file mode 100644 index 0000000000..c504f8cca4 --- /dev/null +++ b/nodebuilder/gateway/defaults_test.go @@ -0,0 +1,12 @@ +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestServerDefaultConstants(t *testing.T) { + assert.Equal(t, "localhost", defaultBindAddress) + assert.Equal(t, "26659", defaultPort) +} diff --git a/nodebuilder/gateway/flags.go b/nodebuilder/gateway/flags.go index cd13e47162..6da4a66f03 100644 --- a/nodebuilder/gateway/flags.go +++ b/nodebuilder/gateway/flags.go @@ -1,6 +1,8 @@ package gateway import ( + "fmt" + "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) @@ -23,12 +25,12 @@ func Flags() *flag.FlagSet { flags.String( addrFlag, "", - "Set a custom gateway listen address (default: localhost)", + fmt.Sprintf("Set a custom gateway listen address (default: %s)", defaultBindAddress), ) flags.String( portFlag, "", - "Set a custom gateway port (default: 26659)", + fmt.Sprintf("Set a custom gateway port (default: %s)", defaultPort), ) return flags diff --git a/nodebuilder/gateway/flags_test.go b/nodebuilder/gateway/flags_test.go new file mode 100644 index 0000000000..5f55ac77f2 --- /dev/null +++ b/nodebuilder/gateway/flags_test.go @@ -0,0 +1,95 @@ +package gateway + +import ( + "fmt" + "strconv" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFlags(t *testing.T) { + flags := Flags() + + enabled := flags.Lookup(enabledFlag) + require.NotNil(t, enabled) + assert.Equal(t, "false", enabled.Value.String()) + assert.Equal(t, "Enables the REST gateway", enabled.Usage) + + addr := flags.Lookup(addrFlag) + require.NotNil(t, addr) + assert.Equal(t, "", addr.Value.String()) + assert.Equal(t, fmt.Sprintf("Set a custom gateway listen address (default: %s)", defaultBindAddress), addr.Usage) + + port := flags.Lookup(portFlag) + require.NotNil(t, port) + assert.Equal(t, "", port.Value.String()) + assert.Equal(t, fmt.Sprintf("Set a custom gateway port (default: %s)", defaultPort), port.Usage) +} + +func TestParseFlags(t *testing.T) { + tests := []struct { + name string + enabledFlag bool + addrFlag string + portFlag string + expectedCfg *Config + }{ + { + name: "Enabled flag is true", + enabledFlag: true, + addrFlag: "127.0.0.1", + portFlag: "8080", + expectedCfg: &Config{ + Enabled: true, + Address: "127.0.0.1", + Port: "8080", + }, + }, + { + name: "Enabled flag is false", + enabledFlag: false, + addrFlag: "127.0.0.1", + portFlag: "8080", + expectedCfg: &Config{ + Enabled: false, + Address: "127.0.0.1", + Port: "8080", + }, + }, + { + name: "Enabled flag is false and address/port flags are not changed", + enabledFlag: false, + addrFlag: "", + portFlag: "", + expectedCfg: &Config{ + Enabled: false, + Address: "", + Port: "", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cmd := &cobra.Command{} + cfg := &Config{} + + cmd.Flags().AddFlagSet(Flags()) + + err := cmd.Flags().Set(enabledFlag, strconv.FormatBool(test.enabledFlag)) + assert.NoError(t, err) + err = cmd.Flags().Set(addrFlag, test.addrFlag) + assert.NoError(t, err) + err = cmd.Flags().Set(portFlag, test.portFlag) + assert.NoError(t, err) + + ParseFlags(cmd, cfg) + assert.Equal(t, test.expectedCfg.Enabled, cfg.Enabled) + assert.Equal(t, test.expectedCfg.Address, cfg.Address) + assert.Equal(t, test.expectedCfg.Port, cfg.Port) + }) + } +} diff --git a/nodebuilder/rpc/config.go b/nodebuilder/rpc/config.go index 306dd562e3..a270768646 100644 --- a/nodebuilder/rpc/config.go +++ b/nodebuilder/rpc/config.go @@ -14,9 +14,9 @@ type Config struct { func DefaultConfig() Config { return Config{ - Address: "0.0.0.0", + Address: defaultBindAddress, // do NOT expose the same port as celestia-core by default so that both can run on the same machine - Port: "26658", + Port: defaultPort, } } diff --git a/nodebuilder/rpc/config_test.go b/nodebuilder/rpc/config_test.go new file mode 100644 index 0000000000..1c78a1a19f --- /dev/null +++ b/nodebuilder/rpc/config_test.go @@ -0,0 +1,59 @@ +package rpc + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestDefaultConfig tests that the default gateway config is correct. +func TestDefaultConfig(t *testing.T) { + expected := Config{ + Address: defaultBindAddress, + Port: defaultPort, + } + + assert.Equal(t, expected, DefaultConfig()) +} + +func TestConfigValidate(t *testing.T) { + tests := []struct { + name string + cfg Config + err bool + }{ + { + name: "valid config", + cfg: Config{ + Address: "127.0.0.1", + Port: "8080", + }, + err: false, + }, + { + name: "invalid address", + cfg: Config{ + Address: "invalid", + Port: "8080", + }, + err: true, + }, + { + name: "invalid port", + cfg: Config{ + Address: "127.0.0.1", + Port: "invalid", + }, + err: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate() + if (err != nil) != tt.err { + t.Errorf("Config.Validate() error = %v, err %v", err, tt.err) + } + }) + } +} diff --git a/nodebuilder/rpc/defaults.go b/nodebuilder/rpc/defaults.go new file mode 100644 index 0000000000..55e51a7c9b --- /dev/null +++ b/nodebuilder/rpc/defaults.go @@ -0,0 +1,6 @@ +package rpc + +const ( + defaultBindAddress = "localhost" + defaultPort = "26658" +) diff --git a/nodebuilder/rpc/defaults_test.go b/nodebuilder/rpc/defaults_test.go new file mode 100644 index 0000000000..74d9c98cfc --- /dev/null +++ b/nodebuilder/rpc/defaults_test.go @@ -0,0 +1,12 @@ +package rpc + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestServerDefaultConstants(t *testing.T) { + assert.Equal(t, "localhost", defaultBindAddress) + assert.Equal(t, "26658", defaultPort) +} diff --git a/nodebuilder/rpc/flags.go b/nodebuilder/rpc/flags.go index 167dbc803a..b7bad333df 100644 --- a/nodebuilder/rpc/flags.go +++ b/nodebuilder/rpc/flags.go @@ -1,6 +1,8 @@ package rpc import ( + "fmt" + "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) @@ -17,12 +19,12 @@ func Flags() *flag.FlagSet { flags.String( addrFlag, "", - "Set a custom RPC listen address (default: localhost)", + fmt.Sprintf("Set a custom RPC listen address (default: %s)", defaultBindAddress), ) flags.String( portFlag, "", - "Set a custom RPC port (default: 26658)", + fmt.Sprintf("Set a custom RPC port (default: %s)", defaultPort), ) return flags diff --git a/nodebuilder/rpc/flags_test.go b/nodebuilder/rpc/flags_test.go new file mode 100644 index 0000000000..1370995833 --- /dev/null +++ b/nodebuilder/rpc/flags_test.go @@ -0,0 +1,95 @@ +package rpc + +import ( + "fmt" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFlags(t *testing.T) { + flags := Flags() + + // Test addrFlag + addr := flags.Lookup(addrFlag) + require.NotNil(t, addr) + assert.Equal(t, "", addr.Value.String()) + assert.Equal(t, fmt.Sprintf("Set a custom RPC listen address (default: %s)", defaultBindAddress), addr.Usage) + + // Test portFlag + port := flags.Lookup(portFlag) + require.NotNil(t, port) + assert.Equal(t, "", port.Value.String()) + assert.Equal(t, fmt.Sprintf("Set a custom RPC port (default: %s)", defaultPort), port.Usage) +} + +// TestParseFlags tests the ParseFlags function in rpc/flags.go +func TestParseFlags(t *testing.T) { + tests := []struct { + name string + addrFlag string + portFlag string + expected *Config + }{ + { + name: "addrFlag is set", + addrFlag: "127.0.0.1:8080", + portFlag: "", + expected: &Config{ + Address: "127.0.0.1:8080", + Port: "", + }, + }, + { + name: "portFlag is set", + addrFlag: "", + portFlag: "9090", + expected: &Config{ + Address: "", + Port: "9090", + }, + }, + { + name: "both addrFlag and portFlag are set", + addrFlag: "192.168.0.1:1234", + portFlag: "5678", + expected: &Config{ + Address: "192.168.0.1:1234", + Port: "5678", + }, + }, + { + name: "neither addrFlag nor portFlag are set", + addrFlag: "", + portFlag: "", + expected: &Config{ + Address: "", + Port: "", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cmd := &cobra.Command{} + cfg := &Config{} + + cmd.Flags().AddFlagSet(Flags()) + + err := cmd.Flags().Set(addrFlag, test.addrFlag) + if err != nil { + t.Errorf(err.Error()) + } + err = cmd.Flags().Set(portFlag, test.portFlag) + if err != nil { + t.Errorf(err.Error()) + } + + ParseFlags(cmd, cfg) + assert.Equal(t, test.expected.Address, cfg.Address) + assert.Equal(t, test.expected.Port, cfg.Port) + }) + } +} From 1d228f0cd1a88490871fefe9496fcefcd40a8f4a Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 18 Dec 2023 11:40:07 +0000 Subject: [PATCH 0946/1008] ignore otel patch updates (#2988) opening this as a possible solution and to discuss if we want to make the matching more greedy, but this implements a suggestion by @walldiss last week to have dependabot be less aggressive with suggesting EVERY patch release for all dependencies, only limiting to major and minor updates as suggestion. Arguably, we could expand the wildcard to more than `*otel*` and apply to everything, but maybe we just let it run on otel for a bit and see what the consequences are before considering applying it more broadly. Also, i think we should review the security alert configuration side of dependabot as a companion to this to ensure we DO get security alerts if a CVE or issue is patched. But that will be separate --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e2becce196..edcd86e11d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,6 +21,9 @@ updates: - renaynay labels: - kind:deps + ignore: + - dependency-name: "*otel*" + update-types: ["version-update:semver-patch"] groups: otel: patterns: From e38a7bfbd8e5e589044083be4f334ce140fa2735 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 18 Dec 2023 14:04:41 +0200 Subject: [PATCH 0947/1008] fix(share/byzantine): fix proof collection (#2957) This PR fixes an issue in the proof collection method. Previously, we were sending `len(dah.RowRoots)/2` requests in order to get proofs but there is a use case when the process can get stuck for a particular share(out-of-order case). The new approach sends n requests, where n is the number of non-empty shares received from rsmt2d library, and then waits until `len(dah.RowRoots)/2` will be received. I've also improved error handling as previously we were panicking in case any error appeared. The new approach will rely only on `context.DeadlineExceeded ` which basically means that for some reason we can't get proofs, so the data is not available. --------- Co-authored-by: ramin --- share/eds/byzantine/bad_encoding_test.go | 111 ++++++++++++++++++++--- share/eds/byzantine/byzantine.go | 46 +++++----- 2 files changed, 119 insertions(+), 38 deletions(-) diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index e7032107ca..adb23d3549 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -2,9 +2,15 @@ package byzantine import ( "context" + "crypto/sha256" + "hash" "testing" "time" + "github.com/ipfs/boxo/blockservice" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + mhcore "github.com/multiformats/go-multihash/core" "github.com/stretchr/testify/require" core "github.com/tendermint/tendermint/types" @@ -35,10 +41,11 @@ func TestBEFP_Validate(t *testing.T) { err = square.Repair(dah.RowRoots, dah.ColumnRoots) require.ErrorAs(t, err, &errRsmt2d) - errByz := NewErrByzantine(ctx, bServ, &dah, errRsmt2d) + byzantine := NewErrByzantine(ctx, bServ, &dah, errRsmt2d) + var errByz *ErrByzantine + require.ErrorAs(t, byzantine, &errByz) befp := CreateBadEncodingProof([]byte("hash"), 0, errByz) - var test = []struct { name string prepareFn func() error @@ -69,7 +76,9 @@ func TestBEFP_Validate(t *testing.T) { Shares: validShares[0:4], }, ) - invalidBefp := CreateBadEncodingProof([]byte("hash"), 0, errInvalidByz) + var errInvalid *ErrByzantine + require.ErrorAs(t, errInvalidByz, &errInvalid) + invalidBefp := CreateBadEncodingProof([]byte("hash"), 0, errInvalid) return invalidBefp.Validate(&header.ExtendedHeader{DAH: &validDah}) }, expectedResult: func(err error) { @@ -198,18 +207,21 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) { } func TestBEFP_ValidateOutOfOrderShares(t *testing.T) { - // skipping it for now because `malicious` package has a small issue: Constructor does not apply - // passed options, so it's not possible to store shares and thus get proofs for them. - // should be ok once app team will fix it. - t.Skip() - eds := edstest.RandEDS(t, 16) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + t.Cleanup(cancel) + + size := 4 + eds := edstest.RandEDS(t, size) + shares := eds.Flattened() - shares[0], shares[1] = shares[1], shares[0] // corrupting eds - bServ := ipld.NewMemBlockservice() - batchAddr := ipld.NewNmtNodeAdder(context.Background(), bServ, ipld.MaxSizeBatchOption(16*2)) + shares[0], shares[4] = shares[4], shares[0] // corrupting eds + + bServ := newNamespacedBlockService() + batchAddr := ipld.NewNmtNodeAdder(ctx, bServ, ipld.MaxSizeBatchOption(size*2)) + eds, err := rsmt2d.ImportExtendedDataSquare(shares, share.DefaultRSMT2DCodec(), - malicious.NewConstructor(16, nmt.NodeVisitor(batchAddr.Visit)), + malicious.NewConstructor(uint64(size), nmt.NodeVisitor(batchAddr.Visit)), ) require.NoError(t, err, "failure to recompute the extended data square") @@ -223,9 +235,80 @@ func TestBEFP_ValidateOutOfOrderShares(t *testing.T) { err = eds.Repair(dah.RowRoots, dah.ColumnRoots) require.ErrorAs(t, err, &errRsmt2d) - errByz := NewErrByzantine(context.Background(), bServ, &dah, errRsmt2d) + byzantine := NewErrByzantine(ctx, bServ, &dah, errRsmt2d) + var errByz *ErrByzantine + require.ErrorAs(t, byzantine, &errByz) befp := CreateBadEncodingProof([]byte("hash"), 0, errByz) err = befp.Validate(&header.ExtendedHeader{DAH: &dah}) - require.Error(t, err) + require.NoError(t, err) +} + +// namespacedBlockService wraps `BlockService` and extends the verification part +// to avoid returning blocks that has out of order namespaces. +type namespacedBlockService struct { + blockservice.BlockService + // the data structure that is used on the networking level, in order + // to verify the order of the namespaces + prefix *cid.Prefix +} + +func newNamespacedBlockService() *namespacedBlockService { + sha256NamespaceFlagged := uint64(0x7701) + // register the nmt hasher to validate the order of namespaces + mhcore.Register(sha256NamespaceFlagged, func() hash.Hash { + nh := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, true) + nh.Reset() + return nh + }) + + bs := &namespacedBlockService{} + bs.BlockService = ipld.NewMemBlockservice() + + bs.prefix = &cid.Prefix{ + Version: 1, + Codec: sha256NamespaceFlagged, + MhType: sha256NamespaceFlagged, + // equals to NmtHasher.Size() + MhLength: sha256.New().Size() + 2*share.NamespaceSize, + } + return bs +} + +func (n *namespacedBlockService) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) { + block, err := n.BlockService.GetBlock(ctx, c) + if err != nil { + return nil, err + } + + _, err = n.prefix.Sum(block.RawData()) + if err != nil { + return nil, err + } + return block, nil +} + +func (n *namespacedBlockService) GetBlocks(ctx context.Context, cids []cid.Cid) <-chan blocks.Block { + blockCh := n.BlockService.GetBlocks(ctx, cids) + resultCh := make(chan blocks.Block) + + go func() { + for { + select { + case <-ctx.Done(): + close(resultCh) + return + case block, ok := <-blockCh: + if !ok { + close(resultCh) + return + } + if _, err := n.prefix.Sum(block.RawData()); err != nil { + continue + } + resultCh <- block + } + } + }() + return resultCh } diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index dfdf681f04..e7aec28959 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -4,11 +4,9 @@ import ( "context" "fmt" - "github.com/ipfs/boxo/blockservice" - "golang.org/x/sync/errgroup" - "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" + "github.com/ipfs/boxo/blockservice" "github.com/celestiaorg/celestia-node/share/ipld" ) @@ -35,7 +33,7 @@ func NewErrByzantine( bGetter blockservice.BlockGetter, dah *da.DataAvailabilityHeader, errByz *rsmt2d.ErrByzantineData, -) *ErrByzantine { +) error { // changing the order to collect proofs against an orthogonal axis roots := [][][]byte{ dah.ColumnRoots, @@ -43,41 +41,41 @@ func NewErrByzantine( }[errByz.Axis] sharesWithProof := make([]*ShareWithProof, len(errByz.Shares)) - sharesAmount := 0 - errGr, ctx := errgroup.WithContext(ctx) + type result struct { + share *ShareWithProof + index int + } + resultCh := make(chan *result) for index, share := range errByz.Shares { - // skip further shares if we already requested half of them, which is enough to recompute the row - // or col - if sharesAmount == len(dah.RowRoots)/2 { - break - } - if share == nil { continue } - sharesAmount++ index := index - errGr.Go(func() error { + go func() { share, err := getProofsAt( ctx, bGetter, ipld.MustCidFromNamespacedSha256(roots[index]), int(errByz.Index), len(errByz.Shares), ) - sharesWithProof[index] = share - return err - }) + if err != nil { + log.Warn("requesting proof failed", "root", roots[index], "err", err) + return + } + resultCh <- &result{share, index} + }() } - if err := errGr.Wait(); err != nil { - // Fatal as rsmt2d proved that error is byzantine, - // but we cannot properly collect the proof, - // so verification will fail and thus services won't be stopped - // while we still have to stop them. - // TODO(@Wondertan): Find a better way to handle - log.Fatalw("getting proof for ErrByzantine", "err", err) + for i := 0; i < len(dah.RowRoots)/2; i++ { + select { + case t := <-resultCh: + sharesWithProof[t.index] = t.share + case <-ctx.Done(): + return ipld.ErrNodeNotFound + } } + return &ErrByzantine{ Index: uint32(errByz.Index), Shares: sharesWithProof, From 8a39228df21cbdc6fe2356663270e65dafb68564 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:15:46 +0000 Subject: [PATCH 0948/1008] chore(deps): Bump actions/upload-artifact from 3 to 4 (#3014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
    Release notes

    Sourced from actions/upload-artifact's releases.

    v4.0.0

    What's Changed

    The release of upload-artifact@v4 and download-artifact@v4 are major changes to the backend architecture of Artifacts. They have numerous performance and behavioral improvements.

    For more information, see the @​actions/artifact documentation.

    New Contributors

    Full Changelog: https://github.com/actions/upload-artifact/compare/v3...v4.0.0

    v3.1.3

    What's Changed

    Full Changelog: https://github.com/actions/upload-artifact/compare/v3...v3.1.3

    v3.1.2

    • Update all @actions/* NPM packages to their latest versions- #374
    • Update all dev dependencies to their most recent versions - #375

    v3.1.1

    • Update actions/core package to latest version to remove set-output deprecation warning #351

    v3.1.0

    What's Changed

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index c14692cd58..b004dcfb59 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -87,7 +87,7 @@ jobs: run: make test-unit ENABLE_VERBOSE=${{ needs.setup.outputs.debug }} - name: Upload unit test output - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && needs.setup.outputs.debug == 'true' with: name: unit-test-output-${{ matrix.os }} From a61ae5c3ad64aa7e6023d5ed6c9c83477e9f3fdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:28:17 +0000 Subject: [PATCH 0949/1008] chore(deps): Bump github.com/hashicorp/go-retryablehttp from 0.7.4 to 0.7.5 (#2924) Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.4 to 0.7.5.
    Changelog

    Sourced from github.com/hashicorp/go-retryablehttp's changelog.

    0.7.5 (Nov 8, 2023)

    BUG FIXES

    • client: fixes an issue where the request body is not preserved on temporary redirects or re-established HTTP/2 connections GH-207
    Commits
    • 4165cf8 Merge pull request #209 from hashicorp/v0.7.5-changelog
    • 6c37e02 v0.7.5 changelog update
    • 309c58e Merge pull request #207 from hashicorp/sebasslash/handle-go-away
    • f95735f Update workflow to use go v1.18
    • 9bb2062 Sets request's GetBody field on wrapper
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/hashicorp/go-retryablehttp&package-manager=go_modules&previous-version=0.7.4&new-version=0.7.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4441c6f6b..e2a35a0f49 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/hashicorp/go-retryablehttp v0.7.4 + github.com/hashicorp/go-retryablehttp v0.7.5 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.15.0 diff --git a/go.sum b/go.sum index 962e46067b..2a9279400c 100644 --- a/go.sum +++ b/go.sum @@ -958,8 +958,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= From 7f51c599b2f7576909a19c4487e1a8c46c5a67e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:38:52 +0000 Subject: [PATCH 0950/1008] chore(deps): Bump cosmossdk.io/math from 1.1.2 to 1.2.0 (#2923) Bumps [cosmossdk.io/math](https://github.com/cosmos/cosmos-sdk) from 1.1.2 to 1.2.0.
    Release notes

    Sourced from cosmossdk.io/math's releases.

    Cosmovisor v1.2.0

    Release Notes

    New Features

    With the cosmovisor init command, all the necessary folders for using cosmovisor are automatically created. You do not need to manually symlink the chain binary anymore.

    We've added a new configuration option: DAEMON_RESTART_DELAY (as env variable). When set, Cosmovisor will wait that delay between the node halt and backup. See the README file for more details.

    Bug Fixes

    • Fix Cosmovisor binary usage for pre-upgrade. Cosmovisor was using the wrong binary when running a pre-upgrade command.

    Changelog

    For more details, please see the CHANGELOG.

    Commits
    • c33b4db chore: prepare log 1.2.0 (#17214)
    • c36c860 refactor(cli): remove duplicate --home flag (#17215)
    • 31f7247 feat(cli): status cmd cli support output text (#17184)
    • 095a641 feat(docker): Add debuging dependencies directly into the dockerfile (#17228)
    • f47749b fix: text output format of block-results cmd cli (#17183)
    • 236fe4c docs: additional fields in context.go to be added to context.md (#17199)
    • 382de33 feat(testutil): adding DefaultContextWithKeys test helper (#17216)
    • 0b7d2d3 fix(log): add fallback to Stringer when type do not implement `json.Marshal...
    • 60ead8d ci: skip fix_registration.go when using LINT_DIFF=1 (#17185)
    • edba186 refactor(keys): remove duplicate --home flag (#17197)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cosmossdk.io/math&package-manager=go_modules&previous-version=1.1.2&new-version=1.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e2a35a0f49..e6f715e2b5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21.1 require ( cosmossdk.io/errors v1.0.0 - cosmossdk.io/math v1.1.2 + cosmossdk.io/math v1.2.0 github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 diff --git a/go.sum b/go.sum index 2a9279400c..b3d4b91036 100644 --- a/go.sum +++ b/go.sum @@ -195,8 +195,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= -cosmossdk.io/math v1.1.2 h1:ORZetZCTyWkI5GlZ6CZS28fMHi83ZYf+A2vVnHNzZBM= -cosmossdk.io/math v1.1.2/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= +cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= +cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= From c34b741ae3d70e31dcf7865ba959e1b039c0f076 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:08:52 +0300 Subject: [PATCH 0951/1008] feat(share/p2p/peer-manager): limit amount of stored pools in peer-manager (#3005) This PR introduces `amountOfStoredPools` param to Peer manager, that will limit amount of stored pools. Defautlt value is 10 pools. Peer manager will try to keep `amountOfStoredPools` amount of pools only for recent headers. Older blocks don't need to be routed by manual pools and could be routed to any full node, as those are expected to have the block by that time. This will lower memory footprint of peer manager as well as resolve memory leaking issues. Resolves https://github.com/celestiaorg/celestia-node/issues/1781 --- share/getters/shrex.go | 15 ++-- share/p2p/peers/manager.go | 103 +++++++++++++------------ share/p2p/peers/manager_test.go | 129 ++++++++++++++++---------------- share/p2p/peers/metrics.go | 10 +-- share/p2p/peers/pool.go | 9 --- 5 files changed, 122 insertions(+), 144 deletions(-) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index 0586826e22..af48a7f8ab 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -135,9 +135,8 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, header *header.ExtendedHeader utils.SetStatusAndEnd(span, err) }() - dah := header.DAH // short circuit if the data root is empty - if dah.Equals(share.EmptyRoot()) { + if header.DAH.Equals(share.EmptyRoot()) { return share.EmptyExtendedDataSquare(), nil } for { @@ -147,10 +146,10 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, header *header.ExtendedHeader } attempt++ start := time.Now() - peer, setStatus, getErr := sg.peerManager.Peer(ctx, dah.Hash()) + peer, setStatus, getErr := sg.peerManager.Peer(ctx, header.DAH.Hash(), header.Height()) if getErr != nil { log.Debugw("eds: couldn't find peer", - "hash", dah.String(), + "hash", header.DAH.String(), "err", getErr, "finished (s)", time.Since(start)) sg.metrics.recordEDSAttempt(ctx, attempt, false) @@ -159,11 +158,11 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, header *header.ExtendedHeader reqStart := time.Now() reqCtx, cancel := ctxWithSplitTimeout(ctx, sg.minAttemptsCount-attempt+1, sg.minRequestTimeout) - eds, getErr := sg.edsClient.RequestEDS(reqCtx, dah.Hash(), peer) + eds, getErr := sg.edsClient.RequestEDS(reqCtx, header.DAH.Hash(), peer) cancel() switch { case getErr == nil: - setStatus(peers.ResultSynced) + setStatus(peers.ResultNoop) sg.metrics.recordEDSAttempt(ctx, attempt, true) return eds, nil case errors.Is(getErr, context.DeadlineExceeded), @@ -182,7 +181,7 @@ func (sg *ShrexGetter) GetEDS(ctx context.Context, header *header.ExtendedHeader err = errors.Join(err, getErr) } log.Debugw("eds: request failed", - "hash", dah.String(), + "hash", header.DAH.String(), "peer", peer.String(), "attempt", attempt, "err", getErr, @@ -223,7 +222,7 @@ func (sg *ShrexGetter) GetSharesByNamespace( } attempt++ start := time.Now() - peer, setStatus, getErr := sg.peerManager.Peer(ctx, dah.Hash()) + peer, setStatus, getErr := sg.peerManager.Peer(ctx, header.DAH.Hash(), header.Height()) if getErr != nil { log.Debugw("nd: couldn't find peer", "hash", dah.String(), diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index e39a181150..23fa18bcb2 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -27,8 +27,6 @@ import ( const ( // ResultNoop indicates operation was successful and no extra action is required ResultNoop result = "result_noop" - // ResultSynced will save the status of pool as "synced" and will remove peers from it - ResultSynced = "result_synced" // ResultCooldownPeer will put returned peer on cooldown, meaning it won't be available by Peer // method for some time ResultCooldownPeer = "result_cooldown_peer" @@ -39,6 +37,9 @@ const ( // eventbusBufSize is the size of the buffered channel to handle // events in libp2p eventbusBufSize = 32 + + // storedPoolsAmount is the amount of pools for recent headers that will be stored in the peer manager + storedPoolsAmount = 10 ) type result string @@ -56,11 +57,14 @@ type Manager struct { host host.Host connGater *conngater.BasicConnectionGater - // pools collecting peers from shrexSub + // pools collecting peers from shrexSub and stores them by datahash pools map[string]*syncPool - // messages from shrex.Sub with height below initialHeight will be ignored, since we don't need to - // track peers for those headers + + // initialHeight is the height of the first header received from headersub initialHeight atomic.Uint64 + // messages from shrex.Sub with height below storeFrom will be ignored, since we don't need to + // track peers for those headers + storeFrom atomic.Uint64 // fullNodes collects full nodes peer.ID found via discovery fullNodes *pool @@ -85,11 +89,8 @@ type syncPool struct { // isValidatedDataHash indicates if datahash was validated by receiving corresponding extended // header from headerSub isValidatedDataHash atomic.Bool - // headerHeight is the height of header corresponding to syncpool - headerHeight atomic.Uint64 - // isSynced will be true if DoneFunc was called with ResultSynced. It indicates that given datahash - // was synced and peer-manager no longer need to keep peers for it - isSynced atomic.Bool + // height is the height of the header that corresponds to datahash + height uint64 // createdAt is the syncPool creation time createdAt time.Time } @@ -190,16 +191,15 @@ func (m *Manager) Stop(ctx context.Context) error { // full nodes, it will wait until any peer appear in either source or timeout happen. // After fetching data using given peer, caller is required to call returned DoneFunc using // appropriate result value -func (m *Manager) Peer( - ctx context.Context, datahash share.DataHash, +func (m *Manager) Peer(ctx context.Context, datahash share.DataHash, height uint64, ) (peer.ID, DoneFunc, error) { - p := m.validatedPool(datahash.String()) + p := m.validatedPool(datahash.String(), height) // first, check if a peer is available for the given datahash peerID, ok := p.tryGet() if ok { if m.removeIfUnreachable(p, peerID) { - return m.Peer(ctx, datahash) + return m.Peer(ctx, datahash, height) } return m.newPeer(ctx, datahash, peerID, sourceShrexSub, p.len(), 0) } @@ -216,7 +216,7 @@ func (m *Manager) Peer( select { case peerID = <-p.next(ctx): if m.removeIfUnreachable(p, peerID) { - return m.Peer(ctx, datahash) + return m.Peer(ctx, datahash, height) } return m.newPeer(ctx, datahash, peerID, sourceShrexSub, p.len(), time.Since(start)) case peerID = <-m.fullNodes.next(ctx): @@ -270,14 +270,12 @@ func (m *Manager) doneFunc(datahash share.DataHash, peerID peer.ID, source peerS m.metrics.observeDoneResult(source, result) switch result { case ResultNoop: - case ResultSynced: - m.markPoolAsSynced(datahash.String()) case ResultCooldownPeer: if source == sourceFullNodes { m.fullNodes.putOnCooldown(peerID) return } - m.getOrCreatePool(datahash.String()).putOnCooldown(peerID) + m.getPool(datahash.String()).putOnCooldown(peerID) case ResultBlacklistPeer: m.blacklistPeers(reasonMisbehave, peerID) } @@ -298,12 +296,16 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri log.Errorw("get next header from sub", "err", err) continue } - m.validatedPool(h.DataHash.String()) + m.validatedPool(h.DataHash.String(), h.Height()) // store first header for validation purposes if m.initialHeight.CompareAndSwap(0, h.Height()) { log.Debugw("stored initial height", "height", h.Height()) } + + // update storeFrom if header heigh + m.storeFrom.Store(uint64(max(0, int(h.Height())-storedPoolsAmount))) + log.Debugw("updated lowest stored height", "height", h.Height()) } } @@ -355,22 +357,12 @@ func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notif return pubsub.ValidationReject } - if msg.Height == 0 { - logger.Debug("received message with 0 height") - return pubsub.ValidationReject - } - - if msg.Height < m.initialHeight.Load() { - // we can use peers from discovery for headers before the first one from headerSub - // if we allow pool creation for those headers, there is chance the pool will not be validated in - // time and will be false-positively trigger blacklisting of hash and all peers that sent msgs for - // that hash + if msg.Height < m.storeFrom.Load() { logger.Debug("received message for past header") return pubsub.ValidationIgnore } - p := m.getOrCreatePool(msg.DataHash.String()) - p.headerHeight.Store(msg.Height) + p := m.getOrCreatePool(msg.DataHash.String(), msg.Height) logger.Debugw("got hash from shrex-sub") p.add(peerID) @@ -381,13 +373,20 @@ func (m *Manager) Validate(_ context.Context, peerID peer.ID, msg shrexsub.Notif return pubsub.ValidationIgnore } -func (m *Manager) getOrCreatePool(datahash string) *syncPool { +func (m *Manager) getPool(datahash string) *syncPool { + m.lock.Lock() + defer m.lock.Unlock() + return m.pools[datahash] +} + +func (m *Manager) getOrCreatePool(datahash string, height uint64) *syncPool { m.lock.Lock() defer m.lock.Unlock() p, ok := m.pools[datahash] if !ok { p = &syncPool{ + height: height, pool: newPool(m.params.PeerCooldown), createdAt: time.Now(), } @@ -432,8 +431,8 @@ func (m *Manager) isBlacklistedHash(hash share.DataHash) bool { return m.blacklistedHashes[hash.String()] } -func (m *Manager) validatedPool(hashStr string) *syncPool { - p := m.getOrCreatePool(hashStr) +func (m *Manager) validatedPool(hashStr string, height uint64) *syncPool { + p := m.getOrCreatePool(hashStr, height) if p.isValidatedDataHash.CompareAndSwap(false, true) { log.Debugw("pool marked validated", "datahash", hashStr) // if pool is proven to be valid, add all collected peers to full nodes @@ -482,12 +481,24 @@ func (m *Manager) cleanUp() []peer.ID { addToBlackList := make(map[peer.ID]struct{}) for h, p := range m.pools { - if !p.isValidatedDataHash.Load() && time.Since(p.createdAt) > m.params.PoolValidationTimeout { - delete(m.pools, h) - if p.headerHeight.Load() < m.initialHeight.Load() { - // outdated pools could still be valid even if not validated, no need to blacklist - continue + if p.isValidatedDataHash.Load() { + // remove pools that are outdated + if p.height < m.storeFrom.Load() { + delete(m.pools, h) } + continue + } + + // can't validate datahashes below initial height + if p.height < m.initialHeight.Load() { + delete(m.pools, h) + continue + } + + // find pools that are not validated in time + if time.Since(p.createdAt) > m.params.PoolValidationTimeout { + delete(m.pools, h) + log.Debug("blacklisting datahash with all corresponding peers", "hash", h, "peer_list", p.peersList) @@ -507,17 +518,3 @@ func (m *Manager) cleanUp() []peer.ID { } return blacklist } - -func (m *Manager) markPoolAsSynced(datahash string) { - p := m.getOrCreatePool(datahash) - if p.isSynced.CompareAndSwap(false, true) { - p.isSynced.Store(true) - p.reset() - } -} - -func (p *syncPool) add(peers ...peer.ID) { - if !p.isSynced.Load() { - p.pool.add(peers...) - } -} diff --git a/share/p2p/peers/manager_test.go b/share/p2p/peers/manager_test.go index 94ec5d5ea2..d4a188ff56 100644 --- a/share/p2p/peers/manager_test.go +++ b/share/p2p/peers/manager_test.go @@ -2,12 +2,12 @@ package peers import ( "context" - sync2 "sync" + "sync" "testing" "time" "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" + dssync "github.com/ipfs/go-datastore/sync" dht "github.com/libp2p/go-libp2p-kad-dht" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/host" @@ -26,10 +26,9 @@ import ( "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) -// TODO: add broadcast to tests func TestManager(t *testing.T) { t.Run("Validate pool by headerSub", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) // create headerSub mock @@ -64,21 +63,16 @@ func TestManager(t *testing.T) { result := manager.Validate(ctx, peerID, msg) require.Equal(t, pubsub.ValidationIgnore, result) - pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + pID, _, err := manager.Peer(ctx, h.DataHash.Bytes(), h.Height()) require.NoError(t, err) require.Equal(t, peerID, pID) // check pool validation - require.True(t, manager.getOrCreatePool(h.DataHash.String()).isValidatedDataHash.Load()) - - done(ResultSynced) - // pool should not be removed after success - require.Len(t, manager.pools, 1) - require.Len(t, manager.getOrCreatePool(h.DataHash.String()).pool.peersList, 0) + require.True(t, manager.getPool(h.DataHash.String()).isValidatedDataHash.Load()) }) t.Run("validator", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*50) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) // create headerSub mock @@ -100,7 +94,7 @@ func TestManager(t *testing.T) { require.Equal(t, pubsub.ValidationIgnore, result) // mark peer as misbehaved to blacklist it - pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) + pID, done, err := manager.Peer(ctx, h.DataHash.Bytes(), h.Height()) require.NoError(t, err) require.Equal(t, peerID, pID) manager.params.EnableBlackListing = true @@ -132,19 +126,21 @@ func TestManager(t *testing.T) { // create unvalidated pool peerID := peer.ID("peer1") msg := shrexsub.Notification{ - DataHash: share.DataHash("datahash1"), + DataHash: share.DataHash("datahash1datahash1datahash1datahash1datahash1"), Height: 2, } manager.Validate(ctx, peerID, msg) // create validated pool validDataHash := share.DataHash("datahash2") - manager.fullNodes.add("full") // add FN to unblock Peer call - manager.Peer(ctx, validDataHash) //nolint:errcheck + manager.fullNodes.add("full") // add FN to unblock Peer call + manager.Peer(ctx, validDataHash, h.Height()) //nolint:errcheck + require.Len(t, manager.pools, 3) // trigger cleanup blacklisted := manager.cleanUp() require.Contains(t, blacklisted, peerID) + require.Len(t, manager.pools, 2) // messages with blacklisted hash should be rejected right away peerID2 := peer.ID("peer2") @@ -172,8 +168,7 @@ func TestManager(t *testing.T) { peers := []peer.ID{"peer1", "peer2", "peer3"} manager.fullNodes.add(peers...) - peerID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) - done(ResultSynced) + peerID, _, err := manager.Peer(ctx, h.DataHash.Bytes(), h.Height()) require.NoError(t, err) require.Contains(t, peers, peerID) @@ -195,7 +190,7 @@ func TestManager(t *testing.T) { // make sure peers are not returned before timeout timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Millisecond) t.Cleanup(cancel) - _, _, err = manager.Peer(timeoutCtx, h.DataHash.Bytes()) + _, _, err = manager.Peer(timeoutCtx, h.DataHash.Bytes(), h.Height()) require.ErrorIs(t, err, context.DeadlineExceeded) peers := []peer.ID{"peer1", "peer2", "peer3"} @@ -204,8 +199,7 @@ func TestManager(t *testing.T) { doneCh := make(chan struct{}) go func() { defer close(doneCh) - peerID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) - done(ResultSynced) + peerID, _, err := manager.Peer(ctx, h.DataHash.Bytes(), h.Height()) require.NoError(t, err) require.Contains(t, peers, peerID) }() @@ -223,38 +217,7 @@ func TestManager(t *testing.T) { stopManager(t, manager) }) - t.Run("mark pool synced", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - t.Cleanup(cancel) - - h := testHeader() - headerSub := newSubLock(h, nil) - - // start test manager - manager, err := testManager(ctx, headerSub) - require.NoError(t, err) - - peerID, msg := peer.ID("peer1"), newShrexSubMsg(h) - result := manager.Validate(ctx, peerID, msg) - require.Equal(t, pubsub.ValidationIgnore, result) - - pID, done, err := manager.Peer(ctx, h.DataHash.Bytes()) - require.NoError(t, err) - require.Equal(t, peerID, pID) - done(ResultSynced) - - // check pool is soft deleted and marked synced - pool := manager.getOrCreatePool(h.DataHash.String()) - require.Len(t, pool.peersList, 0) - require.True(t, pool.isSynced.Load()) - - // add peer on synced pool should be noop - result = manager.Validate(ctx, "peer2", msg) - require.Equal(t, pubsub.ValidationIgnore, result) - require.Len(t, pool.peersList, 0) - }) - - t.Run("shrexSub sends a message lower than first headerSub header height, msg first", func(t *testing.T) { + t.Run("shrexSub sends a message lower than first headerSub header height, headerSub first", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -278,12 +241,16 @@ func TestManager(t *testing.T) { } result := manager.Validate(ctx, "peer", msg) require.Equal(t, pubsub.ValidationIgnore, result) + // pool will be created for first shrexSub message + require.Len(t, manager.pools, 2) - // amount of pools should not change + blacklisted := manager.cleanUp() + require.Empty(t, blacklisted) + // trigger cleanup and outdated pool should be removed require.Len(t, manager.pools, 1) }) - t.Run("shrexSub sends a message lower than first headerSub header height, headerSub first", func(t *testing.T) { + t.Run("shrexSub sends a message lower than first headerSub header height, shrexSub first", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) @@ -303,18 +270,50 @@ func TestManager(t *testing.T) { result := manager.Validate(ctx, "peer", msg) require.Equal(t, pubsub.ValidationIgnore, result) - // unlock header sub after message validator + // pool will be created for first shrexSub message + require.Len(t, manager.pools, 1) + + // unlock headerSub to allow it to send next message require.NoError(t, headerSub.wait(ctx, 1)) - // pool will be created for first headerSub header datahash + // second pool should be created require.Len(t, manager.pools, 2) - // trigger cleanup and check that no peers or hashes were blacklisted - manager.params.PoolValidationTimeout = 0 + // trigger cleanup and outdated pool should be removed blacklisted := manager.cleanUp() + require.Len(t, manager.pools, 1) + + // check that no peers or hashes were blacklisted + manager.params.PoolValidationTimeout = 0 require.Len(t, blacklisted, 0) require.Len(t, manager.blacklistedHashes, 0) + }) + + t.Run("pools store window", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + h := testHeader() + h.RawHeader.Height = storedPoolsAmount * 2 + headerSub := newSubLock(h, nil) + + // start test manager + manager, err := testManager(ctx, headerSub) + require.NoError(t, err) + + // unlock headerSub to read first header + require.NoError(t, headerSub.wait(ctx, 1)) + // pool will be created for first headerSub header datahash + require.Len(t, manager.pools, 1) + + // create shrexSub msg with height lower than storedPoolsAmount + msg := shrexsub.Notification{ + DataHash: share.DataHash("datahash"), + Height: h.Height() - storedPoolsAmount - 3, + } + result := manager.Validate(ctx, "peer", msg) + require.Equal(t, pubsub.ValidationIgnore, result) - // outdated pool should be removed + // shrexSub message should be discarded and amount of pools should not change require.Len(t, manager.pools, 1) }) } @@ -355,7 +354,7 @@ func TestIntegration(t *testing.T) { })) // FN should get message - gotPeer, _, err := fnPeerManager.Peer(ctx, randHash) + gotPeer, _, err := fnPeerManager.Peer(ctx, randHash, 13) require.NoError(t, err) // check that gotPeer matched bridge node @@ -409,7 +408,7 @@ func TestIntegration(t *testing.T) { require.NoError(t, err) // init peer manager for full node - connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + connGater, err := conngater.NewBasicConnectionGater(dssync.MutexWrap(datastore.NewMapDatastore())) require.NoError(t, err) fnPeerManager, err := NewManager( DefaultParameters(), @@ -469,7 +468,7 @@ func testManager(ctx context.Context, headerSub libhead.Subscriber[*header.Exten return nil, err } - connGater, err := conngater.NewBasicConnectionGater(sync.MutexWrap(datastore.NewMapDatastore())) + connGater, err := conngater.NewBasicConnectionGater(dssync.MutexWrap(datastore.NewMapDatastore())) if err != nil { return nil, err } @@ -504,7 +503,7 @@ func testHeader() *header.ExtendedHeader { type subLock struct { next chan struct{} - wg *sync2.WaitGroup + wg *sync.WaitGroup expected []*header.ExtendedHeader } @@ -530,7 +529,7 @@ func (s subLock) release(ctx context.Context) error { } func newSubLock(expected ...*header.ExtendedHeader) *subLock { - wg := &sync2.WaitGroup{} + wg := &sync.WaitGroup{} wg.Add(1) return &subLock{ next: make(chan struct{}), diff --git a/share/p2p/peers/metrics.go b/share/p2p/peers/metrics.go index 95d1ce65d9..3b5913ebb8 100644 --- a/share/p2p/peers/metrics.go +++ b/share/p2p/peers/metrics.go @@ -39,14 +39,11 @@ const ( poolStatusKey = "pool_status" poolStatusCreated poolStatus = "created" poolStatusValidated poolStatus = "validated" - poolStatusSynced poolStatus = "synced" poolStatusBlacklisted poolStatus = "blacklisted" // Pool status model: // created(unvalidated) // / \ - // validated(unsynced) blacklisted - // | - // synced + // validated blacklisted ) var ( @@ -266,11 +263,6 @@ func (m *Manager) shrexPools() map[poolStatus]int64 { continue } - if p.isSynced.Load() { - shrexPools[poolStatusSynced]++ - continue - } - // pool is validated but not synced shrexPools[poolStatusValidated]++ } diff --git a/share/p2p/peers/pool.go b/share/p2p/peers/pool.go index d0cc45ac44..365ef0306d 100644 --- a/share/p2p/peers/pool.go +++ b/share/p2p/peers/pool.go @@ -224,12 +224,3 @@ func (p *pool) len() int { defer p.m.RUnlock() return p.activeCount } - -// reset will reset the pool to its initial state. -func (p *pool) reset() { - lock := &p.m - lock.Lock() - defer lock.Lock() - // swap the pool with an empty one - *p = *newPool(time.Second) -} From 150378fa75333c1a13fac0057788077dcccea668 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 20 Dec 2023 12:55:42 +0100 Subject: [PATCH 0952/1008] chore: bump go-libp2p (#2883) A regular bump with multiple minor fixes as well broader optimizations like TCP eyeballing https://github.com/libp2p/go-libp2p/releases/tag/v0.32.0 It may also fix the issue behind #2837, but this has to be confirmed yet Co-authored-by: ramin --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e6f715e2b5..a9253c2e59 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipld/go-car v0.6.2 - github.com/libp2p/go-libp2p v0.32.0 + github.com/libp2p/go-libp2p v0.32.1 github.com/libp2p/go-libp2p-kad-dht v0.25.1 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-libp2p-record v0.2.0 diff --git a/go.sum b/go.sum index b3d4b91036..77fbd1037a 100644 --- a/go.sum +++ b/go.sum @@ -1414,8 +1414,8 @@ github.com/libp2p/go-libp2p v0.22.0/go.mod h1:UDolmweypBSjQb2f7xutPnwZ/fxioLbMBx github.com/libp2p/go-libp2p v0.23.4/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= github.com/libp2p/go-libp2p v0.25.0/go.mod h1:vXHmFpcfl+xIGN4qW58Bw3a0/SKGAesr5/T4IuJHE3o= github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= -github.com/libp2p/go-libp2p v0.32.0 h1:86I4B7nBUPIyTgw3+5Ibq6K7DdKRCuZw8URCfPc1hQM= -github.com/libp2p/go-libp2p v0.32.0/go.mod h1:hXXC3kXPlBZ1eu8Q2hptGrMB4mZ3048JUoS4EKaHW5c= +github.com/libp2p/go-libp2p v0.32.1 h1:wy1J4kZIZxOaej6NveTWCZmHiJ/kY7GoAqXgqNCnPps= +github.com/libp2p/go-libp2p v0.32.1/go.mod h1:hXXC3kXPlBZ1eu8Q2hptGrMB4mZ3048JUoS4EKaHW5c= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= From ccf9b56317ae8941f91c5d09f24fe8422bbe64bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 12:22:29 +0000 Subject: [PATCH 0953/1008] chore(deps): Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#3050) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
    Commits
    • 9d2ee97 ssh: implement strict KEX protocol changes
    • 4e5a261 ssh: close net.Conn on all NewServerConn errors
    • 152cdb1 x509roots/fallback: update bundle
    • fdfe1f8 ssh: defer channel window adjustment
    • b8ffc16 blake2b: drop Go 1.6, Go 1.8 compatibility
    • 7e6fbd8 ssh: wrap errors from client handshake
    • bda2f3f argon2: avoid clobbering BP
    • 325b735 ssh/test: skip TestSSHCLIAuth on Windows
    • 1eadac5 go.mod: update golang.org/x dependencies
    • b2d7c26 ssh: add (*Client).DialContext method
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.14.0&new-version=0.17.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a9253c2e59..cb3550e470 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.17.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 @@ -325,8 +325,8 @@ require ( golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/tools v0.14.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect diff --git a/go.sum b/go.sum index 77fbd1037a..6097b9c309 100644 --- a/go.sum +++ b/go.sum @@ -2510,8 +2510,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2858,8 +2858,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2868,8 +2868,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 43ce44a5089dc3125e010d67dc8f94ac2aa8b3cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:12:15 +0000 Subject: [PATCH 0954/1008] chore(deps): Bump github.com/prometheus/client_golang from 1.17.0 to 1.18.0 (#3064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.18.0.
    Release notes

    Sourced from github.com/prometheus/client_golang's releases.

    v1.18.0

    What's Changed

    • [FEATURE] promlint: Allow creation of custom metric validations. #1311
    • [FEATURE] Go programs using client_golang can be built in wasip1 OS. #1350
    • [BUGFIX] histograms: Add timer to reset ASAP after bucket limiting has happened. #1367
    • [BUGFIX] testutil: Fix comparison of metrics with empty Help strings. #1378
    • [ENHANCEMENT] Improved performance of MetricVec.WithLabelValues(...). #1360

    New Contributors

    Full Changelog: https://github.com/prometheus/client_golang/compare/v1.17.0...v1.18.0

    Changelog

    Sourced from github.com/prometheus/client_golang's changelog.

    1.18.0 / 2023-12-22

    • [FEATURE] promlint: Allow creation of custom metric validations. #1311
    • [FEATURE] Go programs using client_golang can be built in wasip1 OS. #1350
    • [BUGFIX] histograms: Add timer to reset ASAP after bucket limiting has happened. #1367
    • [BUGFIX] testutil: Fix comparison of metrics with empty Help strings. #1378
    • [ENHANCEMENT] Improved performance of MetricVec.WithLabelValues(...). #1360
    Commits
    • 53be91d Revert "change api http.client to interface"
    • 1a2d072 Add 1.18 changelog
    • 239b123 Merge pull request #1387 from tsipo/main
    • 3f8bd73 Merge pull request #1370 from prometheus/dependabot/go_modules/tutorial/whats...
    • 5e55b31 Bump google.golang.org/grpc from 1.53.0 to 1.56.3 in /tutorial/whatsup
    • e96fb18 Merge pull request #1401 from prometheus/dependabot/go_modules/golang.org/x/s...
    • 2a8fc90 Bump golang.org/x/sys from 0.13.0 to 0.15.0
    • 24d59e9 change client to interface, allow override by other implementations (e.g. git...
    • 80d3f0b Normalize empty help values in CollectAndCompare (#1378)
    • 3f80cd1 Add example of NewConstMetricWithCreatedTimestamp (#1375)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/prometheus/client_golang&package-manager=go_modules&previous-version=1.17.0&new-version=1.18.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index cb3550e470..2e68e686a7 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 - github.com/prometheus/client_golang v1.17.0 + github.com/prometheus/client_golang v1.18.0 github.com/pyroscope-io/client v0.7.2 github.com/pyroscope-io/otel-profiling-go v0.4.0 github.com/spf13/cobra v1.8.0 @@ -83,6 +83,7 @@ require ( github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/supranational/blst v0.3.11 // indirect go.uber.org/mock v0.3.0 // indirect @@ -251,7 +252,6 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.56 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -279,9 +279,9 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.3.4 // indirect @@ -324,7 +324,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/tools v0.14.0 // indirect diff --git a/go.sum b/go.sum index 6097b9c309..ee6e4a0f31 100644 --- a/go.sum +++ b/go.sum @@ -1733,8 +1733,9 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -2034,8 +2035,8 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -2043,8 +2044,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -2059,8 +2060,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -2072,8 +2073,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/pyroscope-io/client v0.7.2 h1:OX2qdUQsS8RSkn/3C8isD7f/P0YiZQlRbAlecAaj/R8= github.com/pyroscope-io/client v0.7.2/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8= @@ -2693,8 +2694,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 87a7a1af3dc414ab60aac2af2c75178a388a3944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:29:36 +0000 Subject: [PATCH 0955/1008] chore(deps): Bump github.com/ipfs/boxo from 0.15.0 to 0.16.0 (#3057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/ipfs/boxo](https://github.com/ipfs/boxo) from 0.15.0 to 0.16.0.
    Release notes

    Sourced from github.com/ipfs/boxo's releases.

    v0.16.0

    Changed

    • 🛠 boxo/namesys: now fails when multiple valid DNSLink entries are found for the same domain. This used to cause undefined behavior before. Now, we return an error, according to the specification.

    Removed

    • 🛠 boxo/gateway: removed support for undocumented legacy ipfs-404.html. Use _redirects instead.
    • 🛠 boxo/namesys: removed support for legacy DNSLink entries at the root of the domain. Use _dnslink. TXT record instead.
    • 🛠 boxo/coreapi, an intrinsic part of Kubo, has been removed and moved to kubo/core/coreiface.

    Fixed

    • boxo/gateway
      • a panic (which is recovered) could sporadically be triggered inside a CAR request, if the right conditions were met.
      • no longer emits http: superfluous response.WriteHeader warnings when an error happens.

    What's Changed

    New Contributors

    Full Changelog: https://github.com/ipfs/boxo/compare/v0.15.0...v0.16.0

    Changelog

    Sourced from github.com/ipfs/boxo's changelog.

    [v0.16.0]

    Changed

    • 🛠 boxo/namesys: now fails when multiple valid DNSLink entries are found for the same domain. This used to cause undefined behavior before. Now, we return an error, according to the specification.

    Removed

    • 🛠 boxo/gateway: removed support for undocumented legacy ipfs-404.html. Use _redirects instead.
    • 🛠 boxo/namesys: removed support for legacy DNSLink entries at the root of the domain. Use _dnslink. TXT record instead.
    • 🛠 boxo/coreapi, an intrinsic part of Kubo, has been removed and moved to kubo/core/coreiface.

    Fixed

    • boxo/gateway
      • a panic (which is recovered) could sporadically be triggered inside a CAR request, if the right conditions were met.
      • no longer emits http: superfluous response.WriteHeader warnings when an error happens.
    Commits
    • 709c7c6 Merge pull request #518 from ipfs/release-v0.16.0
    • df09068 chore: bump to 0.16.0
    • 9afc46a changelog: prepare 0.16.0
    • 08959f2 coreiface: remove and move to Kubo
    • d06f7ff fix(gateway): no duplicate WriteHeader calls (#515)
    • 438b8a6 bitswap/client: explain what the options do
    • fe55533 fix: all go-log import to v2
    • 50f8a08 test: positive and negative test for multi dnslink
    • 006c1c9 changelog: add namesys entries
    • 7b5207a feat!: remove support for legacy root dnslink, plumb errors
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ipfs/boxo&package-manager=go_modules&previous-version=0.15.0&new-version=0.16.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2e68e686a7..0ee3f8c797 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.5 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/imdario/mergo v0.3.16 - github.com/ipfs/boxo v0.15.0 + github.com/ipfs/boxo v0.16.0 github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 diff --git a/go.sum b/go.sum index ee6e4a0f31..99fe996ada 100644 --- a/go.sum +++ b/go.sum @@ -1038,8 +1038,8 @@ github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1: github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.15.0 h1:BriLydj2nlK1nKeJQHxcKSuG5ZXcoutzhBklOtxC5pk= -github.com/ipfs/boxo v0.15.0/go.mod h1:X5ulcbR5Nh7sm3Db8+08AApUo6FsGC5mb23QDKAoB/M= +github.com/ipfs/boxo v0.16.0 h1:A9dUmef5a+mEFki6kbyG7el5gl65CiUBzrDeZxzTWKY= +github.com/ipfs/boxo v0.16.0/go.mod h1:jAgpNQn7T7BnibUeReXcKU9Ha1xmYNyOlwVEl193ow0= github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= From 5605f15e1de5df0deb1bcc64bf4e06fe33ecb533 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:36:51 +0000 Subject: [PATCH 0956/1008] chore(deps): Bump github.com/libp2p/go-libp2p-kad-dht from 0.25.1 to 0.25.2 (#3058) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps [github.com/libp2p/go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht) from 0.25.1 to 0.25.2.
    Release notes

    Sourced from github.com/libp2p/go-libp2p-kad-dht's releases.

    v0.25.2

    What's Changed

    New Contributors

    Full Changelog: https://github.com/libp2p/go-libp2p-kad-dht/compare/v0.25.1...v0.25.2

    Commits
    • bdca144 Merge pull request #961 from libp2p/release-v0.25.2
    • bea8743 release v0.25.2
    • ef7d2b1 Merge pull request #960 from godeamon/bug/send-request-read-msg-ctx-err-check
    • ae0987e reset stream and return err when ctx canceled
    • 097cb9a return canceled err not retry
    • 12fcd27 add ctx canceled err check
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/libp2p/go-libp2p-kad-dht&package-manager=go_modules&previous-version=0.25.1&new-version=0.25.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0ee3f8c797..db247c2e71 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/ipld/go-car v0.6.2 github.com/libp2p/go-libp2p v0.32.1 - github.com/libp2p/go-libp2p-kad-dht v0.25.1 + github.com/libp2p/go-libp2p-kad-dht v0.25.2 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.7.3 diff --git a/go.sum b/go.sum index 99fe996ada..f6401b2eb3 100644 --- a/go.sum +++ b/go.sum @@ -1472,8 +1472,8 @@ github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQO github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.19.0/go.mod h1:qPIXdiZsLczhV4/+4EO1jE8ae0YCW4ZOogc4WVIyTEU= github.com/libp2p/go-libp2p-kad-dht v0.21.0/go.mod h1:Bhm9diAFmc6qcWAr084bHNL159srVZRKADdp96Qqd1I= -github.com/libp2p/go-libp2p-kad-dht v0.25.1 h1:ofFNrf6MMEy4vi3R1VbJ7LOcTn3Csh0cDcaWHTxtWNA= -github.com/libp2p/go-libp2p-kad-dht v0.25.1/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo= +github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ= +github.com/libp2p/go-libp2p-kad-dht v0.25.2/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= github.com/libp2p/go-libp2p-kbucket v0.5.0/go.mod h1:zGzGCpQd78b5BNTDGHNDLaTt9aDK/A02xeZp9QeFC4U= github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= From 53e46c699be5e8a4eb6209f7fbc6ffabf4f65484 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:48:22 +0000 Subject: [PATCH 0957/1008] chore(deps): Bump google.golang.org/protobuf from 1.31.0 to 1.32.0 (#3059) Bumps google.golang.org/protobuf from 1.31.0 to 1.32.0. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/protobuf&package-manager=go_modules&previous-version=1.31.0&new-version=1.32.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db247c2e71..4df8844bd0 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.32.0 ) require ( diff --git a/go.sum b/go.sum index f6401b2eb3..ffdffebd39 100644 --- a/go.sum +++ b/go.sum @@ -3236,8 +3236,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 77cc973ba5523e86dbfdd75a9a52660741feddae Mon Sep 17 00:00:00 2001 From: Alireza <50639733+cyneptic@users.noreply.github.com> Date: Tue, 2 Jan 2024 14:44:16 +0330 Subject: [PATCH 0958/1008] chore(share|das|libs/utils): DRY context resetting in the event of an error for metrics (#2935) this creates a function in the utils/resetctx.go that takes a context as an input and resets it if there is an error. it then uses the function to refactor all the instances of the repeating pattern as requested in the issue - fixes #2928 --------- Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: ramin --- core/exchange_metrics.go | 6 ++-- core/listener_metrics.go | 6 ++-- das/metrics.go | 19 +++++-------- libs/utils/resetctx.go | 12 ++++++++ share/eds/metrics.go | 50 ++++++++++------------------------ share/getters/shrex.go | 8 ++---- share/p2p/discovery/metrics.go | 18 ++++-------- share/p2p/metrics.go | 6 ++-- share/p2p/peers/metrics.go | 13 +++------ 9 files changed, 54 insertions(+), 84 deletions(-) create mode 100644 libs/utils/resetctx.go diff --git a/core/exchange_metrics.go b/core/exchange_metrics.go index 0b454a6e4d..4e5bf5956c 100644 --- a/core/exchange_metrics.go +++ b/core/exchange_metrics.go @@ -5,6 +5,8 @@ import ( "time" "go.opentelemetry.io/otel/metric" + + "github.com/celestiaorg/celestia-node/libs/utils" ) type exchangeMetrics struct { @@ -31,9 +33,7 @@ func (m *exchangeMetrics) observe(ctx context.Context, observeFn func(ctx contex return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) observeFn(ctx) } diff --git a/core/listener_metrics.go b/core/listener_metrics.go index a325149b5d..f17903a91a 100644 --- a/core/listener_metrics.go +++ b/core/listener_metrics.go @@ -6,6 +6,8 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" + + "github.com/celestiaorg/celestia-node/libs/utils" ) var meter = otel.Meter("core") @@ -53,9 +55,7 @@ func (m *listenerMetrics) observe(ctx context.Context, observeFn func(ctx contex return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) observeFn(ctx) } diff --git a/das/metrics.go b/das/metrics.go index 42b472d909..6454e9d138 100644 --- a/das/metrics.go +++ b/das/metrics.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/otel/metric" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/utils" ) const ( @@ -19,9 +20,7 @@ const ( failedLabel = "failed" ) -var ( - meter = otel.Meter("das") -) +var meter = otel.Meter("das") type metrics struct { sampled metric.Int64Counter @@ -146,9 +145,9 @@ func (m *metrics) observeSample( if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + + ctx = utils.ResetContextOnError(ctx) + m.sampleTime.Record(ctx, sampleTime.Seconds(), metric.WithAttributes( attribute.Bool(failedLabel, err != nil), @@ -171,9 +170,7 @@ func (m *metrics) observeGetHeader(ctx context.Context, d time.Duration) { if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.getHeaderTime.Record(ctx, d.Seconds()) } @@ -182,8 +179,6 @@ func (m *metrics) observeNewHead(ctx context.Context) { if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.newHead.Add(ctx, 1) } diff --git a/libs/utils/resetctx.go b/libs/utils/resetctx.go new file mode 100644 index 0000000000..3014ba81db --- /dev/null +++ b/libs/utils/resetctx.go @@ -0,0 +1,12 @@ +package utils + +import "context" + +// ResetContextOnError returns a fresh context if the given context has an error. +func ResetContextOnError(ctx context.Context) context.Context { + if ctx.Err() != nil { + ctx = context.Background() + } + + return ctx +} diff --git a/share/eds/metrics.go b/share/eds/metrics.go index cbebf8321a..8d69a3ec41 100644 --- a/share/eds/metrics.go +++ b/share/eds/metrics.go @@ -7,6 +7,8 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + + "github.com/celestiaorg/celestia-node/libs/utils" ) const ( @@ -27,9 +29,7 @@ const ( dagstoreShardStatusKey = "shard_status" ) -var ( - meter = otel.Meter("eds_store") -) +var meter = otel.Meter("eds_store") type putResult string @@ -163,9 +163,7 @@ func (m *metrics) observeGCtime(ctx context.Context, dur time.Duration, failed b if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.gcTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) } @@ -174,9 +172,7 @@ func (m *metrics) observeShardFailure(ctx context.Context, shardKey string) { if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.shardFailureCount.Add(ctx, 1, metric.WithAttributes(attribute.String("shard_key", shardKey))) } @@ -185,9 +181,7 @@ func (m *metrics) observePut(ctx context.Context, dur time.Duration, result putR if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.putTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.String(putResultKey, string(result)), @@ -198,9 +192,7 @@ func (m *metrics) observeLongOp(ctx context.Context, opName string, dur time.Dur if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.longOpTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.String(opNameKey, opName), @@ -211,9 +203,7 @@ func (m *metrics) observeGetCAR(ctx context.Context, dur time.Duration, failed b if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.getCARTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) @@ -223,9 +213,7 @@ func (m *metrics) observeCARBlockstore(ctx context.Context, dur time.Duration, f if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.getCARBlockstoreTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) @@ -235,9 +223,7 @@ func (m *metrics) observeGetDAH(ctx context.Context, dur time.Duration, failed b if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.getDAHTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) @@ -247,9 +233,7 @@ func (m *metrics) observeRemove(ctx context.Context, dur time.Duration, failed b if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.removeTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) @@ -259,9 +243,7 @@ func (m *metrics) observeGet(ctx context.Context, dur time.Duration, failed bool if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.getTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) @@ -271,9 +253,7 @@ func (m *metrics) observeHas(ctx context.Context, dur time.Duration, failed bool if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.hasTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) @@ -283,9 +263,7 @@ func (m *metrics) observeList(ctx context.Context, dur time.Duration, failed boo if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.listTime.Record(ctx, dur.Seconds(), metric.WithAttributes( attribute.Bool(failedKey, failed))) diff --git a/share/getters/shrex.go b/share/getters/shrex.go index af48a7f8ab..826c6b1a10 100644 --- a/share/getters/shrex.go +++ b/share/getters/shrex.go @@ -43,9 +43,7 @@ func (m *metrics) recordEDSAttempt(ctx context.Context, attemptCount int, succes if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.edsAttempts.Record(ctx, int64(attemptCount), metric.WithAttributes( attribute.Bool("success", success))) @@ -55,9 +53,7 @@ func (m *metrics) recordNDAttempt(ctx context.Context, attemptCount int, success if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.ndAttempts.Record(ctx, int64(attemptCount), metric.WithAttributes( attribute.Bool("success", success))) diff --git a/share/p2p/discovery/metrics.go b/share/p2p/discovery/metrics.go index d0be1c219d..78b62a7d97 100644 --- a/share/p2p/discovery/metrics.go +++ b/share/p2p/discovery/metrics.go @@ -8,6 +8,8 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + + "github.com/celestiaorg/celestia-node/libs/utils" ) const ( @@ -24,9 +26,7 @@ const ( advertiseFailedKey = "failed" ) -var ( - meter = otel.Meter("share_discovery") -) +var meter = otel.Meter("share_discovery") type handlePeerResult string @@ -118,9 +118,7 @@ func (m *metrics) observeFindPeers(ctx context.Context, isEnoughPeers bool) { if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.discoveryResult.Add(ctx, 1, metric.WithAttributes( @@ -131,9 +129,7 @@ func (m *metrics) observeHandlePeer(ctx context.Context, result handlePeerResult if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.handlePeerResult.Add(ctx, 1, metric.WithAttributes( @@ -144,9 +140,7 @@ func (m *metrics) observeAdvertise(ctx context.Context, err error) { if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.advertise.Add(ctx, 1, metric.WithAttributes( diff --git a/share/p2p/metrics.go b/share/p2p/metrics.go index fee3b12413..55aefda81d 100644 --- a/share/p2p/metrics.go +++ b/share/p2p/metrics.go @@ -7,6 +7,8 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + + "github.com/celestiaorg/celestia-node/libs/utils" ) var meter = otel.Meter("shrex/eds") @@ -35,9 +37,7 @@ func (m *Metrics) ObserveRequests(ctx context.Context, count int64, status statu if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.totalRequestCounter.Add(ctx, count, metric.WithAttributes( attribute.String("status", string(status)), diff --git a/share/p2p/peers/metrics.go b/share/p2p/peers/metrics.go index 3b5913ebb8..098610c595 100644 --- a/share/p2p/peers/metrics.go +++ b/share/p2p/peers/metrics.go @@ -12,6 +12,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -46,9 +47,7 @@ const ( // validated blacklisted ) -var ( - meter = otel.Meter("shrex_peer_manager") -) +var meter = otel.Meter("shrex_peer_manager") type blacklistPeerReason string @@ -169,9 +168,7 @@ func (m *metrics) observeGetPeer( if m == nil { return } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.getPeer.Add(ctx, 1, metric.WithAttributes( attribute.String(sourceKey, string(source)), @@ -222,9 +219,7 @@ func (m *metrics) validationObserver(validator shrexsub.ValidatorFn) shrexsub.Va resStr = "unknown" } - if ctx.Err() != nil { - ctx = context.Background() - } + ctx = utils.ResetContextOnError(ctx) m.validationResult.Add(ctx, 1, metric.WithAttributes( From b97175c2ceffa29452031a3e5583f7d015e150df Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 2 Jan 2024 13:11:47 +0100 Subject: [PATCH 0959/1008] chore(go.mod): bump go-header (#3052) Bumps go-header to the latest RC to pull in the fix to the `peerTracker` in https://github.com/celestiaorg/go-header/pull/139. It's suspected that the `peerTracker`'s aggressive scoring mechanism is causing header syncing to stall after periods of poor connectivity. --------- Co-authored-by: ramin --- go.mod | 2 +- go.sum | 4 ++-- nodebuilder/header/constructors.go | 14 ++++++-------- share/eds/byzantine/byzantine.go | 3 ++- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 4df8844bd0..ebeff2c41b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v1.4.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.4.1 + github.com/celestiaorg/go-header v0.5.0-rc1 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index ffdffebd39..a4c1c0866b 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.4.1 h1:bjbUcKDnhrJJ9EoE7vtPpgleNLVjc2S+cB4/qe8nQmo= -github.com/celestiaorg/go-header v0.4.1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.5.0-rc1 h1:DAcVW8V76VI5VU4fOAdXePpq15UFblwZIMZeHCAVr0w= +github.com/celestiaorg/go-header v0.5.0-rc1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= diff --git a/nodebuilder/header/constructors.go b/nodebuilder/header/constructors.go index 15d2da09b1..a78d609d8e 100644 --- a/nodebuilder/header/constructors.go +++ b/nodebuilder/header/constructors.go @@ -99,16 +99,14 @@ func newInitStore[H libhead.Header[H]]( ds datastore.Batching, ex libhead.Exchange[H], ) (libhead.Store[H], error) { - s, err := store.NewStore[H](ds, store.WithParams(cfg.Store)) - if err != nil { - return nil, err + opts := []store.Option{store.WithParams(cfg.Store)} + if MetricsEnabled { + opts = append(opts, store.WithMetrics()) } - if MetricsEnabled { - err = libhead.WithMetrics[H](s) - if err != nil { - return nil, err - } + s, err := store.NewStore[H](ds, opts...) + if err != nil { + return nil, err } trustedHash, err := cfg.trustedHash(net) diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index e7aec28959..d20b56deed 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -4,9 +4,10 @@ import ( "context" "fmt" + "github.com/ipfs/boxo/blockservice" + "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/rsmt2d" - "github.com/ipfs/boxo/blockservice" "github.com/celestiaorg/celestia-node/share/ipld" ) From b4878302f7da390fcfceb99233f1e51e9619f150 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 12:21:40 +0000 Subject: [PATCH 0960/1008] chore(deps): Bump google.golang.org/grpc from 1.59.0 to 1.60.1 (#3060) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.59.0 to 1.60.1.
    Release notes

    Sourced from google.golang.org/grpc's releases.

    Release v1.60.1

    Bug Fixes

    • server: fix two bugs that could lead to panics at shutdown when using NumStreamWorkers (experimental feature).

    Release 1.60.0

    Security

    • credentials/tls: if not set, set TLS MinVersion to 1.2 and CipherSuites according to supported suites not forbidden by RFC7540.
      • This is a behavior change to bring us into better alignment with RFC 7540.

    API Changes

    • resolver: remove deprecated and experimental ClientConn.NewServiceConfig (#6784)
    • client: remove deprecated grpc.WithServiceConfig DialOption (#6800)

    Bug Fixes

    • client: fix race that could cause a deadlock while entering idle mode and receiving a name resolver update (#6804)
    • client: always enable TCP keepalives with OS defaults (#6834)
    • credentials/alts: fix a bug preventing ALTS from connecting to the metadata server if the default scheme is overridden (#6686)

    Behavior Changes

    • server: Do not return from Stop() or GracefulStop() until all resources are released (#6489)

    Documentation

    • codes: clarify that only codes defined by this package are valid and that users should not cast other values to codes.Code (#6701)
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.59.0&new-version=1.60.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ramin Co-authored-by: Ryan --- go.mod | 8 ++++---- go.sum | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index ebeff2c41b..458c41c9df 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/sync v0.5.0 golang.org/x/text v0.14.0 - google.golang.org/grpc v1.59.0 + google.golang.org/grpc v1.60.1 google.golang.org/protobuf v1.32.0 ) @@ -324,16 +324,16 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/tools v0.14.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/api v0.128.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index a4c1c0866b..1ee176676a 100644 --- a/go.sum +++ b/go.sum @@ -2694,8 +2694,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3052,8 +3052,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -3169,8 +3170,8 @@ google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= -google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= -google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -3218,8 +3219,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 6f5fef83daf343116480c06a62755d0eefcd3e06 Mon Sep 17 00:00:00 2001 From: 00x-dx <145253945+00x-dx@users.noreply.github.com> Date: Tue, 2 Jan 2024 22:30:18 +0800 Subject: [PATCH 0961/1008] chore(p2p/flags): Handle empty p2p.network flag. (#3004) Fixes #3003. --------- Co-authored-by: ramin --- nodebuilder/p2p/flags.go | 36 +++++++++++----------- nodebuilder/p2p/flags_test.go | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/nodebuilder/p2p/flags.go b/nodebuilder/p2p/flags.go index 0faf15e3ca..8e7c0f8bc0 100644 --- a/nodebuilder/p2p/flags.go +++ b/nodebuilder/p2p/flags.go @@ -32,10 +32,11 @@ Peers must bidirectionally point to each other. (Format: multiformats.io/multiad ) flags.String( networkFlag, - "", - "The name of the network to connect to, e.g. "+ - listProvidedNetworks()+ - ". Must be passed on both init and start to take effect.", + DefaultNetwork.String(), + fmt.Sprintf("The name of the network to connect to, e.g. %s. Must be passed on "+ + "both init and start to take effect. Assumes mainnet (%s) unless otherwise specified.", + listProvidedNetworks(), + DefaultNetwork.String()), ) return flags @@ -67,22 +68,23 @@ func ParseFlags( // ParseNetwork tries to parse the network from the flags and environment, // and returns either the parsed network or the build's default network func ParseNetwork(cmd *cobra.Command) (Network, error) { - parsed := cmd.Flag(networkFlag).Value.String() - // no network set through the flags, so check if there is an override in the env - if parsed == "" { - envNetwork, err := parseNetworkFromEnv() - // no network found in env, so use the default network - if envNetwork == "" { - return DefaultNetwork, err - } + if envNetwork, err := parseNetworkFromEnv(); envNetwork != "" { return envNetwork, err } - // check if user provided the actual network value - // or an alias - if net, err := Network(parsed).Validate(); err == nil { - return net, nil + parsed := cmd.Flag(networkFlag).Value.String() + switch parsed { + case "": + return "", fmt.Errorf("no network provided, allowed values: %s", listProvidedNetworks()) + + case DefaultNetwork.String(): + return DefaultNetwork, nil + + default: + if net, err := Network(parsed).Validate(); err == nil { + return net, nil + } + return "", fmt.Errorf("invalid network specified: %s, allowed values: %s", parsed, listProvidedNetworks()) } - return "", fmt.Errorf("invalid network specified: %s", parsed) } // parseNetworkFromEnv tries to parse the network from the environment. diff --git a/nodebuilder/p2p/flags_test.go b/nodebuilder/p2p/flags_test.go index bec49f6074..cfbb5fed5d 100644 --- a/nodebuilder/p2p/flags_test.go +++ b/nodebuilder/p2p/flags_test.go @@ -69,3 +69,59 @@ func createCmdWithNetworkFlag() *cobra.Command { cmd.Flags().AddFlagSet(flags) return cmd } + +// Set empty network flag and ensure error returned +func TestParseNetwork_emptyFlag(t *testing.T) { + cmd := createCmdWithNetworkFlag() + + err := cmd.Flags().Set(networkFlag, "") + require.NoError(t, err) + + _, err = ParseNetwork(cmd) + assert.Error(t, err) + +} + +// Set empty network flag and ensure error returned +func TestParseNetwork_emptyEnvEmptyFlag(t *testing.T) { + + t.Setenv(EnvCustomNetwork, "") + + cmd := createCmdWithNetworkFlag() + err := cmd.Flags().Set(networkFlag, "") + require.NoError(t, err) + + _, err = ParseNetwork(cmd) + require.Error(t, err) + +} + +// Env overrides empty flag to take precedence +func TestParseNetwork_envOverridesEmptyFlag(t *testing.T) { + + t.Setenv(EnvCustomNetwork, "custom-network") + + cmd := createCmdWithNetworkFlag() + err := cmd.Flags().Set(networkFlag, "") + require.NoError(t, err) + + network, err := ParseNetwork(cmd) + require.NoError(t, err) + assert.Equal(t, Network("custom-network"), network) + +} + +// Explicitly set flag but env should still override +func TestParseNetwork_envOverridesFlag(t *testing.T) { + + t.Setenv(EnvCustomNetwork, "custom-network") + + cmd := createCmdWithNetworkFlag() + err := cmd.Flags().Set(networkFlag, string(Mocha)) + require.NoError(t, err) + + network, err := ParseNetwork(cmd) + require.NoError(t, err) + assert.Equal(t, Network("custom-network"), network) + +} From d517e3c872523c27f9917597e3c13256c248334e Mon Sep 17 00:00:00 2001 From: smuu <18609909+smuu@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:42:30 +0100 Subject: [PATCH 0962/1008] chore(nodebuilder/p2p)!: add arabica-11 (#3066) Signed-off-by: Smuu <18609909+Smuu@users.noreply.github.com> Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- nodebuilder/p2p/bootstrap.go | 8 ++++---- nodebuilder/p2p/genesis.go | 2 +- nodebuilder/p2p/network.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nodebuilder/p2p/bootstrap.go b/nodebuilder/p2p/bootstrap.go index 3e9da1d77d..8e1856f6fb 100644 --- a/nodebuilder/p2p/bootstrap.go +++ b/nodebuilder/p2p/bootstrap.go @@ -49,10 +49,10 @@ var bootstrapList = map[Network][]string{ "/dns4/da-full-3.celestia-bootstrap.net/tcp/2121/p2p/12D3KooWK6Ftsd4XsWCsQZgZPNhTrE5urwmkoo5P61tGvnKmNVyv", }, Arabica: { - "/dns4/da-bridge.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWM3e9MWtyc8GkP8QRt74Riu17QuhGfZMytB2vq5NwkWAu", - "/dns4/da-bridge-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWKj8mcdiBGxQRe1jqhaMnh2tGoC3rPDmr5UH2q8H4WA9M", - "/dns4/da-full-1.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWBWkgmN7kmJSFovVrCjkeG47FkLGq7yEwJ2kEqNKCsBYk", - "/dns4/da-full-2.celestia-arabica-10.com/tcp/2121/p2p/12D3KooWRByRF67a2kVM2j4MP5Po3jgTw7H2iL2Spu8aUwPkrRfP", + "/dns4/da-bridge-1.celestia-arabica-11.com/tcp/2121/p2p/12D3KooWGqwzdEqM54Dce6LXzfFr97Bnhvm6rN7KM7MFwdomfm4S", + "/dns4/da-bridge-2.celestia-arabica-11.com/tcp/2121/p2p/12D3KooWCMGM5eZWVfCN9ZLAViGfLUWAfXP5pCm78NFKb9jpBtua", + "/dns4/da-bridge-3.celestia-arabica-11.com/tcp/2121/p2p/12D3KooWEWuqrjULANpukDFGVoHW3RoeUU53Ec9t9v5cwW3MkVdQ", + "/dns4/da-bridge-4.celestia-arabica-11.com/tcp/2121/p2p/12D3KooWLT1ysSrD7XWSBjh7tU1HQanF5M64dHV6AuM6cYEJxMPk", }, Mocha: { "/dns4/da-bridge-mocha-4.celestia-mocha.com/tcp/2121/p2p/12D3KooWCBAbQbJSpCpCGKzqz3rAN4ixYbc63K68zJg9aisuAajg", diff --git a/nodebuilder/p2p/genesis.go b/nodebuilder/p2p/genesis.go index dcc19dfa49..e35ca9bf29 100644 --- a/nodebuilder/p2p/genesis.go +++ b/nodebuilder/p2p/genesis.go @@ -24,7 +24,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ Mainnet: "6BE39EFD10BA412A9DB5288488303F5DD32CF386707A5BEF33617F4C43301872", - Arabica: "5904E55478BA4B3002EE885621E007A2A6A2399662841912219AECD5D5CBE393", + Arabica: "27122593765E07329BC348E8D16E92DCB4C75B34CCCB35C640FD7A4484D4C711", Mocha: "B93BBE20A0FBFDF955811B6420F8433904664D45DB4BF51022BE4200C1A1680D", Private: "", } diff --git a/nodebuilder/p2p/network.go b/nodebuilder/p2p/network.go index a7f9ff7236..53893eff7c 100644 --- a/nodebuilder/p2p/network.go +++ b/nodebuilder/p2p/network.go @@ -12,7 +12,7 @@ const ( // DefaultNetwork is the default network of the current build. DefaultNetwork = Mainnet // Arabica testnet. See: celestiaorg/networks. - Arabica Network = "arabica-10" + Arabica Network = "arabica-11" // Mocha testnet. See: celestiaorg/networks. Mocha Network = "mocha-4" // Private can be used to set up any private network, including local testing setups. From f75e255999d3e9c590b03b83f068b58cc32d2548 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 4 Jan 2024 11:27:56 +0100 Subject: [PATCH 0963/1008] chore(go.mod): Bump header to latest release (#3067) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 458c41c9df..6609aeba05 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v1.4.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.5.0-rc1 + github.com/celestiaorg/go-header v0.5.1 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 1ee176676a..a8ad8ad148 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.5.0-rc1 h1:DAcVW8V76VI5VU4fOAdXePpq15UFblwZIMZeHCAVr0w= -github.com/celestiaorg/go-header v0.5.0-rc1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.5.1 h1:1s1lw4fcCHalNK0qw/0a3cxg3ezx3Hl020znIxPZvtk= +github.com/celestiaorg/go-header v0.5.1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= From 45e1847513fde2c1a680fd1b384c4efadc108e51 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:37:21 +0100 Subject: [PATCH 0964/1008] feat(nodebuilder/prune)!: Enable sampling window for light nodes (#2991) This PR enforces a sampling window of 30 days' worth of seconds for light nodes. The DASer will now skip over sampling headers that fall outside this range. I've labeled this PR as breaking as it contains a behavioural break (sampling constrained to a 30-day window rather than all historical headers). --- das/daser.go | 2 ++ das/daser_test.go | 37 +++++++++++++++++++++++++++++++++++++ nodebuilder/prune/module.go | 2 +- pruner/light/window.go | 2 ++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/das/daser.go b/das/daser.go index 40eee3d316..7d569f7e0b 100644 --- a/das/daser.go +++ b/das/daser.go @@ -151,6 +151,8 @@ func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { // short-circuit if pruning is enabled and the header is outside the // availability window if !d.isWithinSamplingWindow(h) { + log.Debugw("skipping header outside sampling window", "height", h.Height(), + "time", h.Time()) return nil } diff --git a/das/daser_test.go b/das/daser_test.go index fd1eb39f7d..9eec6392cc 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -2,6 +2,7 @@ package das import ( "context" + "strconv" "testing" "time" @@ -244,6 +245,42 @@ func TestDASerSampleTimeout(t *testing.T) { } } +// TestDASer_SamplingWindow tests the sampling window determination +// for headers. +func TestDASer_SamplingWindow(t *testing.T) { + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + sub := new(headertest.Subscriber) + fserv := &fraudtest.DummyService[*header.ExtendedHeader]{} + getter := getterStub{} + avail := mocks.NewMockAvailability(gomock.NewController(t)) + + // create and start DASer + daser, err := NewDASer(avail, sub, getter, ds, fserv, newBroadcastMock(1), + WithSamplingWindow(time.Second)) + require.NoError(t, err) + + var tests = []struct { + timestamp time.Time + withinWindow bool + }{ + {timestamp: time.Now().Add(-(time.Second * 5)), withinWindow: false}, + {timestamp: time.Now().Add(-(time.Millisecond * 800)), withinWindow: true}, + {timestamp: time.Now().Add(-(time.Hour)), withinWindow: false}, + {timestamp: time.Now().Add(-(time.Hour * 24 * 30)), withinWindow: false}, + {timestamp: time.Now(), withinWindow: true}, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + eh := headertest.RandExtendedHeader(t) + eh.RawHeader.Time = tt.timestamp + + assert.Equal(t, tt.withinWindow, daser.isWithinSamplingWindow(eh)) + }) + } + +} + // createDASerSubcomponents takes numGetter (number of headers // to store in mockGetter) and numSub (number of headers to store // in the mock header.Subscriber), returning a newly instantiated diff --git a/nodebuilder/prune/module.go b/nodebuilder/prune/module.go index 330ef21cdc..2141b74bf1 100644 --- a/nodebuilder/prune/module.go +++ b/nodebuilder/prune/module.go @@ -39,7 +39,7 @@ func ConstructModule(tp node.Type) fx.Option { fx.Provide(func() pruner.Pruner { return light.NewPruner() }), - fx.Supply(archival.Window), // TODO @renaynay: turn this into light.Window in following PR + fx.Supply(light.Window), ) default: panic("unknown node type") diff --git a/pruner/light/window.go b/pruner/light/window.go index 53bfe4a163..dc1a9e4444 100644 --- a/pruner/light/window.go +++ b/pruner/light/window.go @@ -6,4 +6,6 @@ import ( "github.com/celestiaorg/celestia-node/pruner" ) +// Window is the availability window for light nodes in the Celestia +// network (30 days). const Window = pruner.AvailabilityWindow(time.Second * 86400 * 30) From ade25d7362aa318b7600ebb78b84493bee17f48b Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 8 Jan 2024 11:13:26 +0000 Subject: [PATCH 0965/1008] refactor(makefile + ci): rename swamp -> integration (#3065) Renames `swamp` to `integration` in Makefile and where called in github actions workflows Fixes #2104 --- .github/workflows/go-ci.yml | 8 ++++---- Makefile | 18 +++++++++--------- nodebuilder/tests/README.md | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index b004dcfb59..e4cd0e83cc 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -133,8 +133,8 @@ jobs: with: go-version: ${{ inputs.go-version }} - - name: Swamp Tests - run: make test-swamp + - name: Integration Tests + run: make test-integration - - name: Swamp Tests with Race Detector - run: make test-swamp-race + - name: Integration Tests with Race Detector + run: make test-integration-race diff --git a/Makefile b/Makefile index 9e1635308b..b3b3739cb7 100644 --- a/Makefile +++ b/Makefile @@ -121,19 +121,19 @@ test-unit-race: @go test -race `go list ./... | grep -v nodebuilder/tests` .PHONY: test-unit-race -## test-swamp: Running swamp tests located in nodebuilder/tests -test-swamp: - @echo "--> Running swamp tests" +## test-integration: Running /integration tests located in nodebuilder/tests +test-integration: + @echo "--> Running integrations tests" @go test ./nodebuilder/tests -.PHONY: test-swamp +.PHONY: test-integration -## test-swamp-race: Running swamp tests with data race detector located in node/tests -test-swamp-race: - @echo "--> Running swamp tests with data race detector" +## test-integration-race: Running integration tests with data race detector located in node/tests +test-integration-race: + @echo "--> Running integration tests with data race detector" @go test -race ./nodebuilder/tests -.PHONY: test-swamp-race +.PHONY: test-integration-race -## test: Running both unit and swamp tests +## test: Running both unit and integrations tests test: @echo "--> Running all tests without data race detector" @go test ./... diff --git a/nodebuilder/tests/README.md b/nodebuilder/tests/README.md index 176ee2ba21..dd2040ab42 100644 --- a/nodebuilder/tests/README.md +++ b/nodebuilder/tests/README.md @@ -1,6 +1,6 @@ # Swamp: In-Memory Test Tool -Swamp is a testing tool that creates an environment for deploying `celestia-node` and testing instances against each other. +Swamp is our integration testing tool that creates an environment for deploying `celestia-node` and testing instances against each other. While the swamp takes care of setting up networking and initial configuration of node types, the user can focus on tailoring test scenarios. ## Usage @@ -38,7 +38,7 @@ require.NoError(t, err) light := sw.NewLightClient(node.WithTrustedPeer(addrs[0].String())) ``` -## Concenptual overview +## Conceptual overview Each of the test scenario requires flexibility in network topology. The user can define the necessary amount of each type of node and be able to control each of them. From 1afe0a14b77f52db2742ef580e839222bccd97a9 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 8 Jan 2024 11:56:55 +0000 Subject: [PATCH 0966/1008] chore(deps): update otel group (#3068) reduced myself to fixing dependabot's attempts to upgrade us at https://github.com/celestiaorg/celestia-node/pull/3055 was not working quite right --- go.mod | 23 +++++++++++------------ go.sum | 54 +++++++++++++++++++++++++++--------------------------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 6609aeba05..2a832e3352 100644 --- a/go.mod +++ b/go.mod @@ -49,20 +49,20 @@ require ( github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 github.com/prometheus/client_golang v1.18.0 github.com/pyroscope-io/client v0.7.2 - github.com/pyroscope-io/otel-profiling-go v0.4.0 + github.com/pyroscope-io/otel-profiling-go v0.5.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0 - go.opentelemetry.io/otel v1.19.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 - go.opentelemetry.io/otel/metric v1.19.0 - go.opentelemetry.io/otel/sdk v1.19.0 - go.opentelemetry.io/otel/sdk/metric v1.19.0 - go.opentelemetry.io/otel/trace v1.19.0 + go.opentelemetry.io/otel v1.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 + go.opentelemetry.io/otel/metric v1.21.0 + go.opentelemetry.io/otel/sdk v1.21.0 + go.opentelemetry.io/otel/sdk/metric v1.21.0 + go.opentelemetry.io/otel/trace v1.21.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 @@ -157,7 +157,7 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -175,7 +175,7 @@ require ( github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v1.12.1 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect @@ -317,7 +317,6 @@ require ( github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.1 // indirect diff --git a/go.sum b/go.sum index a8ad8ad148..24303315da 100644 --- a/go.sum +++ b/go.sum @@ -688,8 +688,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= @@ -823,8 +823,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= @@ -2080,8 +2081,8 @@ github.com/pyroscope-io/client v0.7.2 h1:OX2qdUQsS8RSkn/3C8isD7f/P0YiZQlRbAlecAa github.com/pyroscope-io/client v0.7.2/go.mod h1:FEocnjn+Ngzxy6EtU9ZxXWRvQ0+pffkrBxHLnPpxwi8= github.com/pyroscope-io/godeltaprof v0.1.2 h1:MdlEmYELd5w+lvIzmZvXGNMVzW2Qc9jDMuJaPOR75g4= github.com/pyroscope-io/godeltaprof v0.1.2/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= -github.com/pyroscope-io/otel-profiling-go v0.4.0 h1:Hk/rbUqOWoByoWy1tt4r5BX5xoKAvs5drr0511Ki8ic= -github.com/pyroscope-io/otel-profiling-go v0.4.0/go.mod h1:MXaofiWU7PgLP7eISUZJYVO4Z8WYMqpkYgeP4XrPLyg= +github.com/pyroscope-io/otel-profiling-go v0.5.0 h1:LsTP9VuQ5TgeSiyY2gPHy1de/q3jbFyGWE1v3LtHzMk= +github.com/pyroscope-io/otel-profiling-go v0.5.0/go.mod h1:jUUUXTTgntvGJKS8p5uzypXwTyuGnQP31VnWauH/lUg= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc= @@ -2117,8 +2118,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -2382,36 +2383,35 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0 h1:2JydY5UiDpqvj2p7sO9bgHuhTy4hgTZ0ymehdq/Ob0Q= go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0/go.mod h1:ch3a5QxOqVWxas4CzjCFFOOQe+7HgAXC/N1oVxS9DK4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 h1:wNMDy/LVGLj2h3p6zg4d0gypKfWKSWI14E1C4smOgl8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0/go.mod h1:YfbDdXAAkemWJK3H/DshvlrxqFB2rtW4rY6ky/3x/H0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= -go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= From f8f4af735b2eed39ad83889c15fafaf28bd8300e Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 8 Jan 2024 13:05:14 +0000 Subject: [PATCH 0967/1008] test: cleanup of PR 2998 to simplify (#3051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in kind replacement of https://github.com/celestiaorg/celestia-node/pull/2998 with the simplified require assertion --------- Co-authored-by: Håvard Anda Estensen --- libs/utils/address.go | 5 ++++- libs/utils/address_test.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/utils/address.go b/libs/utils/address.go index ae52a03b16..c20d11ad06 100644 --- a/libs/utils/address.go +++ b/libs/utils/address.go @@ -1,11 +1,14 @@ package utils import ( + "errors" "fmt" "net" "strings" ) +var ErrInvalidIP = errors.New("invalid IP address or hostname given") + // SanitizeAddr trims leading protocol scheme and port from the given // IP address or hostname if present. func SanitizeAddr(addr string) (string, error) { @@ -16,7 +19,7 @@ func SanitizeAddr(addr string) (string, error) { addr = strings.TrimSuffix(addr, "/") addr = strings.Split(addr, ":")[0] if addr == "" { - return "", fmt.Errorf("invalid IP address or hostname given: %s", original) + return "", fmt.Errorf("%w: %s", ErrInvalidIP, original) } return addr, nil } diff --git a/libs/utils/address_test.go b/libs/utils/address_test.go index 15452f4d1b..48a7747a4a 100644 --- a/libs/utils/address_test.go +++ b/libs/utils/address_test.go @@ -11,6 +11,7 @@ func TestSanitizeAddr(t *testing.T) { var tests = []struct { addr string want string + err error }{ // Testcase: trims protocol prefix {addr: "http://celestia.org", want: "celestia.org"}, @@ -20,13 +21,15 @@ func TestSanitizeAddr(t *testing.T) { {addr: "tcp://192.168.42.42:5050/", want: "192.168.42.42"}, // Testcase: invariant ip {addr: "192.168.42.42", want: "192.168.42.42"}, + // Testcase: empty addr + {addr: "", want: "", err: ErrInvalidIP}, } for _, tt := range tests { t.Run(tt.addr, func(t *testing.T) { got, err := SanitizeAddr(tt.addr) - require.NoError(t, err) require.Equal(t, tt.want, got) + require.ErrorIs(t, err, tt.err) }) } } From 73336eda96510652881080c7b339d0eca7bc4ca1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:36:22 +0100 Subject: [PATCH 0968/1008] fix(core): Use `Verify` from go-header pkg in `CoreExchange` (#3077) We should have been using the high-level `Verify` check from go-header inside of the `CoreExchange` instead of the `ExtendedHeader`-based implementation of Verify. Co-authored-by: ramin --- core/exchange.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/exchange.go b/core/exchange.go index 79f3d6337a..cf889a38bb 100644 --- a/core/exchange.go +++ b/core/exchange.go @@ -78,7 +78,7 @@ func (ce *Exchange) GetRangeByHeight( ce.metrics.requestDurationPerHeader(ctx, time.Since(start), amount) for _, h := range headers { - err := from.Verify(h) + err := libhead.Verify[*header.ExtendedHeader](from, h, libhead.DefaultHeightThreshold) if err != nil { return nil, fmt.Errorf("verifying next header against last verified height: %d: %w", from.Height(), err) From 49894c0b9ca322884ce4a2ead6f4e9f6834da55d Mon Sep 17 00:00:00 2001 From: Chirag <56735482+Chirag018@users.noreply.github.com> Date: Wed, 10 Jan 2024 22:35:56 +0530 Subject: [PATCH 0969/1008] chore(modstate): Remove IsStopped endpoint from StateModule (#2912) @Wondertan is this what you expected? Closes #2906 --- api/gateway/middleware.go | 20 -------------------- nodebuilder/state/mocks/api.go | 14 -------------- nodebuilder/state/state.go | 10 +++------- nodebuilder/state/stub.go | 4 ---- state/core_access.go | 4 ---- 5 files changed, 3 insertions(+), 49 deletions(-) diff --git a/api/gateway/middleware.go b/api/gateway/middleware.go index 2c88b34185..4b669113dd 100644 --- a/api/gateway/middleware.go +++ b/api/gateway/middleware.go @@ -2,13 +2,8 @@ package gateway import ( "context" - "errors" "net/http" "time" - - "github.com/gorilla/mux" - - "github.com/celestiaorg/celestia-node/nodebuilder/state" ) const timeout = time.Minute @@ -16,7 +11,6 @@ const timeout = time.Minute func (h *Handler) RegisterMiddleware(srv *Server) { srv.RegisterMiddleware( setContentType, - checkPostDisabled(h.state), wrapRequestContext, enableCors, ) @@ -36,20 +30,6 @@ func setContentType(next http.Handler) http.Handler { }) } -// checkPostDisabled ensures that context was canceled and prohibit POST requests. -func checkPostDisabled(state state.Module) mux.MiddlewareFunc { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // check if state service was halted and deny the transaction - if r.Method == http.MethodPost && state.IsStopped(r.Context()) { - writeError(w, http.StatusMethodNotAllowed, r.URL.Path, errors.New("not possible to submit data")) - return - } - next.ServeHTTP(w, r) - }) - } -} - // wrapRequestContext ensures we implement a deadline on serving requests // via the gateway server-side to prevent context leaks. func wrapRequestContext(next http.Handler) http.Handler { diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 754920dee2..6499a6dfd8 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -131,20 +131,6 @@ func (mr *MockModuleMockRecorder) Delegate(arg0, arg1, arg2, arg3, arg4 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delegate", reflect.TypeOf((*MockModule)(nil).Delegate), arg0, arg1, arg2, arg3, arg4) } -// IsStopped mocks base method. -func (m *MockModule) IsStopped(arg0 context.Context) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsStopped", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// IsStopped indicates an expected call of IsStopped. -func (mr *MockModuleMockRecorder) IsStopped(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsStopped", reflect.TypeOf((*MockModule)(nil).IsStopped), arg0) -} - // QueryDelegation mocks base method. func (m *MockModule) QueryDelegation(arg0 context.Context, arg1 types.ValAddress) (*types0.QueryDelegationResponse, error) { m.ctrl.T.Helper() diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 83408680da..624f8dcd3f 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -16,9 +16,8 @@ var _ Module = (*API)(nil) // messages to the Celestia network. // //go:generate mockgen -destination=mocks/api.go -package=mocks . Module +//nolint:dupl type Module interface { - // IsStopped checks if the Module's context has been stopped - IsStopped(ctx context.Context) bool // AccountAddress retrieves the address of the node's account/signer AccountAddress(ctx context.Context) (state.Address, error) @@ -97,10 +96,11 @@ type Module interface { // API is a wrapper around Module for the RPC. // TODO(@distractedm1nd): These structs need to be autogenerated. +// +//nolint:dupl type API struct { Internal struct { AccountAddress func(ctx context.Context) (state.Address, error) `perm:"read"` - IsStopped func(ctx context.Context) bool `perm:"read"` Balance func(ctx context.Context) (*state.Balance, error) `perm:"read"` BalanceForAddress func(ctx context.Context, addr state.Address) (*state.Balance, error) `perm:"read"` Transfer func( @@ -167,10 +167,6 @@ func (api *API) AccountAddress(ctx context.Context) (state.Address, error) { return api.Internal.AccountAddress(ctx) } -func (api *API) IsStopped(ctx context.Context) bool { - return api.Internal.IsStopped(ctx) -} - func (api *API) BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) { return api.Internal.BalanceForAddress(ctx, addr) } diff --git a/nodebuilder/state/stub.go b/nodebuilder/state/stub.go index 8d17d651dd..30a431aba5 100644 --- a/nodebuilder/state/stub.go +++ b/nodebuilder/state/stub.go @@ -17,10 +17,6 @@ var ErrNoStateAccess = errors.New("node is running without state access. run wit // to a core endpoint. type stubbedStateModule struct{} -func (s stubbedStateModule) IsStopped(context.Context) bool { - return true -} - func (s stubbedStateModule) AccountAddress(context.Context) (state.Address, error) { return state.Address{}, ErrNoStateAccess } diff --git a/state/core_access.go b/state/core_access.go index 2a49e70a03..358457b4f0 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -573,10 +573,6 @@ func (ca *CoreAccessor) queryMinimumGasPrice( return coins.AmountOf(app.BondDenom).MustFloat64(), nil } -func (ca *CoreAccessor) IsStopped(context.Context) bool { - return ca.ctx.Err() != nil -} - func withFee(fee Int) apptypes.TxBuilderOption { gasFee := sdktypes.NewCoins(sdktypes.NewCoin(app.BondDenom, fee)) return apptypes.SetFeeAmount(gasFee) From 9b2d82a5d07a0cff52b901fbacdaa402cb6af891 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:49:56 +0100 Subject: [PATCH 0970/1008] chore(deps): Bump deps (#3091) Can close out #3075 --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2a832e3352..4ad8bb9ba8 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.7.3 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.12.0 + github.com/multiformats/go-multiaddr v0.12.1 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 @@ -68,7 +68,7 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.17.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d - golang.org/x/sync v0.5.0 + golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 google.golang.org/grpc v1.60.1 google.golang.org/protobuf v1.32.0 diff --git a/go.sum b/go.sum index 24303315da..f1daccb66c 100644 --- a/go.sum +++ b/go.sum @@ -1830,8 +1830,8 @@ github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= -github.com/multiformats/go-multiaddr v0.12.0 h1:1QlibTFkoXJuDjjYsMHhE73TnzJQl8FSWatk/0gxGzE= -github.com/multiformats/go-multiaddr v0.12.0/go.mod h1:WmZXgObOQOYp9r3cslLlppkrz1FYSHmE834dfz/lWu8= +github.com/multiformats/go-multiaddr v0.12.1 h1:vm+BA/WZA8QZDp1pF1FWhi5CT3g1tbi5GJmqpb6wnlk= +github.com/multiformats/go-multiaddr v0.12.1/go.mod h1:7mPkiBMmLeFipt+nNSq9pHZUeJSt8lHBgH6yhj0YQzE= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= @@ -2712,8 +2712,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 0ce8241d568662b4d203a09b6905cfbe61b96ce1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:17:51 +0100 Subject: [PATCH 0971/1008] chore(deps): Bump go-header (#3096) Bumping go-header. --- go.mod | 26 +++++++++++++------------- go.sum | 51 ++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index 4ad8bb9ba8..dbec09d01a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/celestiaorg/celestia-app v1.4.0 github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.5.1 + github.com/celestiaorg/go-header v0.5.2 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 @@ -36,7 +36,7 @@ require ( github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipld/go-car v0.6.2 - github.com/libp2p/go-libp2p v0.32.1 + github.com/libp2p/go-libp2p v0.32.2 github.com/libp2p/go-libp2p-kad-dht v0.25.2 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-libp2p-record v0.2.0 @@ -66,8 +66,8 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.17.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/crypto v0.18.0 + golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 google.golang.org/grpc v1.60.1 @@ -157,7 +157,7 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -180,7 +180,7 @@ require ( github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect @@ -232,7 +232,7 @@ require ( github.com/jmhodges/levigo v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.2 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/lib/pq v1.10.7 // indirect @@ -285,7 +285,7 @@ require ( github.com/pyroscope-io/godeltaprof v0.1.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.3.4 // indirect - github.com/quic-go/quic-go v0.39.3 // indirect + github.com/quic-go/quic-go v0.39.4 // indirect github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect @@ -321,12 +321,12 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/api v0.128.0 // indirect diff --git a/go.sum b/go.sum index f1daccb66c..05e4c0bfcf 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,8 @@ github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXv github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.5.1 h1:1s1lw4fcCHalNK0qw/0a3cxg3ezx3Hl020znIxPZvtk= -github.com/celestiaorg/go-header v0.5.1/go.mod h1:H8xhnDLDLbkpwmWPhCaZyTnIV3dlVxBHPnxNXS2Qu6c= +github.com/celestiaorg/go-header v0.5.2 h1:CFsTAXcs1o38JVd8YN1Naq/Yzs6m9orMPEPNpLEgFJA= +github.com/celestiaorg/go-header v0.5.2/go.mod h1:7BVR6myjRfACbqW1de6s8OjuK66XzHm8MpFNYr0G+nU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= @@ -688,8 +688,9 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= @@ -873,8 +874,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1345,8 +1346,8 @@ github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8t github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= @@ -1415,8 +1416,8 @@ github.com/libp2p/go-libp2p v0.22.0/go.mod h1:UDolmweypBSjQb2f7xutPnwZ/fxioLbMBx github.com/libp2p/go-libp2p v0.23.4/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= github.com/libp2p/go-libp2p v0.25.0/go.mod h1:vXHmFpcfl+xIGN4qW58Bw3a0/SKGAesr5/T4IuJHE3o= github.com/libp2p/go-libp2p v0.25.1/go.mod h1:xnK9/1d9+jeQCVvi/f1g12KqtVi/jP/SijtKV1hML3g= -github.com/libp2p/go-libp2p v0.32.1 h1:wy1J4kZIZxOaej6NveTWCZmHiJ/kY7GoAqXgqNCnPps= -github.com/libp2p/go-libp2p v0.32.1/go.mod h1:hXXC3kXPlBZ1eu8Q2hptGrMB4mZ3048JUoS4EKaHW5c= +github.com/libp2p/go-libp2p v0.32.2 h1:s8GYN4YJzgUoyeYNPdW7JZeZ5Ee31iNaIBfGYMAY4FQ= +github.com/libp2p/go-libp2p v0.32.2/go.mod h1:E0LKe+diV/ZVJVnOJby8VC5xzHF0660osg71skcxJvk= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= @@ -2091,8 +2092,8 @@ github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8u github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg= github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo= -github.com/quic-go/quic-go v0.39.3 h1:o3YB6t2SR+HU/pgwF29kJ6g4jJIJEwEZ8CKia1h1TKg= -github.com/quic-go/quic-go v0.39.3/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q= +github.com/quic-go/quic-go v0.39.4 h1:PelfiuG7wXEffUT2yceiqz5V6Pc0TA5ruOd1LcmFc1s= +github.com/quic-go/quic-go v0.39.4/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q= github.com/quic-go/webtransport-go v0.5.1/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= @@ -2511,8 +2512,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2535,8 +2536,8 @@ golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= +golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2572,8 +2573,8 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2665,8 +2666,8 @@ golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2859,8 +2860,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2869,8 +2870,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2971,8 +2972,8 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From a7c49d20062327b636e7e08d859802a8f5393ac0 Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 12 Jan 2024 14:33:29 +0000 Subject: [PATCH 0972/1008] ci: add continue-on-error temporarily (#3100) Adding `continue-on-error` to the flakey components so we can see that the entire pipeline w/ generated binaries will complete ASAP / next release, will be replaced with separating/fixing the flakey tests next. --- .github/workflows/go-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index e4cd0e83cc..3e73290500 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -108,6 +108,7 @@ jobs: needs: [lint, go_mod_tidy_check] name: Run Unit Tests with Race Detector runs-on: ubuntu-latest + continue-on-error: true steps: - uses: actions/checkout@v4 @@ -124,6 +125,7 @@ jobs: needs: [lint, go_mod_tidy_check] name: Run Integration Tests runs-on: ubuntu-latest + continue-on-error: true steps: - uses: actions/checkout@v4 From 73771ecd6b3fb4600804b18f04947eebe944b789 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 16 Jan 2024 12:39:01 +0100 Subject: [PATCH 0973/1008] fix: clarifying blob.GetAll godoc (#3090) The previous godoc led to some confusion because it got understood as getting all blobs *under* the given height. Co-authored-by: Viacheslav --- nodebuilder/blob/blob.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go index 5e29d3b90c..47ab255ac6 100644 --- a/nodebuilder/blob/blob.go +++ b/nodebuilder/blob/blob.go @@ -19,7 +19,7 @@ type Module interface { Submit(_ context.Context, _ []*blob.Blob, _ *blob.SubmitOptions) (height uint64, _ error) // Get retrieves the blob by commitment under the given namespace and height. Get(_ context.Context, height uint64, _ share.Namespace, _ blob.Commitment) (*blob.Blob, error) - // GetAll returns all blobs under the given namespaces and height. + // GetAll returns all blobs at the given height under the given namespaces. GetAll(_ context.Context, height uint64, _ []share.Namespace) ([]*blob.Blob, error) // GetProof retrieves proofs in the given namespaces at the given height by commitment. GetProof(_ context.Context, height uint64, _ share.Namespace, _ blob.Commitment) (*blob.Proof, error) From 4083ea82e87e2bffaf6b97e973e5b909804abf90 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Tue, 16 Jan 2024 12:47:57 +0100 Subject: [PATCH 0974/1008] chore: cleanup go.mod file require sections (#3103) This PR does not change any of the dependency, it only reorders the go.mod file into a direct and undirect import section, as go versions do since 1.17. The reason your go.mod file got so many require sections is because during the 1.16 to 1.17 go.mod styling transition you had different contributors updating the go.mod file with pre 1.17 and post 1.17 releases of go. See https://go.dev/doc/modules/gomod-ref: > At go 1.17 or higher: section for a description of what changed. Co-authored-by: ramin --- go.mod | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index dbec09d01a..bbbec88f46 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/cosmos/cosmos-sdk v0.46.14 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 + github.com/dgraph-io/badger/v4 v4.2.1-0.20231013074411-fb1b00959581 github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/dagstore v0.5.6 github.com/filecoin-project/go-jsonrpc v0.3.1 @@ -30,6 +31,7 @@ require ( github.com/imdario/mergo v0.3.16 github.com/ipfs/boxo v0.16.0 github.com/ipfs/go-block-format v0.2.0 + github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ipld-cbor v0.1.0 @@ -74,22 +76,6 @@ require ( google.golang.org/protobuf v1.32.0 ) -require ( - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/bits-and-blooms/bitset v1.7.0 // indirect - github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2 // indirect - github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.1 // indirect - github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect - github.com/ethereum/c-kzg-4844 v0.3.1 // indirect - github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect - github.com/mmcloughlin/addchain v0.4.0 // indirect - github.com/supranational/blst v0.3.11 // indirect - go.uber.org/mock v0.3.0 // indirect - rsc.io/tmplfunc v0.0.3 // indirect -) - require ( cloud.google.com/go v0.110.8 // indirect cloud.google.com/go/compute v1.23.0 // indirect @@ -101,6 +87,7 @@ require ( github.com/99designs/keyring v1.2.1 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/Jorropo/jsync v1.0.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/StackExchange/wmi v1.2.1 // indirect @@ -110,8 +97,10 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect + github.com/celestiaorg/quantum-gravity-bridge/v2 v2.1.2 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -120,6 +109,8 @@ require ( github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/confio/ics23/go v0.9.1 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect @@ -130,6 +121,7 @@ require ( github.com/cosmos/iavl v0.19.6 // indirect github.com/cosmos/ibc-go/v6 v6.2.0 // indirect github.com/cosmos/ledger-cosmos-go v0.13.2 // indirect + github.com/crate-crypto/go-kzg-4844 v0.3.0 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -140,7 +132,6 @@ require ( github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect - github.com/dgraph-io/badger/v4 v4.2.1-0.20231013074411-fb1b00959581 github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -148,6 +139,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.14.2 // indirect github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect + github.com/ethereum/c-kzg-4844 v0.3.1 // indirect github.com/ethereum/go-ethereum v1.13.2 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -199,6 +191,7 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/holiman/uint256 v1.2.3 // indirect @@ -209,7 +202,6 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.12.2 // indirect github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect @@ -252,6 +244,7 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/miekg/dns v1.1.56 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -260,6 +253,7 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -302,6 +296,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.14.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/go-amino v0.16.0 // indirect @@ -320,6 +315,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.1 // indirect + go.uber.org/mock v0.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect @@ -339,6 +335,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect nhooyr.io/websocket v1.8.7 // indirect + rsc.io/tmplfunc v0.0.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) From ad92edbfa874c746a661433e13f4b0e7d808e920 Mon Sep 17 00:00:00 2001 From: Halimao <1065621723@qq.com> Date: Tue, 16 Jan 2024 22:55:19 +0800 Subject: [PATCH 0975/1008] feat(nodebuilder): log node version on start (#3089) Closes #2382 --- ## Screenshot ![image](https://github.com/celestiaorg/celestia-node/assets/25278203/2c04a166-0d41-45df-8194-20cc51ef1ba1) --------- Co-authored-by: ramin --- nodebuilder/node.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 3e6950a6ae..d5d0ab2016 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -108,8 +108,12 @@ func (n *Node) Start(ctx context.Context) error { return fmt.Errorf("node: failed to start: %w", err) } - log.Infof("\n\n/_____/ /_____/ /_____/ /_____/ /_____/ \n\nStarted celestia DA node \nnode "+ - "type: %s\nnetwork: %s\n\n/_____/ /_____/ /_____/ /_____/ /_____/ \n", strings.ToLower(n.Type.String()), + log.Infof("\n\n/_____/ /_____/ /_____/ /_____/ /_____/ \n\n"+ + "Started celestia DA node \n"+ + "node version: %s\nnode type: %s\nnetwork: %s\n\n"+ + "/_____/ /_____/ /_____/ /_____/ /_____/ \n", + node.GetBuildInfo().SemanticVersion, + strings.ToLower(n.Type.String()), n.Network) addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(n.Host)) From 36205ccf88c1e41fec80e7ef21ca84d87b970f8b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:51:52 +0100 Subject: [PATCH 0976/1008] fix(core): Do not propagate blocks if subscribed to blocks from incorrect chain (#3086) Right now, it is possible for a **bridge** node that was initialised and started on one chain (e.g. `mocha-4`) to be stopped and restarted on a different chain (e.g. `mainnet`) and propagate headers from the old chain (`mocha-4`) into the different network (`mainnet`). This PR fixes this issue by causing the listener to fatal if the listener recognises it is receiving blocks from a different chain to that which it expects. Error will look like this: ``` 2024-01-10T16:06:22.001+0100 ERROR core core/listener.go:175 listener: received block with unexpected chain ID: expected arabica-11, received mocha-4 2024-01-10T16:06:22.001+0100 INFO core core/listener.go:177 listener: listening stopped 2024-01-10T16:06:22.001+0100 FATAL core core/listener.go:126 listener: invalid subscription ``` Resolves #3071 --- core/exchange_test.go | 4 ++- core/listener.go | 20 +++++++++++--- core/listener_test.go | 45 +++++++++++++++++++++++++++++--- core/option.go | 10 +++++++ libs/utils/resetctx.go | 4 ++- nodebuilder/core/module.go | 3 ++- nodebuilder/tests/swamp/swamp.go | 1 + 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/core/exchange_test.go b/core/exchange_test.go index c43084c57d..95c7f83385 100644 --- a/core/exchange_test.go +++ b/core/exchange_test.go @@ -20,7 +20,9 @@ func TestCoreExchange_RequestHeaders(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - fetcher, _ := createCoreFetcher(t, DefaultTestConfig()) + cfg := DefaultTestConfig() + cfg.ChainID = networkID + fetcher, _ := createCoreFetcher(t, cfg) // generate 10 blocks generateBlocks(t, fetcher) diff --git a/core/listener.go b/core/listener.go index 8447733506..10255fc4cc 100644 --- a/core/listener.go +++ b/core/listener.go @@ -23,6 +23,8 @@ import ( var ( tracer = otel.Tracer("core/listener") retrySubscriptionDelay = 5 * time.Second + + errInvalidSubscription = errors.New("invalid subscription") ) // Listener is responsible for listening to Core for @@ -41,11 +43,12 @@ type Listener struct { headerBroadcaster libhead.Broadcaster[*header.ExtendedHeader] hashBroadcaster shrexsub.BroadcastFn - listenerTimeout time.Duration - metrics *listenerMetrics - cancel context.CancelFunc + chainID string + + listenerTimeout time.Duration + cancel context.CancelFunc } func NewListener( @@ -81,6 +84,7 @@ func NewListener( store: store, listenerTimeout: 5 * blocktime, metrics: metrics, + chainID: p.chainID, }, nil } @@ -117,6 +121,10 @@ func (cl *Listener) runSubscriber(ctx context.Context, sub <-chan types.EventDat // listener stopped because external context was canceled return } + if errors.Is(err, errInvalidSubscription) { + // stop node if there is a critical issue with the block subscription + log.Fatalf("listener: %v", err) + } log.Warnw("listener: subscriber error, resubscribing...", "err", err) sub = cl.resubscribe(ctx) @@ -163,6 +171,12 @@ func (cl *Listener) listen(ctx context.Context, sub <-chan types.EventDataSigned return errors.New("underlying subscription was closed") } + if cl.chainID != "" && b.Header.ChainID != cl.chainID { + log.Errorf("listener: received block with unexpected chain ID: expected %s,"+ + " received %s", cl.chainID, b.Header.ChainID) + return errInvalidSubscription + } + log.Debugw("listener: new block from core", "height", b.Header.Height) err := cl.handleNewSignedBlock(ctx, b) diff --git a/core/listener_test.go b/core/listener_test.go index bf84c07b41..5ddcd5541d 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -47,10 +47,14 @@ func TestListener(t *testing.T) { t.Cleanup(subs.Cancel) // create one block to store as Head in local store and then unsubscribe from block events - fetcher, _ := createCoreFetcher(t, DefaultTestConfig()) + cfg := DefaultTestConfig() + cfg.ChainID = networkID + fetcher, _ := createCoreFetcher(t, cfg) + eds := createEdsPubSub(ctx, t) + // create Listener and start listening - cl := createListener(ctx, t, fetcher, ps0, eds, createStore(t)) + cl := createListener(ctx, t, fetcher, ps0, eds, createStore(t), networkID) err = cl.Start(ctx) require.NoError(t, err) @@ -80,6 +84,7 @@ func TestListenerWithNonEmptyBlocks(t *testing.T) { // create one block to store as Head in local store and then unsubscribe from block events cfg := DefaultTestConfig() + cfg.ChainID = networkID fetcher, cctx := createCoreFetcher(t, cfg) eds := createEdsPubSub(ctx, t) @@ -92,7 +97,7 @@ func TestListenerWithNonEmptyBlocks(t *testing.T) { }) // create Listener and start listening - cl := createListener(ctx, t, fetcher, ps0, eds, store) + cl := createListener(ctx, t, fetcher, ps0, eds, store, networkID) err = cl.Start(ctx) require.NoError(t, err) @@ -124,6 +129,36 @@ func TestListenerWithNonEmptyBlocks(t *testing.T) { require.Nil(t, cl.cancel) } +func TestListenerWithWrongChainRPC(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + + // create mocknet with two pubsub endpoints + ps0, _ := createMocknetWithTwoPubsubEndpoints(ctx, t) + + // create one block to store as Head in local store and then unsubscribe from block events + cfg := DefaultTestConfig() + cfg.ChainID = networkID + fetcher, _ := createCoreFetcher(t, cfg) + eds := createEdsPubSub(ctx, t) + + store := createStore(t) + err := store.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = store.Stop(ctx) + require.NoError(t, err) + }) + + // create Listener and start listening + cl := createListener(ctx, t, fetcher, ps0, eds, store, "wrong-chain-rpc") + sub, err := cl.fetcher.SubscribeNewBlockEvent(ctx) + require.NoError(t, err) + + err = cl.listen(ctx, sub) + assert.ErrorIs(t, err, errInvalidSubscription) +} + func createMocknetWithTwoPubsubEndpoints(ctx context.Context, t *testing.T) (*pubsub.PubSub, *pubsub.PubSub) { net, err := mocknet.FullMeshLinked(2) require.NoError(t, err) @@ -166,6 +201,7 @@ func createListener( ps *pubsub.PubSub, edsSub *shrexsub.PubSub, store *eds.Store, + chainID string, ) *Listener { p2pSub, err := p2p.NewSubscriber[*header.ExtendedHeader](ps, header.MsgID, p2p.WithSubscriberNetworkID(networkID)) require.NoError(t, err) @@ -180,7 +216,8 @@ func createListener( require.NoError(t, p2pSub.Stop(ctx)) }) - listener, err := NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, store, nodep2p.BlockTime) + listener, err := NewListener(p2pSub, fetcher, edsSub.Broadcast, header.MakeExtendedHeader, + store, nodep2p.BlockTime, WithChainID(nodep2p.Network(chainID))) require.NoError(t, err) return listener } diff --git a/core/option.go b/core/option.go index 6e06fade48..6916ced4d8 100644 --- a/core/option.go +++ b/core/option.go @@ -1,9 +1,13 @@ package core +import "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + type Option func(*params) type params struct { metrics bool + + chainID string } // WithMetrics is a functional option that enables metrics @@ -13,3 +17,9 @@ func WithMetrics() Option { p.metrics = true } } + +func WithChainID(id p2p.Network) Option { + return func(p *params) { + p.chainID = id.String() + } +} diff --git a/libs/utils/resetctx.go b/libs/utils/resetctx.go index 3014ba81db..a108cc27b4 100644 --- a/libs/utils/resetctx.go +++ b/libs/utils/resetctx.go @@ -1,6 +1,8 @@ package utils -import "context" +import ( + "context" +) // ResetContextOnError returns a fresh context if the given context has an error. func ResetContextOnError(ctx context.Context) context.Context { diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index fec7c14b1b..7c5c9e6bfd 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -56,8 +56,9 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option pubsub *shrexsub.PubSub, construct header.ConstructFn, store *eds.Store, + chainID p2p.Network, ) (*core.Listener, error) { - var opts []core.Option + opts := []core.Option{core.WithChainID(chainID)} if MetricsEnabled { opts = append(opts, core.WithMetrics()) } diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index e3ac3ad4f2..617fe76151 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -78,6 +78,7 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { // Now, we are making an assumption that consensus mechanism is already tested out // so, we are not creating bridge nodes with each one containing its own core client // instead we are assigning all created BNs to 1 Core from the swamp + ic.WithChainID("private") cctx := core.StartTestNodeWithConfig(t, ic) swp := &Swamp{ t: t, From 2c7266209bdcda448bf8d0b31d5b5b2e43c8fc99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:55:15 +0000 Subject: [PATCH 0977/1008] chore(deps): Bump cosmossdk.io/errors from 1.0.0 to 1.0.1 (#3105) Bumps [cosmossdk.io/errors](https://github.com/cosmos/cosmos-sdk) from 1.0.0 to 1.0.1.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cosmossdk.io/errors&package-manager=go_modules&previous-version=1.0.0&new-version=1.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 20 ++++++++++---------- go.sum | 41 ++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index bbbec88f46..78e627b68f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/celestiaorg/celestia-node go 1.21.1 require ( - cosmossdk.io/errors v1.0.0 + cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.2.0 github.com/BurntSushi/toml v1.3.2 github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b @@ -77,10 +77,10 @@ require ( ) require ( - cloud.google.com/go v0.110.8 // indirect - cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go v0.110.10 // indirect + cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.2 // indirect + cloud.google.com/go/iam v1.1.5 // indirect cloud.google.com/go/storage v1.30.1 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -171,9 +171,9 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect - github.com/google/s2a-go v0.1.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.5.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -325,11 +325,11 @@ require ( golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/api v0.128.0 // indirect + google.golang.org/api v0.149.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect + google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 05e4c0bfcf..3e05a3e503 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= -cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -75,8 +75,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -116,8 +116,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= -cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -193,8 +193,8 @@ cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuW cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= -cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= +cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0= +cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U= cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= @@ -866,8 +866,8 @@ github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S3 github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -879,8 +879,8 @@ github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4= -github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -2503,7 +2503,6 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -3043,8 +3042,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= -google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3169,12 +3168,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= From 65c6b651df7ddbe8bc48deec189032f3745acbaf Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Fri, 19 Jan 2024 11:32:37 +0100 Subject: [PATCH 0978/1008] chore: unfork badger and ds-badger4 (#3110) * badger merged leak fix, so we don't need to depend on our fork * We still have to depend on a particular commit as it hasn't been released * we maintained go-ds-badger4, as there was no IPFS one. Now it exists, so we can use it and archive our go-ds-badger4 --- go.mod | 6 ++---- go.sum | 8 ++++---- nodebuilder/store.go | 5 ++--- share/eds/inverted_index.go | 3 +-- share/eds/store_test.go | 2 +- share/getters/getter_test.go | 2 +- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 78e627b68f..c75a4023cb 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.4.0 - github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 github.com/celestiaorg/go-fraud v0.2.0 github.com/celestiaorg/go-header v0.5.2 github.com/celestiaorg/go-libp2p-messenger v0.2.0 @@ -18,7 +17,7 @@ require ( github.com/cosmos/cosmos-sdk v0.46.14 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cristalhq/jwt v1.2.0 - github.com/dgraph-io/badger/v4 v4.2.1-0.20231013074411-fb1b00959581 + github.com/dgraph-io/badger/v4 v4.2.1-0.20240106094458-1c417aa3799c github.com/etclabscore/go-openrpc-reflect v0.0.37 github.com/filecoin-project/dagstore v0.5.6 github.com/filecoin-project/go-jsonrpc v0.3.1 @@ -34,6 +33,7 @@ require ( github.com/ipfs/go-blockservice v0.5.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-datastore v0.6.0 + github.com/ipfs/go-ds-badger4 v0.1.5 github.com/ipfs/go-ipld-cbor v0.1.0 github.com/ipfs/go-ipld-format v0.6.0 github.com/ipfs/go-log/v2 v2.5.1 @@ -347,5 +347,3 @@ replace ( github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 ) - -replace github.com/dgraph-io/badger/v4 => github.com/celestiaorg/badger/v4 v4.0.0-20231125230536-2b9e13346f75 diff --git a/go.sum b/go.sum index 3e05a3e503..30c8df771e 100644 --- a/go.sum +++ b/go.sum @@ -358,8 +358,6 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/badger/v4 v4.0.0-20231125230536-2b9e13346f75 h1:dfArr6rKfcB46+/sR/ZZuiBbcB1ZQcpZQT4E4HHcIuE= -github.com/celestiaorg/badger/v4 v4.0.0-20231125230536-2b9e13346f75/go.mod h1:T/uWAYxrXdaXw64ihI++9RMbKTCpKd/yE9+saARew7k= github.com/celestiaorg/celestia-app v1.4.0 h1:hTId3xL8GssN5sHSHPP7svHi/iWp+XVxphStiR7ADiY= github.com/celestiaorg/celestia-app v1.4.0/go.mod h1:zhdQIFGFZRRxrDVtFE4OFIT7/12RE8DRyfvNZdW8ceM= github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= @@ -368,8 +366,6 @@ github.com/celestiaorg/cosmos-sdk v1.18.3-sdk-v0.46.14 h1:+Te28r5Zp4Vp69f82kcON9 github.com/celestiaorg/cosmos-sdk v1.18.3-sdk-v0.46.14/go.mod h1:Og5KKgoBEiQlI6u56lDLG191pfknIdXctFn3COWLQP8= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJx5/hgZ+IeOLEIoLsAveJN/7/ZtQQtPSVw= github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= -github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5 h1:MJgXvhJP1Au8rXTvMMlBXodu9jplEK1DxiLtMnEphOs= -github.com/celestiaorg/go-ds-badger4 v0.0.0-20230712104058-7ede1c814ac5/go.mod h1:r6xB3nvGotmlTACpAr3SunxtoXeesbqb57elgMJqflY= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= github.com/celestiaorg/go-header v0.5.2 h1:CFsTAXcs1o38JVd8YN1Naq/Yzs6m9orMPEPNpLEgFJA= @@ -551,6 +547,8 @@ github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v4 v4.2.1-0.20240106094458-1c417aa3799c h1:Z9rm0wkQBM+VF7vpyrbKnCcSbww0PKygLoptTpkX3d4= +github.com/dgraph-io/badger/v4 v4.2.1-0.20240106094458-1c417aa3799c/go.mod h1:T/uWAYxrXdaXw64ihI++9RMbKTCpKd/yE9+saARew7k= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= @@ -1103,6 +1101,8 @@ github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9Dr github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-badger v0.3.0 h1:xREL3V0EH9S219kFFueOYJJTcjgNSZ2HY1iSvN7U1Ro= github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupVCGm4QUIek= +github.com/ipfs/go-ds-badger4 v0.1.5 h1:MwrTsIUJIqH/ChuDdUOzxwxMxHx/Li1ECoSCKsCUxiA= +github.com/ipfs/go-ds-badger4 v0.1.5/go.mod h1:LUU2FbhNdmhAbJmMeoahVRbe4GsduAODSJHWJJh2Vo4= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= diff --git a/nodebuilder/store.go b/nodebuilder/store.go index f2238c8c3f..7f67a9e782 100644 --- a/nodebuilder/store.go +++ b/nodebuilder/store.go @@ -11,10 +11,9 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/dgraph-io/badger/v4/options" "github.com/ipfs/go-datastore" + dsbadger "github.com/ipfs/go-ds-badger4" "github.com/mitchellh/go-homedir" - dsbadger "github.com/celestiaorg/go-ds-badger4" - "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/share" @@ -69,7 +68,7 @@ func OpenStore(path string, ring keyring.Keyring) (Store, error) { ok := IsInit(path) if !ok { - flock.Unlock() //nolint: errcheck + flock.Unlock() //nolint:errcheck return nil, ErrNotInited } diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index dc33c70447..f30d1c37db 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -10,9 +10,8 @@ import ( "github.com/filecoin-project/dagstore/index" "github.com/filecoin-project/dagstore/shard" ds "github.com/ipfs/go-datastore" + dsbadger "github.com/ipfs/go-ds-badger4" "github.com/multiformats/go-multihash" - - dsbadger "github.com/celestiaorg/go-ds-badger4" ) const invertedIndexPath = "/inverted_index/" diff --git a/share/eds/store_test.go b/share/eds/store_test.go index 09357347d0..6bc6972bb4 100644 --- a/share/eds/store_test.go +++ b/share/eds/store_test.go @@ -13,12 +13,12 @@ import ( "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" + dsbadger "github.com/ipfs/go-ds-badger4" "github.com/ipld/go-car" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - dsbadger "github.com/celestiaorg/go-ds-badger4" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" diff --git a/share/getters/getter_test.go b/share/getters/getter_test.go index 8264f6e822..7297766652 100644 --- a/share/getters/getter_test.go +++ b/share/getters/getter_test.go @@ -10,12 +10,12 @@ import ( "github.com/ipfs/boxo/exchange/offline" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" + dsbadger "github.com/ipfs/go-ds-badger4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" "github.com/celestiaorg/celestia-app/pkg/wrapper" - dsbadger "github.com/celestiaorg/go-ds-badger4" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" From ba3800969143901d01e52e4509fd02d68ca7f887 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 19 Jan 2024 13:21:11 +0200 Subject: [PATCH 0979/1008] fix(tests/befp): fix panic in befp test (#3108) ShareWithProofs collection happens async and it does not guarantee that Shares at index [1] will be non-nil. Panic happened in `incorrect share with Proof` when `befp.Shares[1] == nil` tested with `go test -v -count=300 -run ^TestBEFP_Validate$ github.com/celestiaorg/celestia-node/share/eds/byzantine` --- share/eds/byzantine/bad_encoding_test.go | 32 +++++++++++------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index adb23d3549..e42e3c287c 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -45,7 +45,9 @@ func TestBEFP_Validate(t *testing.T) { var errByz *ErrByzantine require.ErrorAs(t, byzantine, &errByz) - befp := CreateBadEncodingProof([]byte("hash"), 0, errByz) + proof := CreateBadEncodingProof([]byte("hash"), 0, errByz) + befp, ok := proof.(*BadEncodingProof) + require.True(t, ok) var test = []struct { name string prepareFn func() error @@ -54,7 +56,7 @@ func TestBEFP_Validate(t *testing.T) { { name: "valid BEFP", prepareFn: func() error { - return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + return proof.Validate(&header.ExtendedHeader{DAH: &dah}) }, expectedResult: func(err error) { require.NoError(t, err) @@ -88,10 +90,11 @@ func TestBEFP_Validate(t *testing.T) { { name: "incorrect share with Proof", prepareFn: func() error { - befp, ok := befp.(*BadEncodingProof) - require.True(t, ok) - befp.Shares[0].Share = befp.Shares[1].Share - return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + // break the first shareWithProof to test negative case + sh := sharetest.RandShares(t, 2) + nmtProof := nmt.NewInclusionProof(0, 1, nil, false) + befp.Shares[0] = &ShareWithProof{sh[0], &nmtProof} + return proof.Validate(&header.ExtendedHeader{DAH: &dah}) }, expectedResult: func(err error) { require.ErrorIs(t, err, errIncorrectShare) @@ -100,10 +103,8 @@ func TestBEFP_Validate(t *testing.T) { { name: "invalid amount of shares", prepareFn: func() error { - befp, ok := befp.(*BadEncodingProof) - require.True(t, ok) befp.Shares = befp.Shares[0 : len(befp.Shares)/2] - return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + return proof.Validate(&header.ExtendedHeader{DAH: &dah}) }, expectedResult: func(err error) { require.ErrorIs(t, err, errIncorrectAmountOfShares) @@ -112,10 +113,8 @@ func TestBEFP_Validate(t *testing.T) { { name: "not enough shares to recompute the root", prepareFn: func() error { - befp, ok := befp.(*BadEncodingProof) - require.True(t, ok) befp.Shares[0] = nil - return befp.Validate(&header.ExtendedHeader{DAH: &dah}) + return proof.Validate(&header.ExtendedHeader{DAH: &dah}) }, expectedResult: func(err error) { require.ErrorIs(t, err, errIncorrectAmountOfShares) @@ -124,11 +123,8 @@ func TestBEFP_Validate(t *testing.T) { { name: "index out of bounds", prepareFn: func() error { - befp, ok := befp.(*BadEncodingProof) - require.True(t, ok) - befpCopy := *befp - befpCopy.Index = 100 - return befpCopy.Validate(&header.ExtendedHeader{DAH: &dah}) + befp.Index = 100 + return proof.Validate(&header.ExtendedHeader{DAH: &dah}) }, expectedResult: func(err error) { require.ErrorIs(t, err, errIncorrectIndex) @@ -137,7 +133,7 @@ func TestBEFP_Validate(t *testing.T) { { name: "heights mismatch", prepareFn: func() error { - return befp.Validate(&header.ExtendedHeader{ + return proof.Validate(&header.ExtendedHeader{ RawHeader: core.Header{ Height: 42, }, From 2294805ce4146058e9777976de08600354762ad4 Mon Sep 17 00:00:00 2001 From: Artem Bogomaz Date: Fri, 19 Jan 2024 22:59:38 +0700 Subject: [PATCH 0980/1008] chore(nodebuilder/state): SubmitTx endpoint takes read-level permissions (#3072) refactor: changes SubmitTX permissions from write to read. Following the issue requirement, this PR changes SubmitTX permissions from _write_ to _read_ Changes: * Updated nodebuilder/state/state.go from **write** to **read** permissions * Updated following tests (api/rpc_test.go) and added extra check for SubmitTX rpc call (read permissions check). closes #2958 --------- Co-authored-by: [NODERS]TEAM Co-authored-by: ramin --- api/rpc_test.go | 9 ++++++--- nodebuilder/state/state.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api/rpc_test.go b/api/rpc_test.go index 9ff35bf1e4..ec308a2320 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -186,12 +186,15 @@ func TestAuthedRPC(t *testing.T) { // 2. Test method with write-level permissions expectedResp := &state.TxResponse{} if tt.perm > 2 { - server.State.EXPECT().SubmitTx(gomock.Any(), gomock.Any()).Return(expectedResp, nil) - txResp, err := rpcClient.State.SubmitTx(ctx, []byte{}) + server.State.EXPECT().Delegate(gomock.Any(), gomock.Any(), + gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedResp, nil) + txResp, err := rpcClient.State.Delegate(ctx, + state.ValAddress{}, state.Int{}, state.Int{}, 0) require.NoError(t, err) require.Equal(t, expectedResp, txResp) } else { - _, err := rpcClient.State.SubmitTx(ctx, []byte{}) + _, err := rpcClient.State.Delegate(ctx, + state.ValAddress{}, state.Int{}, state.Int{}, 0) require.Error(t, err) require.ErrorContains(t, err, "missing permission") } diff --git a/nodebuilder/state/state.go b/nodebuilder/state/state.go index 624f8dcd3f..52a2317445 100644 --- a/nodebuilder/state/state.go +++ b/nodebuilder/state/state.go @@ -110,7 +110,7 @@ type API struct { fee state.Int, gasLimit uint64, ) (*state.TxResponse, error) `perm:"write"` - SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"write"` + SubmitTx func(ctx context.Context, tx state.Tx) (*state.TxResponse, error) `perm:"read"` SubmitPayForBlob func( ctx context.Context, fee state.Int, From 8f768a71ee11614b49001490347fc33cdd2997a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:39:06 -0600 Subject: [PATCH 0981/1008] chore(deps): Bump the otel group with 4 updates (#3121) Bumps the otel group with 4 updates: [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go), [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp](https://github.com/open-telemetry/opentelemetry-go), [go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://github.com/open-telemetry/opentelemetry-go) and [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go). Updates `go.opentelemetry.io/otel` from 1.21.0 to 1.22.0
    Changelog

    Sourced from go.opentelemetry.io/otel's changelog.

    [1.22.0/0.45.0] 2024-01-17

    Added

    • The go.opentelemetry.io/otel/semconv/v1.22.0 package. The package contains semantic conventions from the v1.22.0 version of the OpenTelemetry Semantic Conventions. (#4735)
    • The go.opentelemetry.io/otel/semconv/v1.23.0 package. The package contains semantic conventions from the v1.23.0 version of the OpenTelemetry Semantic Conventions. (#4746)
    • The go.opentelemetry.io/otel/semconv/v1.23.1 package. The package contains semantic conventions from the v1.23.1 version of the OpenTelemetry Semantic Conventions. (#4749)
    • The go.opentelemetry.io/otel/semconv/v1.24.0 package. The package contains semantic conventions from the v1.24.0 version of the OpenTelemetry Semantic Conventions. (#4770)
    • Add WithResourceAsConstantLabels option to apply resource attributes for every metric emitted by the Prometheus exporter. (#4733)
    • Experimental cardinality limiting is added to the metric SDK. See metric documentation for more information about this feature and how to enable it. (#4457)
    • Add NewMemberRaw and NewKeyValuePropertyRaw in go.opentelemetry.io/otel/baggage. (#4804)

    Changed

    • Upgrade all use of go.opentelemetry.io/otel/semconv to use v1.24.0. (#4754)
    • Update transformations in go.opentelemetry.io/otel/exporters/zipkin to follow v1.19.0 version of the OpenTelemetry specification. (#4754)
    • Record synchronous measurements when the passed context is canceled instead of dropping in go.opentelemetry.io/otel/sdk/metric. If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g if ctx.Err() != nil). (#4671)
    • Improve go.opentelemetry.io/otel/trace.TraceState's performance. (#4722)
    • Improve go.opentelemetry.io/otel/propagation.TraceContext's performance. (#4721)
    • Improve go.opentelemetry.io/otel/baggage performance. (#4743)
    • Improve performance of the (*Set).Filter method in go.opentelemetry.io/otel/attribute when the passed filter does not filter out any attributes from the set. (#4774)
    • Member.String in go.opentelemetry.io/otel/baggage percent-encodes only when necessary. (#4775)
    • Improve go.opentelemetry.io/otel/trace.Span's performance when adding multiple attributes. (#4818)
    • Property.Value in go.opentelemetry.io/otel/baggage now returns a raw string instead of a percent-encoded value. (#4804)

    Fixed

    • Fix Parse in go.opentelemetry.io/otel/baggage to validate member value before percent-decoding. (#4755)
    • Fix whitespace encoding of Member.String in go.opentelemetry.io/otel/baggage. (#4756)
    • Fix observable not registered error when the asynchronous instrument has a drop aggregation in go.opentelemetry.io/otel/sdk/metric. (#4772)
    • Fix baggage item key so that it is not canonicalized in go.opentelemetry.io/otel/bridge/opentracing. (#4776)
    • Fix go.opentelemetry.io/otel/bridge/opentracing to properly handle baggage values that requires escaping during propagation. (#4804)
    • Fix a bug where using multiple readers resulted in incorrect asynchronous counter values in go.opentelemetry.io/otel/sdk/metric. (#4742)
    Commits
    • 279c549 Release v1.22.0/v0.45.0 (#4821)
    • 237ed37 Fix link changes from instrumentation to languages (#4828)
    • 5ed29d9 Bump lycheeverse/lychee-action from 1.9.0 to 1.9.1 (#4824)
    • 4491b39 sdk/trace: use slices.Grow() to avoid excessive runtime.growslice() (#4818)
    • 19622d3 chore(docs): explicitly mark lizthegrey emeritus (#4822)
    • 7fa7d1b sdk/metric: Fix observable not registered error when the asynchronous instrum...
    • 01472db Upgrade use of semconv to v1.24.0 (#4754)
    • 259143a baggage: Add NewMemberRaw and NewKeyValuePropertyRaw (#4804)
    • 6ead8d8 Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 in /internal/tools (#4815)
    • deddec3 Optimize (attribute.Set).Filter for no filtered case (#4774)
    • Additional commits viewable in compare view

    Updates `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` from 0.44.0 to 0.45.0
    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp's changelog.

    [1.22.0/0.45.0] 2024-01-17

    Added

    • The go.opentelemetry.io/otel/semconv/v1.22.0 package. The package contains semantic conventions from the v1.22.0 version of the OpenTelemetry Semantic Conventions. (#4735)
    • The go.opentelemetry.io/otel/semconv/v1.23.0 package. The package contains semantic conventions from the v1.23.0 version of the OpenTelemetry Semantic Conventions. (#4746)
    • The go.opentelemetry.io/otel/semconv/v1.23.1 package. The package contains semantic conventions from the v1.23.1 version of the OpenTelemetry Semantic Conventions. (#4749)
    • The go.opentelemetry.io/otel/semconv/v1.24.0 package. The package contains semantic conventions from the v1.24.0 version of the OpenTelemetry Semantic Conventions. (#4770)
    • Add WithResourceAsConstantLabels option to apply resource attributes for every metric emitted by the Prometheus exporter. (#4733)
    • Experimental cardinality limiting is added to the metric SDK. See metric documentation for more information about this feature and how to enable it. (#4457)
    • Add NewMemberRaw and NewKeyValuePropertyRaw in go.opentelemetry.io/otel/baggage. (#4804)

    Changed

    • Upgrade all use of go.opentelemetry.io/otel/semconv to use v1.24.0. (#4754)
    • Update transformations in go.opentelemetry.io/otel/exporters/zipkin to follow v1.19.0 version of the OpenTelemetry specification. (#4754)
    • Record synchronous measurements when the passed context is canceled instead of dropping in go.opentelemetry.io/otel/sdk/metric. If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g if ctx.Err() != nil). (#4671)
    • Improve go.opentelemetry.io/otel/trace.TraceState's performance. (#4722)
    • Improve go.opentelemetry.io/otel/propagation.TraceContext's performance. (#4721)
    • Improve go.opentelemetry.io/otel/baggage performance. (#4743)
    • Improve performance of the (*Set).Filter method in go.opentelemetry.io/otel/attribute when the passed filter does not filter out any attributes from the set. (#4774)
    • Member.String in go.opentelemetry.io/otel/baggage percent-encodes only when necessary. (#4775)
    • Improve go.opentelemetry.io/otel/trace.Span's performance when adding multiple attributes. (#4818)
    • Property.Value in go.opentelemetry.io/otel/baggage now returns a raw string instead of a percent-encoded value. (#4804)

    Fixed

    • Fix Parse in go.opentelemetry.io/otel/baggage to validate member value before percent-decoding. (#4755)
    • Fix whitespace encoding of Member.String in go.opentelemetry.io/otel/baggage. (#4756)
    • Fix observable not registered error when the asynchronous instrument has a drop aggregation in go.opentelemetry.io/otel/sdk/metric. (#4772)
    • Fix baggage item key so that it is not canonicalized in go.opentelemetry.io/otel/bridge/opentracing. (#4776)
    • Fix go.opentelemetry.io/otel/bridge/opentracing to properly handle baggage values that requires escaping during propagation. (#4804)
    • Fix a bug where using multiple readers resulted in incorrect asynchronous counter values in go.opentelemetry.io/otel/sdk/metric. (#4742)
    Commits
    • 279c549 Release v1.22.0/v0.45.0 (#4821)
    • 237ed37 Fix link changes from instrumentation to languages (#4828)
    • 5ed29d9 Bump lycheeverse/lychee-action from 1.9.0 to 1.9.1 (#4824)
    • 4491b39 sdk/trace: use slices.Grow() to avoid excessive runtime.growslice() (#4818)
    • 19622d3 chore(docs): explicitly mark lizthegrey emeritus (#4822)
    • 7fa7d1b sdk/metric: Fix observable not registered error when the asynchronous instrum...
    • 01472db Upgrade use of semconv to v1.24.0 (#4754)
    • 259143a baggage: Add NewMemberRaw and NewKeyValuePropertyRaw (#4804)
    • 6ead8d8 Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 in /internal/tools (#4815)
    • deddec3 Optimize (attribute.Set).Filter for no filtered case (#4774)
    • Additional commits viewable in compare view

    Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace` from 1.21.0 to 1.22.0
    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlptrace's changelog.

    [1.22.0/0.45.0] 2024-01-17

    Added

    • The go.opentelemetry.io/otel/semconv/v1.22.0 package. The package contains semantic conventions from the v1.22.0 version of the OpenTelemetry Semantic Conventions. (#4735)
    • The go.opentelemetry.io/otel/semconv/v1.23.0 package. The package contains semantic conventions from the v1.23.0 version of the OpenTelemetry Semantic Conventions. (#4746)
    • The go.opentelemetry.io/otel/semconv/v1.23.1 package. The package contains semantic conventions from the v1.23.1 version of the OpenTelemetry Semantic Conventions. (#4749)
    • The go.opentelemetry.io/otel/semconv/v1.24.0 package. The package contains semantic conventions from the v1.24.0 version of the OpenTelemetry Semantic Conventions. (#4770)
    • Add WithResourceAsConstantLabels option to apply resource attributes for every metric emitted by the Prometheus exporter. (#4733)
    • Experimental cardinality limiting is added to the metric SDK. See metric documentation for more information about this feature and how to enable it. (#4457)
    • Add NewMemberRaw and NewKeyValuePropertyRaw in go.opentelemetry.io/otel/baggage. (#4804)

    Changed

    • Upgrade all use of go.opentelemetry.io/otel/semconv to use v1.24.0. (#4754)
    • Update transformations in go.opentelemetry.io/otel/exporters/zipkin to follow v1.19.0 version of the OpenTelemetry specification. (#4754)
    • Record synchronous measurements when the passed context is canceled instead of dropping in go.opentelemetry.io/otel/sdk/metric. If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g if ctx.Err() != nil). (#4671)
    • Improve go.opentelemetry.io/otel/trace.TraceState's performance. (#4722)
    • Improve go.opentelemetry.io/otel/propagation.TraceContext's performance. (#4721)
    • Improve go.opentelemetry.io/otel/baggage performance. (#4743)
    • Improve performance of the (*Set).Filter method in go.opentelemetry.io/otel/attribute when the passed filter does not filter out any attributes from the set. (#4774)
    • Member.String in go.opentelemetry.io/otel/baggage percent-encodes only when necessary. (#4775)
    • Improve go.opentelemetry.io/otel/trace.Span's performance when adding multiple attributes. (#4818)
    • Property.Value in go.opentelemetry.io/otel/baggage now returns a raw string instead of a percent-encoded value. (#4804)

    Fixed

    • Fix Parse in go.opentelemetry.io/otel/baggage to validate member value before percent-decoding. (#4755)
    • Fix whitespace encoding of Member.String in go.opentelemetry.io/otel/baggage. (#4756)
    • Fix observable not registered error when the asynchronous instrument has a drop aggregation in go.opentelemetry.io/otel/sdk/metric. (#4772)
    • Fix baggage item key so that it is not canonicalized in go.opentelemetry.io/otel/bridge/opentracing. (#4776)
    • Fix go.opentelemetry.io/otel/bridge/opentracing to properly handle baggage values that requires escaping during propagation. (#4804)
    • Fix a bug where using multiple readers resulted in incorrect asynchronous counter values in go.opentelemetry.io/otel/sdk/metric. (#4742)
    Commits
    • 279c549 Release v1.22.0/v0.45.0 (#4821)
    • 237ed37 Fix link changes from instrumentation to languages (#4828)
    • 5ed29d9 Bump lycheeverse/lychee-action from 1.9.0 to 1.9.1 (#4824)
    • 4491b39 sdk/trace: use slices.Grow() to avoid excessive runtime.growslice() (#4818)
    • 19622d3 chore(docs): explicitly mark lizthegrey emeritus (#4822)
    • 7fa7d1b sdk/metric: Fix observable not registered error when the asynchronous instrum...
    • 01472db Upgrade use of semconv to v1.24.0 (#4754)
    • 259143a baggage: Add NewMemberRaw and NewKeyValuePropertyRaw (#4804)
    • 6ead8d8 Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 in /internal/tools (#4815)
    • deddec3 Optimize (attribute.Set).Filter for no filtered case (#4774)
    • Additional commits viewable in compare view

    Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` from 1.21.0 to 1.22.0
    Changelog

    Sourced from go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp's changelog.

    [1.22.0/0.45.0] 2024-01-17

    Added

    • The go.opentelemetry.io/otel/semconv/v1.22.0 package. The package contains semantic conventions from the v1.22.0 version of the OpenTelemetry Semantic Conventions. (#4735)
    • The go.opentelemetry.io/otel/semconv/v1.23.0 package. The package contains semantic conventions from the v1.23.0 version of the OpenTelemetry Semantic Conventions. (#4746)
    • The go.opentelemetry.io/otel/semconv/v1.23.1 package. The package contains semantic conventions from the v1.23.1 version of the OpenTelemetry Semantic Conventions. (#4749)
    • The go.opentelemetry.io/otel/semconv/v1.24.0 package. The package contains semantic conventions from the v1.24.0 version of the OpenTelemetry Semantic Conventions. (#4770)
    • Add WithResourceAsConstantLabels option to apply resource attributes for every metric emitted by the Prometheus exporter. (#4733)
    • Experimental cardinality limiting is added to the metric SDK. See metric documentation for more information about this feature and how to enable it. (#4457)
    • Add NewMemberRaw and NewKeyValuePropertyRaw in go.opentelemetry.io/otel/baggage. (#4804)

    Changed

    • Upgrade all use of go.opentelemetry.io/otel/semconv to use v1.24.0. (#4754)
    • Update transformations in go.opentelemetry.io/otel/exporters/zipkin to follow v1.19.0 version of the OpenTelemetry specification. (#4754)
    • Record synchronous measurements when the passed context is canceled instead of dropping in go.opentelemetry.io/otel/sdk/metric. If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g if ctx.Err() != nil). (#4671)
    • Improve go.opentelemetry.io/otel/trace.TraceState's performance. (#4722)
    • Improve go.opentelemetry.io/otel/propagation.TraceContext's performance. (#4721)
    • Improve go.opentelemetry.io/otel/baggage performance. (#4743)
    • Improve performance of the (*Set).Filter method in go.opentelemetry.io/otel/attribute when the passed filter does not filter out any attributes from the set. (#4774)
    • Member.String in go.opentelemetry.io/otel/baggage percent-encodes only when necessary. (#4775)
    • Improve go.opentelemetry.io/otel/trace.Span's performance when adding multiple attributes. (#4818)
    • Property.Value in go.opentelemetry.io/otel/baggage now returns a raw string instead of a percent-encoded value. (#4804)

    Fixed

    • Fix Parse in go.opentelemetry.io/otel/baggage to validate member value before percent-decoding. (#4755)
    • Fix whitespace encoding of Member.String in go.opentelemetry.io/otel/baggage. (#4756)
    • Fix observable not registered error when the asynchronous instrument has a drop aggregation in go.opentelemetry.io/otel/sdk/metric. (#4772)
    • Fix baggage item key so that it is not canonicalized in go.opentelemetry.io/otel/bridge/opentracing. (#4776)
    • Fix go.opentelemetry.io/otel/bridge/opentracing to properly handle baggage values that requires escaping during propagation. (#4804)
    • Fix a bug where using multiple readers resulted in incorrect asynchronous counter values in go.opentelemetry.io/otel/sdk/metric. (#4742)
    Commits
    • 279c549 Release v1.22.0/v0.45.0 (#4821)
    • 237ed37 Fix link changes from instrumentation to languages (#4828)
    • 5ed29d9 Bump lycheeverse/lychee-action from 1.9.0 to 1.9.1 (#4824)
    • 4491b39 sdk/trace: use slices.Grow() to avoid excessive runtime.growslice() (#4818)
    • 19622d3 chore(docs): explicitly mark lizthegrey emeritus (#4822)
    • 7fa7d1b sdk/metric: Fix observable not registered error when the asynchronous instrum...
    • 01472db Upgrade use of semconv to v1.24.0 (#4754)
    • 259143a baggage: Add NewMemberRaw and NewKeyValuePropertyRaw (#4804)
    • 6ead8d8 Bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 in /internal/tools (#4815)
    • deddec3 Optimize (attribute.Set).Filter for no filtered case (#4774)
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index c75a4023cb..0291091e53 100644 --- a/go.mod +++ b/go.mod @@ -57,14 +57,14 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.28 go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0 - go.opentelemetry.io/otel v1.21.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 - go.opentelemetry.io/otel/metric v1.21.0 - go.opentelemetry.io/otel/sdk v1.21.0 - go.opentelemetry.io/otel/sdk/metric v1.21.0 - go.opentelemetry.io/otel/trace v1.21.0 + go.opentelemetry.io/otel v1.22.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 + go.opentelemetry.io/otel/metric v1.22.0 + go.opentelemetry.io/otel/sdk v1.22.0 + go.opentelemetry.io/otel/sdk/metric v1.22.0 + go.opentelemetry.io/otel/trace v1.22.0 go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 diff --git a/go.sum b/go.sum index 30c8df771e..bfa5181462 100644 --- a/go.sum +++ b/go.sum @@ -2387,32 +2387,32 @@ go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzox go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0 h1:+RbSCde0ERway5FwKvXR3aRJIFeDu9rtwC6E7BC6uoM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0/go.mod h1:zcI8u2EJxbLPyoZ3SkVAAcQPgYb1TDRzW93xLFnsggU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= -go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= +go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= From c173a1e34710fd33c485e5b8ada10222fd98669f Mon Sep 17 00:00:00 2001 From: ramin Date: Tue, 23 Jan 2024 10:31:55 +0000 Subject: [PATCH 0982/1008] test(gateway): slight refactor of some presentation and add some endpoint registration tests (#2939) goals: readability, test coverage Appreciate that the gateway is v unimportant but is exists. I was starting at the top just looking for places to give a clean up and add some unit test coverage and found this. Mostly trying to make it cleaner to read, then added some tests, also wanted to add a `/status/health` endpoint just for ease, i would want this as a node operator if i was running with `--gateway` for whatever reason Also took liberty to take some error handling code out of `writeError` that would never execute. In trying to write a test to trigger the json.Marshal error case, i realized that as err.Error() always returns a string it could never trigger json.Unmarshal to fail without panic before even getting there, so removed that code path. i might revisit this and give it another refactor at some point soon, unless we're planning to remove this completely? on to next area --- api/gateway/bindings.go | 73 +++++++++++++++++++++ api/gateway/bindings_test.go | 119 +++++++++++++++++++++++++++++++++++ api/gateway/endpoints.go | 32 ---------- api/gateway/health.go | 16 +++++ api/gateway/server.go | 4 ++ api/gateway/util.go | 16 +++-- api/gateway/util_test.go | 24 +++++++ cmd/celestia/cmd_test.go | 4 +- 8 files changed, 245 insertions(+), 43 deletions(-) create mode 100644 api/gateway/bindings.go create mode 100644 api/gateway/bindings_test.go delete mode 100644 api/gateway/endpoints.go create mode 100644 api/gateway/health.go create mode 100644 api/gateway/util_test.go diff --git a/api/gateway/bindings.go b/api/gateway/bindings.go new file mode 100644 index 0000000000..c01bd2da47 --- /dev/null +++ b/api/gateway/bindings.go @@ -0,0 +1,73 @@ +package gateway + +import ( + "fmt" + "net/http" +) + +func (h *Handler) RegisterEndpoints(rpc *Server) { + // state endpoints + rpc.RegisterHandlerFunc( + fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), + h.handleBalanceRequest, + http.MethodGet, + ) + + rpc.RegisterHandlerFunc( + submitTxEndpoint, + h.handleSubmitTx, + http.MethodPost, + ) + + rpc.RegisterHandlerFunc( + healthEndpoint, + h.handleHealthRequest, + http.MethodGet, + ) + + // share endpoints + rpc.RegisterHandlerFunc( + fmt.Sprintf( + "%s/{%s}/height/{%s}", + namespacedSharesEndpoint, + namespaceKey, + heightKey, + ), + h.handleSharesByNamespaceRequest, + http.MethodGet, + ) + + rpc.RegisterHandlerFunc( + fmt.Sprintf("%s/{%s}", namespacedSharesEndpoint, namespaceKey), + h.handleSharesByNamespaceRequest, + http.MethodGet, + ) + + rpc.RegisterHandlerFunc( + fmt.Sprintf("%s/{%s}/height/{%s}", namespacedDataEndpoint, namespaceKey, heightKey), + h.handleDataByNamespaceRequest, + http.MethodGet, + ) + + rpc.RegisterHandlerFunc( + fmt.Sprintf("%s/{%s}", namespacedDataEndpoint, namespaceKey), + h.handleDataByNamespaceRequest, + http.MethodGet, + ) + + // DAS endpoints + rpc.RegisterHandlerFunc( + fmt.Sprintf("%s/{%s}", heightAvailabilityEndpoint, heightKey), + h.handleHeightAvailabilityRequest, + http.MethodGet, + ) + + // header endpoints + rpc.RegisterHandlerFunc( + fmt.Sprintf("%s/{%s}", headerByHeightEndpoint, heightKey), + h.handleHeaderRequest, + http.MethodGet, + ) + + rpc.RegisterHandlerFunc(headEndpoint, h.handleHeadRequest, http.MethodGet) +} diff --git a/api/gateway/bindings_test.go b/api/gateway/bindings_test.go new file mode 100644 index 0000000000..5d27d5e4c7 --- /dev/null +++ b/api/gateway/bindings_test.go @@ -0,0 +1,119 @@ +package gateway + +import ( + "fmt" + "net/http" + "testing" + + "github.com/gorilla/mux" + "github.com/stretchr/testify/require" +) + +func TestRegisterEndpoints(t *testing.T) { + handler := &Handler{} + rpc := NewServer("localhost", "6969") + + handler.RegisterEndpoints(rpc) + + testCases := []struct { + name string + path string + method string + expected bool + }{ + { + name: "Get balance endpoint", + path: fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), + method: http.MethodGet, + expected: true, + }, + { + name: "Submit transaction endpoint", + path: submitTxEndpoint, + method: http.MethodPost, + expected: true, + }, + { + name: "Get namespaced shares by height endpoint", + path: fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, namespaceKey, heightKey), + method: http.MethodGet, + expected: true, + }, + { + name: "Get namespaced shares endpoint", + path: fmt.Sprintf("%s/{%s}", namespacedSharesEndpoint, namespaceKey), + method: http.MethodGet, + expected: true, + }, + { + name: "Get namespaced data by height endpoint", + path: fmt.Sprintf("%s/{%s}/height/{%s}", namespacedDataEndpoint, namespaceKey, heightKey), + method: http.MethodGet, + expected: true, + }, + { + name: "Get namespaced data endpoint", + path: fmt.Sprintf("%s/{%s}", namespacedDataEndpoint, namespaceKey), + method: http.MethodGet, + expected: true, + }, + { + name: "Get health endpoint", + path: "/status/health", + method: http.MethodGet, + expected: true, + }, + + // Going forward, we can add previously deprecated and since + // removed endpoints here to ensure we don't accidentally re-enable + // them in the future and accidentally expand surface area + { + name: "example totally bogus endpoint", + path: fmt.Sprintf("/wutang/{%s}/%s", "chambers", "36"), + method: http.MethodGet, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal( + t, + tc.expected, + hasEndpointRegistered(rpc.Router(), tc.path, tc.method), + "Endpoint registration mismatch for: %s %s %s", tc.name, tc.method, tc.path) + }) + } +} + +func hasEndpointRegistered(router *mux.Router, path string, method string) bool { + var registered bool + err := router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + template, err := route.GetPathTemplate() + if err != nil { + return err + } + + if template == path { + methods, err := route.GetMethods() + if err != nil { + return err + } + + for _, m := range methods { + if m == method { + registered = true + return nil + } + } + } + return nil + }) + + if err != nil { + fmt.Println("Error walking through routes:", err) + return false + } + + return registered +} diff --git a/api/gateway/endpoints.go b/api/gateway/endpoints.go deleted file mode 100644 index 104d01b053..0000000000 --- a/api/gateway/endpoints.go +++ /dev/null @@ -1,32 +0,0 @@ -package gateway - -import ( - "fmt" - "net/http" -) - -func (h *Handler) RegisterEndpoints(rpc *Server) { - // state endpoints - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", balanceEndpoint, addrKey), h.handleBalanceRequest, - http.MethodGet) - rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) - - // share endpoints - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, namespaceKey, heightKey), - h.handleSharesByNamespaceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", namespacedSharesEndpoint, namespaceKey), - h.handleSharesByNamespaceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedDataEndpoint, namespaceKey, heightKey), - h.handleDataByNamespaceRequest, http.MethodGet) - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", namespacedDataEndpoint, namespaceKey), - h.handleDataByNamespaceRequest, http.MethodGet) - - // DAS endpoints - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", heightAvailabilityEndpoint, heightKey), - h.handleHeightAvailabilityRequest, http.MethodGet) - - // header endpoints - rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", headerByHeightEndpoint, heightKey), h.handleHeaderRequest, - http.MethodGet) - rpc.RegisterHandlerFunc(headEndpoint, h.handleHeadRequest, http.MethodGet) -} diff --git a/api/gateway/health.go b/api/gateway/health.go new file mode 100644 index 0000000000..0ddb56bb67 --- /dev/null +++ b/api/gateway/health.go @@ -0,0 +1,16 @@ +package gateway + +import "net/http" + +const ( + healthEndpoint = "/status/health" +) + +func (h *Handler) handleHealthRequest(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte("ok")) + if err != nil { + log.Errorw("serving request", "endpoint", healthEndpoint, "err", err) + writeError(w, http.StatusBadGateway, healthEndpoint, err) + return + } +} diff --git a/api/gateway/server.go b/api/gateway/server.go index 181bfdfe55..7eab7c7bf9 100644 --- a/api/gateway/server.go +++ b/api/gateway/server.go @@ -36,6 +36,10 @@ func NewServer(address, port string) *Server { return server } +func (s *Server) Router() *mux.Router { + return s.srvMux +} + // Start starts the gateway Server, listening on the given address. func (s *Server) Start(context.Context) error { couldStart := s.started.CompareAndSwap(false, true) diff --git a/api/gateway/util.go b/api/gateway/util.go index bffd7ebc88..d3739f9e9c 100644 --- a/api/gateway/util.go +++ b/api/gateway/util.go @@ -1,7 +1,6 @@ package gateway import ( - "encoding/json" "net/http" ) @@ -9,13 +8,12 @@ func writeError(w http.ResponseWriter, statusCode int, endpoint string, err erro log.Debugw("serving request", "endpoint", endpoint, "err", err) w.WriteHeader(statusCode) - errBody, jerr := json.Marshal(err.Error()) - if jerr != nil { - log.Errorw("serializing error", "endpoint", endpoint, "err", jerr) - return - } - _, werr := w.Write(errBody) - if werr != nil { - log.Errorw("writing error response", "endpoint", endpoint, "err", werr) + + errorMessage := err.Error() // Get the error message as a string + errorBytes := []byte(errorMessage) + + _, err = w.Write(errorBytes) + if err != nil { + log.Errorw("writing error response", "endpoint", endpoint, "err", err) } } diff --git a/api/gateway/util_test.go b/api/gateway/util_test.go new file mode 100644 index 0000000000..d41b0918d2 --- /dev/null +++ b/api/gateway/util_test.go @@ -0,0 +1,24 @@ +package gateway + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWriteError(t *testing.T) { + t.Run("writeError", func(t *testing.T) { + // Create a mock HTTP response writer + w := httptest.NewRecorder() + + testErr := errors.New("test error") + + writeError(w, http.StatusInternalServerError, "/api/endpoint", testErr) + assert.Equal(t, http.StatusInternalServerError, w.Code) + responseBody := w.Body.Bytes() + assert.Equal(t, testErr.Error(), string(responseBody)) + }) +} diff --git a/cmd/celestia/cmd_test.go b/cmd/celestia/cmd_test.go index 9c26489e14..94dd3625b8 100644 --- a/cmd/celestia/cmd_test.go +++ b/cmd/celestia/cmd_test.go @@ -33,7 +33,7 @@ func TestCompletionHelpString(t *testing.T) { } methods := reflect.VisibleFields(reflect.TypeOf(TestFields{})) for i, method := range methods { - require.Equal(t, testOutputs[i], parseSignatureForHelpstring(method)) + require.Equal(t, testOutputs[i], parseSignatureForHelpString(method)) } } @@ -129,7 +129,7 @@ func TestBridge(t *testing.T) { */ } -func parseSignatureForHelpstring(methodSig reflect.StructField) string { +func parseSignatureForHelpString(methodSig reflect.StructField) string { simplifiedSignature := "(" in, out := methodSig.Type.NumIn(), methodSig.Type.NumOut() for i := 1; i < in; i++ { From dd18169ec8a0fa088df39998ed47149500197c23 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 23 Jan 2024 13:01:56 +0200 Subject: [PATCH 0983/1008] feat(blob/service): cover getByCommitment with benchmark (#3116) Added the benchmark for `getByCommitment` `BenchmarkGetByCommitment-12 3139 380827 ns/op 701647 B/op 4990 allocs/op` [profile.pdf](https://github.com/celestiaorg/celestia-node/files/13987571/profile.pdf) --- blob/service_test.go | 23 ++++++++++++++++++++++- header/headertest/testing.go | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/blob/service_test.go b/blob/service_test.go index 6777084eb4..3e22f887af 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -414,7 +414,28 @@ func TestService_GetAllWithoutPadding(t *testing.T) { require.NoError(t, err) } -func createService(ctx context.Context, t *testing.T, blobs []*Blob) *Service { +// BenchmarkGetByCommitment-12 3139 380827 ns/op 701647 B/op 4990 allocs/op +func BenchmarkGetByCommitment(b *testing.B) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + b.Cleanup(cancel) + appBlobs, err := blobtest.GenerateV0Blobs([]int{32, 32}, true) + require.NoError(b, err) + + blobs, err := convertBlobs(appBlobs...) + require.NoError(b, err) + + service := createService(ctx, b, blobs) + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.ReportAllocs() + _, _, err = service.getByCommitment( + ctx, 1, blobs[1].Namespace(), blobs[1].Commitment, + ) + require.NoError(b, err) + } +} + +func createService(ctx context.Context, t testing.TB, blobs []*Blob) *Service { bs := ipld.NewMemBlockservice() batching := ds_sync.MutexWrap(ds.NewMapDatastore()) headerStore, err := store.NewStore[*header.ExtendedHeader](batching) diff --git a/header/headertest/testing.go b/header/headertest/testing.go index 9907fd7eb4..e97f7f7825 100644 --- a/header/headertest/testing.go +++ b/header/headertest/testing.go @@ -297,7 +297,7 @@ func RandBlockID(testing.TB) types.BlockID { return bid } -func ExtendedHeaderFromEDS(t *testing.T, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader { +func ExtendedHeaderFromEDS(t testing.TB, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader { valSet, vals := RandValidatorSet(10, 10) gen := RandRawHeader(t) dah, err := share.NewRoot(eds) From ba969f9fb8e52b3ad1fc0831abed71ed5305dcae Mon Sep 17 00:00:00 2001 From: ramin Date: Tue, 23 Jan 2024 14:16:10 +0000 Subject: [PATCH 0984/1008] chore: make CI green (#3114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building off @vgonkivs's [work to skip tests](https://github.com/celestiaorg/celestia-node/pull/2802), this attempts to get a baseline "green" ci for us. There are still a couple of tests that VERY intermittently flake in CI, but this way they will stand out WHEN they happen and we can track down vs entire thing being red always. This does quite a few things - introduces build tags on swamp tests in each named file to allow us to run parts of the swamp/integration tests independently (ie: `go test ./... -tags=blob` - adds an `integration` tag to allow running all still - splits the integration tests into it's own workflow file (`integration-tests.yml`) which is now triggered from `go-ci.yml` - splits each swamp/integration tests to run as its own job so we can see which are failing/flakey more explicitly - utilizes go's -short test flag to Skip a few swamp/integration tests that are consistently failing. Current we are skipping `TestFullReconstructFromFulls`, `TestFullReconstructFromLights`, `TestSyncStartStopLightWithBridge` which is less than we were originally skipping in https://github.com/celestiaorg/celestia-node/pull/2802/ - plugs in our verbose/debug stuff to integration tests in addition to unit which we had before Unit tests - splits some of the unit tests that were "race flakey" into `*_no_race_test.go` and adds `!race` tag to some others to get to pass consistently when running unit tests with -race flag - macos-latest unit tests still fail on race test ONLY in GitHub actions CI 🤷‍♂️ Next Steps - create issues for each short run / skipped - create issues for any tests that fail race that are NOT the race issue on upstream cosmos-sdk - create issue for macos-latest in GitHub race fail - create issue for macos-latest intermittent fail I think we let this run for a while, then once we see it be consistently green for a bit and isolate any more flakes that pop up, we can toggle on more branch requirements, then in fixing the above issues, we can be green. [Being green is not easy](https://www.youtube.com/watch?v=51BQfPeSK8k). --- .github/workflows/go-ci.yml | 29 ++---- .github/workflows/integration-tests.yml | 115 ++++++++++++++++++++++++ Makefile | 27 +++--- core/fetcher_no_race_test.go | 55 ++++++++++++ core/fetcher_test.go | 43 --------- core/listener_no_race_test.go | 71 +++++++++++++++ core/listener_test.go | 59 ------------ nodebuilder/node_test.go | 2 + nodebuilder/store_test.go | 2 + nodebuilder/tests/api_test.go | 20 ++--- nodebuilder/tests/blob_test.go | 2 + nodebuilder/tests/fraud_test.go | 2 + nodebuilder/tests/helpers_test.go | 35 ++++++++ nodebuilder/tests/nd_test.go | 2 + nodebuilder/tests/p2p_test.go | 7 +- nodebuilder/tests/reconstruct_test.go | 13 ++- nodebuilder/tests/sync_test.go | 12 ++- share/eds/retriever_no_race_test.go | 55 ++++++++++++ share/eds/retriever_test.go | 36 -------- share/p2p/discovery/discovery_test.go | 2 + state/core_access_test.go | 2 + 21 files changed, 390 insertions(+), 201 deletions(-) create mode 100644 .github/workflows/integration-tests.yml create mode 100644 core/fetcher_no_race_test.go create mode 100644 core/listener_no_race_test.go create mode 100644 nodebuilder/tests/helpers_test.go create mode 100644 share/eds/retriever_no_race_test.go diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 3e73290500..5da90de33e 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -14,6 +14,7 @@ concurrency: jobs: setup: + name: Setup runs-on: ubuntu-latest outputs: debug: ${{ steps.debug.outputs.debug }} @@ -104,11 +105,10 @@ jobs: file: ./coverage.txt name: coverage-${{ matrix.os }} - unit_race_test: + unit_test_race: needs: [lint, go_mod_tidy_check] - name: Run Unit Tests with Race Detector + name: Unit Tests with Race Detector (ubuntu-latest) runs-on: ubuntu-latest - continue-on-error: true steps: - uses: actions/checkout@v4 @@ -119,24 +119,11 @@ jobs: go-version: ${{ inputs.go-version }} - name: execute test run - run: make test-unit-race + run: make test-unit-race ENABLE_VERBOSE=${{ needs.setup.outputs.debug }} integration_test: + name: Integration Tests needs: [lint, go_mod_tidy_check] - name: Run Integration Tests - runs-on: ubuntu-latest - continue-on-error: true - - steps: - - uses: actions/checkout@v4 - - - name: set up go - uses: actions/setup-go@v5 - with: - go-version: ${{ inputs.go-version }} - - - name: Integration Tests - run: make test-integration - - - name: Integration Tests with Race Detector - run: make test-integration-race + uses: ./.github/workflows/integration-tests.yml + with: + go-version: ${{ inputs.go-version }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000000..cd0fc62385 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,115 @@ +name: Integration Tests + +on: + workflow_call: + inputs: + go-version: + description: 'Go version' + required: true + type: string + +jobs: + api_tests: + name: Integration Tests API + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run API tests + run: make test-integration TAGS=api + + blob_tests: + name: Integration Tests Blob + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run blob tests + run: make test-integration TAGS=blob + + fraud_tests: + name: Integration Tests Fraud + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run fraud tests + run: make test-integration TAGS=fraud + + nd_tests: + name: Integration Tests ND + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run nd tests + run: make test-integration TAGS=nd + + p2p_tests: + name: Integration Tests p2p + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run p2p tests + run: make test-integration TAGS=p2p + + reconstruction_tests: + name: Integration Tests Reconstruction + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run reconstruction tests + run: make test-integration SHORT=true TAGS=reconstruction + + sync_tests: + name: Integration Tests Sync + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run sync tests + run: make test-integration SHORT=true TAGS=sync diff --git a/Makefile b/Makefile index b3b3739cb7..87bcbbf61c 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ PROJECTNAME=$(shell basename "$(PWD)") DIR_FULLPATH=$(shell pwd) versioningPath := "github.com/celestiaorg/celestia-node/nodebuilder/node" LDFLAGS=-ldflags="-X '$(versioningPath).buildTime=$(shell date)' -X '$(versioningPath).lastCommit=$(shell git rev-parse HEAD)' -X '$(versioningPath).semanticVersion=$(shell git describe --tags --dirty=-dev 2>/dev/null || git rev-parse --abbrev-ref HEAD)'" +TAGS=integration +SHORT= ifeq (${PREFIX},) PREFIX := /usr/local endif @@ -13,6 +15,11 @@ else VERBOSE = LOG_AND_FILTER = endif +ifeq ($(SHORT),true) + INTEGRATION_RUN_LENGTH = -short +else + INTEGRATION_RUN_LENGTH = +endif ## help: Get more info on make commands. help: Makefile @echo " Choose a command run in "$(PROJECTNAME)":" @@ -118,29 +125,21 @@ test-unit: ## test-unit-race: Running unit tests with data race detector test-unit-race: @echo "--> Running unit tests with data race detector" - @go test -race `go list ./... | grep -v nodebuilder/tests` + @go test $(VERBOSE) -race -covermode=atomic -coverprofile=coverage.txt `go list ./... | grep -v nodebuilder/tests` $(LOG_AND_FILTER) .PHONY: test-unit-race ## test-integration: Running /integration tests located in nodebuilder/tests test-integration: - @echo "--> Running integrations tests" - @go test ./nodebuilder/tests + @echo "--> Running integrations tests $(VERBOSE) -tags=$(TAGS) $(INTEGRATION_RUN_LENGTH)" + @go test $(VERBOSE) -tags=$(TAGS) $(INTEGRATION_RUN_LENGTH) ./nodebuilder/tests .PHONY: test-integration ## test-integration-race: Running integration tests with data race detector located in node/tests test-integration-race: - @echo "--> Running integration tests with data race detector" - @go test -race ./nodebuilder/tests + @echo "--> Running integration tests with data race detector -tags=$(TAGS)" + @go test -race -tags=$(TAGS) ./nodebuilder/tests .PHONY: test-integration-race -## test: Running both unit and integrations tests -test: - @echo "--> Running all tests without data race detector" - @go test ./... - @echo "--> Running all tests with data race detector" - @go test -race ./... -.PHONY: test - ## benchmark: Running all benchmarks benchmark: @echo "--> Running benchmarks" @@ -164,7 +163,6 @@ pb-gen: done; .PHONY: pb-gen - ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api openrpc-gen: @echo "--> Generating OpenRPC spec" @@ -221,7 +219,6 @@ goreleaser-release: .PHONY: goreleaser-release # Copied from https://github.com/dgraph-io/badger/blob/main/Makefile - USER_ID = $(shell id -u) HAS_JEMALLOC = $(shell test -f /usr/local/lib/libjemalloc.a && echo "jemalloc") JEMALLOC_URL = "https://github.com/jemalloc/jemalloc/releases/download/5.2.1/jemalloc-5.2.1.tar.bz2" diff --git a/core/fetcher_no_race_test.go b/core/fetcher_no_race_test.go new file mode 100644 index 0000000000..890b7c35c1 --- /dev/null +++ b/core/fetcher_no_race_test.go @@ -0,0 +1,55 @@ +//go:build !race + +package core + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/types" +) + +// TestBlockFetcherHeaderValues tests that both the Commit and ValidatorSet +// endpoints are working as intended. +func TestBlockFetcherHeaderValues(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + t.Cleanup(cancel) + + client := StartTestNode(t).Client + fetcher := NewBlockFetcher(client) + + // generate some blocks + newBlockChan, err := fetcher.SubscribeNewBlockEvent(ctx) + require.NoError(t, err) + // read once from channel to generate next block + var h int64 + select { + case evt := <-newBlockChan: + h = evt.Header.Height + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + } + // get Commit from current height + commit, err := fetcher.Commit(ctx, &h) + require.NoError(t, err) + // get ValidatorSet from current height + valSet, err := fetcher.ValidatorSet(ctx, &h) + require.NoError(t, err) + // get next block + var nextBlock types.EventDataSignedBlock + select { + case nextBlock = <-newBlockChan: + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + } + // compare LastCommit from next block to Commit from first block height + assert.Equal(t, nextBlock.Header.LastCommitHash, commit.Hash()) + assert.Equal(t, nextBlock.Header.Height, commit.Height+1) + // compare ValidatorSet hash to the ValidatorsHash from first block height + hexBytes := valSet.Hash() + assert.Equal(t, nextBlock.ValidatorSet.Hash(), hexBytes) + require.NoError(t, fetcher.UnsubscribeNewBlockEvent(ctx)) +} diff --git a/core/fetcher_test.go b/core/fetcher_test.go index 3380dbb402..261b84d78c 100644 --- a/core/fetcher_test.go +++ b/core/fetcher_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/types" ) func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { @@ -38,45 +37,3 @@ func TestBlockFetcher_GetBlock_and_SubscribeNewBlockEvent(t *testing.T) { } require.NoError(t, fetcher.UnsubscribeNewBlockEvent(ctx)) } - -// TestBlockFetcherHeaderValues tests that both the Commit and ValidatorSet -// endpoints are working as intended. -func TestBlockFetcherHeaderValues(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - t.Cleanup(cancel) - - client := StartTestNode(t).Client - fetcher := NewBlockFetcher(client) - - // generate some blocks - newBlockChan, err := fetcher.SubscribeNewBlockEvent(ctx) - require.NoError(t, err) - // read once from channel to generate next block - var h int64 - select { - case evt := <-newBlockChan: - h = evt.Header.Height - case <-ctx.Done(): - require.NoError(t, ctx.Err()) - } - // get Commit from current height - commit, err := fetcher.Commit(ctx, &h) - require.NoError(t, err) - // get ValidatorSet from current height - valSet, err := fetcher.ValidatorSet(ctx, &h) - require.NoError(t, err) - // get next block - var nextBlock types.EventDataSignedBlock - select { - case nextBlock = <-newBlockChan: - case <-ctx.Done(): - require.NoError(t, ctx.Err()) - } - // compare LastCommit from next block to Commit from first block height - assert.Equal(t, nextBlock.Header.LastCommitHash, commit.Hash()) - assert.Equal(t, nextBlock.Header.Height, commit.Height+1) - // compare ValidatorSet hash to the ValidatorsHash from first block height - hexBytes := valSet.Hash() - assert.Equal(t, nextBlock.ValidatorSet.Hash(), hexBytes) - require.NoError(t, fetcher.UnsubscribeNewBlockEvent(ctx)) -} diff --git a/core/listener_no_race_test.go b/core/listener_no_race_test.go new file mode 100644 index 0000000000..eac12785ee --- /dev/null +++ b/core/listener_no_race_test.go @@ -0,0 +1,71 @@ +//go:build !race + +package core + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/share" +) + +// TestListenerWithNonEmptyBlocks ensures that non-empty blocks are actually +// stored to eds.Store. +func TestListenerWithNonEmptyBlocks(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + + // create mocknet with two pubsub endpoints + ps0, _ := createMocknetWithTwoPubsubEndpoints(ctx, t) + + // create one block to store as Head in local store and then unsubscribe from block events + cfg := DefaultTestConfig() + cfg.ChainID = networkID + fetcher, cctx := createCoreFetcher(t, cfg) + eds := createEdsPubSub(ctx, t) + + store := createStore(t) + err := store.Start(ctx) + require.NoError(t, err) + t.Cleanup(func() { + err = store.Stop(ctx) + require.NoError(t, err) + }) + + // create Listener and start listening + cl := createListener(ctx, t, fetcher, ps0, eds, store, networkID) + err = cl.Start(ctx) + require.NoError(t, err) + + // listen for eds hashes broadcasted through eds-sub and ensure store has + // already stored them + sub, err := eds.Subscribe() + require.NoError(t, err) + t.Cleanup(sub.Cancel) + + empty := share.EmptyRoot() + // TODO extract 16 + for i := 0; i < 16; i++ { + _, err := cctx.FillBlock(16, cfg.Accounts, flags.BroadcastBlock) + require.NoError(t, err) + msg, err := sub.Next(ctx) + require.NoError(t, err) + + if bytes.Equal(empty.Hash(), msg.DataHash) { + continue + } + + has, err := store.Has(ctx, msg.DataHash) + require.NoError(t, err) + require.True(t, has) + } + + err = cl.Stop(ctx) + require.NoError(t, err) + require.Nil(t, cl.cancel) +} diff --git a/core/listener_test.go b/core/listener_test.go index 5ddcd5541d..b3ed11e571 100644 --- a/core/listener_test.go +++ b/core/listener_test.go @@ -1,12 +1,10 @@ package core import ( - "bytes" "context" "testing" "time" - "github.com/cosmos/cosmos-sdk/client/flags" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/event" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" @@ -17,7 +15,6 @@ import ( "github.com/celestiaorg/celestia-node/header" nodep2p "github.com/celestiaorg/celestia-node/nodebuilder/p2p" - "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/eds" "github.com/celestiaorg/celestia-node/share/p2p/shrexsub" ) @@ -73,62 +70,6 @@ func TestListener(t *testing.T) { require.Nil(t, cl.cancel) } -// TestListenerWithNonEmptyBlocks ensures that non-empty blocks are actually -// stored to eds.Store. -func TestListenerWithNonEmptyBlocks(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - t.Cleanup(cancel) - - // create mocknet with two pubsub endpoints - ps0, _ := createMocknetWithTwoPubsubEndpoints(ctx, t) - - // create one block to store as Head in local store and then unsubscribe from block events - cfg := DefaultTestConfig() - cfg.ChainID = networkID - fetcher, cctx := createCoreFetcher(t, cfg) - eds := createEdsPubSub(ctx, t) - - store := createStore(t) - err := store.Start(ctx) - require.NoError(t, err) - t.Cleanup(func() { - err = store.Stop(ctx) - require.NoError(t, err) - }) - - // create Listener and start listening - cl := createListener(ctx, t, fetcher, ps0, eds, store, networkID) - err = cl.Start(ctx) - require.NoError(t, err) - - // listen for eds hashes broadcasted through eds-sub and ensure store has - // already stored them - sub, err := eds.Subscribe() - require.NoError(t, err) - t.Cleanup(sub.Cancel) - - empty := share.EmptyRoot() - // TODO extract 16 - for i := 0; i < 16; i++ { - _, err := cctx.FillBlock(16, cfg.Accounts, flags.BroadcastBlock) - require.NoError(t, err) - msg, err := sub.Next(ctx) - require.NoError(t, err) - - if bytes.Equal(empty.Hash(), msg.DataHash) { - continue - } - - has, err := store.Has(ctx, msg.DataHash) - require.NoError(t, err) - require.True(t, has) - } - - err = cl.Stop(ctx) - require.NoError(t, err) - require.Nil(t, cl.cancel) -} - func TestListenerWithWrongChainRPC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) t.Cleanup(cancel) diff --git a/nodebuilder/node_test.go b/nodebuilder/node_test.go index 1d0f3c4fad..41eff32fab 100644 --- a/nodebuilder/node_test.go +++ b/nodebuilder/node_test.go @@ -1,3 +1,5 @@ +//go:build !race + package nodebuilder import ( diff --git a/nodebuilder/store_test.go b/nodebuilder/store_test.go index bd179c1258..51bd89c5a7 100644 --- a/nodebuilder/store_test.go +++ b/nodebuilder/store_test.go @@ -1,3 +1,5 @@ +//go:build !race + package nodebuilder import ( diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 2fd4b2d3da..a793ba7b33 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -1,3 +1,5 @@ +//go:build api || integration + package tests import ( @@ -13,26 +15,14 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/blob" "github.com/celestiaorg/celestia-node/blob/blobtest" - "github.com/celestiaorg/celestia-node/libs/authtoken" "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) -func getAdminClient(ctx context.Context, nd *nodebuilder.Node, t *testing.T) *client.Client { - t.Helper() - - signer := nd.AdminSigner - listenAddr := "ws://" + nd.RPCServer.ListenAddr() - - jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) - require.NoError(t, err) - - client, err := client.NewClient(ctx, listenAddr, jwt) - require.NoError(t, err) - - return client -} +const ( + btime = time.Millisecond * 300 +) func TestNodeModule(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index 627dd850d0..a30e91b2e6 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -1,3 +1,5 @@ +//go:build blob || integration + package tests import ( diff --git a/nodebuilder/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go index cb07dbb73a..6496cdbb53 100644 --- a/nodebuilder/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -1,3 +1,5 @@ +//go:build fraud || integration + package tests import ( diff --git a/nodebuilder/tests/helpers_test.go b/nodebuilder/tests/helpers_test.go new file mode 100644 index 0000000000..1e7f14d823 --- /dev/null +++ b/nodebuilder/tests/helpers_test.go @@ -0,0 +1,35 @@ +//nolint:unused +package tests + +import ( + "context" + "testing" + "time" + + "github.com/celestiaorg/celestia-node/api/rpc/client" + "github.com/celestiaorg/celestia-node/libs/authtoken" + "github.com/celestiaorg/celestia-node/nodebuilder" + + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/stretchr/testify/require" +) + +func getAdminClient(ctx context.Context, nd *nodebuilder.Node, t *testing.T) *client.Client { + t.Helper() + + signer := nd.AdminSigner + listenAddr := "ws://" + nd.RPCServer.ListenAddr() + + jwt, err := authtoken.NewSignedJWT(signer, []auth.Permission{"public", "read", "write", "admin"}) + require.NoError(t, err) + + client, err := client.NewClient(ctx, listenAddr, jwt) + require.NoError(t, err) + + return client +} + +func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { + cfg.P2P.RoutingTableRefreshPeriod = interval + cfg.Share.Discovery.AdvertiseInterval = interval +} diff --git a/nodebuilder/tests/nd_test.go b/nodebuilder/tests/nd_test.go index f3338ec294..338aa6d0c1 100644 --- a/nodebuilder/tests/nd_test.go +++ b/nodebuilder/tests/nd_test.go @@ -1,3 +1,5 @@ +//go:build nd || integration + package tests import ( diff --git a/nodebuilder/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go index 9fe63fb931..98e9fc15b4 100644 --- a/nodebuilder/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -1,3 +1,5 @@ +//go:build p2p || integration + package tests import ( @@ -199,8 +201,3 @@ func TestRestartNodeDiscovery(t *testing.T) { require.NoError(t, err) assert.Equal(t, connectedness, network.Connected) } - -func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { - cfg.P2P.RoutingTableRefreshPeriod = interval - cfg.Share.Discovery.AdvertiseInterval = interval -} diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 89f4a0171a..d047182669 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -1,7 +1,4 @@ -// Test with light nodes spawns more goroutines than in the race detectors budget, -// and thus we're disabling the race detector. -// TODO(@Wondertan): Remove this once we move to go1.19 with unlimited race detector -//go:build !race +//go:build reconstruction || integration package tests @@ -89,6 +86,10 @@ Test-Case: Full Node reconstructs blocks from each other, after unsuccessfully s block from LN subnetworks. Analog to TestShareAvailable_DisconnectedFullNodes. */ func TestFullReconstructFromFulls(t *testing.T) { + if testing.Short() { + t.Skip() + } + light.DefaultSampleAmount = 10 // s const ( blocks = 10 @@ -255,6 +256,10 @@ Steps: 9. Check that the FN can retrieve shares from 1 to 20 blocks */ func TestFullReconstructFromLights(t *testing.T) { + if testing.Short() { + t.Skip() + } + eds.RetrieveQuadrantTimeout = time.Millisecond * 100 light.DefaultSampleAmount = 20 const ( diff --git a/nodebuilder/tests/sync_test.go b/nodebuilder/tests/sync_test.go index 8b939749d6..ec8386ea43 100644 --- a/nodebuilder/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -1,3 +1,5 @@ +//go:build sync || integration + package tests import ( @@ -16,7 +18,7 @@ import ( const ( numBlocks = 20 bsize = 16 - btime = time.Millisecond * 300 + sbtime = time.Millisecond * 300 ) /* @@ -45,7 +47,7 @@ func TestSyncAgainstBridge_NonEmptyChain(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) - sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(sbtime)) // wait for core network to fill 20 blocks fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, bsize, numBlocks) sw.WaitTillHeight(ctx, numBlocks) @@ -138,7 +140,7 @@ func TestSyncAgainstBridge_EmptyChain(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) - sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(sbtime)) sw.WaitTillHeight(ctx, numBlocks) // create bridge node and set it as the bootstrapper for the suite @@ -211,6 +213,10 @@ Steps: 9. Check LN is synced to height 40 */ func TestSyncStartStopLightWithBridge(t *testing.T) { + if testing.Short() { + t.Skip("skipping TestSyncStartStopLightWithBridge test in short mode.") + } + ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) defer cancel() diff --git a/share/eds/retriever_no_race_test.go b/share/eds/retriever_no_race_test.go new file mode 100644 index 0000000000..15c6aa2fc4 --- /dev/null +++ b/share/eds/retriever_no_race_test.go @@ -0,0 +1,55 @@ +// go:build !race + +package eds + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/celestia-app/pkg/wrapper" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/rsmt2d" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/celestia-node/share/eds/byzantine" + "github.com/celestiaorg/celestia-node/share/eds/edstest" + "github.com/celestiaorg/celestia-node/share/ipld" +) + +func TestRetriever_ByzantineError(t *testing.T) { + const width = 8 + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + bserv := ipld.NewMemBlockservice() + shares := edstest.RandEDS(t, width).Flattened() + _, err := ipld.ImportShares(ctx, shares, bserv) + require.NoError(t, err) + + // corrupt shares so that eds erasure coding does not match + copy(shares[14][share.NamespaceSize:], shares[15][share.NamespaceSize:]) + + // import corrupted eds + batchAdder := ipld.NewNmtNodeAdder(ctx, bserv, ipld.MaxSizeBatchOption(width*2)) + attackerEDS, err := rsmt2d.ImportExtendedDataSquare( + shares, + share.DefaultRSMT2DCodec(), + wrapper.NewConstructor(uint64(width), + nmt.NodeVisitor(batchAdder.Visit)), + ) + require.NoError(t, err) + err = batchAdder.Commit() + require.NoError(t, err) + + // ensure we rcv an error + dah, err := da.NewDataAvailabilityHeader(attackerEDS) + require.NoError(t, err) + r := NewRetriever(bserv) + _, err = r.Retrieve(ctx, &dah) + var errByz *byzantine.ErrByzantine + require.ErrorAs(t, err, &errByz) +} diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index f3f7ccca64..95da345d17 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -12,8 +12,6 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" - "github.com/celestiaorg/celestia-app/pkg/wrapper" - "github.com/celestiaorg/nmt" "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/header" @@ -67,40 +65,6 @@ func TestRetriever_Retrieve(t *testing.T) { } } -func TestRetriever_ByzantineError(t *testing.T) { - const width = 8 - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - - bserv := ipld.NewMemBlockservice() - shares := edstest.RandEDS(t, width).Flattened() - _, err := ipld.ImportShares(ctx, shares, bserv) - require.NoError(t, err) - - // corrupt shares so that eds erasure coding does not match - copy(shares[14][share.NamespaceSize:], shares[15][share.NamespaceSize:]) - - // import corrupted eds - batchAdder := ipld.NewNmtNodeAdder(ctx, bserv, ipld.MaxSizeBatchOption(width*2)) - attackerEDS, err := rsmt2d.ImportExtendedDataSquare( - shares, - share.DefaultRSMT2DCodec(), - wrapper.NewConstructor(uint64(width), - nmt.NodeVisitor(batchAdder.Visit)), - ) - require.NoError(t, err) - err = batchAdder.Commit() - require.NoError(t, err) - - // ensure we rcv an error - dah, err := da.NewDataAvailabilityHeader(attackerEDS) - require.NoError(t, err) - r := NewRetriever(bserv) - _, err = r.Retrieve(ctx, &dah) - var errByz *byzantine.ErrByzantine - require.ErrorAs(t, err, &errByz) -} - // TestRetriever_MultipleRandQuadrants asserts that reconstruction succeeds // when any three random quadrants requested. func TestRetriever_MultipleRandQuadrants(t *testing.T) { diff --git a/share/p2p/discovery/discovery_test.go b/share/p2p/discovery/discovery_test.go index c02931e1a4..1d0078196f 100644 --- a/share/p2p/discovery/discovery_test.go +++ b/share/p2p/discovery/discovery_test.go @@ -1,3 +1,5 @@ +// go:build !race + package discovery import ( diff --git a/state/core_access_test.go b/state/core_access_test.go index 68d05a955a..ad7b916ea3 100644 --- a/state/core_access_test.go +++ b/state/core_access_test.go @@ -1,3 +1,5 @@ +//go:build !race + package state import ( From af417b098d4fb29971d18ae8de976cf30d3c4c33 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:52:01 -0600 Subject: [PATCH 0985/1008] fix(libs/pidstore): Detect corruption on startup and reset pidstore (#3132) The recovery of an existing pidstore is not critical to node functionality and should not block the node's start-up if it was corrupted during an ungraceful shutdown. If corruption is detected, a fresh pidstore can be instantiated and used by the node. Found by conduit. --- libs/pidstore/pidstore.go | 20 ++++++++++++++++++++ libs/pidstore/pidstore_test.go | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libs/pidstore/pidstore.go b/libs/pidstore/pidstore.go index 7e416d98a0..17241aa4a9 100644 --- a/libs/pidstore/pidstore.go +++ b/libs/pidstore/pidstore.go @@ -28,6 +28,7 @@ func NewPeerIDStore(ctx context.Context, ds datastore.Datastore) (*PeerIDStore, pidstore := &PeerIDStore{ ds: namespace.Wrap(ds, storePrefix), } + // check if pidstore is already initialized, and if not, // initialize the pidstore exists, err := pidstore.ds.Has(ctx, peersKey) @@ -37,6 +38,14 @@ func NewPeerIDStore(ctx context.Context, ds datastore.Datastore) (*PeerIDStore, if !exists { return pidstore, pidstore.Put(ctx, []peer.ID{}) } + + // if pidstore exists, ensure its contents are uncorrupted + _, err = pidstore.Load(ctx) + if err != nil { + log.Warn("pidstore: corrupted pidstore detected, resetting...", "err", err) + return pidstore, pidstore.reset(ctx) + } + return pidstore, nil } @@ -75,3 +84,14 @@ func (p *PeerIDStore) Put(ctx context.Context, peers []peer.ID) error { log.Infow("Persisted peers successfully", "amount", len(peers)) return nil } + +// reset resets the pidstore in case of corruption. +func (p *PeerIDStore) reset(ctx context.Context) error { + log.Warn("pidstore: resetting the pidstore...") + err := p.ds.Delete(ctx, peersKey) + if err != nil { + return fmt.Errorf("pidstore: error resetting datastore: %w", err) + } + + return p.Put(ctx, []peer.ID{}) +} diff --git a/libs/pidstore/pidstore_test.go b/libs/pidstore/pidstore_test.go index d8d214c83e..56f8a308ec 100644 --- a/libs/pidstore/pidstore_test.go +++ b/libs/pidstore/pidstore_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" @@ -29,6 +30,27 @@ func TestPutLoad(t *testing.T) { }) } +// TestCorruptedPidstore tests whether a pidstore can detect +// corruption and reset itself on construction. +func TestCorruptedPidstore(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer t.Cleanup(cancel) + + ds := sync.MutexWrap(datastore.NewMapDatastore()) + + // intentionally corrupt the store + wrappedDS := namespace.Wrap(ds, storePrefix) + err := wrappedDS.Put(ctx, peersKey, []byte("corrupted")) + require.NoError(t, err) + + pidstore, err := NewPeerIDStore(ctx, ds) + require.NoError(t, err) + + got, err := pidstore.Load(ctx) + require.NoError(t, err) + assert.Equal(t, []peer.ID{}, got) +} + func testPutLoad(ctx context.Context, ds datastore.Datastore, t *testing.T) { peerstore, err := NewPeerIDStore(ctx, ds) require.NoError(t, err) From debad37cdd8db3858651b2c0506f7ef24fe43293 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 23 Jan 2024 17:06:50 -0600 Subject: [PATCH 0986/1008] refactor(blob)!: replace SubmitOptions with a GasPrice parameter (#3094) This PR modifies the blob interface to replace SubmitOptions with GasLimit. The reasoning here is that setting a gas price is a lot more intuitive/easier than calculating the fee and gas limit, which take cosmossdk and celestia-app imports to calculate without much effort. Related: https://github.com/rollkit/go-da/issues/30 Based on a suggestion from @vgonkivs we alias float64 to be able to provide a default value, but now that its there I think we can just have float64 and the same constructor, as it doesn't change that much. Breaks the API, cc @jcstein, @tuxcanfly, and @Ferret-san . Closes #3053. --- blob/service.go | 32 +++++++++++++++++++----- nodebuilder/blob/blob.go | 8 +++--- nodebuilder/blob/cmd/blob.go | 29 +++++++-------------- nodebuilder/blob/mocks/api.go | 5 ++-- nodebuilder/das/mocks/api.go | 3 +-- nodebuilder/fraud/mocks/api.go | 3 +-- nodebuilder/header/mocks/api.go | 3 +-- nodebuilder/node/mocks/api.go | 3 +-- nodebuilder/share/mocks/api.go | 13 +++++----- nodebuilder/state/mocks/api.go | 5 ++-- nodebuilder/tests/api_test.go | 2 +- nodebuilder/tests/blob_test.go | 6 ++--- share/availability/mocks/availability.go | 3 +-- share/mocks/getter.go | 3 +-- 14 files changed, 59 insertions(+), 59 deletions(-) diff --git a/blob/service.go b/blob/service.go index 79e7dd7937..6a70772797 100644 --- a/blob/service.go +++ b/blob/service.go @@ -4,13 +4,17 @@ import ( "context" "errors" "fmt" + "math" "sync" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth/types" logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" + blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/share" @@ -23,11 +27,21 @@ var ( log = logging.Logger("blob") ) +// GasPrice represents the amount to be paid per gas unit. Fee is set by +// multiplying GasPrice by GasLimit, which is determined by the blob sizes. +type GasPrice float64 + +// DefaultGasPrice returns the default gas price, letting node automatically +// determine the Fee based on the passed blob sizes. +func DefaultGasPrice() GasPrice { + return -1.0 +} + // Submitter is an interface that allows submitting blobs to the celestia-core. It is used to // avoid a circular dependency between the blob and the state package, since the state package needs // the blob.Blob type for this signature. type Submitter interface { - SubmitPayForBlob(ctx context.Context, fee math.Int, gasLim uint64, blobs []*Blob) (*types.TxResponse, error) + SubmitPayForBlob(ctx context.Context, fee sdkmath.Int, gasLim uint64, blobs []*Blob) (*types.TxResponse, error) } type Service struct { @@ -66,15 +80,21 @@ func DefaultSubmitOptions() *SubmitOptions { } } -// Submit sends PFB transaction and reports the height in which it was included. +// Submit sends PFB transaction and reports the height at which it was included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. // Handles gas estimation and fee calculation. -func (s *Service) Submit(ctx context.Context, blobs []*Blob, options *SubmitOptions) (uint64, error) { +func (s *Service) Submit(ctx context.Context, blobs []*Blob, gasPrice GasPrice) (uint64, error) { log.Debugw("submitting blobs", "amount", len(blobs)) - if options == nil { - options = DefaultSubmitOptions() + options := DefaultSubmitOptions() + if gasPrice >= 0 { + blobSizes := make([]uint32, len(blobs)) + for i, blob := range blobs { + blobSizes[i] = uint32(len(blob.Data)) + } + options.GasLimit = blobtypes.EstimateGas(blobSizes, appconsts.DefaultGasPerBlobByte, auth.DefaultTxSizeCostPerByte) + options.Fee = types.NewInt(int64(math.Ceil(float64(gasPrice) * float64(options.GasLimit)))).Int64() } resp, err := s.blobSubmitter.SubmitPayForBlob(ctx, types.NewInt(options.Fee), options.GasLimit, blobs) diff --git a/nodebuilder/blob/blob.go b/nodebuilder/blob/blob.go index 47ab255ac6..f87105541a 100644 --- a/nodebuilder/blob/blob.go +++ b/nodebuilder/blob/blob.go @@ -16,7 +16,7 @@ type Module interface { // Submit sends Blobs and reports the height in which they were included. // Allows sending multiple Blobs atomically synchronously. // Uses default wallet registered on the Node. - Submit(_ context.Context, _ []*blob.Blob, _ *blob.SubmitOptions) (height uint64, _ error) + Submit(_ context.Context, _ []*blob.Blob, _ blob.GasPrice) (height uint64, _ error) // Get retrieves the blob by commitment under the given namespace and height. Get(_ context.Context, height uint64, _ share.Namespace, _ blob.Commitment) (*blob.Blob, error) // GetAll returns all blobs at the given height under the given namespaces. @@ -30,7 +30,7 @@ type Module interface { type API struct { Internal struct { - Submit func(context.Context, []*blob.Blob, *blob.SubmitOptions) (uint64, error) `perm:"write"` + Submit func(context.Context, []*blob.Blob, blob.GasPrice) (uint64, error) `perm:"write"` Get func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Blob, error) `perm:"read"` GetAll func(context.Context, uint64, []share.Namespace) ([]*blob.Blob, error) `perm:"read"` GetProof func(context.Context, uint64, share.Namespace, blob.Commitment) (*blob.Proof, error) `perm:"read"` @@ -38,8 +38,8 @@ type API struct { } } -func (api *API) Submit(ctx context.Context, blobs []*blob.Blob, options *blob.SubmitOptions) (uint64, error) { - return api.Internal.Submit(ctx, blobs, options) +func (api *API) Submit(ctx context.Context, blobs []*blob.Blob, gasPrice blob.GasPrice) (uint64, error) { + return api.Internal.Submit(ctx, blobs, gasPrice) } func (api *API) Get( diff --git a/nodebuilder/blob/cmd/blob.go b/nodebuilder/blob/cmd/blob.go index f3510edfe1..7862eaa204 100644 --- a/nodebuilder/blob/cmd/blob.go +++ b/nodebuilder/blob/cmd/blob.go @@ -16,8 +16,7 @@ import ( var ( base64Flag bool - fee int64 - gasLimit uint64 + gasPrice float64 ) func init() { @@ -37,23 +36,13 @@ func init() { "printed blob's data as a base64 string", ) - submitCmd.PersistentFlags().Int64Var( - &fee, - "fee", - -1, - "specifies fee (in utia) for blob submission.\n"+ - "Fee will be automatically calculated if negative value is passed [optional]", + submitCmd.PersistentFlags().Float64Var( + &gasPrice, + "gas.price", + float64(blob.DefaultGasPrice()), + "specifies gas price (in utia) for blob submission.\n"+ + "Gas price will be set to default (0.002) if no value is passed", ) - - submitCmd.PersistentFlags().Uint64Var( - &gasLimit, - "gas.limit", - 0, - "sets the amount of gas that is consumed during blob submission [optional]", - ) - - // unset the default value to avoid users confusion - submitCmd.PersistentFlags().Lookup("fee").DefValue = "0" } var Cmd = &cobra.Command{ @@ -135,7 +124,7 @@ var submitCmd = &cobra.Command{ Short: "Submit the blob at the given namespace.\n" + "Note:\n" + "* only one blob is allowed to submit through the RPC.\n" + - "* fee and gas.limit params will be calculated automatically if they are not provided as arguments", + "* fee and gas limit params will be calculated automatically.\n", RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) if err != nil { @@ -156,7 +145,7 @@ var submitCmd = &cobra.Command{ height, err := client.Blob.Submit( cmd.Context(), []*blob.Blob{parsedBlob}, - &blob.SubmitOptions{Fee: fee, GasLimit: gasLimit}, + blob.GasPrice(gasPrice), ) response := struct { diff --git a/nodebuilder/blob/mocks/api.go b/nodebuilder/blob/mocks/api.go index 40bf299da1..0898e70459 100644 --- a/nodebuilder/blob/mocks/api.go +++ b/nodebuilder/blob/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - blob "github.com/celestiaorg/celestia-node/blob" share "github.com/celestiaorg/celestia-node/share" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -98,7 +97,7 @@ func (mr *MockModuleMockRecorder) Included(arg0, arg1, arg2, arg3, arg4 interfac } // Submit mocks base method. -func (m *MockModule) Submit(arg0 context.Context, arg1 []*blob.Blob, arg2 *blob.SubmitOptions) (uint64, error) { +func (m *MockModule) Submit(arg0 context.Context, arg1 []*blob.Blob, arg2 blob.GasPrice) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Submit", arg0, arg1, arg2) ret0, _ := ret[0].(uint64) diff --git a/nodebuilder/das/mocks/api.go b/nodebuilder/das/mocks/api.go index 68ffaf3c8c..c4046e90e8 100644 --- a/nodebuilder/das/mocks/api.go +++ b/nodebuilder/das/mocks/api.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - das "github.com/celestiaorg/celestia-node/das" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/fraud/mocks/api.go b/nodebuilder/fraud/mocks/api.go index 10111b81a8..fcc7a58231 100644 --- a/nodebuilder/fraud/mocks/api.go +++ b/nodebuilder/fraud/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - fraud "github.com/celestiaorg/celestia-node/nodebuilder/fraud" fraud0 "github.com/celestiaorg/go-fraud" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/header/mocks/api.go b/nodebuilder/header/mocks/api.go index 9b15f2242e..b0d2b961d9 100644 --- a/nodebuilder/header/mocks/api.go +++ b/nodebuilder/header/mocks/api.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" header0 "github.com/celestiaorg/go-header" sync "github.com/celestiaorg/go-header/sync" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/node/mocks/api.go b/nodebuilder/node/mocks/api.go index 14357316dc..d8789a771c 100644 --- a/nodebuilder/node/mocks/api.go +++ b/nodebuilder/node/mocks/api.go @@ -8,10 +8,9 @@ import ( context "context" reflect "reflect" + node "github.com/celestiaorg/celestia-node/nodebuilder/node" auth "github.com/filecoin-project/go-jsonrpc/auth" gomock "github.com/golang/mock/gomock" - - node "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/share/mocks/api.go b/nodebuilder/share/mocks/api.go index 87f98e4f73..4e21cecae0 100644 --- a/nodebuilder/share/mocks/api.go +++ b/nodebuilder/share/mocks/api.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - - da "github.com/celestiaorg/celestia-app/pkg/da" + header "github.com/celestiaorg/celestia-node/header" share "github.com/celestiaorg/celestia-node/share" rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockModule is a mock of Module interface. @@ -39,7 +38,7 @@ func (m *MockModule) EXPECT() *MockModuleMockRecorder { } // GetEDS mocks base method. -func (m *MockModule) GetEDS(arg0 context.Context, arg1 *da.DataAvailabilityHeader) (*rsmt2d.ExtendedDataSquare, error) { +func (m *MockModule) GetEDS(arg0 context.Context, arg1 *header.ExtendedHeader) (*rsmt2d.ExtendedDataSquare, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEDS", arg0, arg1) ret0, _ := ret[0].(*rsmt2d.ExtendedDataSquare) @@ -54,7 +53,7 @@ func (mr *MockModuleMockRecorder) GetEDS(arg0, arg1 interface{}) *gomock.Call { } // GetShare mocks base method. -func (m *MockModule) GetShare(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2, arg3 int) ([]byte, error) { +func (m *MockModule) GetShare(arg0 context.Context, arg1 *header.ExtendedHeader, arg2, arg3 int) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetShare", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([]byte) @@ -69,7 +68,7 @@ func (mr *MockModuleMockRecorder) GetShare(arg0, arg1, arg2, arg3 interface{}) * } // GetSharesByNamespace mocks base method. -func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *da.DataAvailabilityHeader, arg2 share.Namespace) (share.NamespacedShares, error) { +func (m *MockModule) GetSharesByNamespace(arg0 context.Context, arg1 *header.ExtendedHeader, arg2 share.Namespace) (share.NamespacedShares, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSharesByNamespace", arg0, arg1, arg2) ret0, _ := ret[0].(share.NamespacedShares) @@ -84,7 +83,7 @@ func (mr *MockModuleMockRecorder) GetSharesByNamespace(arg0, arg1, arg2 interfac } // SharesAvailable mocks base method. -func (m *MockModule) SharesAvailable(arg0 context.Context, arg1 *da.DataAvailabilityHeader) error { +func (m *MockModule) SharesAvailable(arg0 context.Context, arg1 *header.ExtendedHeader) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SharesAvailable", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/nodebuilder/state/mocks/api.go b/nodebuilder/state/mocks/api.go index 6499a6dfd8..1861a86e66 100644 --- a/nodebuilder/state/mocks/api.go +++ b/nodebuilder/state/mocks/api.go @@ -9,13 +9,12 @@ import ( reflect "reflect" math "cosmossdk.io/math" + blob "github.com/celestiaorg/celestia-node/blob" + state "github.com/celestiaorg/celestia-node/state" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" types1 "github.com/tendermint/tendermint/types" - - blob "github.com/celestiaorg/celestia-node/blob" - state "github.com/celestiaorg/celestia-node/state" ) // MockModule is a mock of Module interface. diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index a793ba7b33..960ce38875 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -113,7 +113,7 @@ func TestBlobRPC(t *testing.T) { ) require.NoError(t, err) - height, err := rpcClient.Blob.Submit(ctx, []*blob.Blob{newBlob}, nil) + height, err := rpcClient.Blob.Submit(ctx, []*blob.Blob{newBlob}, blob.DefaultGasPrice()) require.NoError(t, err) require.True(t, height != 0) } diff --git a/nodebuilder/tests/blob_test.go b/nodebuilder/tests/blob_test.go index a30e91b2e6..d0aeefd568 100644 --- a/nodebuilder/tests/blob_test.go +++ b/nodebuilder/tests/blob_test.go @@ -60,7 +60,7 @@ func TestBlobModule(t *testing.T) { fullClient := getAdminClient(ctx, fullNode, t) lightClient := getAdminClient(ctx, lightNode, t) - height, err := fullClient.Blob.Submit(ctx, blobs, nil) + height, err := fullClient.Blob.Submit(ctx, blobs, blob.DefaultGasPrice()) require.NoError(t, err) _, err = fullClient.Header.WaitForHeight(ctx, height) @@ -143,7 +143,7 @@ func TestBlobModule(t *testing.T) { ) require.NoError(t, err) - height, err := fullClient.Blob.Submit(ctx, []*blob.Blob{b, b}, nil) + height, err := fullClient.Blob.Submit(ctx, []*blob.Blob{b, b}, blob.DefaultGasPrice()) require.NoError(t, err) _, err = fullClient.Header.WaitForHeight(ctx, height) @@ -172,7 +172,7 @@ func TestBlobModule(t *testing.T) { // different pfbs. name: "Submit the same blob in different pfb", doFn: func(t *testing.T) { - h, err := fullClient.Blob.Submit(ctx, []*blob.Blob{blobs[0]}, nil) + h, err := fullClient.Blob.Submit(ctx, []*blob.Blob{blobs[0]}, blob.DefaultGasPrice()) require.NoError(t, err) _, err = fullClient.Header.WaitForHeight(ctx, h) diff --git a/share/availability/mocks/availability.go b/share/availability/mocks/availability.go index bb1c01bf85..fc68d3d2bc 100644 --- a/share/availability/mocks/availability.go +++ b/share/availability/mocks/availability.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" + gomock "github.com/golang/mock/gomock" ) // MockAvailability is a mock of Availability interface. diff --git a/share/mocks/getter.go b/share/mocks/getter.go index 2adfa50cfe..738e2b246c 100644 --- a/share/mocks/getter.go +++ b/share/mocks/getter.go @@ -8,11 +8,10 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - header "github.com/celestiaorg/celestia-node/header" share "github.com/celestiaorg/celestia-node/share" rsmt2d "github.com/celestiaorg/rsmt2d" + gomock "github.com/golang/mock/gomock" ) // MockGetter is a mock of Getter interface. From 4850c901675696095e2df4af234b5421a270e680 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 29 Jan 2024 02:25:51 -0600 Subject: [PATCH 0987/1008] feat!(rpc): rpc.disable-auth flag (#3117) In many setups, nodes are running in trusted environments and setting the jwt token is not necessary and very cumbersome. This PR enables a quick fix by providing a flag/config option to the RPC to disable auth. Breaking config. New flag: `--rpc.skip-auth`. New default config: ```toml [RPC] Address = "localhost" Port = "26658" SkipAuth = false ``` _______________ Comes from a discussion with @joroshiba at Astria. --- api/rpc/server.go | 15 ++++++++++----- nodebuilder/rpc/config.go | 8 +++++--- nodebuilder/rpc/constructors.go | 2 +- nodebuilder/rpc/flags.go | 16 ++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/api/rpc/server.go b/api/rpc/server.go index 3357140e68..3bf58870bc 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -20,16 +20,17 @@ import ( var log = logging.Logger("rpc") type Server struct { - srv *http.Server - rpc *jsonrpc.RPCServer - listener net.Listener + srv *http.Server + rpc *jsonrpc.RPCServer + listener net.Listener + authDisabled bool started atomic.Bool auth jwt.Signer } -func NewServer(address, port string, secret jwt.Signer) *Server { +func NewServer(address, port string, authDisabled bool, secret jwt.Signer) *Server { rpc := jsonrpc.NewServer() srv := &Server{ rpc: rpc, @@ -38,7 +39,8 @@ func NewServer(address, port string, secret jwt.Signer) *Server { // the amount of time allowed to read request headers. set to the default 2 seconds ReadHeaderTimeout: 2 * time.Second, }, - auth: secret, + auth: secret, + authDisabled: authDisabled, } srv.srv.Handler = &auth.Handler{ Verify: srv.verifyAuth, @@ -51,6 +53,9 @@ func NewServer(address, port string, secret jwt.Signer) *Server { // reached if a token is provided in the header of the request, otherwise only // methods with `read` permissions are accessible. func (s *Server) verifyAuth(_ context.Context, token string) ([]auth.Permission, error) { + if s.authDisabled { + return perms.AllPerms, nil + } return authtoken.ExtractSignedPermissions(s.auth, token) } diff --git a/nodebuilder/rpc/config.go b/nodebuilder/rpc/config.go index a270768646..d6031082a8 100644 --- a/nodebuilder/rpc/config.go +++ b/nodebuilder/rpc/config.go @@ -8,15 +8,17 @@ import ( ) type Config struct { - Address string - Port string + Address string + Port string + SkipAuth bool } func DefaultConfig() Config { return Config{ Address: defaultBindAddress, // do NOT expose the same port as celestia-core by default so that both can run on the same machine - Port: defaultPort, + Port: defaultPort, + SkipAuth: false, } } diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index ca30af6305..194dea8a03 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -37,5 +37,5 @@ func registerEndpoints( } func server(cfg *Config, auth jwt.Signer) *rpc.Server { - return rpc.NewServer(cfg.Address, cfg.Port, auth) + return rpc.NewServer(cfg.Address, cfg.Port, cfg.SkipAuth, auth) } diff --git a/nodebuilder/rpc/flags.go b/nodebuilder/rpc/flags.go index b7bad333df..d37014004d 100644 --- a/nodebuilder/rpc/flags.go +++ b/nodebuilder/rpc/flags.go @@ -3,13 +3,16 @@ package rpc import ( "fmt" + logging "github.com/ipfs/go-log/v2" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) var ( + log = logging.Logger("rpc") addrFlag = "rpc.addr" portFlag = "rpc.port" + authFlag = "rpc.skip-auth" ) // Flags gives a set of hardcoded node/rpc package flags. @@ -26,6 +29,11 @@ func Flags() *flag.FlagSet { "", fmt.Sprintf("Set a custom RPC port (default: %s)", defaultPort), ) + flags.Bool( + authFlag, + false, + "Skips authentication for RPC requests", + ) return flags } @@ -40,4 +48,12 @@ func ParseFlags(cmd *cobra.Command, cfg *Config) { if port != "" { cfg.Port = port } + ok, err := cmd.Flags().GetBool(authFlag) + if err != nil { + panic(err) + } + if ok { + log.Warn("RPC authentication is disabled") + cfg.SkipAuth = true + } } From 9484e15125d43cffedac5f284cfbd7397a5bb59c Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 29 Jan 2024 06:58:00 -0600 Subject: [PATCH 0988/1008] feat(rpc/node): readiness check (#3118) Implements a readiness check on the node module. Question: I have set the permission level to public, but remember that we removed all public methods in favor of `read`. Is this fine or should this method also be `read`? ____________ Comes from a conversation with @joroshiba from Astria --------- Co-authored-by: ramin --- nodebuilder/module.go | 2 +- nodebuilder/node/admin.go | 8 ++++++++ nodebuilder/node/node.go | 8 ++++++++ nodebuilder/tests/api_test.go | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 26dc1d9e33..f9948b2011 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -50,7 +50,6 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store state.ConstructModule(tp, &cfg.State, &cfg.Core), modhead.ConstructModule[*header.ExtendedHeader](tp, &cfg.Header), share.ConstructModule(tp, &cfg.Share), - rpc.ConstructModule(tp, &cfg.RPC), gateway.ConstructModule(tp, &cfg.Gateway), core.ConstructModule(tp, &cfg.Core), das.ConstructModule(tp, &cfg.DASer), @@ -58,6 +57,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store blob.ConstructModule(), node.ConstructModule(tp), prune.ConstructModule(tp), + rpc.ConstructModule(tp, &cfg.RPC), ) return fx.Module( diff --git a/nodebuilder/node/admin.go b/nodebuilder/node/admin.go index 8063835f1d..f71af66081 100644 --- a/nodebuilder/node/admin.go +++ b/nodebuilder/node/admin.go @@ -38,6 +38,14 @@ func (m *module) Info(context.Context) (Info, error) { }, nil } +func (m *module) Ready(context.Context) (bool, error) { + // Because the node uses FX to provide the RPC last, all services' lifecycles have been started by + // the point this endpoint is available. It is not 100% guaranteed at this point that all services + // are fully ready, but it is very high probability and all endpoints are available at this point + // regardless. + return true, nil +} + func (m *module) LogLevelSet(_ context.Context, name, level string) error { return logging.SetLogLevel(name, level) } diff --git a/nodebuilder/node/node.go b/nodebuilder/node/node.go index 18ce93615b..b2bc7dac31 100644 --- a/nodebuilder/node/node.go +++ b/nodebuilder/node/node.go @@ -14,6 +14,9 @@ type Module interface { // Info returns administrative information about the node. Info(context.Context) (Info, error) + // Ready returns true once the node's RPC is ready to accept requests. + Ready(context.Context) (bool, error) + // LogLevelSet sets the given component log level to the given level. LogLevelSet(ctx context.Context, name, level string) error @@ -28,6 +31,7 @@ var _ Module = (*API)(nil) type API struct { Internal struct { Info func(context.Context) (Info, error) `perm:"admin"` + Ready func(context.Context) (bool, error) `perm:"read"` LogLevelSet func(ctx context.Context, name, level string) error `perm:"admin"` AuthVerify func(ctx context.Context, token string) ([]auth.Permission, error) `perm:"admin"` AuthNew func(ctx context.Context, perms []auth.Permission) (string, error) `perm:"admin"` @@ -38,6 +42,10 @@ func (api *API) Info(ctx context.Context) (Info, error) { return api.Internal.Info(ctx) } +func (api *API) Ready(ctx context.Context) (bool, error) { + return api.Internal.Ready(ctx) +} + func (api *API) LogLevelSet(ctx context.Context, name, level string) error { return api.Internal.LogLevelSet(ctx, name, level) } diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 960ce38875..751eaa63e5 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -48,6 +48,10 @@ func TestNodeModule(t *testing.T) { require.NoError(t, err) require.Equal(t, info.APIVersion, node.APIVersion) + ready, err := client.Node.Ready(ctx) + require.NoError(t, err) + require.True(t, ready) + perms, err := client.Node.AuthVerify(ctx, jwt) require.NoError(t, err) require.Equal(t, perms, adminPerms) From 1bae0600307a64bc0a94279ca50eab08ab9770b6 Mon Sep 17 00:00:00 2001 From: ramin Date: Mon, 29 Jan 2024 14:01:11 +0000 Subject: [PATCH 0989/1008] chore(spelling): fix all stray typos across comments and tests (#3142) Used a tool to find stray spellings then fix them, hopefully this will prevent future small PRs for little fixes emerging and we can encourage more significant / valuable PRs from external contributors --- libs/pidstore/pidstore_test.go | 2 +- nodebuilder/p2p/cmd/p2p.go | 2 +- nodebuilder/tests/api_test.go | 2 +- nodebuilder/tests/swamp/swamp.go | 4 ++-- share/getters/shrex_test.go | 4 ++-- share/ipld/corrupted_data_test.go | 8 ++++---- share/p2p/peers/manager.go | 2 +- state/core_access.go | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libs/pidstore/pidstore_test.go b/libs/pidstore/pidstore_test.go index 56f8a308ec..4a35783db3 100644 --- a/libs/pidstore/pidstore_test.go +++ b/libs/pidstore/pidstore_test.go @@ -22,7 +22,7 @@ func TestPutLoad(t *testing.T) { ds := sync.MutexWrap(datastore.NewMapDatastore()) - t.Run("unitialized-pidstore", func(t *testing.T) { + t.Run("uninitialized-pidstore", func(t *testing.T) { testPutLoad(ctx, ds, t) }) t.Run("initialized-pidstore", func(t *testing.T) { diff --git a/nodebuilder/p2p/cmd/p2p.go b/nodebuilder/p2p/cmd/p2p.go index 5951595fa4..8b44802947 100644 --- a/nodebuilder/p2p/cmd/p2p.go +++ b/nodebuilder/p2p/cmd/p2p.go @@ -224,7 +224,7 @@ var connectednessCmd = &cobra.Command{ var natStatusCmd = &cobra.Command{ Use: "nat-status", - Short: "Gets the currrent NAT status", + Short: "Gets the current NAT status", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) diff --git a/nodebuilder/tests/api_test.go b/nodebuilder/tests/api_test.go index 751eaa63e5..a3b99a750b 100644 --- a/nodebuilder/tests/api_test.go +++ b/nodebuilder/tests/api_test.go @@ -93,7 +93,7 @@ func TestGetByHeight(t *testing.T) { require.ErrorContains(t, err, "given height is from the future") } -// TestBlobRPC ensures that blobs can be submited via rpc +// TestBlobRPC ensures that blobs can be submitted via rpc func TestBlobRPC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout) t.Cleanup(cancel) diff --git a/nodebuilder/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go index 617fe76151..9faf69744d 100644 --- a/nodebuilder/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -39,7 +39,7 @@ import ( var blackholeIP6 = net.ParseIP("100::") -// DefaultTestTimeout should be used as the default timout on all the Swamp tests. +// DefaultTestTimeout should be used as the default timeout on all the Swamp tests. // It's generously set to 5 minutes to give enough time for CI. const DefaultTestTimeout = time.Minute * 5 @@ -331,7 +331,7 @@ func (s *Swamp) Disconnect(t *testing.T, peerA, peerB *nodebuilder.Node) { // SetBootstrapper sets the given bootstrappers as the "bootstrappers" for the // Swamp test suite. Every new full or light node created on the suite afterwards // will automatically add the suite's bootstrappers as trusted peers to their config. -// NOTE: Bridge nodes do not automaatically add the bootstrappers as trusted peers. +// NOTE: Bridge nodes do not automatically add the bootstrappers as trusted peers. // NOTE: Use `NewNodeWithStore` to avoid this automatic configuration. func (s *Swamp) SetBootstrapper(t *testing.T, bootstrappers ...*nodebuilder.Node) { for _, trusted := range bootstrappers { diff --git a/share/getters/shrex_test.go b/share/getters/shrex_test.go index b625bb4c10..075735579b 100644 --- a/share/getters/shrex_test.go +++ b/share/getters/shrex_test.go @@ -121,7 +121,7 @@ func TestShrexGetter(t *testing.T) { t.Cleanup(cancel) // generate test data - eds, dah, maxNamesapce := generateTestEDS(t) + eds, dah, maxNamespace := generateTestEDS(t) eh := headertest.RandExtendedHeaderWithRoot(t, dah) require.NoError(t, edsStore.Put(ctx, dah.Hash(), eds)) peerManager.Validate(ctx, srvHost.ID(), shrexsub.Notification{ @@ -129,7 +129,7 @@ func TestShrexGetter(t *testing.T) { Height: 1, }) - namespace, err := addToNamespace(maxNamesapce, 1) + namespace, err := addToNamespace(maxNamespace, 1) require.NoError(t, err) // check for namespace to be not in root require.Len(t, ipld.FilterRootByNamespace(dah, namespace), 0) diff --git a/share/ipld/corrupted_data_test.go b/share/ipld/corrupted_data_test.go index d1d6e6b4d5..0d0af6dd35 100644 --- a/share/ipld/corrupted_data_test.go +++ b/share/ipld/corrupted_data_test.go @@ -25,7 +25,7 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { t.Cleanup(cancel) net := availability_test.NewTestDAGNet(ctx, t) - requestor := full.Node(net) + requester := full.Node(net) provider, mockBS := availability_test.MockNode(t, net) provider.Availability = full.TestAvailability(t, getters.NewIPLDGetter(provider.BlockService)) net.ConnectAll() @@ -37,15 +37,15 @@ func TestNamespaceHasher_CorruptedData(t *testing.T) { eh := headertest.RandExtendedHeaderWithRoot(t, root) getCtx, cancelGet := context.WithTimeout(ctx, sharesAvailableTimeout) t.Cleanup(cancelGet) - err := requestor.SharesAvailable(getCtx, eh) + err := requester.SharesAvailable(getCtx, eh) require.NoError(t, err) // clear the storage of the requester so that it must retrieve again, then start attacking // we reinitialize the node to clear the eds store - requestor = full.Node(net) + requester = full.Node(net) mockBS.Attacking = true getCtx, cancelGet = context.WithTimeout(ctx, sharesAvailableTimeout) t.Cleanup(cancelGet) - err = requestor.SharesAvailable(getCtx, eh) + err = requester.SharesAvailable(getCtx, eh) require.ErrorIs(t, err, share.ErrNotAvailable) } diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 23fa18bcb2..964b022249 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -303,7 +303,7 @@ func (m *Manager) subscribeHeader(ctx context.Context, headerSub libhead.Subscri log.Debugw("stored initial height", "height", h.Height()) } - // update storeFrom if header heigh + // update storeFrom if header height m.storeFrom.Store(uint64(max(0, int(h.Height())-storedPoolsAmount))) log.Debugw("updated lowest stored height", "height", h.Height()) } diff --git a/state/core_access.go b/state/core_access.go index 358457b4f0..c3fbd4836a 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -181,7 +181,7 @@ func (ca *CoreAccessor) constructSignedTx( } // SubmitPayForBlob builds, signs, and synchronously submits a MsgPayForBlob. It blocks until the -// transaction is committed and returns the TxReponse. If gasLim is set to 0, the method will +// transaction is committed and returns the TxResponse. If gasLim is set to 0, the method will // automatically estimate the gas limit. If the fee is negative, the method will use the nodes min // gas price multiplied by the gas limit. func (ca *CoreAccessor) SubmitPayForBlob( From cc087accfdd4420a892f20d6af8fe8d889f56972 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:15:23 +0000 Subject: [PATCH 0990/1008] chore(deps): Bump github.com/multiformats/go-multiaddr from 0.12.1 to 0.12.2 (#3144) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/multiformats/go-multiaddr](https://github.com/multiformats/go-multiaddr) from 0.12.1 to 0.12.2.
    Release notes

    Sourced from github.com/multiformats/go-multiaddr's releases.

    v0.12.2

    What's Changed

    New Contributors

    Full Changelog: https://github.com/multiformats/go-multiaddr/compare/v0.12.1...v0.12.2

    Commits
    • e2ad674 chore: release v0.12.2
    • 77f3edf tests: add round trip equality check to fuzz (#232)
    • ac5c928 fix: correctly parse ports as uint16 and explicitely fail on overflows (#228)
    • 45314d9 replace custom random tests with testing.F (#227)
    • See full diff in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/multiformats/go-multiaddr&package-manager=go_modules&previous-version=0.12.1&new-version=0.12.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0291091e53..66d12f6780 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.7.3 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.12.1 + github.com/multiformats/go-multiaddr v0.12.2 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 diff --git a/go.sum b/go.sum index bfa5181462..d2fcb82e20 100644 --- a/go.sum +++ b/go.sum @@ -1831,8 +1831,8 @@ github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= -github.com/multiformats/go-multiaddr v0.12.1 h1:vm+BA/WZA8QZDp1pF1FWhi5CT3g1tbi5GJmqpb6wnlk= -github.com/multiformats/go-multiaddr v0.12.1/go.mod h1:7mPkiBMmLeFipt+nNSq9pHZUeJSt8lHBgH6yhj0YQzE= +github.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24= +github.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= From b57ad60e6eb34fbafd9a28594e614a2b06fc1691 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:35:52 +0000 Subject: [PATCH 0991/1008] chore(deps): Bump alpine from 3.19.0 to 3.19.1 (#3143) Bumps alpine from 3.19.0 to 3.19.1. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.19.0&new-version=3.19.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e15d7d4f1c..01fccafe2e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN uname -a &&\ CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ make build && make cel-key -FROM docker.io/alpine:3.19.0 +FROM docker.io/alpine:3.19.1 # Read here why UID 10001: https://github.com/hexops/dockerfile/blob/main/README.md#do-not-use-a-uid-below-10000 ARG UID=10001 From f235d1056d0f76e3c55697ebfab41cf7ce0eb0e5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 30 Jan 2024 14:22:00 +0200 Subject: [PATCH 0992/1008] feat(blob/trace): cover blob service with traces (#3113) Cover Blob service with Otel traces. Partly fixes #3112 --- blob/service.go | 50 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/blob/service.go b/blob/service.go index 6a70772797..fc1d630e62 100644 --- a/blob/service.go +++ b/blob/service.go @@ -11,12 +11,17 @@ import ( "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/types" logging "github.com/ipfs/go-log/v2" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/pkg/shares" blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/utils" "github.com/celestiaorg/celestia-node/share" ) @@ -24,7 +29,8 @@ var ( ErrBlobNotFound = errors.New("blob: not found") ErrInvalidProof = errors.New("blob: invalid proof") - log = logging.Logger("blob") + log = logging.Logger("blob") + tracer = otel.Tracer("blob/service") ) // GasPrice represents the amount to be paid per gas unit. Fee is set by @@ -185,7 +191,11 @@ func (s *Service) Included( namespace share.Namespace, proof *Proof, com Commitment, -) (bool, error) { +) (_ bool, err error) { + ctx, span := tracer.Start(ctx, "included") + defer func() { + utils.SetStatusAndEnd(span, err) + }() // In the current implementation, LNs will have to download all shares to recompute the commitment. // To achieve 1. we need to modify Proof structure and to store all subtree roots, that were // involved in commitment creation and then call `merkle.HashFromByteSlices`(tendermint package). @@ -213,24 +223,47 @@ func (s *Service) getByCommitment( height uint64, namespace share.Namespace, commitment Commitment, -) (*Blob, *Proof, error) { +) (_ *Blob, _ *Proof, err error) { log.Infow("requesting blob", "height", height, "namespace", namespace.String()) - header, err := s.headerGetter(ctx, height) + ctx, span := tracer.Start(ctx, "get-by-commitment") + defer func() { + utils.SetStatusAndEnd(span, err) + }() + span.SetAttributes( + attribute.Int64("height", int64(height)), + attribute.String("commitment", string(commitment)), + ) + + getCtx, headerGetterSpan := tracer.Start(ctx, "header-getter") + + header, err := s.headerGetter(getCtx, height) if err != nil { + headerGetterSpan.SetStatus(codes.Error, err.Error()) return nil, nil, err } - namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header, namespace) + headerGetterSpan.SetStatus(codes.Ok, "") + headerGetterSpan.AddEvent("received eds", trace.WithAttributes( + attribute.Int64("eds-size", int64(len(header.DAH.RowRoots))))) + + getCtx, getSharesSpan := tracer.Start(ctx, "get-shares-by-namespace") + + namespacedShares, err := s.shareGetter.GetSharesByNamespace(getCtx, header, namespace) if err != nil { if errors.Is(err, share.ErrNotFound) { err = ErrBlobNotFound } + getSharesSpan.SetStatus(codes.Error, err.Error()) return nil, nil, err } + getSharesSpan.SetStatus(codes.Ok, "") + getSharesSpan.AddEvent("received shares", trace.WithAttributes( + attribute.Int64("eds-size", int64(len(header.DAH.RowRoots))))) + var ( rawShares = make([]shares.Share, 0) proofs = make(Proof, 0) @@ -260,6 +293,7 @@ func (s *Service) getByCommitment( } for _, b := range blobs { if b.Commitment.Equal(commitment) { + span.AddEvent("blob reconstructed") return b, &proofs, nil } // Falling under this flag means that the data from the last row @@ -296,7 +330,11 @@ func (s *Service) getBlobs( ctx context.Context, namespace share.Namespace, header *header.ExtendedHeader, -) ([]*Blob, error) { +) (_ []*Blob, err error) { + ctx, span := tracer.Start(ctx, "get-blobs") + defer func() { + utils.SetStatusAndEnd(span, err) + }() namespacedShares, err := s.shareGetter.GetSharesByNamespace(ctx, header, namespace) if err != nil { return nil, err From d7c7697b7003b3b803a61d72e46073d947672504 Mon Sep 17 00:00:00 2001 From: ramin Date: Wed, 31 Jan 2024 12:51:47 +0000 Subject: [PATCH 0993/1008] ci(unit tests): YOINK unit tests with race detector temporarily (#3147) suggesting we YEET the `unit test with race` temporarily as the job intermittently fails across a handful of tests pretty regularly and we don't require this job to pass for a PR merge anyway. Lets remove, i'll get it working and also enable for `macos-latest` which fails ALWAYS on github then we can / replace the normal unit tests with unit tests with race after we get it all running and keep CI green vs just ignoring a red x most of the time --- .github/workflows/go-ci.yml | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 5da90de33e..cb8187b82e 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -105,21 +105,27 @@ jobs: file: ./coverage.txt name: coverage-${{ matrix.os }} - unit_test_race: - needs: [lint, go_mod_tidy_check] - name: Unit Tests with Race Detector (ubuntu-latest) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: set up go - uses: actions/setup-go@v5 - with: - go-version: ${{ inputs.go-version }} - - - name: execute test run - run: make test-unit-race ENABLE_VERBOSE=${{ needs.setup.outputs.debug }} + # @ramin - Temporarily removed while we figure out getting + # these unit tests consistently running on ubuntu-latest + # and then enabled for macos-latest. We aren't requiring + # unit_race_test to pass for PRs so lets remove and reintroduce + # once green + # + # unit_test_race: + # needs: [lint, go_mod_tidy_check] + # name: Unit Tests with Race Detector (ubuntu-latest) + # runs-on: ubuntu-latest + + # steps: + # - uses: actions/checkout@v4 + + # - name: set up go + # uses: actions/setup-go@v5 + # with: + # go-version: ${{ inputs.go-version }} + + # - name: execute test run + # run: make test-unit-race ENABLE_VERBOSE=${{ needs.setup.outputs.debug }} integration_test: name: Integration Tests From c32ccf5933abef5fc7631a62d3c404151d3fee48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:33:21 +0000 Subject: [PATCH 0994/1008] chore(deps): Bump codecov/codecov-action from 3.1.4 to 3.1.6 (#3148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.4 to 3.1.6.
    Release notes

    Sourced from codecov/codecov-action's releases.

    v3.1.6

    Full Changelog: https://github.com/codecov/codecov-action/compare/v3.1.5...v3.1.6

    v3.1.5

    What's Changed

    New Contributors

    Full Changelog: https://github.com/codecov/codecov-action/compare/v3.1.4...v3.1.5

    Changelog

    Sourced from codecov/codecov-action's changelog.

    4.0.0-beta.2

    Fixes

    • #1085 not adding -n if empty to do-upload command

    4.0.0-beta.1

    v4 represents a move from the universal uploader to the Codecov CLI. Although this will unlock new features for our users, the CLI is not yet at feature parity with the universal uploader.

    Breaking Changes

    • No current support for aarch64 and alpine architectures.
    • Tokenless uploading is unsuported
    • Various arguments to the Action have been removed
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=3.1.4&new-version=3.1.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index cb8187b82e..0a8807f212 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -98,7 +98,7 @@ jobs: retention-days: 5 - name: upload coverage - uses: codecov/codecov-action@v3.1.4 + uses: codecov/codecov-action@v3.1.6 with: env_vars: OS token: ${{ secrets.CODECOV_TOKEN }} From 625cdf3b1f31afe5371cca8c355d59a572ae6ea6 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 1 Feb 2024 17:00:07 +0100 Subject: [PATCH 0995/1008] feat(rpc): implementing go-da interface (#3146) Integrates go-da directly to eliminate the need for [celestia-da](https://github.com/rollkit/celestia-da). --- Makefile | 2 +- api/rpc/client/client.go | 3 + api/rpc_test.go | 6 + blob/blob.go | 27 +++++ go.mod | 1 + go.sum | 2 + nodebuilder/da/da.go | 54 +++++++++ nodebuilder/da/mocks/api.go | 140 ++++++++++++++++++++++ nodebuilder/da/module.go | 14 +++ nodebuilder/da/service.go | 188 ++++++++++++++++++++++++++++++ nodebuilder/module.go | 2 + nodebuilder/node.go | 2 + nodebuilder/rpc/constructors.go | 3 + nodebuilder/tests/da_test.go | 144 +++++++++++++++++++++++ nodebuilder/tests/helpers_test.go | 6 +- share/p2p/peers/manager.go | 3 +- 16 files changed, 592 insertions(+), 5 deletions(-) create mode 100644 nodebuilder/da/da.go create mode 100644 nodebuilder/da/mocks/api.go create mode 100644 nodebuilder/da/module.go create mode 100644 nodebuilder/da/service.go create mode 100644 nodebuilder/tests/da_test.go diff --git a/Makefile b/Makefile index 87bcbbf61c..a43c917345 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +166,7 @@ pb-gen: ## openrpc-gen: Generate OpenRPC spec for Celestia-Node's RPC api openrpc-gen: @echo "--> Generating OpenRPC spec" - @go run ./cmd/docgen fraud header state share das p2p node blob + @go run ./cmd/docgen fraud header state share das p2p node blob da .PHONY: openrpc-gen ## lint-imports: Lint only Go imports. diff --git a/api/rpc/client/client.go b/api/rpc/client/client.go index 9cd5fe08e3..1d8142048b 100644 --- a/api/rpc/client/client.go +++ b/api/rpc/client/client.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc/perms" "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -33,6 +34,7 @@ type Client struct { P2P p2p.API Node node.API Blob blob.API + DA da.API closer multiClientCloser } @@ -91,5 +93,6 @@ func moduleMap(client *Client) map[string]interface{} { "p2p": &client.P2P.Internal, "node": &client.Node.Internal, "blob": &client.Blob.Internal, + "da": &client.DA.Internal, } } diff --git a/api/rpc_test.go b/api/rpc_test.go index ec308a2320..80dd29ddb4 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -22,6 +22,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/blob" blobMock "github.com/celestiaorg/celestia-node/nodebuilder/blob/mocks" + "github.com/celestiaorg/celestia-node/nodebuilder/da" + daMock "github.com/celestiaorg/celestia-node/nodebuilder/da/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/das" dasMock "github.com/celestiaorg/celestia-node/nodebuilder/das/mocks" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" @@ -91,6 +93,7 @@ type api struct { Node node.Module P2P p2p.Module Blob blob.Module + DA da.Module } func TestModulesImplementFullAPI(t *testing.T) { @@ -297,6 +300,7 @@ func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, * p2pMock.NewMockModule(ctrl), nodeMock.NewMockModule(ctrl), blobMock.NewMockModule(ctrl), + daMock.NewMockModule(ctrl), } // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root @@ -310,6 +314,7 @@ func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, * srv.RegisterAuthedService("p2p", mockAPI.P2P, &p2p.API{}) srv.RegisterAuthedService("node", mockAPI.Node, &node.API{}) srv.RegisterAuthedService("blob", mockAPI.Blob, &blob.API{}) + srv.RegisterAuthedService("da", mockAPI.DA, &da.API{}) }) // fx.Replace does not work here, but fx.Decorate does nd := nodebuilder.TestNode(t, node.Full, invokeRPC, fx.Decorate(func() (jwt.Signer, error) { @@ -334,4 +339,5 @@ type mockAPI struct { P2P *p2pMock.MockModule Node *nodeMock.MockModule Blob *blobMock.MockModule + DA *daMock.MockModule } diff --git a/blob/blob.go b/blob/blob.go index 945ffdf587..9843441dd2 100644 --- a/blob/blob.go +++ b/blob/blob.go @@ -35,6 +35,33 @@ type Proof []*nmt.Proof func (p Proof) Len() int { return len(p) } +func (p Proof) MarshalJSON() ([]byte, error) { + proofs := make([]string, 0, len(p)) + for _, proof := range p { + proofBytes, err := proof.MarshalJSON() + if err != nil { + return nil, err + } + proofs = append(proofs, string(proofBytes)) + } + return json.Marshal(proofs) +} + +func (p *Proof) UnmarshalJSON(b []byte) error { + var proofs []string + if err := json.Unmarshal(b, &proofs); err != nil { + return err + } + for _, proof := range proofs { + var nmtProof nmt.Proof + if err := nmtProof.UnmarshalJSON([]byte(proof)); err != nil { + return err + } + *p = append(*p, &nmtProof) + } + return nil +} + // equal is a temporary method that compares two proofs. // should be removed in BlobService V1. func (p Proof) equal(input Proof) error { diff --git a/go.mod b/go.mod index 66d12f6780..1a3f54a6bc 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( github.com/prometheus/client_golang v1.18.0 github.com/pyroscope-io/client v0.7.2 github.com/pyroscope-io/otel-profiling-go v0.5.0 + github.com/rollkit/go-da v0.4.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index d2fcb82e20..0712788b90 100644 --- a/go.sum +++ b/go.sum @@ -2121,6 +2121,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rollkit/go-da v0.4.0 h1:/s7ZrVq7DC2aK8UXIvB7rsXrZ2mVGRw7zrexcxRvhlw= +github.com/rollkit/go-da v0.4.0/go.mod h1:Kef0XI5ecEKd3TXzI8S+9knAUJnZg0svh2DuXoCsPlM= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= diff --git a/nodebuilder/da/da.go b/nodebuilder/da/da.go new file mode 100644 index 0000000000..0d604d769f --- /dev/null +++ b/nodebuilder/da/da.go @@ -0,0 +1,54 @@ +package da + +import ( + "context" + + "github.com/rollkit/go-da" +) + +//go:generate mockgen -destination=mocks/api.go -package=mocks . Module +type Module interface { + da.DA +} + +// API is a wrapper around Module for the RPC. +// TODO(@distractedm1nd): These structs need to be autogenerated. +type API struct { + Internal struct { + MaxBlobSize func(ctx context.Context) (uint64, error) `perm:"read"` + Get func(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Blob, error) `perm:"read"` + GetIDs func(ctx context.Context, height uint64, ns da.Namespace) ([]da.ID, error) `perm:"read"` + GetProofs func(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Proof, error) `perm:"read"` + Commit func(ctx context.Context, blobs []da.Blob, ns da.Namespace) ([]da.Commitment, error) `perm:"read"` + Validate func(context.Context, []da.ID, []da.Proof, da.Namespace) ([]bool, error) `perm:"read"` + Submit func(context.Context, []da.Blob, float64, da.Namespace) ([]da.ID, error) `perm:"write"` + } +} + +func (api *API) MaxBlobSize(ctx context.Context) (uint64, error) { + return api.Internal.MaxBlobSize(ctx) +} + +func (api *API) Get(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Blob, error) { + return api.Internal.Get(ctx, ids, ns) +} + +func (api *API) GetIDs(ctx context.Context, height uint64, ns da.Namespace) ([]da.ID, error) { + return api.Internal.GetIDs(ctx, height, ns) +} + +func (api *API) GetProofs(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Proof, error) { + return api.Internal.GetProofs(ctx, ids, ns) +} + +func (api *API) Commit(ctx context.Context, blobs []da.Blob, ns da.Namespace) ([]da.Commitment, error) { + return api.Internal.Commit(ctx, blobs, ns) +} + +func (api *API) Validate(ctx context.Context, ids []da.ID, proofs []da.Proof, ns da.Namespace) ([]bool, error) { + return api.Internal.Validate(ctx, ids, proofs, ns) +} + +func (api *API) Submit(ctx context.Context, blobs []da.Blob, gasPrice float64, ns da.Namespace) ([]da.ID, error) { + return api.Internal.Submit(ctx, blobs, gasPrice, ns) +} diff --git a/nodebuilder/da/mocks/api.go b/nodebuilder/da/mocks/api.go new file mode 100644 index 0000000000..5895240906 --- /dev/null +++ b/nodebuilder/da/mocks/api.go @@ -0,0 +1,140 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/celestiaorg/celestia-node/nodebuilder/da (interfaces: Module) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockModule is a mock of Module interface. +type MockModule struct { + ctrl *gomock.Controller + recorder *MockModuleMockRecorder +} + +// MockModuleMockRecorder is the mock recorder for MockModule. +type MockModuleMockRecorder struct { + mock *MockModule +} + +// NewMockModule creates a new mock instance. +func NewMockModule(ctrl *gomock.Controller) *MockModule { + mock := &MockModule{ctrl: ctrl} + mock.recorder = &MockModuleMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockModule) EXPECT() *MockModuleMockRecorder { + return m.recorder +} + +// Commit mocks base method. +func (m *MockModule) Commit(arg0 context.Context, arg1 [][]byte, arg2 []byte) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Commit", arg0, arg1, arg2) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Commit indicates an expected call of Commit. +func (mr *MockModuleMockRecorder) Commit(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockModule)(nil).Commit), arg0, arg1, arg2) +} + +// Get mocks base method. +func (m *MockModule) Get(arg0 context.Context, arg1 [][]byte, arg2 []byte) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockModuleMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockModule)(nil).Get), arg0, arg1, arg2) +} + +// GetIDs mocks base method. +func (m *MockModule) GetIDs(arg0 context.Context, arg1 uint64, arg2 []byte) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetIDs", arg0, arg1, arg2) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetIDs indicates an expected call of GetIDs. +func (mr *MockModuleMockRecorder) GetIDs(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIDs", reflect.TypeOf((*MockModule)(nil).GetIDs), arg0, arg1, arg2) +} + +// GetProofs mocks base method. +func (m *MockModule) GetProofs(arg0 context.Context, arg1 [][]byte, arg2 []byte) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProofs", arg0, arg1, arg2) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProofs indicates an expected call of GetProofs. +func (mr *MockModuleMockRecorder) GetProofs(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProofs", reflect.TypeOf((*MockModule)(nil).GetProofs), arg0, arg1, arg2) +} + +// MaxBlobSize mocks base method. +func (m *MockModule) MaxBlobSize(arg0 context.Context) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MaxBlobSize", arg0) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MaxBlobSize indicates an expected call of MaxBlobSize. +func (mr *MockModuleMockRecorder) MaxBlobSize(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MaxBlobSize", reflect.TypeOf((*MockModule)(nil).MaxBlobSize), arg0) +} + +// Submit mocks base method. +func (m *MockModule) Submit(arg0 context.Context, arg1 [][]byte, arg2 float64, arg3 []byte) ([][]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Submit", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([][]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Submit indicates an expected call of Submit. +func (mr *MockModuleMockRecorder) Submit(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockModule)(nil).Submit), arg0, arg1, arg2, arg3) +} + +// Validate mocks base method. +func (m *MockModule) Validate(arg0 context.Context, arg1, arg2 [][]byte, arg3 []byte) ([]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Validate", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Validate indicates an expected call of Validate. +func (mr *MockModuleMockRecorder) Validate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockModule)(nil).Validate), arg0, arg1, arg2, arg3) +} diff --git a/nodebuilder/da/module.go b/nodebuilder/da/module.go new file mode 100644 index 0000000000..b119d11076 --- /dev/null +++ b/nodebuilder/da/module.go @@ -0,0 +1,14 @@ +package da + +import ( + "go.uber.org/fx" +) + +func ConstructModule() fx.Option { + return fx.Module("da", + fx.Provide(NewService), + fx.Provide(func(serv *Service) Module { + return serv + }), + ) +} diff --git a/nodebuilder/da/service.go b/nodebuilder/da/service.go new file mode 100644 index 0000000000..b775e10396 --- /dev/null +++ b/nodebuilder/da/service.go @@ -0,0 +1,188 @@ +package da + +import ( + "context" + "encoding/binary" + "fmt" + "strings" + + logging "github.com/ipfs/go-log/v2" + "github.com/rollkit/go-da" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + + "github.com/celestiaorg/celestia-node/blob" + nodeblob "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/share" +) + +var _ da.DA = (*Service)(nil) + +var log = logging.Logger("go-da") + +// heightLen is a length (in bytes) of serialized height. +// +// This is 8 as uint64 consist of 8 bytes. +const heightLen = 8 + +type Service struct { + blobServ nodeblob.Module +} + +func NewService(blobMod nodeblob.Module) *Service { + return &Service{ + blobServ: blobMod, + } +} + +// MaxBlobSize returns the max blob size +func (s *Service) MaxBlobSize(context.Context) (uint64, error) { + return appconsts.DefaultMaxBytes, nil +} + +// Get returns Blob for each given ID, or an error. +func (s *Service) Get(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Blob, error) { + blobs := make([]da.Blob, 0, len(ids)) + for _, id := range ids { + height, commitment := SplitID(id) + log.Debugw("getting blob", "height", height, "commitment", commitment, "namespace", share.Namespace(ns)) + currentBlob, err := s.blobServ.Get(ctx, height, ns, commitment) + log.Debugw("got blob", "height", height, "commitment", commitment, "namespace", share.Namespace(ns)) + if err != nil { + return nil, err + } + blobs = append(blobs, currentBlob.Data) + } + return blobs, nil +} + +// GetIDs returns IDs of all Blobs located in DA at given height. +func (s *Service) GetIDs(ctx context.Context, height uint64, namespace da.Namespace) ([]da.ID, error) { + var ids []da.ID //nolint:prealloc + log.Debugw("getting ids", "height", height, "namespace", share.Namespace(namespace)) + blobs, err := s.blobServ.GetAll(ctx, height, []share.Namespace{namespace}) + log.Debugw("got ids", "height", height, "namespace", share.Namespace(namespace)) + if err != nil { + if strings.Contains(err.Error(), blob.ErrBlobNotFound.Error()) { + return nil, nil + } + return nil, err + } + for _, b := range blobs { + ids = append(ids, MakeID(height, b.Commitment)) + } + return ids, nil +} + +// GetProofs returns inclusion Proofs for all Blobs located in DA at given height. +func (s *Service) GetProofs(ctx context.Context, ids []da.ID, namespace da.Namespace) ([]da.Proof, error) { + proofs := make([]da.Proof, len(ids)) + for i, id := range ids { + height, commitment := SplitID(id) + proof, err := s.blobServ.GetProof(ctx, height, namespace, commitment) + if err != nil { + return nil, err + } + proofs[i], err = proof.MarshalJSON() + if err != nil { + return nil, err + } + } + return proofs, nil +} + +// Commit creates a Commitment for each given Blob. +func (s *Service) Commit(_ context.Context, daBlobs []da.Blob, namespace da.Namespace) ([]da.Commitment, error) { + _, commitments, err := s.blobsAndCommitments(daBlobs, namespace) + return commitments, err +} + +// Submit submits the Blobs to Data Availability layer. +func (s *Service) Submit( + ctx context.Context, + daBlobs []da.Blob, + gasPrice float64, + namespace da.Namespace, +) ([]da.ID, error) { + blobs, _, err := s.blobsAndCommitments(daBlobs, namespace) + if err != nil { + return nil, err + } + + height, err := s.blobServ.Submit(ctx, blobs, blob.GasPrice(gasPrice)) + if err != nil { + log.Error("failed to submit blobs", "height", height, "gas price", gasPrice) + return nil, err + } + log.Info("successfully submitted blobs", "height", height, "gas price", gasPrice) + ids := make([]da.ID, len(blobs)) + for i, blob := range blobs { + ids[i] = MakeID(height, blob.Commitment) + } + return ids, nil +} + +// blobsAndCommitments converts []da.Blob to []*blob.Blob and generates corresponding +// []da.Commitment +func (s *Service) blobsAndCommitments( + daBlobs []da.Blob, namespace da.Namespace, +) ([]*blob.Blob, []da.Commitment, error) { + blobs := make([]*blob.Blob, 0, len(daBlobs)) + commitments := make([]da.Commitment, 0, len(daBlobs)) + for _, daBlob := range daBlobs { + b, err := blob.NewBlobV0(namespace, daBlob) + if err != nil { + return nil, nil, err + } + blobs = append(blobs, b) + + commitments = append(commitments, b.Commitment) + } + return blobs, commitments, nil +} + +// Validate validates Commitments against the corresponding Proofs. This should be possible without +// retrieving the Blobs. +func (s *Service) Validate( + ctx context.Context, + ids []da.ID, + daProofs []da.Proof, + namespace da.Namespace, +) ([]bool, error) { + included := make([]bool, len(ids)) + proofs := make([]*blob.Proof, len(ids)) + for i, daProof := range daProofs { + blobProof := &blob.Proof{} + err := blobProof.UnmarshalJSON(daProof) + if err != nil { + return nil, err + } + proofs[i] = blobProof + } + for i, id := range ids { + height, commitment := SplitID(id) + // TODO(tzdybal): for some reason, if proof doesn't match commitment, API returns (false, "blob: + // invalid proof") but analysis of the code in celestia-node implies this should never happen - + // maybe it's caused by openrpc? there is no way of gently handling errors here, but returned + // value is fine for us + fmt.Println("proof", proofs[i] == nil, "commitment", commitment == nil) + isIncluded, _ := s.blobServ.Included(ctx, height, namespace, proofs[i], commitment) + included = append(included, isIncluded) + } + return included, nil +} + +func MakeID(height uint64, commitment da.Commitment) da.ID { + id := make([]byte, heightLen+len(commitment)) + binary.LittleEndian.PutUint64(id, height) + copy(id[heightLen:], commitment) + return id +} + +func SplitID(id da.ID) (uint64, da.Commitment) { + if len(id) <= heightLen { + return 0, nil + } + commitment := id[heightLen:] + return binary.LittleEndian.Uint64(id[:heightLen]), commitment +} diff --git a/nodebuilder/module.go b/nodebuilder/module.go index f9948b2011..ad287b1ac8 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -9,6 +9,7 @@ import ( "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/blob" "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/gateway" @@ -55,6 +56,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store das.ConstructModule(tp, &cfg.DASer), fraud.ConstructModule(tp), blob.ConstructModule(), + da.ConstructModule(), node.ConstructModule(tp), prune.ConstructModule(tp), rpc.ConstructModule(tp, &cfg.RPC), diff --git a/nodebuilder/node.go b/nodebuilder/node.go index d5d0ab2016..b16a376cc1 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -22,6 +22,7 @@ import ( "github.com/celestiaorg/celestia-node/api/gateway" "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -71,6 +72,7 @@ type Node struct { BlobServ blob.Module // not optional DASer das.Module // not optional AdminServ node.Module // not optional + DAMod da.Module // not optional // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop start, stop lifecycleFunc diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index 194dea8a03..c8e928a98a 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -5,6 +5,7 @@ import ( "github.com/celestiaorg/celestia-node/api/rpc" "github.com/celestiaorg/celestia-node/nodebuilder/blob" + "github.com/celestiaorg/celestia-node/nodebuilder/da" "github.com/celestiaorg/celestia-node/nodebuilder/das" "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/header" @@ -24,6 +25,7 @@ func registerEndpoints( p2pMod p2p.Module, nodeMod node.Module, blobMod blob.Module, + daMod da.Module, serv *rpc.Server, ) { serv.RegisterAuthedService("fraud", fraudMod, &fraud.API{}) @@ -34,6 +36,7 @@ func registerEndpoints( serv.RegisterAuthedService("p2p", p2pMod, &p2p.API{}) serv.RegisterAuthedService("node", nodeMod, &node.API{}) serv.RegisterAuthedService("blob", blobMod, &blob.API{}) + serv.RegisterAuthedService("da", daMod, &da.API{}) } func server(cfg *Config, auth jwt.Signer) *rpc.Server { diff --git a/nodebuilder/tests/da_test.go b/nodebuilder/tests/da_test.go new file mode 100644 index 0000000000..2925898e1e --- /dev/null +++ b/nodebuilder/tests/da_test.go @@ -0,0 +1,144 @@ +//go:build blob || integration + +package tests + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/appconsts" + + "github.com/celestiaorg/celestia-node/blob" + "github.com/celestiaorg/celestia-node/blob/blobtest" + "github.com/celestiaorg/celestia-node/nodebuilder/da" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" + "github.com/celestiaorg/celestia-node/share" +) + +func TestDaModule(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) + t.Cleanup(cancel) + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Second)) + + namespace, err := share.NewBlobNamespaceV0([]byte("namespace")) + require.NoError(t, err) + + appBlobs0, err := blobtest.GenerateV0Blobs([]int{8, 4}, true) + require.NoError(t, err) + appBlobs1, err := blobtest.GenerateV0Blobs([]int{4}, false) + require.NoError(t, err) + blobs := make([]*blob.Blob, 0, len(appBlobs0)+len(appBlobs1)) + daBlobs := make([][]byte, 0, len(appBlobs0)+len(appBlobs1)) + + for _, b := range append(appBlobs0, appBlobs1...) { + blob, err := blob.NewBlob(b.ShareVersion, namespace, b.Data) + require.NoError(t, err) + blobs = append(blobs, blob) + daBlobs = append(daBlobs, blob.Data) + } + + require.NoError(t, err) + bridge := sw.NewBridgeNode() + require.NoError(t, bridge.Start(ctx)) + + addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + require.NoError(t, err) + + fullCfg := sw.DefaultTestConfig(node.Full) + fullCfg.Header.TrustedPeers = append(fullCfg.Header.TrustedPeers, addrs[0].String()) + fullNode := sw.NewNodeWithConfig(node.Full, fullCfg) + require.NoError(t, fullNode.Start(ctx)) + + addrsFull, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(fullNode.Host)) + require.NoError(t, err) + + lightCfg := sw.DefaultTestConfig(node.Light) + lightCfg.Header.TrustedPeers = append(lightCfg.Header.TrustedPeers, addrsFull[0].String()) + lightNode := sw.NewNodeWithConfig(node.Light, lightCfg) + require.NoError(t, lightNode.Start(ctx)) + + fullClient := getAdminClient(ctx, fullNode, t) + lightClient := getAdminClient(ctx, lightNode, t) + + ids, err := fullClient.DA.Submit(ctx, daBlobs, -1, namespace) + require.NoError(t, err) + + var test = []struct { + name string + doFn func(t *testing.T) + }{ + { + name: "MaxBlobSize", + doFn: func(t *testing.T) { + mbs, err := fullClient.DA.MaxBlobSize(ctx) + require.NoError(t, err) + require.Equal(t, mbs, uint64(appconsts.DefaultMaxBytes)) + }, + }, + { + name: "GetProofs + Validate", + doFn: func(t *testing.T) { + t.Skip() + h, _ := da.SplitID(ids[0]) + lightClient.Header.WaitForHeight(ctx, h) + proofs, err := lightClient.DA.GetProofs(ctx, ids, namespace) + require.NoError(t, err) + require.NotEmpty(t, proofs) + valid, err := fullClient.DA.Validate(ctx, ids, proofs, namespace) + require.NoError(t, err) + for _, v := range valid { + require.True(t, v) + } + }, + }, + { + name: "GetIDs", + doFn: func(t *testing.T) { + height, _ := da.SplitID(ids[0]) + ids2, err := fullClient.DA.GetIDs(ctx, height, namespace) + require.NoError(t, err) + require.EqualValues(t, ids, ids2) + }, + }, + { + name: "Get", + doFn: func(t *testing.T) { + h, _ := da.SplitID(ids[0]) + lightClient.Header.WaitForHeight(ctx, h) + fetched, err := lightClient.DA.Get(ctx, ids, namespace) + require.NoError(t, err) + require.Len(t, fetched, len(ids)) + for i := range fetched { + require.True(t, bytes.Equal(fetched[i], daBlobs[i])) + } + }, + }, + { + name: "Commit", + doFn: func(t *testing.T) { + t.Skip() + fetched, err := fullClient.DA.Commit(ctx, ids, namespace) + require.NoError(t, err) + require.Len(t, fetched, len(ids)) + for i := range fetched { + _, commitment := da.SplitID(ids[i]) + require.EqualValues(t, fetched[i], commitment) + } + }, + }, + } + + for _, tt := range test { + tt := tt + t.Run(tt.name, func(t *testing.T) { + tt.doFn(t) + }) + } +} diff --git a/nodebuilder/tests/helpers_test.go b/nodebuilder/tests/helpers_test.go index 1e7f14d823..978b66553d 100644 --- a/nodebuilder/tests/helpers_test.go +++ b/nodebuilder/tests/helpers_test.go @@ -6,12 +6,12 @@ import ( "testing" "time" + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/api/rpc/client" "github.com/celestiaorg/celestia-node/libs/authtoken" "github.com/celestiaorg/celestia-node/nodebuilder" - - "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/stretchr/testify/require" ) func getAdminClient(ctx context.Context, nd *nodebuilder.Node, t *testing.T) *client.Client { diff --git a/share/p2p/peers/manager.go b/share/p2p/peers/manager.go index 964b022249..1a00059628 100644 --- a/share/p2p/peers/manager.go +++ b/share/p2p/peers/manager.go @@ -38,7 +38,8 @@ const ( // events in libp2p eventbusBufSize = 32 - // storedPoolsAmount is the amount of pools for recent headers that will be stored in the peer manager + // storedPoolsAmount is the amount of pools for recent headers that will be stored in the peer + // manager storedPoolsAmount = 10 ) From a33c80e20da684d656c7213580be7878bcd27cf4 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Thu, 1 Feb 2024 20:26:42 +0100 Subject: [PATCH 0996/1008] chore: bump go-header (#3158) https://github.com/celestiaorg/go-header/releases/tag/v0.5.3 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1a3f54a6bc..b79e18ff12 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/benbjohnson/clock v1.3.5 github.com/celestiaorg/celestia-app v1.4.0 github.com/celestiaorg/go-fraud v0.2.0 - github.com/celestiaorg/go-header v0.5.2 + github.com/celestiaorg/go-header v0.5.3 github.com/celestiaorg/go-libp2p-messenger v0.2.0 github.com/celestiaorg/nmt v0.20.0 github.com/celestiaorg/rsmt2d v0.11.0 diff --git a/go.sum b/go.sum index 0712788b90..ec8fe9276c 100644 --- a/go.sum +++ b/go.sum @@ -368,8 +368,8 @@ github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403 h1:Lj73O3S+KJ github.com/celestiaorg/dagstore v0.0.0-20230824094345-537c012aa403/go.mod h1:cCGM1UoMvyTk8k62mkc+ReVu8iHBCtSBAAL4wYU7KEI= github.com/celestiaorg/go-fraud v0.2.0 h1:aaq2JiW0gTnhEdac3l51UCqSyJ4+VjFGTTpN83V4q7I= github.com/celestiaorg/go-fraud v0.2.0/go.mod h1:lNY1i4K6kUeeE60Z2VK8WXd+qXb8KRzfBhvwPkK6aUc= -github.com/celestiaorg/go-header v0.5.2 h1:CFsTAXcs1o38JVd8YN1Naq/Yzs6m9orMPEPNpLEgFJA= -github.com/celestiaorg/go-header v0.5.2/go.mod h1:7BVR6myjRfACbqW1de6s8OjuK66XzHm8MpFNYr0G+nU= +github.com/celestiaorg/go-header v0.5.3 h1:8CcflT6aIlcQXKNWcMekoBNs3EU50mEmDp17gbn1pP4= +github.com/celestiaorg/go-header v0.5.3/go.mod h1:7BVR6myjRfACbqW1de6s8OjuK66XzHm8MpFNYr0G+nU= github.com/celestiaorg/go-libp2p-messenger v0.2.0 h1:/0MuPDcFamQMbw9xTZ73yImqgTO3jHV7wKHvWD/Irao= github.com/celestiaorg/go-libp2p-messenger v0.2.0/go.mod h1:s9PIhMi7ApOauIsfBcQwbr7m+HBzmVfDIS+QLdgzDSo= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= From 01585ba111b819a74071e1f1e1cfaf3ef09dbf9f Mon Sep 17 00:00:00 2001 From: Tien Nguyen <116023870+htiennv@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:37:06 +0700 Subject: [PATCH 0997/1008] chore: add missing stop ticker (#3161) --- share/p2p/discovery/backoff.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/p2p/discovery/backoff.go b/share/p2p/discovery/backoff.go index 70581bc519..7294915727 100644 --- a/share/p2p/discovery/backoff.go +++ b/share/p2p/discovery/backoff.go @@ -91,6 +91,8 @@ func (b *backoffConnector) HasBackoff(p peer.ID) bool { // GC is a perpetual GCing loop. func (b *backoffConnector) GC(ctx context.Context) { ticker := time.NewTicker(gcInterval) + defer ticker.Stop() + for { select { case <-ctx.Done(): From e55e1c88708b46839867bcbbed9bcdd8a3ffa830 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 6 Feb 2024 12:23:00 +0100 Subject: [PATCH 0998/1008] fix: improve rpc.skip-auth (#3152) --- api/rpc/server.go | 13 ++++++------- api/rpc_test.go | 18 +++++++++--------- nodebuilder/rpc/constructors.go | 18 +++++++++--------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/api/rpc/server.go b/api/rpc/server.go index 3bf58870bc..f247682083 100644 --- a/api/rpc/server.go +++ b/api/rpc/server.go @@ -61,15 +61,14 @@ func (s *Server) verifyAuth(_ context.Context, token string) ([]auth.Permission, // RegisterService registers a service onto the RPC server. All methods on the service will then be // exposed over the RPC. -func (s *Server) RegisterService(namespace string, service interface{}) { - s.rpc.Register(namespace, service) -} +func (s *Server) RegisterService(namespace string, service interface{}, out interface{}) { + if s.authDisabled { + s.rpc.Register(namespace, service) + return + } -// RegisterAuthedService registers a service onto the RPC server. All methods on the service will -// then be exposed over the RPC. -func (s *Server) RegisterAuthedService(namespace string, service interface{}, out interface{}) { auth.PermissionedProxy(perms.AllPerms, perms.DefaultPerms, service, getInternalStruct(out)) - s.RegisterService(namespace, out) + s.rpc.Register(namespace, out) } func getInternalStruct(api interface{}) interface{} { diff --git a/api/rpc_test.go b/api/rpc_test.go index 80dd29ddb4..ff38a42045 100644 --- a/api/rpc_test.go +++ b/api/rpc_test.go @@ -306,15 +306,15 @@ func setupNodeWithAuthedRPC(t *testing.T, auth jwt.Signer) (*nodebuilder.Node, * // given the behavior of fx.Invoke, this invoke will be called last as it is added at the root // level module. For further information, check the documentation on fx.Invoke. invokeRPC := fx.Invoke(func(srv *rpc.Server) { - srv.RegisterAuthedService("state", mockAPI.State, &statemod.API{}) - srv.RegisterAuthedService("share", mockAPI.Share, &share.API{}) - srv.RegisterAuthedService("fraud", mockAPI.Fraud, &fraud.API{}) - srv.RegisterAuthedService("header", mockAPI.Header, &header.API{}) - srv.RegisterAuthedService("das", mockAPI.Das, &das.API{}) - srv.RegisterAuthedService("p2p", mockAPI.P2P, &p2p.API{}) - srv.RegisterAuthedService("node", mockAPI.Node, &node.API{}) - srv.RegisterAuthedService("blob", mockAPI.Blob, &blob.API{}) - srv.RegisterAuthedService("da", mockAPI.DA, &da.API{}) + srv.RegisterService("fraud", mockAPI.Fraud, &fraud.API{}) + srv.RegisterService("das", mockAPI.Das, &das.API{}) + srv.RegisterService("header", mockAPI.Header, &header.API{}) + srv.RegisterService("state", mockAPI.State, &statemod.API{}) + srv.RegisterService("share", mockAPI.Share, &share.API{}) + srv.RegisterService("p2p", mockAPI.P2P, &p2p.API{}) + srv.RegisterService("node", mockAPI.Node, &node.API{}) + srv.RegisterService("blob", mockAPI.Blob, &blob.API{}) + srv.RegisterService("da", mockAPI.DA, &da.API{}) }) // fx.Replace does not work here, but fx.Decorate does nd := nodebuilder.TestNode(t, node.Full, invokeRPC, fx.Decorate(func() (jwt.Signer, error) { diff --git a/nodebuilder/rpc/constructors.go b/nodebuilder/rpc/constructors.go index c8e928a98a..43a8055207 100644 --- a/nodebuilder/rpc/constructors.go +++ b/nodebuilder/rpc/constructors.go @@ -28,15 +28,15 @@ func registerEndpoints( daMod da.Module, serv *rpc.Server, ) { - serv.RegisterAuthedService("fraud", fraudMod, &fraud.API{}) - serv.RegisterAuthedService("das", daserMod, &das.API{}) - serv.RegisterAuthedService("header", headerMod, &header.API{}) - serv.RegisterAuthedService("state", stateMod, &state.API{}) - serv.RegisterAuthedService("share", shareMod, &share.API{}) - serv.RegisterAuthedService("p2p", p2pMod, &p2p.API{}) - serv.RegisterAuthedService("node", nodeMod, &node.API{}) - serv.RegisterAuthedService("blob", blobMod, &blob.API{}) - serv.RegisterAuthedService("da", daMod, &da.API{}) + serv.RegisterService("fraud", fraudMod, &fraud.API{}) + serv.RegisterService("das", daserMod, &das.API{}) + serv.RegisterService("header", headerMod, &header.API{}) + serv.RegisterService("state", stateMod, &state.API{}) + serv.RegisterService("share", shareMod, &share.API{}) + serv.RegisterService("p2p", p2pMod, &p2p.API{}) + serv.RegisterService("node", nodeMod, &node.API{}) + serv.RegisterService("blob", blobMod, &blob.API{}) + serv.RegisterService("da", daMod, &da.API{}) } func server(cfg *Config, auth jwt.Signer) *rpc.Server { From 257e6cef44cd49930b1ee9c35596aa458b3a33f9 Mon Sep 17 00:00:00 2001 From: Josh Stein <46639943+jcstein@users.noreply.github.com> Date: Fri, 9 Feb 2024 05:52:34 -0500 Subject: [PATCH 0999/1008] feat: add example value for blob.GasPrice (#3171) # Summary Open RPC spec generation errors, due to a missing sample value for blob.GasPrice. # Problem Missing sample value in `api/docgen/examples.go`. Logs from running on v0.13.0: ```json --> Generating OpenRPC spec failed to retrieve example value for type: blob.GasPrice on parent 'blob.GasPrice') { "openrpc": "1.2.6", "info": { "title": "Celestia Node API", "description": "The Celestia Node API is the collection of RPC methods that can be used to interact with the services provided by Celestia Data Availability Nodes.", "version": "v0.11.0" }, ``` Output from logs running on v0.11.0, as another check to see that this worked before: ```json --> Generating OpenRPC spec { "openrpc": "1.2.6", "info": { "title": "Celestia Node API", "description": "The Celestia Node API is the collection of RPC methods that can be used to interact with the services provided by Celestia Data Availability Nodes.", "version": "v0.11.0" }, ``` # Solution Add sample value. [Output after testing](https://gist.github.com/jcstein/02c459fb781889405d7ebc802484509a) ## Other issues found while testing API version is still v0.11.0, which is the same as last few releases, i just ran make openrpc-gen on v0.11.0 and the API version is the same there. This is already, addressed in https://github.com/celestiaorg/celestia-node/issues/2549, and I am highlighting to bring it out from stale state. ### Proposed solutions for other issues found To avoid this in the future, i would like to see if we can work this [feature request to add openrpc.json on main](https://github.com/celestiaorg/celestia-node/issues/2611) into celestia-node, so that there is validation before publishing specs, and a history of past versions of the openrpc.json, stored on main with releases. otherwise we JTMB the person updating node-rpc-docs [on almost 10000 lines of spec code](https://github.com/celestiaorg/node-rpc-docs/pull/35/files) > Note, this output must not contain the output which precedes the json: > `--> Generating OpenRPC spec` --- api/docgen/examples.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/docgen/examples.go b/api/docgen/examples.go index b873e7e050..d90fa3a53f 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -51,6 +51,7 @@ var ExampleValues = map[reflect.Type]interface{}{ reflect.TypeOf(42): 42, reflect.TypeOf(byte(7)): byte(7), reflect.TypeOf(float64(42)): float64(42), + reflect.TypeOf(blob.GasPrice(0)): blob.GasPrice(0.002), reflect.TypeOf(true): true, reflect.TypeOf([]byte{}): []byte("byte array"), reflect.TypeOf(node.Full): node.Full, From 3b97e7152dfcdb75d3f911b623f68470393f3fa4 Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 9 Feb 2024 11:09:42 +0000 Subject: [PATCH 1000/1008] ci: run unit tests on m1 mac runner vs macos-latest intel mac (#3155) These are now [generally available](https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/) and these will a) be faster b) also be closer to developer machines also removed a commented out job in the ci_release.yml file that had been sitting dormant for a few years --- .github/workflows/ci_release.yml | 8 -------- .github/workflows/go-ci.yml | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index ad15a083bc..73572e4cef 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -138,11 +138,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - - # TODO: permission issue, but not worth fixing as this should be refactored - # into the celestiaorg/.github repo, at which point any permission issues will - # be resolved. - # - # docker: - # needs: [release] - # uses: ./.github/workflows/docker-build.yml diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 0a8807f212..9640d38fb9 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-14] runs-on: ${{ matrix.os }} env: OS: ${{ matrix.os }} From 87544d7af3511615cf8f4488805e8624dbeed524 Mon Sep 17 00:00:00 2001 From: ramin Date: Fri, 9 Feb 2024 11:42:46 +0000 Subject: [PATCH 1001/1008] chore(ci): split da into own tag, and skip a consistently failing test (#3172) - split the new flakey DA tests into own tag and call so we don't think blob has started failing suddenly - Total shutdown of DA integration test `GetIDs` until we can figure out what is going on --- .github/workflows/integration-tests.yml | 15 +++++++++++++++ nodebuilder/tests/da_test.go | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index cd0fc62385..cc1196fccf 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -39,6 +39,21 @@ jobs: - name: run blob tests run: make test-integration TAGS=blob + da_tests: + name: Da Tests Sync + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: set up go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: run da tests + run: make test-integration SHORT=true TAGS=da + fraud_tests: name: Integration Tests Fraud runs-on: ubuntu-latest diff --git a/nodebuilder/tests/da_test.go b/nodebuilder/tests/da_test.go index 2925898e1e..bdcd4e638c 100644 --- a/nodebuilder/tests/da_test.go +++ b/nodebuilder/tests/da_test.go @@ -1,4 +1,4 @@ -//go:build blob || integration +//go:build da || integration package tests @@ -101,6 +101,7 @@ func TestDaModule(t *testing.T) { { name: "GetIDs", doFn: func(t *testing.T) { + t.Skip() height, _ := da.SplitID(ids[0]) ids2, err := fullClient.DA.GetIDs(ctx, height, namespace) require.NoError(t, err) From 5e1895c8043ef492b604ef4c06c01dc534f0c851 Mon Sep 17 00:00:00 2001 From: son trinh Date: Wed, 14 Feb 2024 16:40:53 +0700 Subject: [PATCH 1002/1008] feat(nodebuilder/blob/cmd): Allow submit multiple blobs through file input (#3008) --- nodebuilder/blob/cmd/blob.go | 104 ++++++++++++++++++++++++++++++----- nodebuilder/blob/cmd/util.go | 32 +++++++++++ 2 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 nodebuilder/blob/cmd/util.go diff --git a/nodebuilder/blob/cmd/blob.go b/nodebuilder/blob/cmd/blob.go index 7862eaa204..25a102843b 100644 --- a/nodebuilder/blob/cmd/blob.go +++ b/nodebuilder/blob/cmd/blob.go @@ -2,7 +2,9 @@ package cmd import ( "encoding/base64" + "errors" "fmt" + "path/filepath" "reflect" "strconv" @@ -17,6 +19,10 @@ var ( base64Flag bool gasPrice float64 + + // flagFileInput allows the user to provide file path to the json file + // for submitting multiple blobs. + flagFileInput = "input-file" ) func init() { @@ -43,6 +49,8 @@ func init() { "specifies gas price (in utia) for blob submission.\n"+ "Gas price will be set to default (0.002) if no value is passed", ) + + submitCmd.PersistentFlags().String(flagFileInput, "", "Specify the file input") } var Cmd = &cobra.Command{ @@ -119,11 +127,46 @@ var getAllCmd = &cobra.Command{ } var submitCmd = &cobra.Command{ - Use: "submit [namespace] [blobData]", - Args: cobra.ExactArgs(2), - Short: "Submit the blob at the given namespace.\n" + + Use: "submit [namespace] [blobData]", + Args: func(cmd *cobra.Command, args []string) error { + path, err := cmd.Flags().GetString(flagFileInput) + if err != nil { + return err + } + + // If there is a file path input we'll check for the file extension + if path != "" { + if filepath.Ext(path) != ".json" { + return fmt.Errorf("invalid file extension, require json got %s", filepath.Ext(path)) + } + + return nil + } + + if len(args) < 2 { + return errors.New("submit requires two arguments: namespace and blobData") + } + + return nil + }, + Short: "Submit the blob(s) at the given namespace(s).\n" + + "User can use namespace and blobData as argument for single blob submission \n" + + "or use --input-file flag with the path to a json file for multiple blobs submission, \n" + + `where the json file contains: + + { + "Blobs": [ + { + "namespace": "0x00010203040506070809", + "blobData": "0x676d" + }, + { + "namespace": "0x42690c204d39600fddd3", + "blobData": "0x676d" + } + ] + }` + "Note:\n" + - "* only one blob is allowed to submit through the RPC.\n" + "* fee and gas limit params will be calculated automatically.\n", RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) @@ -132,33 +175,66 @@ var submitCmd = &cobra.Command{ } defer client.Close() - namespace, err := cmdnode.ParseV0Namespace(args[0]) + path, err := cmd.Flags().GetString(flagFileInput) if err != nil { - return fmt.Errorf("error parsing a namespace:%v", err) + return err } - parsedBlob, err := blob.NewBlobV0(namespace, []byte(args[1])) - if err != nil { - return fmt.Errorf("error creating a blob:%v", err) + jsonBlobs := make([]blobJSON, 0) + // In case of there is a file input, get the namespace and blob from the arguments + if path != "" { + paresdBlobs, err := parseSubmitBlobs(path) + if err != nil { + return err + } + + jsonBlobs = append(jsonBlobs, paresdBlobs...) + } else { + jsonBlobs = append(jsonBlobs, blobJSON{Namespace: args[0], BlobData: args[1]}) + } + + var blobs []*blob.Blob + var commitments []blob.Commitment + for _, jsonBlob := range jsonBlobs { + blob, err := getBlobFromArguments(jsonBlob.Namespace, jsonBlob.BlobData) + if err != nil { + return err + } + blobs = append(blobs, blob) + commitments = append(commitments, blob.Commitment) } height, err := client.Blob.Submit( cmd.Context(), - []*blob.Blob{parsedBlob}, + blobs, blob.GasPrice(gasPrice), ) response := struct { - Height uint64 `json:"height"` - Commitment blob.Commitment `json:"commitment"` + Height uint64 `json:"height"` + Commitments []blob.Commitment `json:"commitments"` }{ - Height: height, - Commitment: parsedBlob.Commitment, + Height: height, + Commitments: commitments, } return cmdnode.PrintOutput(response, err, nil) }, } +func getBlobFromArguments(namespaceArg, blobArg string) (*blob.Blob, error) { + namespace, err := cmdnode.ParseV0Namespace(namespaceArg) + if err != nil { + return nil, fmt.Errorf("error parsing a namespace:%v", err) + } + + parsedBlob, err := blob.NewBlobV0(namespace, []byte(blobArg)) + if err != nil { + return nil, fmt.Errorf("error creating a blob:%v", err) + } + + return parsedBlob, nil +} + var getProofCmd = &cobra.Command{ Use: "get-proof [height] [namespace] [commitment]", Args: cobra.ExactArgs(3), diff --git a/nodebuilder/blob/cmd/util.go b/nodebuilder/blob/cmd/util.go new file mode 100644 index 0000000000..a33b9c1a84 --- /dev/null +++ b/nodebuilder/blob/cmd/util.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "encoding/json" + "os" +) + +// Define the raw content from the file input. +type blobs struct { + Blobs []blobJSON +} + +type blobJSON struct { + Namespace string + BlobData string +} + +func parseSubmitBlobs(path string) ([]blobJSON, error) { + var rawBlobs blobs + + content, err := os.ReadFile(path) + if err != nil { + return []blobJSON{}, err + } + + err = json.Unmarshal(content, &rawBlobs) + if err != nil { + return []blobJSON{}, err + } + + return rawBlobs.Blobs, err +} From 0350121a36eac798eea3ecc72e33d1645d6356c3 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 19 Feb 2024 15:03:54 +0100 Subject: [PATCH 1003/1008] specs: initialize spec with mdbook (#3186) The time has come. Finally, the node will have a spec. Using mdbook and gh pages as app team. --- .github/workflows/github_pages.yml | 48 ++++++++++++++++++++++++++++++ specs/.gitignore | 1 + specs/book.toml | 13 ++++++++ specs/src/SUMMARY.md | 3 ++ specs/src/WIP.md | 1 + 5 files changed, 66 insertions(+) create mode 100644 .github/workflows/github_pages.yml create mode 100644 specs/.gitignore create mode 100644 specs/book.toml create mode 100644 specs/src/SUMMARY.md create mode 100644 specs/src/WIP.md diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml new file mode 100644 index 0000000000..b212c2d885 --- /dev/null +++ b/.github/workflows/github_pages.yml @@ -0,0 +1,48 @@ +name: github-pages + +on: + push: + branches: + - main + paths: + - specs/** + pull_request: + paths: + - specs/** + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: "latest" + + - name: Build book + run: mdbook build specs + + - name: Deploy main + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./specs/book + # keep_files is to prevent PR preview files from being overwritten. + # If we need to overwrite such files, trigger this workflow manually. + keep_files: ${{ github.event_name != 'workflow_dispatch' }} + + - name: Deploy PR preview + # Only run this job if the PR was created from a branch on celestiaorg/celestia-node + # because this job will fail for branches from forks. + # https://github.com/celestiaorg/celestia-app/issues/1506 + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + uses: rossjrw/pr-preview-action@v1 + with: + source-dir: ./specs/book diff --git a/specs/.gitignore b/specs/.gitignore new file mode 100644 index 0000000000..7585238efe --- /dev/null +++ b/specs/.gitignore @@ -0,0 +1 @@ +book diff --git a/specs/book.toml b/specs/book.toml new file mode 100644 index 0000000000..2ab3d5a398 --- /dev/null +++ b/specs/book.toml @@ -0,0 +1,13 @@ +[book] +authors = ["Celestia Labs"] +language = "en" +multilingual = false +src = "src" +title = "Celestia Node Specification" + +[output.html] +git-repository-url = "https://github.com/celestiaorg/celestia-node" + +[preprocessor.toc] +command = "mdbook-toc" +renderer = ["html"] diff --git a/specs/src/SUMMARY.md b/specs/src/SUMMARY.md new file mode 100644 index 0000000000..dd6d59d972 --- /dev/null +++ b/specs/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [WIP](./WIP.md) diff --git a/specs/src/WIP.md b/specs/src/WIP.md new file mode 100644 index 0000000000..85e6ff194b --- /dev/null +++ b/specs/src/WIP.md @@ -0,0 +1 @@ +# WIP From ec978ec8e378ee6e75ae54dde61e61f52beb7c43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:42:30 +0000 Subject: [PATCH 1004/1008] chore(deps): Bump golang.org/x/crypto from 0.18.0 to 0.19.0 (#3182) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.18.0 to 0.19.0.
    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.18.0&new-version=0.19.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b79e18ff12..ca449edfe7 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 go.uber.org/fx v1.20.1 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.18.0 + golang.org/x/crypto v0.19.0 golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e golang.org/x/sync v0.6.0 golang.org/x/text v0.14.0 @@ -321,8 +321,8 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.13.0 // indirect diff --git a/go.sum b/go.sum index ec8fe9276c..6fe259ff0c 100644 --- a/go.sum +++ b/go.sum @@ -2513,8 +2513,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2861,8 +2861,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2871,8 +2871,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 1809680284c15eb0171d825d8ba91fca308f44bf Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:19:24 +0100 Subject: [PATCH 1005/1008] chore(nodebuilder/share): Make module construction more digestable (#3170) I tried to clean up the share module construction a bit so that it's easier to parse. Hopefully this is more helpful than the mess that was there prior. **Also please carefully review this PR!!** I tried my best to make sure that all components are added where they are supposed to be but it's possible I may have missed something (although all related units are passing). --- api/gateway/health.go | 4 +- nodebuilder/share/constructors.go | 2 +- nodebuilder/share/module.go | 247 ++++++++++++++++++------------ 3 files changed, 153 insertions(+), 100 deletions(-) diff --git a/api/gateway/health.go b/api/gateway/health.go index 0ddb56bb67..2a96e0200e 100644 --- a/api/gateway/health.go +++ b/api/gateway/health.go @@ -1,6 +1,8 @@ package gateway -import "net/http" +import ( + "net/http" +) const ( healthEndpoint = "/status/health" diff --git a/nodebuilder/share/constructors.go b/nodebuilder/share/constructors.go index aa2ac5bec1..e13786a4d9 100644 --- a/nodebuilder/share/constructors.go +++ b/nodebuilder/share/constructors.go @@ -42,7 +42,7 @@ func newDiscovery(cfg *disc.Parameters, } } -func newModule(getter share.Getter, avail share.Availability) Module { +func newShareModule(getter share.Getter, avail share.Availability) Module { return &module{getter, avail} } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 3fa55b2d35..7caaf39a92 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -34,7 +34,72 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Supply(*cfg), fx.Error(cfgErr), fx.Options(options...), - fx.Provide(newModule), + fx.Provide(newShareModule), + peerManagerComponents(tp, cfg), + discoveryComponents(cfg), + shrexSubComponents(), + ) + + bridgeAndFullComponents := fx.Options( + fx.Provide(getters.NewStoreGetter), + shrexServerComponents(cfg), + edsStoreComponents(cfg), + fullAvailabilityComponents(), + shrexGetterComponents(cfg), + fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { + return shrexSub.Broadcast + }), + ) + + switch tp { + case node.Bridge: + return fx.Module( + "share", + baseComponents, + bridgeAndFullComponents, + fx.Provide(func() peers.Parameters { + return cfg.PeerManagerParams + }), + fx.Provide(bridgeGetter), + fx.Invoke(func(lc fx.Lifecycle, sub *shrexsub.PubSub) error { + lc.Append(fx.Hook{ + OnStart: sub.Start, + OnStop: sub.Stop, + }) + return nil + }), + ) + case node.Full: + return fx.Module( + "share", + baseComponents, + bridgeAndFullComponents, + fx.Provide(getters.NewIPLDGetter), + fx.Provide(fullGetter), + ) + case node.Light: + return fx.Module( + "share", + baseComponents, + shrexGetterComponents(cfg), + lightAvailabilityComponents(cfg), + fx.Invoke(ensureEmptyEDSInBS), + fx.Provide(getters.NewIPLDGetter), + fx.Provide(lightGetter), + // shrexsub broadcaster stub for daser + fx.Provide(func() shrexsub.BroadcastFn { + return func(context.Context, shrexsub.Notification) error { + return nil + } + }), + ) + default: + panic("invalid node type") + } +} + +func discoveryComponents(cfg *Config) fx.Option { + return fx.Options( fx.Invoke(func(disc *disc.Discovery) {}), fx.Provide(fx.Annotate( newDiscovery(cfg.Discovery), @@ -45,29 +110,71 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return d.Stop(ctx) }), )), - fx.Provide( - func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { - return shrexsub.NewPubSub(ctx, h, network.String()) - }, - ), ) +} - shrexGetterComponents := fx.Options( - fx.Provide(func() peers.Parameters { - return cfg.PeerManagerParams - }), +func peerManagerComponents(tp node.Type, cfg *Config) fx.Option { + switch tp { + case node.Full, node.Light: + return fx.Options( + fx.Provide(func() peers.Parameters { + return cfg.PeerManagerParams + }), + fx.Provide( + func( + params peers.Parameters, + host host.Host, + connGater *conngater.BasicConnectionGater, + shrexSub *shrexsub.PubSub, + headerSub libhead.Subscriber[*header.ExtendedHeader], + // we must ensure Syncer is started before PeerManager + // so that Syncer registers header validator before PeerManager subscribes to headers + _ *sync.Syncer[*header.ExtendedHeader], + ) (*peers.Manager, error) { + return peers.NewManager( + params, + host, + connGater, + peers.WithShrexSubPools(shrexSub, headerSub), + ) + }, + ), + ) + case node.Bridge: + return fx.Provide(peers.NewManager) + default: + panic("invalid node type") + } +} + +func shrexSubComponents() fx.Option { + return fx.Provide( + func(ctx context.Context, h host.Host, network modp2p.Network) (*shrexsub.PubSub, error) { + return shrexsub.NewPubSub(ctx, h, network.String()) + }, + ) +} + +// shrexGetterComponents provides components for a shrex getter that +// is capable of requesting +func shrexGetterComponents(cfg *Config) fx.Option { + return fx.Options( + // shrex-nd client fx.Provide( func(host host.Host, network modp2p.Network) (*shrexnd.Client, error) { cfg.ShrExNDParams.WithNetworkID(network.String()) return shrexnd.NewClient(cfg.ShrExNDParams, host) }, ), + + // shrex-eds client fx.Provide( func(host host.Host, network modp2p.Network) (*shrexeds.Client, error) { cfg.ShrExEDSParams.WithNetworkID(network.String()) return shrexeds.NewClient(cfg.ShrExEDSParams, host) }, ), + fx.Provide(fx.Annotate( getters.NewShrexGetter, fx.OnStart(func(ctx context.Context, getter *getters.ShrexGetter) error { @@ -78,9 +185,10 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option }), )), ) +} - bridgeAndFullComponents := fx.Options( - fx.Provide(getters.NewStoreGetter), +func shrexServerComponents(cfg *Config) fx.Option { + return fx.Options( fx.Invoke(func(edsSrv *shrexeds.Server, ndSrc *shrexnd.Server) {}), fx.Provide(fx.Annotate( func(host host.Host, store *eds.Store, network modp2p.Network) (*shrexeds.Server, error) { @@ -108,8 +216,13 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option }), fx.OnStop(func(ctx context.Context, server *shrexnd.Server) error { return server.Stop(ctx) - }), - )), + })), + ), + ) +} + +func edsStoreComponents(cfg *Config) fx.Option { + return fx.Options( fx.Provide(fx.Annotate( func(path node.StorePath, ds datastore.Batching) (*eds.Store, error) { return eds.NewStore(cfg.EDSStoreParams, string(path), ds) @@ -125,6 +238,11 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option return store.Stop(ctx) }), )), + ) +} + +func fullAvailabilityComponents() fx.Option { + return fx.Options( fx.Provide(fx.Annotate( full.NewShareAvailability, fx.OnStart(func(ctx context.Context, avail *full.ShareAvailability) error { @@ -137,91 +255,24 @@ func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option fx.Provide(func(avail *full.ShareAvailability) share.Availability { return avail }), - fx.Provide(func(shrexSub *shrexsub.PubSub) shrexsub.BroadcastFn { - return shrexSub.Broadcast - }), - ) - - peerManagerWithShrexPools := fx.Options( - fx.Provide( - func( - params peers.Parameters, - host host.Host, - connGater *conngater.BasicConnectionGater, - shrexSub *shrexsub.PubSub, - headerSub libhead.Subscriber[*header.ExtendedHeader], - // we must ensure Syncer is started before PeerManager - // so that Syncer registers header validator before PeerManager subscribes to headers - _ *sync.Syncer[*header.ExtendedHeader], - ) (*peers.Manager, error) { - return peers.NewManager( - params, - host, - connGater, - peers.WithShrexSubPools(shrexSub, headerSub), - ) - }, - ), ) +} - switch tp { - case node.Bridge: - return fx.Module( - "share", - baseComponents, - fx.Provide(peers.NewManager), - bridgeAndFullComponents, - shrexGetterComponents, - fx.Provide(bridgeGetter), - fx.Invoke(func(lc fx.Lifecycle, sub *shrexsub.PubSub) error { - lc.Append(fx.Hook{ - OnStart: sub.Start, - OnStop: sub.Stop, - }) - return nil - }), - ) - case node.Full: - return fx.Module( - "share", - peerManagerWithShrexPools, - baseComponents, - bridgeAndFullComponents, - shrexGetterComponents, - fx.Provide(getters.NewIPLDGetter), - fx.Provide(fullGetter), - ) - case node.Light: - return fx.Module( - "share", - baseComponents, - fx.Provide(func() []light.Option { - return []light.Option{ - light.WithSampleAmount(cfg.LightAvailability.SampleAmount), - } - }), - peerManagerWithShrexPools, - shrexGetterComponents, - fx.Invoke(ensureEmptyEDSInBS), - fx.Provide(getters.NewIPLDGetter), - fx.Provide(lightGetter), - // shrexsub broadcaster stub for daser - fx.Provide(func() shrexsub.BroadcastFn { - return func(context.Context, shrexsub.Notification) error { - return nil - } - }), - fx.Provide(fx.Annotate( - light.NewShareAvailability, - fx.OnStop(func(ctx context.Context, la *light.ShareAvailability) error { - return la.Close(ctx) - }), - )), - fx.Provide(func(avail *light.ShareAvailability) share.Availability { - return avail +func lightAvailabilityComponents(cfg *Config) fx.Option { + return fx.Options( + fx.Provide(fx.Annotate( + light.NewShareAvailability, + fx.OnStop(func(ctx context.Context, la *light.ShareAvailability) error { + return la.Close(ctx) }), - ) - default: - panic("invalid node type") - } + )), + fx.Provide(func() []light.Option { + return []light.Option{ + light.WithSampleAmount(cfg.LightAvailability.SampleAmount), + } + }), + fx.Provide(func(avail *light.ShareAvailability) share.Availability { + return avail + }), + ) } From c65cb4dfcf54a2d5f4eea265f4c0df2cd6775a17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:48:39 +0000 Subject: [PATCH 1006/1008] chore(deps): Bump codecov/codecov-action from 3.1.6 to 4.0.1 (#3162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.6 to 4.0.1.
    Release notes

    Sourced from codecov/codecov-action's releases.

    v4.0.1

    What's Changed

    Full Changelog: https://github.com/codecov/codecov-action/compare/v4.0.0...v4.0.1

    v4.0.0

    v4 of the Codecov Action uses the CLI as the underlying upload. The CLI has helped to power new features including local upload, the global upload token, and new upcoming features.

    Breaking Changes

    • The Codecov Action runs as a node20 action due to node16 deprecation. See this post from GitHub on how to migrate.
    • Tokenless uploading is unsupported. However, PRs made from forks to the upstream public repos will support tokenless (e.g. contributors to OS projects do not need the upstream repo's Codecov token). This doc shows instructions on how to add the Codecov token.
    • OS platforms have been added, though some may not be automatically detected. To see a list of platforms, see our CLI download page
    • Various arguments to the Action have been changed. Please be aware that the arguments match with the CLI's needs

    v3 versions and below will not have access to CLI features (e.g. global upload token, ATS).

    What's Changed

    ... (truncated)

    Changelog

    Sourced from codecov/codecov-action's changelog.

    4.0.0-beta.2

    Fixes

    • #1085 not adding -n if empty to do-upload command

    4.0.0-beta.1

    v4 represents a move from the universal uploader to the Codecov CLI. Although this will unlock new features for our users, the CLI is not yet at feature parity with the universal uploader.

    Breaking Changes

    • No current support for aarch64 and alpine architectures.
    • Tokenless uploading is unsuported
    • Various arguments to the Action have been removed

    3.1.4

    Fixes

    • #967 Fix typo in README.md
    • #971 fix: add back in working dir
    • #969 fix: CLI option names for uploader

    Dependencies

    • #970 build(deps-dev): bump @​types/node from 18.15.12 to 18.16.3
    • #979 build(deps-dev): bump @​types/node from 20.1.0 to 20.1.2
    • #981 build(deps-dev): bump @​types/node from 20.1.2 to 20.1.4

    3.1.3

    Fixes

    • #960 fix: allow for aarch64 build

    Dependencies

    • #957 build(deps-dev): bump jest-junit from 15.0.0 to 16.0.0
    • #958 build(deps): bump openpgp from 5.7.0 to 5.8.0
    • #959 build(deps-dev): bump @​types/node from 18.15.10 to 18.15.12

    3.1.2

    Fixes

    • #718 Update README.md
    • #851 Remove unsupported path_to_write_report argument
    • #898 codeql-analysis.yml
    • #901 Update README to contain correct information - inputs and negate feature
    • #955 fix: add in all the extra arguments for uploader

    Dependencies

    • #819 build(deps): bump openpgp from 5.4.0 to 5.5.0
    • #835 build(deps): bump node-fetch from 3.2.4 to 3.2.10
    • #840 build(deps): bump ossf/scorecard-action from 1.1.1 to 2.0.4
    • #841 build(deps): bump @​actions/core from 1.9.1 to 1.10.0
    • #843 build(deps): bump @​actions/github from 5.0.3 to 5.1.1
    • #869 build(deps): bump node-fetch from 3.2.10 to 3.3.0
    • #872 build(deps-dev): bump jest-junit from 13.2.0 to 15.0.0
    • #879 build(deps): bump decode-uri-component from 0.2.0 to 0.2.2

    ... (truncated)

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codecov/codecov-action&package-manager=github_actions&previous-version=3.1.6&new-version=4.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 9640d38fb9..f912ce1c53 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -98,7 +98,7 @@ jobs: retention-days: 5 - name: upload coverage - uses: codecov/codecov-action@v3.1.6 + uses: codecov/codecov-action@v4.0.1 with: env_vars: OS token: ${{ secrets.CODECOV_TOKEN }} From 74b54e40e360a5a6f7314571dcc312ebc4e22860 Mon Sep 17 00:00:00 2001 From: Qt Date: Mon, 26 Feb 2024 18:25:05 +0800 Subject: [PATCH 1007/1008] improve: use errors.New to replace fmt.Errorf with no parameters (#3206) Some parameterless errors should be using errors.New instead of fmt.Errorf --- api/docgen/examples.go | 3 ++- cmd/auth.go | 2 +- cmd/cel-shed/header.go | 5 +++-- cmd/rpc.go | 2 +- core/fetcher.go | 3 ++- core/listener.go | 2 +- das/daser.go | 2 +- das/options.go | 3 ++- share/eds/inverted_index.go | 2 +- share/eds/store_options.go | 8 ++++---- 10 files changed, 18 insertions(+), 14 deletions(-) diff --git a/api/docgen/examples.go b/api/docgen/examples.go index d90fa3a53f..83d25da6df 100644 --- a/api/docgen/examples.go +++ b/api/docgen/examples.go @@ -3,6 +3,7 @@ package docgen import ( _ "embed" "encoding/json" + "errors" "fmt" "reflect" @@ -66,7 +67,7 @@ var ExampleValues = map[reflect.Type]interface{}{ Shares: []*byzantine.ShareWithProof{}, }, ), - reflect.TypeOf((*error)(nil)).Elem(): fmt.Errorf("error"), + reflect.TypeOf((*error)(nil)).Elem(): errors.New("error"), } func init() { diff --git a/cmd/auth.go b/cmd/auth.go index a637373242..6ffdab656e 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -27,7 +27,7 @@ func AuthCmd(fsets ...*flag.FlagSet) *cobra.Command { "the node has already been initialized and started.", RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return fmt.Errorf("must specify permissions") + return errors.New("must specify permissions") } permissions, err := convertToPerms(args[0]) if err != nil { diff --git a/cmd/cel-shed/header.go b/cmd/cel-shed/header.go index 8216f19698..379e8aac85 100644 --- a/cmd/cel-shed/header.go +++ b/cmd/cel-shed/header.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "strconv" "strings" @@ -30,12 +31,12 @@ Custom store path is not supported yet.`, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 3 { - return fmt.Errorf("not enough arguments") + return errors.New("not enough arguments") } tp := node.ParseType(args[0]) if !tp.IsValid() { - return fmt.Errorf("invalid node-type") + return errors.New("invalid node-type") } network := args[1] diff --git a/cmd/rpc.go b/cmd/rpc.go index 62e9fe7923..1935069229 100644 --- a/cmd/rpc.go +++ b/cmd/rpc.go @@ -49,7 +49,7 @@ func InitClient(cmd *cobra.Command, _ []string) error { if authTokenFlag == "" { storePath := "" if !cmd.Flag(nodeStoreFlag).Changed { - return fmt.Errorf("cant get the access to the auth token: token/node-store flag was not specified") + return errors.New("cant get the access to the auth token: token/node-store flag was not specified") } storePath = cmd.Flag(nodeStoreFlag).Value.String() token, err := getToken(storePath) diff --git a/core/fetcher.go b/core/fetcher.go index c24d6f0fac..35c9a83dc9 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -2,6 +2,7 @@ package core import ( "context" + "errors" "fmt" logging "github.com/ipfs/go-log/v2" @@ -127,7 +128,7 @@ func (f *BlockFetcher) ValidatorSet(ctx context.Context, height *int64) (*types. func (f *BlockFetcher) SubscribeNewBlockEvent(ctx context.Context) (<-chan types.EventDataSignedBlock, error) { // start the client if not started yet if !f.client.IsRunning() { - return nil, fmt.Errorf("client not running") + return nil, errors.New("client not running") } ctx, cancel := context.WithCancel(ctx) diff --git a/core/listener.go b/core/listener.go index 10255fc4cc..367aa34181 100644 --- a/core/listener.go +++ b/core/listener.go @@ -91,7 +91,7 @@ func NewListener( // Start kicks off the Listener listener loop. func (cl *Listener) Start(context.Context) error { if cl.cancel != nil { - return fmt.Errorf("listener: already started") + return errors.New("listener: already started") } ctx, cancel := context.WithCancel(context.Background()) diff --git a/das/daser.go b/das/daser.go index 7d569f7e0b..c04a8dcdb8 100644 --- a/das/daser.go +++ b/das/daser.go @@ -79,7 +79,7 @@ func NewDASer( // Start initiates subscription for new ExtendedHeaders and spawns a sampling routine. func (d *DASer) Start(ctx context.Context) error { if !atomic.CompareAndSwapInt32(&d.running, 0, 1) { - return fmt.Errorf("da: DASer already started") + return errors.New("da: DASer already started") } sub, err := d.hsub.Subscribe() diff --git a/das/options.go b/das/options.go index c2f2b2fedb..69deab52da 100644 --- a/das/options.go +++ b/das/options.go @@ -1,6 +1,7 @@ package das import ( + "errors" "fmt" "time" ) @@ -8,7 +9,7 @@ import ( // ErrInvalidOption is an error that is returned by Parameters.Validate // when supplied with invalid values. // This error will also be returned by NewDASer if supplied with an invalid option -var ErrInvalidOption = fmt.Errorf("das: invalid option") +var ErrInvalidOption = errors.New("das: invalid option") // errInvalidOptionValue is a utility function to dedup code for error-returning // when dealing with invalid parameter values diff --git a/share/eds/inverted_index.go b/share/eds/inverted_index.go index f30d1c37db..799ab6208d 100644 --- a/share/eds/inverted_index.go +++ b/share/eds/inverted_index.go @@ -17,7 +17,7 @@ import ( const invertedIndexPath = "/inverted_index/" // ErrNotFoundInIndex is returned instead of ErrNotFound if the multihash doesn't exist in the index -var ErrNotFoundInIndex = fmt.Errorf("does not exist in index") +var ErrNotFoundInIndex = errors.New("does not exist in index") // simpleInvertedIndex is an inverted index that only stores a single shard key per multihash. Its // implementation is modified from the default upstream implementation in dagstore/index. diff --git a/share/eds/store_options.go b/share/eds/store_options.go index e5e6ffa73d..c8dcc69136 100644 --- a/share/eds/store_options.go +++ b/share/eds/store_options.go @@ -1,7 +1,7 @@ package eds import ( - "fmt" + "errors" "time" ) @@ -29,15 +29,15 @@ func DefaultParameters() *Parameters { func (p *Parameters) Validate() error { if p.GCInterval < 0 { - return fmt.Errorf("eds: GC interval cannot be negative") + return errors.New("eds: GC interval cannot be negative") } if p.RecentBlocksCacheSize < 1 { - return fmt.Errorf("eds: recent blocks cache size must be positive") + return errors.New("eds: recent blocks cache size must be positive") } if p.BlockstoreCacheSize < 1 { - return fmt.Errorf("eds: blockstore cache size must be positive") + return errors.New("eds: blockstore cache size must be positive") } return nil } From 1a2077606c9db0c61482b28425099cafdfd36976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:44:08 +0000 Subject: [PATCH 1008/1008] chore(deps): Bump golangci/golangci-lint-action from 3.7.0 to 4.0.0 (#3180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.7.0 to 4.0.0.
    Release notes

    Sourced from golangci/golangci-lint-action's releases.

    v4.0.0

    What's Changed

    Documentation

    Dependencies

    ... (truncated)

    Commits
    • 3cfe3a4 build(deps): bump @​actions/cache from 3.2.3 to 3.2.4 (#963)
    • cbc59cf build(deps-dev): bump prettier from 3.2.4 to 3.2.5 (#960)
    • 459a04b build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.19.1 to 6.20.0 ...
    • e2315b6 build(deps-dev): bump @​typescript-eslint/parser from 6.19.1 to 6.20.0 (#961)
    • d6173a4 build(deps): bump @​types/node from 20.11.10 to 20.11.16 (#962)
    • 0e8f5bf build(deps): bump @​types/node from 20.11.5 to 20.11.10 (#958)
    • 349d206 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.19.0 to 6.19.1 ...
    • 2221aee build(deps-dev): bump @​typescript-eslint/parser from 6.18.1 to 6.19.1 (#954)
    • 3b44ae5 build(deps-dev): bump @​typescript-eslint/eslint-plugin from 6.18.1 to 6.19.0 ...
    • 323b871 build(deps-dev): bump prettier from 3.2.2 to 3.2.4 (#950)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=3.7.0&new-version=4.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index f912ce1c53..81fec7dfcb 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -41,7 +41,7 @@ jobs: go-version: ${{ inputs.go-version }} - name: golangci-lint - uses: golangci/golangci-lint-action@v3.7.0 + uses: golangci/golangci-lint-action@v4.0.0 with: args: --timeout 10m version: v1.55

    bU;}XwVda@oWTK|atmqq3#vjEh>|=QE9Pfx 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 R_E&*?`}O`^MLT`Z{~w`kIhz0g From 842ae78ca90ae12128813ff42bb616069f6de65f Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Tue, 9 Aug 2022 21:55:52 -0400 Subject: [PATCH 0016/1008] update changelog --- docs/adr/adr-010-incentivized-testnet-monitoring.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 96a9c6b852..4b9288703f 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -9,6 +9,7 @@ - 2022-8-1: Revise architecture to minimize Celestia managed components - 2022-8-4: Add section on "Why doesn't the Celestia team host OTEL Collectors for node operators?" - 2022-8-8: Rename section to "Which actor should run OTEL Collector(s) during the incentivized testnet?" +- 2022-8-9: Update diagrams and add "Scenario D" ## Context From d448bd5c09c860adb857081eaf50f16d8ee43ee8 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Wed, 10 Aug 2022 12:11:56 -0400 Subject: [PATCH 0017/1008] add decision for scenario D --- docs/adr/adr-010-incentivized-testnet-monitoring.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/adr/adr-010-incentivized-testnet-monitoring.md b/docs/adr/adr-010-incentivized-testnet-monitoring.md index 4b9288703f..2d197bb920 100644 --- a/docs/adr/adr-010-incentivized-testnet-monitoring.md +++ b/docs/adr/adr-010-incentivized-testnet-monitoring.md @@ -10,6 +10,7 @@ - 2022-8-4: Add section on "Why doesn't the Celestia team host OTEL Collectors for node operators?" - 2022-8-8: Rename section to "Which actor should run OTEL Collector(s) during the incentivized testnet?" - 2022-8-9: Update diagrams and add "Scenario D" +- 2022-8-10: Add decision for "Which actor should run OTEL Collector(s) during the incentivized testnet?" ## Context @@ -211,6 +212,11 @@ This scenario differs from Scenario C in the docs for node deployment. The docs The docs may also contain steps on connecting the agent OTEL Collector to Prometheus and Grafana. + +#### Decision + +We agreed on **Scenario D** at this time. Our docs will describe how to connect to the Celestia team managed OTEL Collector (i.e. `--metrics.endpoint`). Our docs will also include an advanced guide for node operators if they wish to deploy a self-managed OTEL Collector. + ### Should node operators be able to configure celestia-node to export to multiple OTEL collectors? This is not supported by [open-telemetry/opentelemetry-go#3055](https://github.com/open-telemetry/opentelemetry-go/issues/3055). This means node operators can only configure one OTLP backend for their node. If they wish to export metrics to multiple OTEL Collectors then they must route traffic through an agent OTEL Collector that they have deployed. Their agent OTEL Collector can forward metrics to any other OTEL Collector that they have access to. From e46aea45e0239faf268004697900192217268c2d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:27:09 +0200 Subject: [PATCH 0018/1008] chore: add closing bracket to description in cel-key (#995) --- cmd/cel-key/node_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 5fb3dea225..f167ca345f 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -20,7 +20,7 @@ var ( func DirectoryFlags() *flag.FlagSet { flags := &flag.FlagSet{} flags.String(nodeDirKey, "", "Sets key utility to use the node type's directory (e.g. "+ - "~/.celestia-light if --node.type light is passed.") + "~/.celestia-light if --node.type light is passed).") return flags } From cdbc28a172e2850200e4054e0ec599406205f150 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 12 Aug 2022 02:34:47 -0700 Subject: [PATCH 0019/1008] feat(ipld): concurrent GetSharesByNamespace (#970) * feat(ipld): concurrent SharesByNamespace * fix: incrementing wg counter before adding new jobs * moving to own file, adding WrappedWaitGroup, changing loop condition * removing result struct * overallocating with row length, adding comments * local imports * changing buffer size * fixes from review * fix: atomic counter in wrappedWaitGroup and fetchedBounds * fix: changing loop syntax and bounds update api * removing wrappedWaitGroup constructor * Update ipld/get_namespaced_shares.go Co-authored-by: Hlib Kanunnikov * improvement: loop optimization from Wondertan * feat: errgroup wrapping retrieval errors * linter: goimports error * test: Adding test for partial retrieval, opening discussion to return data * feat: adding tracing * fix: fixing race between counter read and channel close * fix: not passing context from errgroup to avoid unwanted cancellation * linter: goimports makes me cry * refactor: removing errgroup, removing unnecessary sync.Once, simplifying logic * tracing: removing traces in get_shares.go for separate pr * lint: goimports * Apply suggestions from code review Co-authored-by: Hlib Kanunnikov * Apply suggestions from code review Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> * refactor: fixes from reviews * fix: comment punctuation and import formatting * docs: adding issue number for pool worker limit Co-authored-by: Hlib Kanunnikov Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- ipld/get.go | 63 ---------- ipld/get_namespaced_shares.go | 222 ++++++++++++++++++++++++++++++++++ ipld/get_shares.go | 7 +- ipld/get_test.go | 60 ++++++++- ipld/helpers.go | 12 +- service/share/share.go | 3 +- 6 files changed, 290 insertions(+), 77 deletions(-) create mode 100644 ipld/get_namespaced_shares.go diff --git a/ipld/get.go b/ipld/get.go index 6e00ca5f32..b05f9af385 100644 --- a/ipld/get.go +++ b/ipld/get.go @@ -8,9 +8,6 @@ import ( ipld "github.com/ipfs/go-ipld-format" "github.com/celestiaorg/celestia-node/ipld/plugin" - "github.com/celestiaorg/nmt" - - "github.com/celestiaorg/nmt/namespace" ) // GetShare fetches and returns the data for leaf `leafIndex` of root `rootCid`. @@ -126,66 +123,6 @@ func GetProof( return GetProof(ctx, bGetter, root, proof, leaf, total) } -// GetSharesByNamespace returns all the shares from the given root -// with the given namespace.ID. -func GetSharesByNamespace( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - nID namespace.ID, -) ([]Share, error) { - leaves, err := GetLeavesByNamespace(ctx, bGetter, root, nID) - if err != nil { - return nil, err - } - - shares := make([]Share, len(leaves)) - for i, leaf := range leaves { - shares[i] = leafToShare(leaf) - } - - return shares, nil -} - -// GetLeavesByNamespace returns all the leaves from the given root with the given namespace.ID. -// If nothing is found it returns both data and err as nil. -func GetLeavesByNamespace( - ctx context.Context, - bGetter blockservice.BlockGetter, - root cid.Cid, - nID namespace.ID, -) ([]ipld.Node, error) { - err := SanityCheckNID(nID) - if err != nil { - return nil, err - } - rootH := plugin.NamespacedSha256FromCID(root) - if nID.Less(nmt.MinNamespace(rootH, nID.Size())) || !nID.LessOrEqual(nmt.MaxNamespace(rootH, nID.Size())) { - return nil, nil - } - // request the node - nd, err := plugin.GetNode(ctx, bGetter, root) - if err != nil { - return nil, err - } - // check links - lnks := nd.Links() - if len(lnks) == 1 { - // if there is one link, then this is a leaf node, so just return it - return []ipld.Node{nd}, nil - } - // if there are some links, then traverse them - var out []ipld.Node - for _, lnk := range nd.Links() { - nds, err := GetLeavesByNamespace(ctx, bGetter, lnk.Cid, nID) - if err != nil { - return out, err - } - out = append(out, nds...) - } - return out, nil -} - // leafToShare converts an NMT leaf into a Share. func leafToShare(nd ipld.Node) Share { // * First byte represents the type of the node, and is unrelated to the actual share data diff --git a/ipld/get_namespaced_shares.go b/ipld/get_namespaced_shares.go new file mode 100644 index 0000000000..80b870a33e --- /dev/null +++ b/ipld/get_namespaced_shares.go @@ -0,0 +1,222 @@ +package ipld + +import ( + "context" + "sync" + "sync/atomic" + + "github.com/gammazero/workerpool" + "github.com/ipfs/go-blockservice" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + + "github.com/celestiaorg/celestia-node/ipld/plugin" + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" +) + +// TODO(@distractedm1nd) Find a better figure than NumWorkersLimit for this pool. Issue #970 +// namespacePool is a worker pool responsible for the goroutines spawned by getLeavesByNamespace +var namespacePool = workerpool.New(NumWorkersLimit) + +// GetSharesByNamespace walks the tree of a given root and returns its shares within the given namespace.ID. +// If a share could not be retrieved, err is not nil, and the returned array +// contains nil shares in place of the shares it was unable to retrieve. +func GetSharesByNamespace( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + nID namespace.ID, + maxShares int, +) ([]Share, error) { + ctx, span := tracer.Start(ctx, "get-shares-by-namespace") + defer span.End() + + leaves, err := getLeavesByNamespace(ctx, bGetter, root, nID, maxShares) + if err != nil && leaves == nil { + return nil, err + } + + shares := make([]Share, len(leaves)) + for i, leaf := range leaves { + if leaf != nil { + shares[i] = leafToShare(leaf) + } + } + + return shares, err +} + +// wrappedWaitGroup is needed because waitgroups do not expose their internal counter, +// and we don't know in advance how many jobs we will have to wait for. +type wrappedWaitGroup struct { + wg sync.WaitGroup + jobs chan *job + counter int64 +} + +func (w *wrappedWaitGroup) add(count int64) { + w.wg.Add(int(count)) + atomic.AddInt64(&w.counter, count) +} + +func (w *wrappedWaitGroup) done() { + w.wg.Done() + numRemaining := atomic.AddInt64(&w.counter, -1) + + // Close channel if this job was the last one + if numRemaining == 0 { + close(w.jobs) + } +} + +type fetchedBounds struct { + lowest int64 + highest int64 +} + +// update checks if the passed index is outside the current bounds, +// and updates the bounds atomically if it extends them. +func (b *fetchedBounds) update(index int64) { + lowest := atomic.LoadInt64(&b.lowest) + // try to write index to the lower bound if appropriate, and retry until the atomic op is successful + // CAS ensures that we don't overwrite if the bound has been updated in another goroutine after the comparison here + for index < lowest && !atomic.CompareAndSwapInt64(&b.lowest, lowest, index) { + lowest = atomic.LoadInt64(&b.lowest) + } + // we always run both checks because element can be both the lower and higher bound + // for example, if there is only one share in the namespace + highest := atomic.LoadInt64(&b.highest) + for index > highest && !atomic.CompareAndSwapInt64(&b.highest, highest, index) { + highest = atomic.LoadInt64(&b.highest) + } +} + +// getLeavesByNamespace returns as many leaves from the given root with the given namespace.ID as it can retrieve. +// If no shares are found, it returns both data and error as nil. +// A non-nil error means that only partial data is returned, because at least one share retrieval failed +// The following implementation is based on `GetShares`. +func getLeavesByNamespace( + ctx context.Context, + bGetter blockservice.BlockGetter, + root cid.Cid, + nID namespace.ID, + maxShares int, +) ([]ipld.Node, error) { + err := SanityCheckNID(nID) + if err != nil { + return nil, err + } + + ctx, span := tracer.Start(ctx, "get-leaves-by-namespace") + defer span.End() + + span.SetAttributes( + attribute.String("namespace", nID.String()), + attribute.String("root", root.String()), + ) + + // we don't know where in the tree the leaves in the namespace are, + // so we keep track of the bounds to return the correct slice + // maxShares acts as a sentinel to know if we find any leaves + bounds := fetchedBounds{int64(maxShares), 0} + + // buffer the jobs to avoid blocking, we only need as many + // queued as the number of shares in the second-to-last layer + jobs := make(chan *job, (maxShares+1)/2) + jobs <- &job{id: root} + + var wg wrappedWaitGroup + wg.jobs = jobs + wg.add(1) + + var ( + singleErr sync.Once + retrievalErr error + ) + + // we overallocate space for leaves since we do not know how many we will find + // on the level above, the length of the Row is passed in as maxShares + leaves := make([]ipld.Node, maxShares) + + for { + select { + case j, ok := <-jobs: + if !ok { + // if there were no leaves under the given root in the given namespace, + // both return values are nil. otherwise, the error will also be non-nil. + if bounds.lowest == int64(maxShares) { + return nil, retrievalErr + } + + return leaves[bounds.lowest : bounds.highest+1], retrievalErr + } + namespacePool.Submit(func() { + ctx, span := tracer.Start(ctx, "process-job") + defer span.End() + defer wg.done() + + // if an error is likely to be returned or not depends on + // the underlying impl of the blockservice, currently it is not a realistic probability + nd, err := plugin.GetNode(ctx, bGetter, j.id) + if err != nil { + singleErr.Do(func() { + retrievalErr = err + }) + log.Errorw("getSharesByNamespace: could not retrieve node", "nID", nID, "pos", j.pos, "err", err) + span.RecordError(err, trace.WithAttributes( + attribute.Int("pos", j.pos), + )) + // we still need to update the bounds + bounds.update(int64(j.pos)) + return + } + + links := nd.Links() + linkCount := uint64(len(links)) + if linkCount == 1 { + // successfully fetched a leaf belonging to the namespace + span.AddEvent("found-leaf") + leaves[j.pos] = nd + // we found a leaf, so we update the bounds + // the update routine is repeated until the atomic swap is successful + bounds.update(int64(j.pos)) + return + } + + // this node has links in the namespace, so keep walking + for i, lnk := range links { + newJob := &job{ + id: lnk.Cid, + // position represents the index in a flattened binary tree, + // so we can return a slice of leaves in order + pos: j.pos*2 + i, + } + + // if the link's nID isn't in range we don't need to create a new job for it + jobNid := plugin.NamespacedSha256FromCID(newJob.id) + if nID.Less(nmt.MinNamespace(jobNid, nID.Size())) || !nID.LessOrEqual(nmt.MaxNamespace(jobNid, nID.Size())) { + continue + } + + // by passing the previous check, we know we will have one more node to process + // note: it is important to increase the counter before sending to the channel + wg.add(1) + select { + case jobs <- newJob: + span.AddEvent("added-job", trace.WithAttributes( + attribute.String("cid", newJob.id.String()), + attribute.Int("pos", newJob.pos), + )) + case <-ctx.Done(): + return + } + } + }) + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} diff --git a/ipld/get_shares.go b/ipld/get_shares.go index 5253a1f2eb..07186cc20c 100644 --- a/ipld/get_shares.go +++ b/ipld/get_shares.go @@ -52,11 +52,6 @@ var pool = workerpool.New(NumWorkersLimit) // implementation that rely on this property are explicitly tagged with // (bin-tree-feat). func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, shares int, put func(int, Share)) { - // job is not used anywhere else, so can be kept here - type job struct { - id cid.Cid - pos int - } // this buffer ensures writes to 'jobs' are never blocking (bin-tree-feat) jobs := make(chan *job, (shares+1)/2) // +1 for the case where 'shares' is 1 jobs <- &job{id: root} @@ -91,7 +86,7 @@ func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.C return } // successfully fetched a share/leaf - // ladies and gentlemen, we got em! + // ladies and gentlemen, we got em put(j.pos, leafToShare(nd)) return } diff --git a/ipld/get_test.go b/ipld/get_test.go index dade592cfa..e925fc43cb 100644 --- a/ipld/get_test.go +++ b/ipld/get_test.go @@ -169,7 +169,7 @@ func TestGetSharesByNamespace(t *testing.T) { for _, row := range eds.RowRoots() { rcid := plugin.MustCidFromNamespacedSha256(row) - shares, err := GetSharesByNamespace(ctx, bServ, rcid, nID) + shares, err := GetSharesByNamespace(ctx, bServ, rcid, nID, len(eds.RowRoots())) require.NoError(t, err) for _, share := range shares { @@ -180,6 +180,53 @@ func TestGetSharesByNamespace(t *testing.T) { } } +func TestGetLeavesByNamespace_IncompleteData(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + bServ := mdutils.Bserv() + + shares := RandShares(t, 16) + + // set all shares to the same namespace id + nid := shares[0][:NamespaceSize] + + for i, nspace := range shares { + if i == len(shares) { + break + } + + copy(nspace[:NamespaceSize], nid) + } + + eds, err := AddShares(ctx, shares, bServ) + require.NoError(t, err) + + roots := eds.RowRoots() + + // remove the second share from the first row + rcid := plugin.MustCidFromNamespacedSha256(roots[0]) + node, err := plugin.GetNode(ctx, bServ, rcid) + require.NoError(t, err) + + // Left side of the tree contains the original shares + data, err := plugin.GetNode(ctx, bServ, node.Links()[0].Cid) + require.NoError(t, err) + + // Second share is the left side's right child + l, err := plugin.GetNode(ctx, bServ, data.Links()[0].Cid) + require.NoError(t, err) + r, err := plugin.GetNode(ctx, bServ, l.Links()[1].Cid) + require.NoError(t, err) + err = bServ.DeleteBlock(ctx, r.Cid()) + require.NoError(t, err) + + nodes, err := getLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) + assert.Equal(t, nil, nodes[1]) + // TODO(distractedm1nd): Decide if we should return an array containing nil + assert.Equal(t, 4, len(nodes)) + require.Error(t, err) +} + func TestGetLeavesByNamespace_AbsentNamespaceId(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -255,11 +302,11 @@ func TestGetLeavesByNamespace_MultipleRowsContainingSameNamespaceId(t *testing.T for _, row := range eds.RowRoots() { rcid := plugin.MustCidFromNamespacedSha256(row) - nodes, err := GetLeavesByNamespace(ctx, bServ, rcid, nid) - require.NoError(t, err) + nodes, err := getLeavesByNamespace(ctx, bServ, rcid, nid, len(shares)) + assert.Nil(t, err) for _, node := range nodes { - // test that the data returned by GetLeavesByNamespace for nid + // test that the data returned by getLeavesByNamespace for nid // matches the commonNamespaceData that was copied across almost all data share := node.RawData()[1:] assert.Equal(t, commonNamespaceData, share[NamespaceSize:]) @@ -309,15 +356,16 @@ func assertNoRowContainsNID( eds *rsmt2d.ExtendedDataSquare, nID namespace.ID, ) { + rowRootCount := len(eds.RowRoots()) // get all row root cids - rowRootCIDs := make([]cid.Cid, len(eds.RowRoots())) + rowRootCIDs := make([]cid.Cid, rowRootCount) for i, rowRoot := range eds.RowRoots() { rowRootCIDs[i] = plugin.MustCidFromNamespacedSha256(rowRoot) } // for each row root cid check if the minNID exists for _, rowCID := range rowRootCIDs { - data, err := GetLeavesByNamespace(context.Background(), bServ, rowCID, nID) + data, err := getLeavesByNamespace(context.Background(), bServ, rowCID, nID, rowRootCount) assert.Nil(t, data) assert.Nil(t, err) } diff --git a/ipld/helpers.go b/ipld/helpers.go index 791f7ee239..17e1c6b4da 100644 --- a/ipld/helpers.go +++ b/ipld/helpers.go @@ -1,6 +1,16 @@ package ipld -import "fmt" +import ( + "fmt" + + "github.com/ipfs/go-cid" +) + +// job represents an encountered node to investigate during the `GetShares` and `GetSharesByNamespace` routines. +type job struct { + id cid.Cid + pos int +} func SanityCheckNID(nID []byte) error { if len(nID) != NamespaceSize { diff --git a/service/share/share.go b/service/share/share.go index 85a1bb679f..85124bc7e6 100644 --- a/service/share/share.go +++ b/service/share/share.go @@ -122,6 +122,7 @@ func (s *Service) GetShares(ctx context.Context, root *Root) ([][]Share, error) return shares, nil } +// GetSharesByNamespace iterates over a square's row roots and accumulates the found shares in the given namespace.ID. func (s *Service) GetSharesByNamespace(ctx context.Context, root *Root, nID namespace.ID) ([]Share, error) { err := ipld.SanityCheckNID(nID) if err != nil { @@ -143,7 +144,7 @@ func (s *Service) GetSharesByNamespace(ctx context.Context, root *Root, nID name // shadow loop variables, to ensure correct values are captured i, rootCID := i, rootCID errGroup.Go(func() (err error) { - shares[i], err = ipld.GetSharesByNamespace(ctx, s.bServ, rootCID, nID) + shares[i], err = ipld.GetSharesByNamespace(ctx, s.bServ, rootCID, nID, len(root.RowsRoots)) return }) } From 1c0c9cb55b84fa60c898460ab98f65209e3cca67 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 12 Aug 2022 05:18:41 -0700 Subject: [PATCH 0020/1008] cmd: Drop Env in favour of vanilla context.WithValue wrappers (#1004) * wip: proof of concept for discussion * fix: no strings as keys for context * fix: typecast error, removing header options for bridge node * Update cmd/env.go Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> * refactor: var blocks and defferring context set * refactor: matching context setter naming pattern * refactor: reverting defer, fixing comments * refactor: WithNodeOptions Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- cmd/cel-key/main.go | 3 +- cmd/cel-shed/main.go | 4 +-- cmd/celestia/bridge.go | 23 +++++++------- cmd/celestia/cmd_test.go | 6 ++-- cmd/celestia/full.go | 25 ++++++++------- cmd/celestia/light.go | 26 ++++++++------- cmd/celestia/main.go | 3 +- cmd/env.go | 69 ++++++++++++++++------------------------ cmd/flags_core.go | 19 +++++------ cmd/flags_header.go | 31 +++++++++--------- cmd/flags_key.go | 7 ++-- cmd/flags_misc.go | 23 +++++++------- cmd/flags_node.go | 11 ++++--- cmd/flags_p2p.go | 11 ++++--- cmd/flags_rpc.go | 10 +++--- cmd/init.go | 7 ++-- cmd/start.go | 9 ++---- 17 files changed, 138 insertions(+), 149 deletions(-) diff --git a/cmd/cel-key/main.go b/cmd/cel-key/main.go index d22e0f3696..e65f0441a4 100644 --- a/cmd/cel-key/main.go +++ b/cmd/cel-key/main.go @@ -14,7 +14,6 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" - "github.com/celestiaorg/celestia-node/cmd" ) var encodingConfig = encoding.MakeEncodingConfig(app.ModuleEncodingRegisters...) @@ -65,5 +64,5 @@ func run() error { cfg.Seal() ctx := context.WithValue(context.Background(), client.ClientContextKey, &initClientCtx) - return rootCmd.ExecuteContext(cmd.WithEnv(ctx)) + return rootCmd.ExecuteContext(ctx) } diff --git a/cmd/cel-shed/main.go b/cmd/cel-shed/main.go index 75c47c4044..7982cfc1be 100644 --- a/cmd/cel-shed/main.go +++ b/cmd/cel-shed/main.go @@ -5,8 +5,6 @@ import ( "os" "github.com/spf13/cobra" - - "github.com/celestiaorg/celestia-node/cmd" ) func init() { @@ -28,5 +26,5 @@ func main() { } func run() error { - return rootCmd.ExecuteContext(cmd.WithEnv(context.Background())) + return rootCmd.ExecuteContext(context.Background()) } diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index a5937e982c..04491866e4 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -36,40 +36,41 @@ var bridgeCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Bridge node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - env, err := cmdnode.GetEnv(cmd.Context()) - if err != nil { - return err - } + var ( + ctx = cmd.Context() + err error + ) - env.SetNodeType(node.Bridge) + ctx = cmdnode.WithNodeType(ctx, node.Bridge) - err = cmdnode.ParseNodeFlags(cmd, env) + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseP2PFlags(cmd, env) + ctx, err = cmdnode.ParseP2PFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseCoreFlags(cmd, env) + ctx, err = cmdnode.ParseCoreFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseMiscFlags(cmd, env) + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseRPCFlags(cmd, env) + ctx, err = cmdnode.ParseRPCFlags(ctx, cmd) if err != nil { return err } - cmdnode.ParseKeyFlags(cmd, env) + ctx = cmdnode.ParseKeyFlags(ctx, cmd) + cmd.SetContext(ctx) return nil }, } diff --git a/cmd/celestia/cmd_test.go b/cmd/celestia/cmd_test.go index 0fee7ebf17..4d1dee1b08 100644 --- a/cmd/celestia/cmd_test.go +++ b/cmd/celestia/cmd_test.go @@ -8,8 +8,6 @@ import ( "testing" "github.com/stretchr/testify/require" - - cmdnode "github.com/celestiaorg/celestia-node/cmd" ) func TestLight(t *testing.T) { @@ -29,7 +27,7 @@ func TestLight(t *testing.T) { "--node.store", ".celestia-light", "init", }) - err := rootCmd.ExecuteContext(cmdnode.WithEnv(context.Background())) + err := rootCmd.ExecuteContext(context.Background()) require.NoError(t, err) }) @@ -76,7 +74,7 @@ func TestBridge(t *testing.T) { "--node.store", ".celestia-bridge", "init", }) - err := rootCmd.ExecuteContext(cmdnode.WithEnv(context.Background())) + err := rootCmd.ExecuteContext(context.Background()) require.NoError(t, err) }) diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 1c61f1f1e3..a30c761898 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -43,45 +43,46 @@ var fullCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Full node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - env, err := cmdnode.GetEnv(cmd.Context()) - if err != nil { - return err - } + var ( + ctx = cmd.Context() + err error + ) - env.SetNodeType(node.Full) + ctx = cmdnode.WithNodeType(ctx, node.Full) - err = cmdnode.ParseNodeFlags(cmd, env) + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseP2PFlags(cmd, env) + ctx, err = cmdnode.ParseP2PFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseCoreFlags(cmd, env) + ctx, err = cmdnode.ParseCoreFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseHeadersFlags(cmd, env) + ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseMiscFlags(cmd, env) + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseRPCFlags(cmd, env) + ctx, err = cmdnode.ParseRPCFlags(ctx, cmd) if err != nil { return err } - cmdnode.ParseKeyFlags(cmd, env) + ctx = cmdnode.ParseKeyFlags(ctx, cmd) + cmd.SetContext(ctx) return nil }, } diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 452c54b6f5..72bbbfd269 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -43,44 +43,46 @@ var lightCmd = &cobra.Command{ Args: cobra.NoArgs, Short: "Manage your Light node", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - env, err := cmdnode.GetEnv(cmd.Context()) - if err != nil { - return err - } - env.SetNodeType(node.Light) + var ( + ctx = cmd.Context() + err error + ) + + ctx = cmdnode.WithNodeType(ctx, node.Light) - err = cmdnode.ParseNodeFlags(cmd, env) + ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseP2PFlags(cmd, env) + ctx, err = cmdnode.ParseP2PFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseCoreFlags(cmd, env) + ctx, err = cmdnode.ParseCoreFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseHeadersFlags(cmd, env) + ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseMiscFlags(cmd, env) + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) if err != nil { return err } - err = cmdnode.ParseRPCFlags(cmd, env) + ctx, err = cmdnode.ParseRPCFlags(ctx, cmd) if err != nil { return err } - cmdnode.ParseKeyFlags(cmd, env) + ctx = cmdnode.ParseKeyFlags(ctx, cmd) + cmd.SetContext(ctx) return nil }, } diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index 74c5bf4062..a004b8cbf6 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/cobra" "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-node/cmd" ) func init() { @@ -39,7 +38,7 @@ func main() { func run() error { rand.Seed(time.Now().Unix()) - return rootCmd.ExecuteContext(cmd.WithEnv(context.Background())) + return rootCmd.ExecuteContext(context.Background()) } var rootCmd = &cobra.Command{ diff --git a/cmd/env.go b/cmd/env.go index 8c4c0e155b..2d085a96db 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -2,60 +2,47 @@ package cmd import ( "context" - "fmt" "github.com/celestiaorg/celestia-node/node" ) -// Env is an environment for CLI commands. -// It can be used to: -// 1. Propagate values from parent to child commands. -// 2. To group common logic that multiple commands rely on. -// Usage can be extended. -// TODO(@Wondertan): We should move to using context only instead. -// Env, in fact, only keeps some additional fields which should be -// kept in the context directly using WithValue (#965) -type Env struct { - NodeType node.Type - StorePath string - - opts []node.Option +// NodeType reads the node.Type from the context. +func NodeType(ctx context.Context) node.Type { + return ctx.Value(nodeTypeKey{}).(node.Type) } -// WithEnv wraps given ctx with Env. -func WithEnv(ctx context.Context) context.Context { - _, err := GetEnv(ctx) - if err == nil { - panic("cmd: only one Env is allowed to be set in a ctx") - } - - return context.WithValue(ctx, envCtxKey{}, &Env{}) +// StorePath reads the store path from the context. +func StorePath(ctx context.Context) string { + return ctx.Value(storePathKey{}).(string) } -// GetEnv takes Env from the given ctx, if any. -func GetEnv(ctx context.Context) (*Env, error) { - env, ok := ctx.Value(envCtxKey{}).(*Env) - if !ok { - return nil, fmt.Errorf("cmd: Env is not set in ctx.Context") - } - - return env, nil +// WithNodeType sets Node Type in the given context. +func WithNodeType(ctx context.Context, tp node.Type) context.Context { + return context.WithValue(ctx, nodeTypeKey{}, tp) } -// SetNodeType sets Node Type to the Env. -func (env *Env) SetNodeType(tp node.Type) { - env.NodeType = tp +// WithStorePath sets Store Path in the given context. +func WithStorePath(ctx context.Context, storePath string) context.Context { + return context.WithValue(ctx, storePathKey{}, storePath) } -// Options returns Node Options parsed from Environment(Flags, ENV vars, etc) -func (env *Env) Options() []node.Option { - return env.opts +// NodeOptions returns node options parsed from Environment(Flags, ENV vars, etc) +func NodeOptions(ctx context.Context) []node.Option { + options, ok := ctx.Value(optionsKey{}).([]node.Option) + if !ok { + return []node.Option{} + } + return options } -// AddOptions add new options to Env. -func (env *Env) AddOptions(opts ...node.Option) { - env.opts = append(env.opts, opts...) +// WithNodeOptions add new options to Env. +func WithNodeOptions(ctx context.Context, opts ...node.Option) context.Context { + options := NodeOptions(ctx) + return context.WithValue(ctx, optionsKey{}, append(options, opts...)) } -// envCtxKey is a key used to identify Env on a ctx.Context. -type envCtxKey struct{} +type ( + optionsKey struct{} + storePathKey struct{} + nodeTypeKey struct{} +) diff --git a/cmd/flags_core.go b/cmd/flags_core.go index 3c05e87ed0..93dd7f8b98 100644 --- a/cmd/flags_core.go +++ b/cmd/flags_core.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "strconv" "strings" @@ -42,36 +43,36 @@ func CoreFlags() *flag.FlagSet { } // ParseCoreFlags parses Core flags from the given cmd and applies values to Env. -func ParseCoreFlags(cmd *cobra.Command, env *Env) error { +func ParseCoreFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { coreIP := cmd.Flag(coreFlag).Value.String() if coreIP == "" { if cmd.Flag(coreGRPCFlag).Changed || cmd.Flag(coreRPCFlag).Changed { - return fmt.Errorf("cannot specify RPC/gRPC ports without specifying an IP address for --core.ip") + return ctx, fmt.Errorf("cannot specify RPC/gRPC ports without specifying an IP address for --core.ip") } - return nil + return ctx, nil } // sanity check given core ip addr and strip leading protocol ip, err := sanityCheckIP(coreIP) if err != nil { - return err + return ctx, err } rpc := cmd.Flag(coreRPCFlag).Value.String() // sanity check rpc endpoint _, err = strconv.Atoi(rpc) if err != nil { - return err + return ctx, err } - env.AddOptions(node.WithRemoteCoreIP(ip), node.WithRemoteCorePort(rpc)) + ctx = WithNodeOptions(ctx, node.WithRemoteCoreIP(ip), node.WithRemoteCorePort(rpc)) grpc := cmd.Flag(coreGRPCFlag).Value.String() // sanity check gRPC endpoint _, err = strconv.Atoi(grpc) if err != nil { - return err + return ctx, err } - env.AddOptions(node.WithGRPCPort(grpc)) - return nil + ctx = WithNodeOptions(ctx, node.WithGRPCPort(grpc)) + return ctx, nil } // sanityCheckIP trims leading protocol scheme and port from the given diff --git a/cmd/flags_header.go b/cmd/flags_header.go index 669665dc51..d32226d471 100644 --- a/cmd/flags_header.go +++ b/cmd/flags_header.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "encoding/hex" "fmt" @@ -27,15 +28,15 @@ func HeadersFlags() *flag.FlagSet { } // ParseHeadersFlags parses Header package flags from the given cmd and applies values to Env. -func ParseHeadersFlags(cmd *cobra.Command, env *Env) error { - if err := ParseTrustedHashFlags(cmd, env); err != nil { - return err +func ParseHeadersFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { + if ctx, err := ParseTrustedHashFlags(ctx, cmd); err != nil { + return ctx, err } - if err := ParseTrustedPeerFlags(cmd, env); err != nil { - return err + if ctx, err := ParseTrustedPeerFlags(ctx, cmd); err != nil { + return ctx, err } - return nil + return ctx, nil } // TrustedPeersFlags returns a set of flags. @@ -51,22 +52,22 @@ func TrustedPeersFlags() *flag.FlagSet { } // ParseTrustedPeerFlags parses Header package flags from the given cmd and applies values to Env. -func ParseTrustedPeerFlags(cmd *cobra.Command, env *Env) error { +func ParseTrustedPeerFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { tpeers, err := cmd.Flags().GetStringSlice(headersTrustedPeersFlag) if err != nil { - return err + return ctx, err } for _, tpeer := range tpeers { _, err := multiaddr.NewMultiaddr(tpeer) if err != nil { - return fmt.Errorf("cmd: while parsing '%s' with peer addr '%s': %w", headersTrustedPeersFlag, tpeer, err) + return ctx, fmt.Errorf("cmd: while parsing '%s' with peer addr '%s': %w", headersTrustedPeersFlag, tpeer, err) } } - env.AddOptions(node.WithTrustedPeers(tpeers...)) + ctx = WithNodeOptions(ctx, node.WithTrustedPeers(tpeers...)) - return nil + return ctx, nil } // TrustedHashFlags returns a set of flags related to configuring a `TrustedHash`. @@ -83,16 +84,16 @@ func TrustedHashFlags() *flag.FlagSet { } // ParseTrustedHashFlags parses Header package flags from the given cmd and applies values to Env. -func ParseTrustedHashFlags(cmd *cobra.Command, env *Env) error { +func ParseTrustedHashFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { hash := cmd.Flag(headersTrustedHashFlag).Value.String() if hash != "" { _, err := hex.DecodeString(hash) if err != nil { - return fmt.Errorf("cmd: while parsing '%s': %w", headersTrustedHashFlag, err) + return ctx, fmt.Errorf("cmd: while parsing '%s': %w", headersTrustedHashFlag, err) } - env.AddOptions(node.WithTrustedHash(hash)) + ctx = WithNodeOptions(ctx, node.WithTrustedHash(hash)) } - return nil + return ctx, nil } diff --git a/cmd/flags_key.go b/cmd/flags_key.go index 58c6a0e753..86253ce8be 100644 --- a/cmd/flags_key.go +++ b/cmd/flags_key.go @@ -4,6 +4,8 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" + "context" + "github.com/celestiaorg/celestia-node/node" ) @@ -17,9 +19,10 @@ func KeyFlags() *flag.FlagSet { return flags } -func ParseKeyFlags(cmd *cobra.Command, env *Env) { +func ParseKeyFlags(ctx context.Context, cmd *cobra.Command) context.Context { keyringAccName := cmd.Flag(keyringAccNameFlag).Value.String() if keyringAccName != "" { - env.AddOptions(node.WithKeyringAccName(keyringAccName)) + return WithNodeOptions(ctx, node.WithKeyringAccName(keyringAccName)) } + return ctx } diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 60363cb79c..92c451326c 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "log" "net/http" @@ -101,12 +102,12 @@ and their lower-case forms`, } // ParseMiscFlags parses miscellaneous flags from the given cmd and applies values to Env. -func ParseMiscFlags(cmd *cobra.Command, env *Env) error { +func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { logLevel := cmd.Flag(logLevelFlag).Value.String() if logLevel != "" { level, err := logging.LevelFromString(logLevel) if err != nil { - return fmt.Errorf("cmd: while parsing '%s': %w", logLevelFlag, err) + return ctx, fmt.Errorf("cmd: while parsing '%s': %w", logLevelFlag, err) } logs.SetAllLoggers(level) @@ -119,12 +120,12 @@ func ParseMiscFlags(cmd *cobra.Command, env *Env) error { for _, ll := range logModules { params := strings.Split(ll, ":") if len(params) != 2 { - return fmt.Errorf("cmd: %s arg must be in form :, e.g. pubsub:debug", logLevelModuleFlag) + return ctx, fmt.Errorf("cmd: %s arg must be in form :, e.g. pubsub:debug", logLevelModuleFlag) } err := logging.SetLogLevel(params[0], params[1]) if err != nil { - return err + return ctx, err } } @@ -166,7 +167,7 @@ func ParseMiscFlags(cmd *cobra.Command, env *Env) error { exp, err := otlptracehttp.New(cmd.Context(), opts...) if err != nil { - return err + return ctx, err } tp := tracesdk.NewTracerProvider( @@ -175,7 +176,7 @@ func ParseMiscFlags(cmd *cobra.Command, env *Env) error { // Record information about this application in a Resource. tracesdk.WithResource(resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", env.NodeType.String())), + semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", NodeType(ctx).String())), // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey )), ) @@ -200,7 +201,7 @@ func ParseMiscFlags(cmd *cobra.Command, env *Env) error { exp, err := otlpmetrichttp.New(cmd.Context(), opts...) if err != nil { - return err + return ctx, err } pusher := controller.New( @@ -212,19 +213,19 @@ func ParseMiscFlags(cmd *cobra.Command, env *Env) error { controller.WithCollectPeriod(2*time.Second), controller.WithResource(resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", env.NodeType.String())), + semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", NodeType(ctx).String())), // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey )), ) err = pusher.Start(cmd.Context()) if err != nil { - return err + return ctx, err } global.SetMeterProvider(pusher) - env.AddOptions(node.WithMetrics(true)) + ctx = WithNodeOptions(ctx, node.WithMetrics(true)) } - return err + return ctx, err } diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 776d06ce03..9d3ad72dfa 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "strings" @@ -34,18 +35,18 @@ func NodeFlags(tp node.Type) *flag.FlagSet { } // ParseNodeFlags parses Node flags from the given cmd and applies values to Env. -func ParseNodeFlags(cmd *cobra.Command, env *Env) error { - env.StorePath = cmd.Flag(nodeStoreFlag).Value.String() +func ParseNodeFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { + ctx = WithStorePath(ctx, cmd.Flag(nodeStoreFlag).Value.String()) nodeConfig := cmd.Flag(nodeConfigFlag).Value.String() if nodeConfig != "" { cfg, err := node.LoadConfig(nodeConfig) if err != nil { - return fmt.Errorf("cmd: while parsing '%s': %w", nodeConfigFlag, err) + return ctx, fmt.Errorf("cmd: while parsing '%s': %w", nodeConfigFlag, err) } - env.AddOptions(node.WithConfig(cfg)) + ctx = WithNodeOptions(ctx, node.WithConfig(cfg)) } - return nil + return ctx, nil } diff --git a/cmd/flags_p2p.go b/cmd/flags_p2p.go index db7733f01f..9a2d8f6a33 100644 --- a/cmd/flags_p2p.go +++ b/cmd/flags_p2p.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "github.com/multiformats/go-multiaddr" @@ -31,21 +32,21 @@ Peers must bidirectionally point to each other. (Format: multiformats.io/multiad } // ParseP2PFlags parses P2P flags from the given cmd and applies values to Env. -func ParseP2PFlags(cmd *cobra.Command, env *Env) error { +func ParseP2PFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { mutualPeers, err := cmd.Flags().GetStringSlice(p2pMutualFlag) if err != nil { - return err + return ctx, err } for _, peer := range mutualPeers { _, err := multiaddr.NewMultiaddr(peer) if err != nil { - return fmt.Errorf("cmd: while parsing '%s': %w", p2pMutualFlag, err) + return ctx, fmt.Errorf("cmd: while parsing '%s': %w", p2pMutualFlag, err) } } if len(mutualPeers) != 0 { - env.AddOptions(node.WithMutualPeers(mutualPeers)) + ctx = WithNodeOptions(ctx, node.WithMutualPeers(mutualPeers)) } - return nil + return ctx, nil } diff --git a/cmd/flags_rpc.go b/cmd/flags_rpc.go index d52a0cfc8d..f43fc7f725 100644 --- a/cmd/flags_rpc.go +++ b/cmd/flags_rpc.go @@ -4,6 +4,8 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" + "context" + "github.com/celestiaorg/celestia-node/node" ) @@ -31,14 +33,14 @@ func RPCFlags() *flag.FlagSet { } // ParseRPCFlags parses RPC flags from the given cmd and applies values to Env. -func ParseRPCFlags(cmd *cobra.Command, env *Env) error { +func ParseRPCFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { addr := cmd.Flag(addrFlag).Value.String() if addr != "" { - env.AddOptions(node.WithRPCAddress(addr)) + ctx = WithNodeOptions(ctx, node.WithRPCAddress(addr)) } port := cmd.Flag(portFlag).Value.String() if port != "" { - env.AddOptions(node.WithRPCPort(port)) + ctx = WithNodeOptions(ctx, node.WithRPCPort(port)) } - return nil + return ctx, nil } diff --git a/cmd/init.go b/cmd/init.go index f48c16ac4a..7f9d2f6776 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -14,12 +14,9 @@ func Init(fsets ...*flag.FlagSet) *cobra.Command { Short: "Initialization for Celestia Node. Passed flags have persisted effect.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - env, err := GetEnv(cmd.Context()) - if err != nil { - return err - } + ctx := cmd.Context() - return node.Init(env.StorePath, env.NodeType, env.Options()...) + return node.Init(StorePath(ctx), NodeType(ctx), NodeOptions(ctx)...) }, } for _, set := range fsets { diff --git a/cmd/start.go b/cmd/start.go index 25b5779c42..c1a81c9ac7 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -20,17 +20,14 @@ Options passed on start override configuration options only on start and are not Args: cobra.NoArgs, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - env, err := GetEnv(cmd.Context()) - if err != nil { - return err - } + ctx := cmd.Context() - store, err := node.OpenStore(env.StorePath) + store, err := node.OpenStore(StorePath(ctx)) if err != nil { return err } - nd, err := node.New(env.NodeType, store, env.Options()...) + nd, err := node.New(NodeType(ctx), store, NodeOptions(ctx)...) if err != nil { return err } From cb6f845ce9045bbaf66eac2bc2cc1b17b332f9f8 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 12 Aug 2022 17:48:41 +0300 Subject: [PATCH 0021/1008] dep: update pubsub fork version (#1006) --- go.mod | 7 +++---- go.sum | 27 ++++++++++++++------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 34381f9e1d..7e94204298 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.6.0 - github.com/libp2p/go-libp2p v0.20.2 + github.com/libp2p/go-libp2p v0.20.3 github.com/libp2p/go-libp2p-core v0.17.0 github.com/libp2p/go-libp2p-kad-dht v0.16.0 github.com/libp2p/go-libp2p-peerstore v0.6.0 @@ -178,7 +178,6 @@ require ( github.com/libp2p/go-eventbus v0.2.1 // indirect github.com/libp2p/go-flow-metrics v0.0.3 // indirect github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect - github.com/libp2p/go-libp2p-discovery v0.6.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect github.com/libp2p/go-libp2p-loggables v0.1.0 // indirect github.com/libp2p/go-libp2p-resource-manager v0.3.0 // indirect @@ -210,7 +209,7 @@ require ( github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.0.3 // indirect github.com/multiformats/go-multicodec v0.4.1 // indirect - github.com/multiformats/go-multistream v0.3.2 // indirect + github.com/multiformats/go-multistream v0.3.3 // indirect github.com/multiformats/go-varint v0.0.6 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b // indirect @@ -287,6 +286,6 @@ require ( replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.1.0-sdk-v0.46.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220628100036-657948473f1f + github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.2.4-tm-v0.35.6 ) diff --git a/go.sum b/go.sum index ef04032eca..00253cc28c 100644 --- a/go.sum +++ b/go.sum @@ -140,6 +140,7 @@ github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -168,8 +169,8 @@ github.com/celestiaorg/go-leopard v0.1.0 h1:28z2EkvKJIez5J9CEaiiUEC+OxalRLtTGJJ1 github.com/celestiaorg/go-leopard v0.1.0/go.mod h1:NtO/rjlB8dw2aq7jr06vZFKGvryQcTDXaNHelmPNOAM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= github.com/celestiaorg/go-libp2p-messenger v0.1.0/go.mod h1:XzNksXrH0VxuNRGOnjPL9Ck4UyQlbmMpCYg9YwSBerI= -github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220628100036-657948473f1f h1:jS/yGji/w6ddoSEd6cAwoV3jycdKIg9mJ7avwoiMCk0= -github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220628100036-657948473f1f/go.mod h1:EuyBJFtF8qF67IEA98biwK8Xnw5MNJpJ/Z+8iWCMFwc= +github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 h1:Tb1lPVAGSJvBjRCM7YpC+VaITzdZjjno4+KEnbPT6tU= +github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2/go.mod h1:y+eARQ8AoaWpe0mlz3/uYKdl+GhFOtIxjG5xeek2kbM= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch h1:9TSe3w1cmJmbWlweCwCTIZkan7jV8M+KwglXpdD+UG8= github.com/celestiaorg/go-verifcid v0.0.1-lazypatch/go.mod h1:kXPYu0XqTNUKWA1h3M95UHjUqBzDwXVVt/RXZDjKJmQ= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= @@ -624,6 +625,7 @@ github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-cid v0.2.0 h1:01JTiihFq9en9Vz0lc0VDWvZe/uBonGpzo4THP0vcQ0= github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= @@ -821,8 +823,8 @@ github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qD github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= github.com/libp2p/go-libp2p v0.18.0/go.mod h1:+veaZ9z1SZQhmc5PW78jvnnxZ89Mgvmh4cggO11ETmw= -github.com/libp2p/go-libp2p v0.20.2 h1:uPCbLjx1VIGt4noOoGsSQKsoUqd+WwOq0IeFbrAThXM= -github.com/libp2p/go-libp2p v0.20.2/go.mod h1:heAEqZPMOagd26sado6/P4ifArxkUe9uV8PGrTn9K2k= +github.com/libp2p/go-libp2p v0.20.3 h1:tjjDNfp7FqdI/7v1rXtB/BtELaPlAThL2uzlj18kcrw= +github.com/libp2p/go-libp2p v0.20.3/go.mod h1:I+vndVanE/p/SjFbnA+BEmmfAUEpWxrdXZeyQ1Dus5c= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= @@ -835,7 +837,6 @@ github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfha github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= -github.com/libp2p/go-libp2p-blankhost v0.3.0 h1:kTnLArltMabZlzY63pgGDA4kkUcLkBFSM98zBssn/IY= github.com/libp2p/go-libp2p-blankhost v0.3.0/go.mod h1:urPC+7U01nCGgJ3ZsV8jdwTp6Ji9ID0dMTvq+aJ+nZU= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= @@ -875,6 +876,7 @@ github.com/libp2p/go-libp2p-core v0.11.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQR github.com/libp2p/go-libp2p-core v0.12.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= github.com/libp2p/go-libp2p-core v0.14.0/go.mod h1:tLasfcVdTXnixsLB0QYaT1syJOhsbrhG7q6pGrHtBg8= github.com/libp2p/go-libp2p-core v0.15.1/go.mod h1:agSaboYM4hzB1cWekgVReqV5M4g5M+2eNNejV+1EEhs= +github.com/libp2p/go-libp2p-core v0.16.1/go.mod h1:O3i/7y+LqUb0N+qhzXjBjjpchgptWAVMG1Voegk7b4c= github.com/libp2p/go-libp2p-core v0.17.0 h1:QGU8mlxHytwTc4pq/aVQX9VDoAPiCHxfe/oOSwF+YDg= github.com/libp2p/go-libp2p-core v0.17.0/go.mod h1:h/iAbFij28ASmI+tvXfjoipg1g2N33O4UN6LIb6QfoU= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= @@ -882,8 +884,6 @@ github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfx github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-discovery v0.6.0 h1:1XdPmhMJr8Tmj/yUfkJMIi8mgwWrLUsCB3bMxdT+DSo= -github.com/libp2p/go-libp2p-discovery v0.6.0/go.mod h1:/u1voHt0tKIe5oIA1RHBKQLVCWPna2dXmPNHc2zR9S8= github.com/libp2p/go-libp2p-kad-dht v0.16.0 h1:epVRYl3O8dn47uV3wVD2+IobEvBPapEMVj4sWlvwQHU= github.com/libp2p/go-libp2p-kad-dht v0.16.0/go.mod h1:YYLlG8AbpWVGbI/zFeSbiGT0n0lluH7IG0sHeounyWA= github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= @@ -926,9 +926,8 @@ github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqU github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc= github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= +github.com/libp2p/go-libp2p-quic-transport v0.16.1 h1:N/XqYXHurphPLDfXYhll8NyqzdZYQqAF4GIr7+SmLV8= github.com/libp2p/go-libp2p-quic-transport v0.16.1/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= -github.com/libp2p/go-libp2p-quic-transport v0.17.0 h1:yFh4Gf5MlToAYLuw/dRvuzYd1EnE2pX3Lq1N6KDiWRQ= -github.com/libp2p/go-libp2p-quic-transport v0.17.0/go.mod h1:x4pw61P3/GRCcSLypcQJE/Q2+E9f4X+5aRcZLXf20LM= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0= @@ -965,8 +964,8 @@ github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotl github.com/libp2p/go-libp2p-testing v0.5.0/go.mod h1:QBk8fqIL1XNcno/l3/hhaIEn4aLRijpYOR+zVjjlh+A= github.com/libp2p/go-libp2p-testing v0.7.0/go.mod h1:OLbdn9DbgdMwv00v+tlp1l3oe2Cl+FAjoWIA2pa0X6E= github.com/libp2p/go-libp2p-testing v0.8.0/go.mod h1:gRdsNxQSxAZowTgcLY7CC33xPmleZzoBpqSYbWenqPc= -github.com/libp2p/go-libp2p-testing v0.9.0/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= github.com/libp2p/go-libp2p-testing v0.9.2 h1:dCpODRtRaDZKF8HXT9qqqgON+OMEB423Knrgeod8j84= +github.com/libp2p/go-libp2p-testing v0.9.2/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= github.com/libp2p/go-libp2p-tls v0.3.0/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5oFlg+wo2FxJDY= @@ -1006,6 +1005,7 @@ github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= github.com/libp2p/go-mplex v0.6.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= +github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= @@ -1082,7 +1082,6 @@ github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= -github.com/lucas-clemente/quic-go v0.27.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= github.com/lucas-clemente/quic-go v0.27.1 h1:sOw+4kFSVrdWOYmUjufQ9GBVPqZ+tu+jMtXxXNmRJyk= github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -1233,8 +1232,8 @@ github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wS github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= -github.com/multiformats/go-multistream v0.3.2 h1:YRJzBzM8BdZuOn3FjIns1ceKEyEQrT+8JJ581PNyGyI= -github.com/multiformats/go-multistream v0.3.2/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= +github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o= +github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -1662,6 +1661,7 @@ golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1916,6 +1916,7 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= From 208eb0b8ea446eed76e2e20c5aacb96a55efe448 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 12 Aug 2022 17:49:31 +0300 Subject: [PATCH 0022/1008] swamp: rework fill blocks (#1000) * swamp: rework fill blocks * swamp: attempt to fix flakiness in TestFullReconstructFromLights * swamp: subscribe on fraud proof before node starts --- node/tests/fraud_test.go | 7 ++-- node/tests/reconstruct_test.go | 64 ++++++++++++++++++++++------------ node/tests/swamp/swamp_tx.go | 26 ++++++++------ 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/node/tests/fraud_test.go b/node/tests/fraud_test.go index e7d355bca1..74136155f3 100644 --- a/node/tests/fraud_test.go +++ b/node/tests/fraud_test.go @@ -44,11 +44,14 @@ func TestFraudProofBroadcasting(t *testing.T) { store := node.MockStore(t, node.DefaultConfig(node.Full)) full := sw.NewNodeWithStore(node.Full, store, node.WithTrustedPeers(addrs[0].String())) - err = full.Start(ctx) + // subscribe to fraud proof before node starts helps + // to prevent flakiness when fraud proof is propagating before subscribing on it + subscr, err := full.FraudServ.Subscribe(fraud.BadEncoding) require.NoError(t, err) - subscr, err := full.FraudServ.Subscribe(fraud.BadEncoding) + err = full.Start(ctx) require.NoError(t, err) + _, err = subscr.Proof(ctx) require.NoError(t, err) diff --git a/node/tests/reconstruct_test.go b/node/tests/reconstruct_test.go index e127049515..e2cf98df52 100644 --- a/node/tests/reconstruct_test.go +++ b/node/tests/reconstruct_test.go @@ -44,7 +44,10 @@ func TestFullReconstructFromBridge(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - go sw.FillBlocks(ctx, t, bsize, blocks) + errCh := make(chan error) + go func() { + errCh <- sw.FillBlocks(ctx, bsize, blocks) + }() bridge := sw.NewBridgeNode() err := bridge.Start(ctx) @@ -66,9 +69,8 @@ func TestFullReconstructFromBridge(t *testing.T) { return full.ShareServ.SharesAvailable(bctx, h.DAH) }) } - - err = errg.Wait() - require.NoError(t, err) + require.NoError(t, <-errCh) + require.NoError(t, errg.Wait()) } /* @@ -79,12 +81,13 @@ Pre-Reqs: Steps: 1. Create a Bridge Node(BN) 2. Start a BN -3. Create 69 Light Nodes(LNs) with BN as a trusted peer -4. Start 69 LNs -5. Create a Full Node(FN) with 69 LNs as trusted peers -6. Unlink FN connection to BN -7. Start a FN -8. Check that a FN can retrieve shares from 1 to 20 blocks +3. Create a Full Node(FN) that will act as a bootstraper +4. Create 69 Light Nodes(LNs) with BN as a trusted peer and a bootstaper +5. Start 69 LNs +6. Create a Full Node(FN) with a bootstraper +7. Unlink FN connection to BN +8. Start a FN +9. Check that a FN can retrieve shares from 1 to 20 blocks */ func TestFullReconstructFromLights(t *testing.T) { ipld.RetrieveQuadrantTimeout = time.Millisecond * 100 @@ -96,27 +99,38 @@ func TestFullReconstructFromLights(t *testing.T) { lnodes = 69 ) - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) - go sw.FillBlocks(ctx, t, bsize, blocks) + errCh := make(chan error) + go func() { + errCh <- sw.FillBlocks(ctx, bsize, blocks) + }() - cfg := node.DefaultConfig(node.Bridge) - cfg.P2P.Bootstrapper = true - const defaultTimeInterval = time.Second * 10 + const defaultTimeInterval = time.Second * 5 var defaultOptions = []node.Option{ node.WithRefreshRoutingTablePeriod(defaultTimeInterval), node.WithDiscoveryInterval(defaultTimeInterval), node.WithAdvertiseInterval(defaultTimeInterval), } - bridgeConfig := append([]node.Option{node.WithConfig(cfg)}, defaultOptions...) + cfg := node.DefaultConfig(node.Full) cfg.P2P.Bootstrapper = true - bridge := sw.NewBridgeNode(bridgeConfig...) + bridge := sw.NewBridgeNode() + addrsBridge, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) + require.NoError(t, err) + bootstrapConfig := append([]node.Option{node.WithConfig(cfg)}, defaultOptions...) + bootstapFN := sw.NewFullNode(bootstrapConfig...) + require.NoError(t, bootstapFN.Start(ctx)) require.NoError(t, bridge.Start(ctx)) - addr := host.InfoFromHost(bridge.Host) + addrBootstrapNode := host.InfoFromHost(bootstapFN.Host) - nodesConfig := append([]node.Option{node.WithBootstrappers([]peer.AddrInfo{*addr})}, defaultOptions...) + nodesConfig := append( + []node.Option{ + node.WithTrustedPeers(addrsBridge[0].String()), + node.WithBootstrappers([]peer.AddrInfo{*addrBootstrapNode})}, + defaultOptions..., + ) full := sw.NewFullNode(nodesConfig...) lights := make([]*node.Node, lnodes) subs := make([]event.Subscription, lnodes) @@ -124,8 +138,13 @@ func TestFullReconstructFromLights(t *testing.T) { for i := 0; i < lnodes; i++ { i := i errg.Go(func() error { - light := sw.NewLightNode(nodesConfig...) - sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) + lnConfig := append( + []node.Option{ + node.WithTrustedPeers(addrsBridge[0].String())}, + nodesConfig..., + ) + light := sw.NewLightNode(lnConfig...) + sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) if err != nil { return err } @@ -141,6 +160,7 @@ func TestFullReconstructFromLights(t *testing.T) { case <-ctx.Done(): t.Fatal("peer was not found") case <-subs[i].Out(): + require.NoError(t, subs[i].Close()) continue } } @@ -156,7 +176,7 @@ func TestFullReconstructFromLights(t *testing.T) { return full.ShareServ.SharesAvailable(bctx, h.DAH) }) } - + require.NoError(t, <-errCh) require.NoError(t, errg.Wait()) } diff --git a/node/tests/swamp/swamp_tx.go b/node/tests/swamp/swamp_tx.go index 5a7eed9a2a..e1e0420dcd 100644 --- a/node/tests/swamp/swamp_tx.go +++ b/node/tests/swamp/swamp_tx.go @@ -2,25 +2,29 @@ package swamp import ( "context" + "fmt" "math/rand" - "testing" "time" - "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/ipld" ) // SubmitData submits given data in the block. // TODO(@Wondertan): This must be a real PFD using celestia-app, once we able to run App // in the Swamp. -func (s *Swamp) SubmitData(ctx context.Context, t *testing.T, data []byte) { +func (s *Swamp) SubmitData(ctx context.Context, data []byte) error { result, err := s.CoreClient.BroadcastTxSync(ctx, append([]byte("key="), data...)) - require.NoError(t, err) - require.Zero(t, result.Code) + if err != nil { + return err + } + if result.Code != 0 { + return fmt.Errorf("invalid status code: %d", result.Code) + } + + return nil } -func (s *Swamp) FillBlocks(ctx context.Context, t *testing.T, bsize, blocks int) { +func (s *Swamp) FillBlocks(ctx context.Context, bsize, blocks int) error { btime := s.comps.CoreCfg.Consensus.CreateEmptyBlocksInterval timer := time.NewTimer(btime) defer timer.Stop() @@ -28,13 +32,15 @@ func (s *Swamp) FillBlocks(ctx context.Context, t *testing.T, bsize, blocks int) data := make([]byte, bsize*ipld.ShareSize) for range make([]int, blocks) { rand.Read(data) //nolint:gosec - s.SubmitData(ctx, t, data) - + if err := s.SubmitData(ctx, data); err != nil { + return err + } timer.Reset(btime) select { case <-timer.C: case <-ctx.Done(): - return + return ctx.Err() } } + return nil } From 9cbe833ee0952434598bf57ac611007b03d86f1f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:50:46 +0200 Subject: [PATCH 0023/1008] feat (service/state): If account doesn't exist at given height, fail gracefully (#996) * feat: if account doesnt exist at given height, fail gracefully * refactor: instead of returning error, return 0 and log out err on node side * log: remove prompt to wait for sync as will be resolved by new head determination from syncer --- service/state/core_access.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/service/state/core_access.go b/service/state/core_access.go index bc082a578a..52dd47c389 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -9,6 +9,7 @@ import ( sdktx "github.com/cosmos/cosmos-sdk/types/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" proofutils "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" + logging "github.com/ipfs/go-log/v2" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client/http" "google.golang.org/grpc" @@ -21,6 +22,8 @@ import ( "github.com/celestiaorg/nmt/namespace" ) +var log = logging.Logger("state") + // CoreAccessor implements Accessor over a gRPC connection // with a celestia-core node. type CoreAccessor struct { @@ -157,6 +160,14 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B } // unmarshal balance information value := result.Response.Value + // if the value returned is empty, the account balance does not yet exist + if len(value) == 0 { + log.Errorf("balance for account %s does not exist at block height %d", addr.String(), head.Height) + return &Balance{ + Denom: app.BondDenom, + Amount: sdktypes.NewInt(0), + }, nil + } coin, ok := sdktypes.NewIntFromString(string(value)) if !ok { return nil, fmt.Errorf("cannot convert %s into sdktypes.Int", string(value)) From b595b74432df6f24f12ce9f93b63d764c9806af5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 14:51:38 +0000 Subject: [PATCH 0024/1008] chore(deps): bump github.com/libp2p/go-libp2p-peerstore Bumps [github.com/libp2p/go-libp2p-peerstore](https://github.com/libp2p/go-libp2p-peerstore) from 0.6.0 to 0.7.1. - [Release notes](https://github.com/libp2p/go-libp2p-peerstore/releases) - [Commits](https://github.com/libp2p/go-libp2p-peerstore/compare/v0.6.0...v0.7.1) --- updated-dependencies: - dependency-name: github.com/libp2p/go-libp2p-peerstore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7e94204298..9f4f653563 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/libp2p/go-libp2p v0.20.3 github.com/libp2p/go-libp2p-core v0.17.0 github.com/libp2p/go-libp2p-kad-dht v0.16.0 - github.com/libp2p/go-libp2p-peerstore v0.6.0 + github.com/libp2p/go-libp2p-peerstore v0.7.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 github.com/libp2p/go-libp2p-record v0.1.3 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 diff --git a/go.sum b/go.sum index 00253cc28c..54f60f7775 100644 --- a/go.sum +++ b/go.sum @@ -918,8 +918,9 @@ github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuD github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0= -github.com/libp2p/go-libp2p-peerstore v0.6.0 h1:HJminhQSGISBIRb93N6WK3t6Fa8OOTnHd/VBjL4mY5A= github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= +github.com/libp2p/go-libp2p-peerstore v0.7.1 h1:7FpALlqR+3+oOBXdzm3AVt0vjMYLW1b7jM03E4iEHlw= +github.com/libp2p/go-libp2p-peerstore v0.7.1/go.mod h1:cdUWTHro83vpg6unCpGUr8qJoX3e93Vy8o97u5ppIM0= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= From d831e10ea1de1b547706ca0fea5cc725ef0fd262 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 16 Aug 2022 16:31:41 +0200 Subject: [PATCH 0025/1008] feat(rpc): Transaction proxy for staking tx types (#984) * staking tx types in `core_access.go` * extending Accessor interface with new staking tx types * adding transaction types to rpc * docs: adding punctuation, removing todo * fix: setting bech32 prefix for validators inside node, new undelegation request types * Update service/rpc/state.go Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- cmd/cel-key/main.go | 1 + cmd/celestia/main.go | 1 + service/rpc/endpoints.go | 4 + service/rpc/state.go | 184 ++++++++++++++++++++++++++++++++++- service/state/core_access.go | 99 +++++++++++++++++++ service/state/interface.go | 9 ++ service/state/service.go | 28 ++++++ 7 files changed, 322 insertions(+), 4 deletions(-) diff --git a/cmd/cel-key/main.go b/cmd/cel-key/main.go index e65f0441a4..d835770d2c 100644 --- a/cmd/cel-key/main.go +++ b/cmd/cel-key/main.go @@ -61,6 +61,7 @@ func main() { func run() error { cfg := sdk.GetConfig() cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) cfg.Seal() ctx := context.WithValue(context.Background(), client.ClientContextKey, &initClientCtx) diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index a004b8cbf6..ada2f3d221 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -17,6 +17,7 @@ func init() { // as in the celestia application. cfg := sdk.GetConfig() cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) cfg.Seal() rootCmd.AddCommand( diff --git a/service/rpc/endpoints.go b/service/rpc/endpoints.go index 3ba7dbc1a7..1d5fea4f2a 100644 --- a/service/rpc/endpoints.go +++ b/service/rpc/endpoints.go @@ -13,6 +13,10 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { rpc.RegisterHandlerFunc(submitTxEndpoint, h.handleSubmitTx, http.MethodPost) rpc.RegisterHandlerFunc(submitPFDEndpoint, h.handleSubmitPFD, http.MethodPost) rpc.RegisterHandlerFunc(transferEndpoint, h.handleTransfer, http.MethodPost) + rpc.RegisterHandlerFunc(delegationEndpoint, h.handleDelegation, http.MethodPost) + rpc.RegisterHandlerFunc(undelegationEndpoint, h.handleUndelegation, http.MethodPost) + rpc.RegisterHandlerFunc(cancelUnbondingEndpoint, h.handleCancelUnbonding, http.MethodPost) + rpc.RegisterHandlerFunc(beginRedelegationEndpoint, h.handleRedelegation, http.MethodPost) // share endpoints rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, nIDKey, heightKey), diff --git a/service/rpc/state.go b/service/rpc/state.go index 998c5bfc2b..ef45d353a8 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -13,10 +13,14 @@ import ( ) const ( - balanceEndpoint = "/balance" - submitTxEndpoint = "/submit_tx" - submitPFDEndpoint = "/submit_pfd" - transferEndpoint = "/transfer" + balanceEndpoint = "/balance" + submitTxEndpoint = "/submit_tx" + submitPFDEndpoint = "/submit_pfd" + transferEndpoint = "/transfer" + delegationEndpoint = "/delegate" + undelegationEndpoint = "/begin_unbonding" + cancelUnbondingEndpoint = "/cancel_unbond" + beginRedelegationEndpoint = "/begin_redelegate" ) var addrKey = "address" @@ -40,6 +44,37 @@ type transferRequest struct { GasLimit uint64 `json:"gas_limit"` } +// delegationRequest represents a request for both delegation +// and for beginning and canceling undelegation +type delegationRequest struct { + To string `json:"to"` + Amount int64 `json:"amount"` + GasLimit uint64 `json:"gas_limit"` +} + +// redelegationRequest represents a request for redelegation +type redelegationRequest struct { + From string `json:"from"` + To string `json:"to"` + Amount int64 `json:"amount"` + GasLimit uint64 `json:"gas_limit"` +} + +// unbondRequest represents a request to begin unbonding +type unbondRequest struct { + From string `json:"from"` + Amount int64 `json:"amount"` + GasLimit uint64 `json:"gas_limit"` +} + +// cancelUnbondRequest represents a request to cancel unbonding +type cancelUnbondRequest struct { + From string `json:"from"` + Amount int64 `json:"amount"` + Height int64 `json:"height"` + GasLimit uint64 `json:"gas_limit"` +} + func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { var ( bal *state.Balance @@ -172,3 +207,144 @@ func (h *Handler) handleTransfer(w http.ResponseWriter, r *http.Request) { log.Errorw("writing response", "endpoint", transferEndpoint, "err", err) } } + +func (h *Handler) handleDelegation(w http.ResponseWriter, r *http.Request) { + var req delegationRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + writeError(w, http.StatusBadRequest, delegationEndpoint, err) + return + } + if req.Amount <= 0 { + writeError(w, http.StatusBadRequest, delegationEndpoint, errors.New("amount must be greater than 0")) + return + } + addr, err := types.ValAddressFromBech32(req.To) + if err != nil { + writeError(w, http.StatusBadRequest, delegationEndpoint, err) + return + } + amount := types.NewInt(req.Amount) + + txResp, err := h.state.Delegate(r.Context(), addr, amount, req.GasLimit) + if err != nil { + writeError(w, http.StatusInternalServerError, delegationEndpoint, err) + return + } + resp, err := json.Marshal(txResp) + if err != nil { + writeError(w, http.StatusInternalServerError, delegationEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", delegationEndpoint, "err", err) + } +} + +func (h *Handler) handleUndelegation(w http.ResponseWriter, r *http.Request) { + var req unbondRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + writeError(w, http.StatusBadRequest, undelegationEndpoint, err) + return + } + if req.Amount <= 0 { + writeError(w, http.StatusBadRequest, undelegationEndpoint, errors.New("amount must be greater than 0")) + return + } + addr, err := types.ValAddressFromBech32(req.From) + if err != nil { + writeError(w, http.StatusBadRequest, undelegationEndpoint, err) + return + } + amount := types.NewInt(req.Amount) + + txResp, err := h.state.Undelegate(r.Context(), addr, amount, req.GasLimit) + if err != nil { + writeError(w, http.StatusInternalServerError, undelegationEndpoint, err) + return + } + resp, err := json.Marshal(txResp) + if err != nil { + writeError(w, http.StatusInternalServerError, undelegationEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", undelegationEndpoint, "err", err) + } +} + +func (h *Handler) handleCancelUnbonding(w http.ResponseWriter, r *http.Request) { + var req cancelUnbondRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, err) + return + } + if req.Amount <= 0 { + writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, errors.New("amount must be greater than 0")) + return + } + addr, err := types.ValAddressFromBech32(req.From) + if err != nil { + writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, err) + return + } + amount := types.NewInt(req.Amount) + height := types.NewInt(req.Height) + txResp, err := h.state.CancelUnbondingDelegation(r.Context(), addr, amount, height, req.GasLimit) + if err != nil { + writeError(w, http.StatusInternalServerError, cancelUnbondingEndpoint, err) + return + } + resp, err := json.Marshal(txResp) + if err != nil { + writeError(w, http.StatusInternalServerError, cancelUnbondingEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", cancelUnbondingEndpoint, "err", err) + } +} + +func (h *Handler) handleRedelegation(w http.ResponseWriter, r *http.Request) { + var req redelegationRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) + return + } + if req.Amount <= 0 { + writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, errors.New("amount must be greater than 0")) + return + } + srcAddr, err := types.ValAddressFromBech32(req.From) + if err != nil { + writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) + return + } + dstAddr, err := types.ValAddressFromBech32(req.To) + if err != nil { + writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, err) + return + } + amount := types.NewInt(req.Amount) + + txResp, err := h.state.BeginRedelegate(r.Context(), srcAddr, dstAddr, amount, req.GasLimit) + if err != nil { + writeError(w, http.StatusInternalServerError, beginRedelegationEndpoint, err) + return + } + resp, err := json.Marshal(txResp) + if err != nil { + writeError(w, http.StatusInternalServerError, beginRedelegationEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", beginRedelegationEndpoint, "err", err) + } +} diff --git a/service/state/core_access.go b/service/state/core_access.go index 52dd47c389..e0cdf07884 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -8,6 +8,7 @@ import ( sdktypes "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" proofutils "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" logging "github.com/ipfs/go-log/v2" rpcclient "github.com/tendermint/tendermint/rpc/client" @@ -233,3 +234,101 @@ func (ca *CoreAccessor) Transfer( } return ca.SubmitTx(ctx, signedTx) } + +func (ca *CoreAccessor) CancelUnbondingDelegation( + ctx context.Context, + valAddr Address, + amount, + height Int, + gasLim uint64, +) (*TxResponse, error) { + validator, ok := valAddr.(sdktypes.ValAddress) + if !ok { + return nil, fmt.Errorf("state: unsupported address type") + } + from, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + coins := sdktypes.NewCoin(app.BondDenom, amount) + msg := stakingtypes.NewMsgCancelUnbondingDelegation(from, validator, height.Int64(), coins) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + if err != nil { + return nil, err + } + return ca.SubmitTx(ctx, signedTx) +} + +func (ca *CoreAccessor) BeginRedelegate( + ctx context.Context, + srcValAddr, + dstValAddr Address, + amount Int, + gasLim uint64, +) (*TxResponse, error) { + srcValidator, ok := srcValAddr.(sdktypes.ValAddress) + if !ok { + return nil, fmt.Errorf("state: unsupported address type") + } + dstValidator, ok := dstValAddr.(sdktypes.ValAddress) + if !ok { + return nil, fmt.Errorf("state: unsupported address type") + } + from, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + coins := sdktypes.NewCoin(app.BondDenom, amount) + msg := stakingtypes.NewMsgBeginRedelegate(from, srcValidator, dstValidator, coins) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + if err != nil { + return nil, err + } + return ca.SubmitTx(ctx, signedTx) +} + +func (ca *CoreAccessor) Undelegate( + ctx context.Context, + delAddr Address, + amount Int, + gasLim uint64, +) (*TxResponse, error) { + delegate, ok := delAddr.(sdktypes.ValAddress) + if !ok { + return nil, fmt.Errorf("state: unsupported address type") + } + from, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + coins := sdktypes.NewCoin(app.BondDenom, amount) + msg := stakingtypes.NewMsgUndelegate(from, delegate, coins) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + if err != nil { + return nil, err + } + return ca.SubmitTx(ctx, signedTx) +} + +func (ca *CoreAccessor) Delegate( + ctx context.Context, + delAddr Address, + amount Int, + gasLim uint64, +) (*TxResponse, error) { + delegate, ok := delAddr.(sdktypes.ValAddress) + if !ok { + return nil, fmt.Errorf("state: unsupported address type") + } + from, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + coins := sdktypes.NewCoin(app.BondDenom, amount) + msg := stakingtypes.NewMsgDelegate(from, delegate, coins) + signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) + if err != nil { + return nil, err + } + return ca.SubmitTx(ctx, signedTx) +} diff --git a/service/state/interface.go b/service/state/interface.go index 2f2a53ccd8..5be184de25 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -36,4 +36,13 @@ type Accessor interface { SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) // SubmitPayForData builds, signs and submits a PayForData transaction. SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*TxResponse, error) + + // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. + CancelUnbondingDelegation(ctx context.Context, valAddr Address, amount, height Int, gasLim uint64) (*TxResponse, error) + // BeginRedelegate sends a user's delegated tokens to a new validator for redelegation. + BeginRedelegate(ctx context.Context, srcValAddr, dstValAddr Address, amount Int, gasLim uint64) (*TxResponse, error) + // Undelegate undelegates a user's delegated tokens, unbonding them from the current validator. + Undelegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) + // Delegate sends a user's liquid tokens to a validator for delegation. + Delegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) } diff --git a/service/state/service.go b/service/state/service.go index f982940422..e4307641f2 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -51,6 +51,34 @@ func (s *Service) Transfer(ctx context.Context, to Address, amount Int, gasLimit return s.accessor.Transfer(ctx, to, amount, gasLimit) } +func (s *Service) CancelUnbondingDelegation( + ctx context.Context, + valAddr Address, + amount, + height Int, + gasLim uint64, +) (*TxResponse, error) { + return s.accessor.CancelUnbondingDelegation(ctx, valAddr, amount, height, gasLim) +} + +func (s *Service) BeginRedelegate( + ctx context.Context, + srcValAddr, + dstValAddr Address, + amount Int, + gasLim uint64, +) (*TxResponse, error) { + return s.accessor.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, gasLim) +} + +func (s *Service) Undelegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) { + return s.accessor.Undelegate(ctx, delAddr, amount, gasLim) +} + +func (s *Service) Delegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) { + return s.accessor.Delegate(ctx, delAddr, amount, gasLim) +} + func (s *Service) Start(context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) return nil From 7466aa8c10ce53049c64a58e84079478fd05e1de Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 17 Aug 2022 15:37:59 +0200 Subject: [PATCH 0026/1008] chore: backport to v0.34.x tendermint (#979) * wip: rebase + downgrading tendermint * upstreaming other repo's PRs, which breaks building of keyring stuff * MakeEncodingConfig -> MakeConfig for encoding * pulling upstream celestia-app * fix: setting test config * fix: rpctest.StopTendermint * fix: removing deletion of rpc config's root dir * upstreaming celestia-core to fix swamp test configuration reading * deps: upstreaming celestia-app (but still needs release) * deps: new release candidates from celestia-app and celestia-core * lint: replacing sdk.Int with new math.Int * deps: adding go.mod and go.sum --- cmd/cel-key/main.go | 2 +- core/client.go | 1 + core/fetcher.go | 2 +- core/testing.go | 16 +-- go.mod | 67 ++++++------ go.sum | 194 +++++++++++++++-------------------- header/testing.go | 6 +- ipld/get_test.go | 2 +- ipld/retriever.go | 2 +- node/state/keyring.go | 2 +- node/testing.go | 2 +- node/tests/swamp/config.go | 6 +- node/tests/swamp/swamp.go | 6 +- service/state/core_access.go | 2 +- service/state/interface.go | 4 +- service/state/state.go | 3 +- 16 files changed, 145 insertions(+), 172 deletions(-) diff --git a/cmd/cel-key/main.go b/cmd/cel-key/main.go index d835770d2c..852ae4f4ed 100644 --- a/cmd/cel-key/main.go +++ b/cmd/cel-key/main.go @@ -16,7 +16,7 @@ import ( "github.com/celestiaorg/celestia-app/app/encoding" ) -var encodingConfig = encoding.MakeEncodingConfig(app.ModuleEncodingRegisters...) +var encodingConfig = encoding.MakeConfig(app.ModuleEncodingRegisters...) var initClientCtx = client.Context{}. WithCodec(encodingConfig.Codec). diff --git a/core/client.go b/core/client.go index 0f14957165..4ea3ac10a9 100644 --- a/core/client.go +++ b/core/client.go @@ -21,6 +21,7 @@ func NewRemote(ip, port string) (Client, error) { return http.NewWithClient( fmt.Sprintf("tcp://%s:%s", ip, port), + "/websocket", httpClient.StandardClient(), ) } diff --git a/core/fetcher.go b/core/fetcher.go index c75cb2a6b2..5585a82c8c 100644 --- a/core/fetcher.go +++ b/core/fetcher.go @@ -13,7 +13,7 @@ const newBlockSubscriber = "NewBlock/Events" var ( log = logging.Logger("core/fetcher") - newBlockEventQuery = types.QueryForEvent(types.EventNewBlockValue).String() + newBlockEventQuery = types.QueryForEvent(types.EventNewBlock).String() ) type BlockFetcher struct { diff --git a/core/testing.go b/core/testing.go index 8b45ecc25f..5b48c5454c 100644 --- a/core/testing.go +++ b/core/testing.go @@ -25,18 +25,18 @@ const defaultRetainBlocks int64 = 10000 // StartTestNode starts a mock Core node background process and returns it. func StartTestNode(ctx context.Context, t *testing.T, app types.Application, cfg *config.Config) tmservice.Service { - nd, closer, err := rpctest.StartTendermint(ctx, cfg, app, rpctest.SuppressStdout) - require.NoError(t, err) + nd := rpctest.StartTendermint(app, rpctest.SuppressStdout, func(options *rpctest.Options) { + options.SpecificConfig = cfg + }) t.Cleanup(func() { - require.NoError(t, closer(ctx)) + rpctest.StopTendermint(nd) }) return nd } // StartTestKVApp starts Tendermint KVApp. func StartTestKVApp(ctx context.Context, t *testing.T) (tmservice.Service, types.Application, *config.Config) { - cfg, err := rpctest.CreateConfig("Dummy_TmNode") - require.NoError(t, err) + cfg := rpctest.GetConfig(true) app := CreateKVStore(defaultRetainBlocks) return StartTestNode(ctx, t, app, cfg), app, cfg } @@ -88,7 +88,7 @@ func RandValidator(randPower bool, minPower int64) (*tmtypes.Validator, tmtypes. // nolint:gosec // G404: Use of weak random number generator votePower += int64(rand.Uint32()) } - pubKey, err := privVal.GetPubKey(context.Background()) + pubKey, err := privVal.GetPubKey() if err != nil { panic(fmt.Errorf("could not retrieve pubkey %w", err)) } @@ -118,7 +118,7 @@ func MakeCommit(blockID tmtypes.BlockID, height int64, round int32, // all sign for i := 0; i < len(validators); i++ { - pubKey, err := validators[i].GetPubKey(context.Background()) + pubKey, err := validators[i].GetPubKey() if err != nil { return nil, fmt.Errorf("can't get pubkey: %w", err) } @@ -143,7 +143,7 @@ func MakeCommit(blockID tmtypes.BlockID, height int64, round int32, func signAddVote(privVal tmtypes.PrivValidator, vote *tmtypes.Vote, voteSet *tmtypes.VoteSet) (signed bool, err error) { v := vote.ToProto() - err = privVal.SignVote(context.Background(), voteSet.ChainID(), v) + err = privVal.SignVote(voteSet.ChainID(), v) if err != nil { return false, err } diff --git a/go.mod b/go.mod index 9f4f653563..014a9a2069 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,12 @@ go 1.18 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch require ( + cosmossdk.io/math v1.0.0-beta.2 github.com/BurntSushi/toml v1.2.0 - github.com/celestiaorg/celestia-app v0.5.4 + github.com/celestiaorg/celestia-app v0.7.0-rc-1 github.com/celestiaorg/go-libp2p-messenger v0.1.0 github.com/celestiaorg/nmt v0.10.0 - github.com/celestiaorg/rsmt2d v0.5.0 + github.com/celestiaorg/rsmt2d v0.6.0 github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20220418184507-c53157dd63f6 github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cosmos/ibc-go/v4 v4.0.0-rc0 @@ -57,7 +58,7 @@ require ( go.opentelemetry.io/otel/trace v1.8.0 go.uber.org/fx v1.17.1 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e - golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 + golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f google.golang.org/grpc v1.48.0 ) @@ -66,8 +67,11 @@ require ( cloud.google.com/go/compute v1.6.1 // indirect cloud.google.com/go/iam v0.3.0 // indirect cloud.google.com/go/storage v1.14.0 // indirect + cosmossdk.io/errors v1.0.0-beta.7 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect - github.com/99designs/keyring v1.1.6 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.1 // indirect + github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.0 // indirect github.com/aws/aws-sdk-go v1.40.45 // indirect @@ -84,30 +88,27 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect - github.com/coinbase/rosetta-sdk-go v0.7.8 // indirect github.com/confio/ics23/go v0.7.0 // indirect github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect - github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.18.0 // indirect + github.com/cosmos/iavl v0.19.0 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect github.com/cskr/pubsub v1.0.2 // indirect - github.com/danieljoos/wincred v1.0.2 // indirect + github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect + github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/elastic/gosigar v0.12.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -115,6 +116,8 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gammazero/deque v0.1.0 // indirect github.com/go-kit/kit v0.12.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect @@ -133,21 +136,21 @@ require ( github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/gtank/merlin v0.1.1 // indirect + github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.5.11 // indirect + github.com/hashicorp/go-getter v1.6.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.4.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204 // indirect - github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect @@ -168,12 +171,11 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect - github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect - github.com/klauspost/compress v1.15.1 // indirect + github.com/klauspost/compress v1.15.6 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/koron/go-ssdp v0.0.2 // indirect github.com/lib/pq v1.10.6 // indirect - github.com/libp2p/go-buffer-pool v0.0.2 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-eventbus v0.2.1 // indirect github.com/libp2p/go-flow-metrics v0.0.3 // indirect @@ -198,6 +200,7 @@ require ( github.com/miekg/dns v1.1.43 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect + github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -212,26 +215,23 @@ require ( github.com/multiformats/go-multistream v0.3.3 // indirect github.com/multiformats/go-varint v0.0.6 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b // indirect github.com/onsi/ginkgo v1.16.4 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.2 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.33.0 // indirect + github.com/prometheus/common v0.34.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rs/cors v1.8.2 // indirect - github.com/rs/zerolog v1.26.1 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -239,7 +239,7 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.12.0 // indirect - github.com/subosito/gotenv v1.3.0 // indirect + github.com/subosito/gotenv v1.4.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect @@ -262,30 +262,29 @@ require ( go.uber.org/dig v1.14.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect + golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect + golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1 // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/api v0.81.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect + google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.1.7 // indirect - nhooyr.io/websocket v1.8.6 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.1.0-sdk-v0.46.0 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.2.4-tm-v0.35.6 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.3.1-tm-v0.34.20 ) diff --git a/go.sum b/go.sum index 54f60f7775..468f93c852 100644 --- a/go.sum +++ b/go.sum @@ -59,6 +59,10 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= +cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= +cosmossdk.io/math v1.0.0-beta.2 h1:17hSVc9ne1c31IaLDfjRojtN+y4Rd2N8H/6Fht2sBzw= +cosmossdk.io/math v1.0.0-beta.2/go.mod h1:u/MXvf8wbUbCsAEyQSSYXXMsczAsFX48e2D6JI86T4o= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= @@ -67,8 +71,10 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcIuM= -github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= @@ -77,11 +83,13 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -91,7 +99,7 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= -github.com/adlio/schema v1.3.0 h1:eSVYLxYWbm/6ReZBCkLw4Fz7uqC+ZNoPvA39bOwi52A= +github.com/adlio/schema v1.1.13 h1:LeNMVg5Z1FX+Qgz8tJUijBLRdcpbFUElz+d1489On98= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -159,12 +167,12 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/celestiaorg/celestia-app v0.5.4 h1:X8FJxaxa8oEF/A4Bg11dGEp1BKgAGWatdghGXG1+24k= -github.com/celestiaorg/celestia-app v0.5.4/go.mod h1:z4GmdNZAcPBkAQ76tCeZwBxr5nlsbqzWKb0PMZEI9Dc= -github.com/celestiaorg/celestia-core v1.2.4-tm-v0.35.6 h1:vKYvdlHpmWDcDP+jKfU64oEp2H74jAWJxCDjTHlXYSs= -github.com/celestiaorg/celestia-core v1.2.4-tm-v0.35.6/go.mod h1:4J51zu8jaHB7vlgwfKC1eyGKpRP3C0REiipTwPPfvCE= -github.com/celestiaorg/cosmos-sdk v1.1.0-sdk-v0.46.0 h1:Avs0lxSYabPTGwCfS7nV0yCF1QKO0O1mOEE1L6C8tGo= -github.com/celestiaorg/cosmos-sdk v1.1.0-sdk-v0.46.0/go.mod h1:SqOn+Sol7ydGocB4Qqo24chrwG6YW4OhFh6NSpsFYbk= +github.com/celestiaorg/celestia-app v0.7.0-rc-1 h1:5d6z/qmzM93yYLSXWule29tfwQ2cY7HGigt6PhZm2GI= +github.com/celestiaorg/celestia-app v0.7.0-rc-1/go.mod h1:Ic44p6PGAdd/H5MS4Ga85wcKIQr+MvgsQNmxhnMG0Co= +github.com/celestiaorg/celestia-core v1.3.1-tm-v0.34.20 h1:/kcfjyOXA6QoJbyVT0LM2I9WeCQHEoPVWJ/MYz+S/r4= +github.com/celestiaorg/celestia-core v1.3.1-tm-v0.34.20/go.mod h1:bFyLvRuHAPewkGy4WazXJhfYhdV4u79iuoYaeNL3EO4= +github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 h1:A1F7L/09uGClsU+kmugMy47Ezv4ll0uxNRNdaGa37T8= +github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0/go.mod h1:OXRC0p460CFKl77uQZWY/8p5uZmDrNum7BmVZDupq0Q= github.com/celestiaorg/go-leopard v0.1.0 h1:28z2EkvKJIez5J9CEaiiUEC+OxalRLtTGJJ1oScfE1g= github.com/celestiaorg/go-leopard v0.1.0/go.mod h1:NtO/rjlB8dw2aq7jr06vZFKGvryQcTDXaNHelmPNOAM= github.com/celestiaorg/go-libp2p-messenger v0.1.0 h1:rFldTa3ZWcRRn8E2bRWS94Qp1GFYXO2a0uvqpIey1B8= @@ -177,12 +185,13 @@ github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= github.com/celestiaorg/nmt v0.10.0 h1:HLfVWvpagHz5+uiE0QSjzv350wLhhnybNmrxq9NHLKc= github.com/celestiaorg/nmt v0.10.0/go.mod h1:3bqzTj8xKj0DgQUpOgZzoxvtNkC3MS/hTbQ6dn8SIa0= -github.com/celestiaorg/rsmt2d v0.5.0 h1:Wa0uNZUXl8lIMJnSunjoD65ktqBedXZD0z2ZU3xKYYw= -github.com/celestiaorg/rsmt2d v0.5.0/go.mod h1:EZ+O2KdCq8xI7WFwjATLdhtMdrdClmAs2w7zENDr010= +github.com/celestiaorg/rsmt2d v0.6.0 h1:32Eq5t7lPNbhftPFFjxwCUeEjWg/yGgeMbshxnGw03c= +github.com/celestiaorg/rsmt2d v0.6.0/go.mod h1:EZ+O2KdCq8xI7WFwjATLdhtMdrdClmAs2w7zENDr010= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -213,13 +222,12 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coinbase/rosetta-sdk-go v0.7.8 h1:op/O3/ZngTfcrZnp3p/TziRfKGdo7AUZGUmBu6+8qCc= -github.com/coinbase/rosetta-sdk-go v0.7.8/go.mod h1:vB6hZ0ZnZmln3ThA4x0mZvOAPDJ5BhfgnjH76hxoy10= +github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 h1:7grrpcfCtbZLsjtB0DgMuzs1umsJmpzaHMZ6cO6iAWw= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/continuity v0.2.1 h1:/EeEo2EtN3umhbbgCveyjifoMYg0pS+nMMEemaYw634= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -228,8 +236,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= @@ -237,15 +245,13 @@ github.com/cosmos/cosmos-proto v1.0.0-alpha7 h1:yqYUOHF2jopwZh4dVQp3xgqwftE5/2hk github.com/cosmos/cosmos-proto v1.0.0-alpha7/go.mod h1:dosO4pSAbJF8zWCzCoTWP7nNsjcvSUBQmniFxDg5daw= github.com/cosmos/cosmos-sdk/api v0.1.0 h1:xfSKM0e9p+EJTMQnf5PbWE6VT8ruxTABIJ64Rd064dE= github.com/cosmos/cosmos-sdk/api v0.1.0/go.mod h1:CupqQBskAOiTXO1XDZ/wrtWzN/wTxUvbQmOqdUhR8wI= -github.com/cosmos/cosmos-sdk/db v1.0.0-beta.1 h1:6YvzjQtc+cDwCe9XwYPPa8zFCxNG79N7vmCjpK+vGOg= -github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3 h1:Ep7FHNViVwwGnwLFEPewZYsyN2CJNVMmMvFmtNQtbnw= -github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3/go.mod h1:HFea93YKmoMJ/mNKtkSeJZDtyJ4inxBsUK928KONcqo= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.18.0 h1:02ur4vnalMR2GuWCFNkuseUcl/BCVmg9tOeHOGiZOkE= -github.com/cosmos/iavl v0.18.0/go.mod h1:L0VZHfq0tqMNJvXlslGExaaiZM7eSm+90Vh9QUbp6j4= +github.com/cosmos/iavl v0.19.0 h1:sgyrjqOkycXiN7Tuupuo4QAldKFg7Sipyfeg/IL7cps= +github.com/cosmos/iavl v0.19.0/go.mod h1:l5h9pAB3m5fihB3pXVgwYqdY8aBsMagqz7T0MUjxZeA= github.com/cosmos/ibc-go/v4 v4.0.0-rc0 h1:zeMr6PNE7L300AcGkrMwRvtp62/RpGc7qU1LwhUcPKc= github.com/cosmos/ibc-go/v4 v4.0.0-rc0/go.mod h1:4LK+uPycPhebJrJ8ebIqvsMEZ0lVRVNTiEyeI9zfB0U= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= @@ -262,8 +268,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= -github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= -github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -277,7 +283,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= -github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -301,8 +306,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b h1:HBah4D48ypg3J7Np4N+HY/ZR76fx3HEUGxDU6Uk39oQ= -github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -347,10 +352,6 @@ github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2K github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -364,32 +365,22 @@ github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -528,10 +519,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -542,6 +531,11 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QG github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -553,8 +547,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.5.11 h1:wioTuNmaBU3IE9vdFtFMcmZWj0QzLc6DYaP6sNe5onY= -github.com/hashicorp/go-getter v1.5.11/go.mod h1:9i48BP6wpWweI/0/+FBjqLrp9S8XtwUGjiu0QkWHEaY= +github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY= +github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -578,8 +572,8 @@ github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -604,7 +598,6 @@ github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= -github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -731,7 +724,6 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.12.0 h1:1NQ4FpWMgn3by/n1X0fbeKEUxP1wBt7+Oitpv01HR10= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -749,7 +741,6 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -759,18 +750,16 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= -github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= -github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= +github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= @@ -792,16 +781,15 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= -github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= @@ -1115,7 +1103,6 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -1137,6 +1124,8 @@ github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUM github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -1160,11 +1149,9 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= @@ -1241,9 +1228,7 @@ github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -1253,11 +1238,10 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b h1:MKwruh+HeCSKWphkxuzvRzU4QzDkg7yiPkDVV0cDFgI= -github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b/go.mod h1:TLJifjWF6eotcfzDjKZsDqWJ+73Uvj/N85MvVyrvynM= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -1279,9 +1263,9 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/runc v1.0.3 h1:1hbqejyQWCJBvtKAfdO0b1FmaEf2z/bxnjqbARass5k= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -1307,8 +1291,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= @@ -1359,8 +1343,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.33.0 h1:rHgav/0a6+uYgGdNt3jwz8FNSesO/Hsang3O0T9A5SE= -github.com/prometheus/common v0.33.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= +github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= +github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1368,7 +1352,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= @@ -1382,7 +1365,6 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= -github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1390,12 +1372,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1475,7 +1454,6 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1485,10 +1463,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= -github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= +github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -1505,11 +1484,7 @@ github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk1 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1545,7 +1520,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 h1:O9XLFXGkVswDFmH9LaYpqu+r/AAFWqr0DL6V00KEVFg= github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -1646,6 +1620,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1654,14 +1629,12 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= @@ -1676,7 +1649,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1703,8 +1675,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1739,7 +1711,6 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1764,7 +1735,6 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1773,8 +1743,9 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220517181318-183a9ca12b87/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1809,8 +1780,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1838,7 +1810,6 @@ golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1862,7 +1833,6 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1904,8 +1874,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1920,12 +1890,14 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY= +golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2005,9 +1977,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II= -golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1 h1:NHLFZ56qCjD+0hYY3kE5Wl40Z7q4Gn9Ln/7YU0lsGko= +golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2111,7 +2082,6 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -2151,8 +2121,9 @@ google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2175,7 +2146,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= @@ -2194,6 +2164,7 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -2216,6 +2187,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -2224,8 +2196,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= @@ -2259,8 +2231,6 @@ lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= -nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/header/testing.go b/header/testing.go index 113d767802..ac5263bc07 100644 --- a/header/testing.go +++ b/header/testing.go @@ -14,11 +14,11 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/bytes" tmrand "github.com/tendermint/tendermint/libs/rand" - tmtime "github.com/tendermint/tendermint/libs/time" "github.com/tendermint/tendermint/pkg/da" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/types" - "github.com/tendermint/tendermint/version" + tmtime "github.com/tendermint/tendermint/types/time" "github.com/celestiaorg/celestia-node/core" @@ -171,7 +171,7 @@ func RandExtendedHeader(t *testing.T) *ExtendedHeader { // RandRawHeader provides a RawHeader fixture. func RandRawHeader(t *testing.T) *RawHeader { return &RawHeader{ - Version: version.Consensus{Block: uint64(11), App: uint64(1)}, + Version: version.Consensus{Block: 11, App: 1}, ChainID: "test", Height: mrand.Int63(), //nolint:gosec Time: time.Now(), diff --git a/ipld/get_test.go b/ipld/get_test.go index e925fc43cb..ce8b2fa433 100644 --- a/ipld/get_test.go +++ b/ipld/get_test.go @@ -96,7 +96,7 @@ func TestBlockRecovery(t *testing.T) { ) require.NoError(t, err) - err = eds.Repair(rowRoots, colRoots, rsmt2d.NewRSGF8Codec(), recoverTree.Constructor) + err = eds.Repair(rowRoots, colRoots) if tc.expectErr { require.Error(t, err) require.Contains(t, err.Error(), tc.errString) diff --git a/ipld/retriever.go b/ipld/retriever.go index ea2224898d..65802e0238 100644 --- a/ipld/retriever.go +++ b/ipld/retriever.go @@ -189,7 +189,7 @@ func (rs *retrievalSession) Reconstruct(ctx context.Context) (*rsmt2d.ExtendedDa defer span.End() // and try to repair with what we have - err := rs.squareImported.Repair(rs.dah.RowsRoots, rs.dah.ColumnRoots, rs.codec, rs.treeFn) + err := rs.squareImported.Repair(rs.dah.RowsRoots, rs.dah.ColumnRoots) if err != nil { span.RecordError(err) return nil, err diff --git a/node/state/keyring.go b/node/state/keyring.go index 4b5502eaa7..e7da28d776 100644 --- a/node/state/keyring.go +++ b/node/state/keyring.go @@ -21,7 +21,7 @@ func Keyring(cfg key.Config) func(keystore.Keystore, params.Network) (*apptypes. // implementation of https://github.com/celestiaorg/celestia-node/issues/415. // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed // here instead of hardcoded `BackendTest`: https://github.com/celestiaorg/celestia-node/issues/603. - encConf := encoding.MakeEncodingConfig(app.ModuleEncodingRegisters...) + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) ring, err := keyring.New(app.Name, keyring.BackendTest, ks.Path(), os.Stdin, encConf.Codec) if err != nil { return nil, err diff --git a/node/testing.go b/node/testing.go index 2961402c37..914b8d4378 100644 --- a/node/testing.go +++ b/node/testing.go @@ -49,7 +49,7 @@ func TestNode(t *testing.T, tp Type, opts ...Option) *Node { } func TestKeyringSigner(t *testing.T) *apptypes.KeyringSigner { - encConf := encoding.MakeEncodingConfig(app.ModuleEncodingRegisters...) + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) ring := keyring.NewInMemory(encConf.Codec) signer := apptypes.NewKeyringSigner(ring, "", string(params.Private)) _, _, err := signer.NewMnemonic("test_celes", keyring.English, "", diff --git a/node/tests/swamp/config.go b/node/tests/swamp/config.go index 2545b90bbe..10a6f408c5 100644 --- a/node/tests/swamp/config.go +++ b/node/tests/swamp/config.go @@ -5,7 +5,6 @@ import ( "github.com/tendermint/tendermint/abci/types" tn "github.com/tendermint/tendermint/config" - rpctest "github.com/tendermint/tendermint/rpc/test" "github.com/celestiaorg/celestia-node/core" ) @@ -20,10 +19,7 @@ type Components struct { // In addition, the empty block interval is set to 200ms func DefaultComponents() *Components { app := core.CreateKVStore(2000) - tnCfg, err := rpctest.CreateConfig("swamp_tm") - if err != nil { - panic(err) - } + tnCfg := tn.ResetTestRoot("swamp_tests") tnCfg.Consensus.CreateEmptyBlocksInterval = 100 * time.Millisecond return &Components{ App: app, diff --git a/node/tests/swamp/swamp.go b/node/tests/swamp/swamp.go index ade91645ca..3cd8e4d818 100644 --- a/node/tests/swamp/swamp.go +++ b/node/tests/swamp/swamp.go @@ -27,7 +27,7 @@ var blackholeIP6 = net.ParseIP("100::") const subscriberID string = "NewBlockSwamp/Events" -var queryEvent string = types.QueryForEvent(types.EventNewBlockValue).String() +var queryEvent string = types.QueryForEvent(types.EventNewBlock).String() // Swamp represents the main functionality that is needed for the test-case: // - Network to link the nodes @@ -70,6 +70,10 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { require.NoError(t, err) remote, err := core.NewRemote(ip, port) require.NoError(t, err) + t.Cleanup(func() { + err := remote.Stop() + require.NoError(t, err) + }) err = remote.Start() require.NoError(t, err) diff --git a/service/state/core_access.go b/service/state/core_access.go index e0cdf07884..cb2d8b9e91 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -74,7 +74,7 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { queryCli := banktypes.NewQueryClient(ca.coreConn) ca.queryCli = queryCli // create ABCI query client - cli, err := http.New(fmt.Sprintf("http://%s:%s", ca.coreIP, ca.rpcPort)) + cli, err := http.New(fmt.Sprintf("http://%s:%s", ca.coreIP, ca.rpcPort), "/websocket") if err != nil { return err } diff --git a/service/state/interface.go b/service/state/interface.go index 5be184de25..9bae4c9fd0 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -1,6 +1,8 @@ package state import ( + "cosmossdk.io/math" + "context" "github.com/cosmos/cosmos-sdk/types" @@ -29,7 +31,7 @@ type Accessor interface { BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) // Transfer sends the given amount of coins from default wallet of the node to the given account address. - Transfer(ctx context.Context, to types.Address, amount types.Int, gasLimit uint64) (*TxResponse, error) + Transfer(ctx context.Context, to types.Address, amount math.Int, gasLimit uint64) (*TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in // a block. diff --git a/service/state/state.go b/service/state/state.go index 9b010c6f45..a7bab14bff 100644 --- a/service/state/state.go +++ b/service/state/state.go @@ -1,6 +1,7 @@ package state import ( + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" coretypes "github.com/tendermint/tendermint/types" ) @@ -18,4 +19,4 @@ type TxResponse = sdk.TxResponse type Address = sdk.Address // Int is an alias to the Int type from Cosmos-SDK. -type Int = sdk.Int +type Int = math.Int From 552328a9858d093d514ca927a5b707293b39f793 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:41:56 +0000 Subject: [PATCH 0027/1008] chore(deps): bump go.opentelemetry.io/otel from 1.8.0 to 1.9.0 Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.8.0 to 1.9.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.8.0...v1.9.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 014a9a2069..9105630dcb 100644 --- a/go.mod +++ b/go.mod @@ -49,13 +49,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 github.com/tendermint/tendermint v0.35.4 - go.opentelemetry.io/otel v1.8.0 + go.opentelemetry.io/otel v1.9.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0 go.opentelemetry.io/otel/metric v0.31.0 go.opentelemetry.io/otel/sdk v1.8.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 - go.opentelemetry.io/otel/trace v1.8.0 + go.opentelemetry.io/otel/trace v1.9.0 go.uber.org/fx v1.17.1 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f diff --git a/go.sum b/go.sum index 468f93c852..05b29a479f 100644 --- a/go.sum +++ b/go.sum @@ -191,7 +191,6 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -1544,8 +1543,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= -go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= -go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= +go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw= +go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 h1:ao8CJIShCaIbaMsGxy+jp2YHSudketpDgDRcbirov78= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= @@ -1563,8 +1562,8 @@ go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023 go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= -go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= -go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= +go.opentelemetry.io/otel/trace v1.9.0 h1:oZaCNJUjWcg60VXWee8lJKlqhPbXAPB51URuR47pQYc= +go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= From 31ac8d70d1a5ab1585256802ac745fb4ee5e2a16 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:23:32 +0200 Subject: [PATCH 0028/1008] dep: upgrade ibc-go to v4.0.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9105630dcb..28158354e1 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/celestiaorg/rsmt2d v0.6.0 github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20220418184507-c53157dd63f6 github.com/cosmos/cosmos-sdk/api v0.1.0 - github.com/cosmos/ibc-go/v4 v4.0.0-rc0 + github.com/cosmos/ibc-go/v4 v4.0.0 github.com/dgraph-io/badger/v2 v2.2007.4 github.com/gammazero/workerpool v1.1.2 github.com/gogo/protobuf v1.3.3 diff --git a/go.sum b/go.sum index 05b29a479f..c993cbdf0a 100644 --- a/go.sum +++ b/go.sum @@ -251,8 +251,8 @@ github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4 github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.0 h1:sgyrjqOkycXiN7Tuupuo4QAldKFg7Sipyfeg/IL7cps= github.com/cosmos/iavl v0.19.0/go.mod h1:l5h9pAB3m5fihB3pXVgwYqdY8aBsMagqz7T0MUjxZeA= -github.com/cosmos/ibc-go/v4 v4.0.0-rc0 h1:zeMr6PNE7L300AcGkrMwRvtp62/RpGc7qU1LwhUcPKc= -github.com/cosmos/ibc-go/v4 v4.0.0-rc0/go.mod h1:4LK+uPycPhebJrJ8ebIqvsMEZ0lVRVNTiEyeI9zfB0U= +github.com/cosmos/ibc-go/v4 v4.0.0 h1:kqbbRDFB9bNLVfxImZ1wIsXZKjbS5NZ5xvnv/MoX138= +github.com/cosmos/ibc-go/v4 v4.0.0/go.mod h1:qxfXOjgJKDzdJeTTDFowrFxxI1KD3x1k0pZifdf9fOg= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= From c4fa6d78e54226fe439a82be1311bd26759bd530 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 15 Aug 2022 20:12:42 +0200 Subject: [PATCH 0029/1008] rebased on backport pr --- header/testing.go | 1 - 1 file changed, 1 deletion(-) diff --git a/header/testing.go b/header/testing.go index ac5263bc07..05e0d7982b 100644 --- a/header/testing.go +++ b/header/testing.go @@ -21,7 +21,6 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-node/ipld" ) From 289cdf3bcfcf015701f0ec522bf5dd083a298c70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 15:55:25 +0000 Subject: [PATCH 0030/1008] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.8.0 to 1.9.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.8.0...v1.9.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 28158354e1..fb32bbcc64 100644 --- a/go.mod +++ b/go.mod @@ -51,9 +51,9 @@ require ( github.com/tendermint/tendermint v0.35.4 go.opentelemetry.io/otel v1.9.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 go.opentelemetry.io/otel/metric v0.31.0 - go.opentelemetry.io/otel/sdk v1.8.0 + go.opentelemetry.io/otel/sdk v1.9.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.9.0 go.uber.org/fx v1.17.1 @@ -254,9 +254,9 @@ require ( github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 // indirect go.opentelemetry.io/proto/otlp v0.18.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.14.0 // indirect diff --git a/go.sum b/go.sum index c993cbdf0a..9d9d75dab6 100644 --- a/go.sum +++ b/go.sum @@ -1545,20 +1545,20 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw= go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 h1:ao8CJIShCaIbaMsGxy+jp2YHSudketpDgDRcbirov78= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 h1:ggqApEjDKczicksfvZUCxuvoyDmR6Sbm56LwiK8DVR0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0/go.mod h1:nkenGD8vcvs0uN6WhR90ZVHQlgDsRmXicnNadMnk+XQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 h1:MuEG0gG27QZQrqhNl0f7vQ5Nl03OQfFeDAqWkGt+1zM= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0/go.mod h1:52qtPFDDaa0FaSyyzPnxWMehx2SZv0xuobTlNEZA2JA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 h1:LrHL1A3KqIgAgi6mK7Q0aczmzU414AONAGT5xtnp+uo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0/go.mod h1:w8aZL87GMOvOBa2lU/JlVXE1q4chk/0FX+8ai4513bw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0 h1:SMO1HopgdAqNRit+WA3w3dcJSGANuH/ihKXDekEHfuY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.8.0/go.mod h1:tsw+QO2+pGo7xOrPXrS27HxW8uqGQkw5AzJwdsoyvgw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 h1:NN90Cuna0CnBg8YNu1Q0V35i2E8LDByFOwHRCq/ZP9I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0/go.mod h1:0EsCXjZAiiZGnLdEUXM9YjCKuuLZMYyglh2QDXcYKVA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 h1:FAF9l8Wjxi9Ad2k/vLTfHZyzXYX72C62wBGpV3G6AIo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0/go.mod h1:smUdtylgc0YQiUr2PuifS4hBXhAS5xtR6WQhxP1wiNA= go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk= -go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c= +go.opentelemetry.io/otel/sdk v1.9.0 h1:LNXp1vrr83fNXTHgU8eO89mhzxb/bbWAsHG6fNf3qWo= +go.opentelemetry.io/otel/sdk v1.9.0/go.mod h1:AEZc8nt5bd2F7BC24J5R0mrjYnpEgYHyTcM/vrSple4= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= From e2e0d82f3b262b2654c97315a876b246e1ab2f2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 15:59:34 +0000 Subject: [PATCH 0031/1008] chore(deps): bump github.com/gammazero/workerpool from 1.1.2 to 1.1.3 Bumps [github.com/gammazero/workerpool](https://github.com/gammazero/workerpool) from 1.1.2 to 1.1.3. - [Release notes](https://github.com/gammazero/workerpool/releases) - [Commits](https://github.com/gammazero/workerpool/compare/v1.1.2...v1.1.3) --- updated-dependencies: - dependency-name: github.com/gammazero/workerpool dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index fb32bbcc64..fabb32c0f3 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/cosmos/cosmos-sdk/api v0.1.0 github.com/cosmos/ibc-go/v4 v4.0.0 github.com/dgraph-io/badger/v2 v2.2007.4 - github.com/gammazero/workerpool v1.1.2 + github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 @@ -114,7 +114,7 @@ require ( github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/gammazero/deque v0.1.0 // indirect + github.com/gammazero/deque v0.2.0 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect diff --git a/go.sum b/go.sum index 9d9d75dab6..6453709ffb 100644 --- a/go.sum +++ b/go.sum @@ -346,10 +346,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik= -github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M= -github.com/gammazero/workerpool v1.1.2 h1:vuioDQbgrz4HoaCi2q1HLlOXdpbap5AET7xu5/qj87g= -github.com/gammazero/workerpool v1.1.2/go.mod h1:UelbXcO0zCIGFcufcirHhq2/xtLXJdQ29qZNlXG9OjQ= +github.com/gammazero/deque v0.2.0 h1:SkieyNB4bg2/uZZLxvya0Pq6diUlwx7m2TeT7GAIWaA= +github.com/gammazero/deque v0.2.0/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= +github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -1581,8 +1581,8 @@ go.uber.org/fx v1.17.1/go.mod h1:yO7KN5rhlARljyo4LR047AjaV6J+KFzd/Z7rnTbEn0A= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= From 42d82a5b85fc3f251d6cc1b443286c08d4d31df8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 15:59:49 +0000 Subject: [PATCH 0032/1008] chore(deps): bump go.uber.org/fx from 1.17.1 to 1.18.1 Bumps [go.uber.org/fx](https://github.com/uber-go/fx) from 1.17.1 to 1.18.1. - [Release notes](https://github.com/uber-go/fx/releases) - [Changelog](https://github.com/uber-go/fx/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/fx/compare/v1.17.1...v1.18.1) --- updated-dependencies: - dependency-name: go.uber.org/fx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index fabb32c0f3..b3eb9f5782 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( go.opentelemetry.io/otel/sdk v1.9.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.9.0 - go.uber.org/fx v1.17.1 + go.uber.org/fx v1.18.1 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f google.golang.org/grpc v1.48.0 @@ -259,7 +259,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 // indirect go.opentelemetry.io/proto/otlp v0.18.0 // indirect go.uber.org/atomic v1.9.0 // indirect - go.uber.org/dig v1.14.0 // indirect + go.uber.org/dig v1.15.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect diff --git a/go.sum b/go.sum index 6453709ffb..eb0bbc1536 100644 --- a/go.sum +++ b/go.sum @@ -1574,10 +1574,10 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/dig v1.14.0 h1:VmGvIH45/aapXPQkaOrK5u4B5B7jxZB98HM/utx0eME= -go.uber.org/dig v1.14.0/go.mod h1:jHAn/z1Ld1luVVyGKOAIFYz/uBFqKjjEEdIqVAqfQ2o= -go.uber.org/fx v1.17.1 h1:S42dZ6Pok8hQ3jxKwo6ZMYcCgHQA/wAS/gnpRa1Pksg= -go.uber.org/fx v1.17.1/go.mod h1:yO7KN5rhlARljyo4LR047AjaV6J+KFzd/Z7rnTbEn0A= +go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= +go.uber.org/fx v1.18.1 h1:I7VWkdv4iKcbpH7KVSi9Fe1LGmpJv+pbBIb9NidPb+E= +go.uber.org/fx v1.18.1/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= From aa14091a1f4e8347d8cd17d6056eb4df58359d34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 15:59:36 +0000 Subject: [PATCH 0033/1008] chore(deps): bump github.com/ipfs/go-datastore from 0.5.1 to 0.6.0 Bumps [github.com/ipfs/go-datastore](https://github.com/ipfs/go-datastore) from 0.5.1 to 0.6.0. - [Release notes](https://github.com/ipfs/go-datastore/releases) - [Commits](https://github.com/ipfs/go-datastore/compare/v0.5.1...v0.6.0) --- updated-dependencies: - dependency-name: github.com/ipfs/go-datastore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index b3eb9f5782..fbf2dd8a46 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.3.0 github.com/ipfs/go-cid v0.2.0 - github.com/ipfs/go-datastore v0.5.1 + github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 github.com/ipfs/go-ipfs-exchange-interface v0.1.0 diff --git a/go.sum b/go.sum index eb0bbc1536..42ff221798 100644 --- a/go.sum +++ b/go.sum @@ -629,8 +629,9 @@ github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13X github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= github.com/ipfs/go-datastore v0.4.6/go.mod h1:XSipLSc64rFKSFRFGo1ecQl+WhYce3K7frtpHkyPFUc= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= -github.com/ipfs/go-datastore v0.5.1 h1:WkRhLuISI+XPD0uk3OskB0fYFSyqK8Ob5ZYew9Qa1nQ= github.com/ipfs/go-datastore v0.5.1/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= +github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= From 555482a1ba74c0b48d8c7d50c942267db936b86a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 16:22:00 +0000 Subject: [PATCH 0034/1008] chore(deps): bump cosmossdk.io/math from 1.0.0-beta.2 to 1.0.0-beta.3 Bumps [cosmossdk.io/math](https://github.com/cosmos/cosmos-sdk) from 1.0.0-beta.2 to 1.0.0-beta.3. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/math/v1.0.0-beta.2...math/v1.0.0-beta.3) --- updated-dependencies: - dependency-name: cosmossdk.io/math dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fbf2dd8a46..9dfa200613 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch require ( - cosmossdk.io/math v1.0.0-beta.2 + cosmossdk.io/math v1.0.0-beta.3 github.com/BurntSushi/toml v1.2.0 github.com/celestiaorg/celestia-app v0.7.0-rc-1 github.com/celestiaorg/go-libp2p-messenger v0.1.0 diff --git a/go.sum b/go.sum index 42ff221798..319815f756 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= -cosmossdk.io/math v1.0.0-beta.2 h1:17hSVc9ne1c31IaLDfjRojtN+y4Rd2N8H/6Fht2sBzw= -cosmossdk.io/math v1.0.0-beta.2/go.mod h1:u/MXvf8wbUbCsAEyQSSYXXMsczAsFX48e2D6JI86T4o= +cosmossdk.io/math v1.0.0-beta.3 h1:TbZxSopz2LqjJ7aXYfn7nJSb8vNaBklW6BLpcei1qwM= +cosmossdk.io/math v1.0.0-beta.3/go.mod h1:3LYasri3Zna4XpbrTNdKsWmD5fHHkaNAod/mNT9XdE4= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= From ac4324ccbbf4bcae5da64bf9dbfc9ef459d9fb10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:37:16 +0000 Subject: [PATCH 0035/1008] chore(deps): bump github.com/ipfs/go-blockservice from 0.3.0 to 0.4.0 Bumps [github.com/ipfs/go-blockservice](https://github.com/ipfs/go-blockservice) from 0.3.0 to 0.4.0. - [Release notes](https://github.com/ipfs/go-blockservice/releases) - [Commits](https://github.com/ipfs/go-blockservice/compare/v0.3.0...v0.4.0) --- updated-dependencies: - dependency-name: github.com/ipfs/go-blockservice dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 13 ++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9dfa200613..9cbad260b8 100644 --- a/go.mod +++ b/go.mod @@ -20,15 +20,15 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/ipfs/go-bitswap v0.7.0 + github.com/ipfs/go-bitswap v0.8.0 github.com/ipfs/go-block-format v0.0.3 - github.com/ipfs/go-blockservice v0.3.0 + github.com/ipfs/go-blockservice v0.4.0 github.com/ipfs/go-cid v0.2.0 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 - github.com/ipfs/go-ipfs-exchange-interface v0.1.0 - github.com/ipfs/go-ipfs-exchange-offline v0.2.0 + github.com/ipfs/go-ipfs-exchange-interface v0.2.0 + github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-ipfs-routing v0.2.1 github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 diff --git a/go.sum b/go.sum index 319815f756..bb8c618cc4 100644 --- a/go.sum +++ b/go.sum @@ -603,13 +603,14 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= -github.com/ipfs/go-bitswap v0.7.0 h1:vSte4lll4Rob7cMQERUouxtFbuD7Vl4Hq+XEAp2ipKY= -github.com/ipfs/go-bitswap v0.7.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= +github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= +github.com/ipfs/go-bitswap v0.8.0/go.mod h1:/h8sBij8UVEaNWl8ABzpLRA5Y1cttdNUnpeGo2AA/LQ= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= -github.com/ipfs/go-blockservice v0.3.0 h1:cDgcZ+0P0Ih3sl8+qjFr2sVaMdysg/YZpLj5WJ8kiiw= github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= +github.com/ipfs/go-blockservice v0.4.0 h1:7MUijAW5SqdsqEW/EhnNFRJXVF8mGU5aGhZ3CQaCWbY= +github.com/ipfs/go-blockservice v0.4.0/go.mod h1:kRjO3wlGW9mS1aKuiCeGhx9K1DagQ10ACpVO59qgAx4= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -658,10 +659,12 @@ github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= -github.com/ipfs/go-ipfs-exchange-interface v0.1.0 h1:TiMekCrOGQuWYtZO3mf4YJXDIdNgnKWZ9IE3fGlnWfo= github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= -github.com/ipfs/go-ipfs-exchange-offline v0.2.0 h1:2PF4o4A7W656rC0RxuhUace997FTcDTcIQ6NoEtyjAI= +github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= +github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= github.com/ipfs/go-ipfs-exchange-offline v0.2.0/go.mod h1:HjwBeW0dvZvfOMwDP0TSKXIHf2s+ksdP4E3MLDRtLKY= +github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= +github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= From 69a4ee5aee495299930575bfefec7a012fe47059 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 16:53:24 +0000 Subject: [PATCH 0036/1008] chore(deps): bump github.com/ipfs/go-bitswap from 0.7.0 to 0.9.0 Bumps [github.com/ipfs/go-bitswap](https://github.com/ipfs/go-bitswap) from 0.7.0 to 0.9.0. - [Release notes](https://github.com/ipfs/go-bitswap/releases) - [Commits](https://github.com/ipfs/go-bitswap/compare/v0.7.0...v0.9.0) --- updated-dependencies: - dependency-name: github.com/ipfs/go-bitswap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9cbad260b8..75b5583954 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/ipfs/go-bitswap v0.8.0 + github.com/ipfs/go-bitswap v0.9.0 github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.4.0 github.com/ipfs/go-cid v0.2.0 diff --git a/go.sum b/go.sum index bb8c618cc4..5edaf400d8 100644 --- a/go.sum +++ b/go.sum @@ -603,8 +603,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= -github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= -github.com/ipfs/go-bitswap v0.8.0/go.mod h1:/h8sBij8UVEaNWl8ABzpLRA5Y1cttdNUnpeGo2AA/LQ= +github.com/ipfs/go-bitswap v0.9.0 h1:/dZi/XhUN/aIk78pI4kaZrilUglJ+7/SCmOHWIpiy8E= +github.com/ipfs/go-bitswap v0.9.0/go.mod h1:zkfBcGWp4dQTQd0D0akpudhpOVUAJT9GbH9tDmR8/s4= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= From 9e9fc18e1c0e3c4bd3453e555dfdec438fbad966 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 22 Aug 2022 17:06:59 +0300 Subject: [PATCH 0037/1008] dep: bump celestia-core version (#1031) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 75b5583954..5f7df72a51 100644 --- a/go.mod +++ b/go.mod @@ -286,5 +286,5 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 => github.com/celestiaorg/go-libp2p-pubsub v0.6.2-0.20220812132010-46b2a019f2f2 - github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.3.1-tm-v0.34.20 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20 ) diff --git a/go.sum b/go.sum index 5edaf400d8..84c012bb36 100644 --- a/go.sum +++ b/go.sum @@ -169,8 +169,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/celestiaorg/celestia-app v0.7.0-rc-1 h1:5d6z/qmzM93yYLSXWule29tfwQ2cY7HGigt6PhZm2GI= github.com/celestiaorg/celestia-app v0.7.0-rc-1/go.mod h1:Ic44p6PGAdd/H5MS4Ga85wcKIQr+MvgsQNmxhnMG0Co= -github.com/celestiaorg/celestia-core v1.3.1-tm-v0.34.20 h1:/kcfjyOXA6QoJbyVT0LM2I9WeCQHEoPVWJ/MYz+S/r4= -github.com/celestiaorg/celestia-core v1.3.1-tm-v0.34.20/go.mod h1:bFyLvRuHAPewkGy4WazXJhfYhdV4u79iuoYaeNL3EO4= +github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20 h1:XspZtkoIfJ68TCVOOGWkI4W7taQFJLLqSu6GME5RbqU= +github.com/celestiaorg/celestia-core v1.3.3-tm-v0.34.20/go.mod h1:f4R8qNJrP1CDH0SNwj4jA3NymBLQM4lNdx6Ijmfllbw= github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0 h1:A1F7L/09uGClsU+kmugMy47Ezv4ll0uxNRNdaGa37T8= github.com/celestiaorg/cosmos-sdk v1.2.0-sdk-v0.46.0/go.mod h1:OXRC0p460CFKl77uQZWY/8p5uZmDrNum7BmVZDupq0Q= github.com/celestiaorg/go-leopard v0.1.0 h1:28z2EkvKJIez5J9CEaiiUEC+OxalRLtTGJJ1oScfE1g= From 70867f4c20249478f5203de6672d4a72a9a394a7 Mon Sep 17 00:00:00 2001 From: iofq Date: Tue, 23 Aug 2022 16:23:42 -0500 Subject: [PATCH 0038/1008] Add `concurrency` block to cancel previous CI runs (#894) --- .github/workflows/docker-build.yml | 4 ++++ .github/workflows/go-ci.yml | 4 ++++ .github/workflows/labels.yml | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 13cad5a212..5a4ed89ede 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -11,6 +11,10 @@ env: IMAGE_NAME: ${{ github.repository }} REGISTRY: ghcr.io +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: docker-build: runs-on: "ubuntu-latest" diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 4d1d7e4ad2..575fb1e2d8 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -11,6 +11,10 @@ on: env: GO_VERSION: 1.18 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: lint: name: Lint diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index b967061b8e..e628ebc292 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -3,6 +3,11 @@ name: Required Labels on: pull_request: types: [opened, labeled, unlabeled, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: label: runs-on: ubuntu-latest From 1286f818ab9d1ac3df9e9f7691ddb0a9b556cea9 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 23 Aug 2022 19:20:24 +0200 Subject: [PATCH 0039/1008] Revert "chore(deps): bump github.com/ipfs/go-bitswap from 0.7.0 to 0.9.0" This reverts commit 69a4ee5aee495299930575bfefec7a012fe47059. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5f7df72a51..f6cd5e2013 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-retryablehttp v0.7.1-0.20211018174820-ff6d014e72d9 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d - github.com/ipfs/go-bitswap v0.9.0 + github.com/ipfs/go-bitswap v0.8.0 github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.4.0 github.com/ipfs/go-cid v0.2.0 diff --git a/go.sum b/go.sum index 84c012bb36..83e7ccfac7 100644 --- a/go.sum +++ b/go.sum @@ -603,8 +603,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= -github.com/ipfs/go-bitswap v0.9.0 h1:/dZi/XhUN/aIk78pI4kaZrilUglJ+7/SCmOHWIpiy8E= -github.com/ipfs/go-bitswap v0.9.0/go.mod h1:zkfBcGWp4dQTQd0D0akpudhpOVUAJT9GbH9tDmR8/s4= +github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= +github.com/ipfs/go-bitswap v0.8.0/go.mod h1:/h8sBij8UVEaNWl8ABzpLRA5Y1cttdNUnpeGo2AA/LQ= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= From fe7fcedfa6bffc63e4ca2e169f0dac271a7f6b92 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 24 Aug 2022 12:06:24 +0200 Subject: [PATCH 0040/1008] tracing: Adding traces to `get_shares.go` (#1001) * tracing: adding tracing * feat: setting trace status * lint: goimports * feat: tracing in trees * refactor: removing unnecessary SetStatus tags * refactor: moving attributes to outer span * chore: linter imports * fix: decrement waitgroup before ending span --- ipld/get_namespaced_shares.go | 24 +++++++++++++----------- ipld/get_shares.go | 27 ++++++++++++++++++++++++--- ipld/helpers.go | 2 ++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/ipld/get_namespaced_shares.go b/ipld/get_namespaced_shares.go index 80b870a33e..0a387aa652 100644 --- a/ipld/get_namespaced_shares.go +++ b/ipld/get_namespaced_shares.go @@ -10,7 +10,7 @@ import ( "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/codes" "github.com/celestiaorg/celestia-node/ipld/plugin" "github.com/celestiaorg/nmt" @@ -126,7 +126,7 @@ func getLeavesByNamespace( // buffer the jobs to avoid blocking, we only need as many // queued as the number of shares in the second-to-last layer jobs := make(chan *job, (maxShares+1)/2) - jobs <- &job{id: root} + jobs <- &job{id: root, ctx: ctx} var wg wrappedWaitGroup wg.jobs = jobs @@ -154,10 +154,15 @@ func getLeavesByNamespace( return leaves[bounds.lowest : bounds.highest+1], retrievalErr } namespacePool.Submit(func() { - ctx, span := tracer.Start(ctx, "process-job") + ctx, span := tracer.Start(j.ctx, "process-job") defer span.End() defer wg.done() + span.SetAttributes( + attribute.String("cid", j.id.String()), + attribute.Int("pos", j.pos), + ) + // if an error is likely to be returned or not depends on // the underlying impl of the blockservice, currently it is not a realistic probability nd, err := plugin.GetNode(ctx, bGetter, j.id) @@ -166,9 +171,7 @@ func getLeavesByNamespace( retrievalErr = err }) log.Errorw("getSharesByNamespace: could not retrieve node", "nID", nID, "pos", j.pos, "err", err) - span.RecordError(err, trace.WithAttributes( - attribute.Int("pos", j.pos), - )) + span.SetStatus(codes.Error, err.Error()) // we still need to update the bounds bounds.update(int64(j.pos)) return @@ -178,7 +181,7 @@ func getLeavesByNamespace( linkCount := uint64(len(links)) if linkCount == 1 { // successfully fetched a leaf belonging to the namespace - span.AddEvent("found-leaf") + span.SetStatus(codes.Ok, "") leaves[j.pos] = nd // we found a leaf, so we update the bounds // the update routine is repeated until the atomic swap is successful @@ -193,6 +196,9 @@ func getLeavesByNamespace( // position represents the index in a flattened binary tree, // so we can return a slice of leaves in order pos: j.pos*2 + i, + // we pass the context to job so that spans are tracked in a tree + // structure + ctx: ctx, } // if the link's nID isn't in range we don't need to create a new job for it @@ -206,10 +212,6 @@ func getLeavesByNamespace( wg.add(1) select { case jobs <- newJob: - span.AddEvent("added-job", trace.WithAttributes( - attribute.String("cid", newJob.id.String()), - attribute.Int("pos", newJob.pos), - )) case <-ctx.Done(): return } diff --git a/ipld/get_shares.go b/ipld/get_shares.go index 07186cc20c..1c4ef3b363 100644 --- a/ipld/get_shares.go +++ b/ipld/get_shares.go @@ -7,6 +7,8 @@ import ( "github.com/gammazero/workerpool" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "github.com/celestiaorg/celestia-node/ipld/plugin" ) @@ -52,9 +54,12 @@ var pool = workerpool.New(NumWorkersLimit) // implementation that rely on this property are explicitly tagged with // (bin-tree-feat). func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid, shares int, put func(int, Share)) { + ctx, span := tracer.Start(ctx, "get-shares") + defer span.End() + // this buffer ensures writes to 'jobs' are never blocking (bin-tree-feat) jobs := make(chan *job, (shares+1)/2) // +1 for the case where 'shares' is 1 - jobs <- &job{id: root} + jobs <- &job{id: root, ctx: ctx} // total is an amount of routines spawned and total amount of nodes we process (bin-tree-feat) // so we can specify exact amount of loops we do, and wait for this amount // of routines to finish processing @@ -68,11 +73,21 @@ func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.C // work over each job concurrently, s.t. shares do not block // processing of each other pool.Submit(func() { + ctx, span := tracer.Start(j.ctx, "process-job") + defer span.End() defer wg.Done() + + span.SetAttributes( + attribute.String("cid", j.id.String()), + attribute.Int("pos", j.pos), + ) + nd, err := plugin.GetNode(ctx, bGetter, j.id) if err != nil { // we don't really care about errors here // just fetch as much as possible + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return } // check links to know what we should do with the node @@ -82,11 +97,14 @@ func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.C // leaf has its own additional leaf(hack) so get it nd, err := plugin.GetNode(ctx, bGetter, lnks[0].Cid) if err != nil { - // again, we don't care + // again, we don't really care much, just fetch as much as possible + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return } // successfully fetched a share/leaf - // ladies and gentlemen, we got em + // ladies and gentlemen, we got em! + span.SetStatus(codes.Ok, "") put(j.pos, leafToShare(nd)) return } @@ -99,6 +117,9 @@ func GetShares(ctx context.Context, bGetter blockservice.BlockGetter, root cid.C // calc position for children nodes (bin-tree-feat), // s.t. 'if' above knows where to put a share pos: j.pos*2 + i, + // we pass the context to job so that spans are tracked in a tree + // structure + ctx: ctx, }: case <-ctx.Done(): return diff --git a/ipld/helpers.go b/ipld/helpers.go index 17e1c6b4da..83a21a8947 100644 --- a/ipld/helpers.go +++ b/ipld/helpers.go @@ -1,6 +1,7 @@ package ipld import ( + "context" "fmt" "github.com/ipfs/go-cid" @@ -10,6 +11,7 @@ import ( type job struct { id cid.Cid pos int + ctx context.Context } func SanityCheckNID(nID []byte) error { From c6960098600e3bf54a0f1d013a3f90cda7c5e9d5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 26 Aug 2022 17:00:11 +0300 Subject: [PATCH 0041/1008] fraud: add proof mock (#1033) * fraud: add proof mock * chore: rework register method --- docs/adr/adr-006-fraud-service.md | 16 +- fraud/bad_encoding.go | 11 +- fraud/helpers.go | 10 +- fraud/proof.go | 14 +- fraud/registry.go | 18 +- fraud/service.go | 2 +- fraud/service_test.go | 377 +++++++++--------------------- fraud/store.go | 8 +- fraud/store_test.go | 56 ++--- fraud/testing.go | 55 +++++ 10 files changed, 217 insertions(+), 350 deletions(-) diff --git a/docs/adr/adr-006-fraud-service.md b/docs/adr/adr-006-fraud-service.md index 0abb1064d9..29f2c2c4c8 100644 --- a/docs/adr/adr-006-fraud-service.md +++ b/docs/adr/adr-006-fraud-service.md @@ -10,10 +10,13 @@ - changed from NamespaceShareWithProof to ShareWithProof; - made ProofUnmarshaler public and extended return params; - fixed typo issues; -- 2022.06.15 - Extend Proof interface with HeaderHash method -- 2022.06.22 - Updated rsmt2d to change isRow to Axis -- 2022.07.03 - Add storage description -- 2022.07.23 - rework unmarshalers registration +- 2022.06.15 - Extend Proof interface with HeaderHash method; +- 2022.06.22 - Updated rsmt2d to change isRow to Axis; +- 2022.07.03 - Added storage description; +- 2022.07.23 - Reworked unmarshalers registration; +- 2022.08.25 - + - Added BinaryUnmarshaller to Proof interface; + - Changed ProofType type from int to string; ## Authors @@ -68,7 +71,7 @@ In addition, `das.Daser`: ```go // Currently, we support only one fraud proof. But this enum will be extended in the future with other const ( - BadEncoding ProofType = 0 + BadEncoding ProofType = "badencoding" ) type BadEncodingProof struct { @@ -126,7 +129,7 @@ In addition, `das.Daser`: ```go // ProofType is a enum type that represents a particular type of fraud proof. - type ProofType int + type ProofType string // Proof is a generic interface that will be used for all types of fraud proofs in the network. type Proof interface { @@ -136,6 +139,7 @@ In addition, `das.Daser`: Validate(*header.ExtendedHeader) error encoding.BinaryMarshaller + encoding.BinaryUnmarshaler } ``` diff --git a/fraud/bad_encoding.go b/fraud/bad_encoding.go index 071d672920..143b4a7189 100644 --- a/fraud/bad_encoding.go +++ b/fraud/bad_encoding.go @@ -18,7 +18,7 @@ import ( ) func init() { - Register(BadEncoding, UnmarshalBEFP) + Register(&BadEncodingProof{}) } type BadEncodingProof struct { @@ -83,15 +83,6 @@ func (p *BadEncodingProof) MarshalBinary() ([]byte, error) { return badEncodingFraudProof.Marshal() } -// UnmarshalBEFP converts given data to BadEncodingProof. -func UnmarshalBEFP(data []byte) (Proof, error) { - befp := &BadEncodingProof{} - if err := befp.UnmarshalBinary(data); err != nil { - return nil, err - } - return befp, nil -} - // UnmarshalBinary converts binary to BadEncodingProof. func (p *BadEncodingProof) UnmarshalBinary(data []byte) error { in := pb.BadEncoding{} diff --git a/fraud/helpers.go b/fraud/helpers.go index 9cc685bc1d..d4b7fa9624 100644 --- a/fraud/helpers.go +++ b/fraud/helpers.go @@ -7,20 +7,14 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" ) -const fraudSubSuffix = "-sub" - -func getSubTopic(p ProofType) string { - return p.String() + fraudSubSuffix -} - func join(p *pubsub.PubSub, proofType ProofType, validate func(context.Context, ProofType, peer.ID, *pubsub.Message) pubsub.ValidationResult) (*pubsub.Topic, error) { - t, err := p.Join(getSubTopic(proofType)) + t, err := p.Join(string(proofType)) if err != nil { return nil, err } err = p.RegisterTopicValidator( - getSubTopic(proofType), + string(proofType), func(ctx context.Context, from peer.ID, msg *pubsub.Message) pubsub.ValidationResult { return validate(ctx, proofType, from, msg) }, diff --git a/fraud/proof.go b/fraud/proof.go index 86e29c5e6b..2261d3cdf9 100644 --- a/fraud/proof.go +++ b/fraud/proof.go @@ -24,21 +24,12 @@ func (e *errNoUnmarshaler) Error() string { return fmt.Sprintf("fraud: unmarshaler for %s type is not registered", e.proofType) } -type ProofType int +type ProofType string const ( - BadEncoding ProofType = iota + BadEncoding ProofType = "badencoding" ) -func (p ProofType) String() string { - switch p { - case BadEncoding: - return "badencoding" - default: - panic(fmt.Sprintf("fraud: invalid proof type: %d", p)) - } -} - // Proof is a generic interface that will be used for all types of fraud proofs in the network. type Proof interface { // Type returns the exact type of fraud proof. @@ -53,6 +44,7 @@ type Proof interface { Validate(*header.ExtendedHeader) error encoding.BinaryMarshaler + encoding.BinaryUnmarshaler } // OnProof subscribes to the given Fraud Proof topic via the given Subscriber. diff --git a/fraud/registry.go b/fraud/registry.go index 388ce26cc0..ed2a492cce 100644 --- a/fraud/registry.go +++ b/fraud/registry.go @@ -1,6 +1,8 @@ package fraud import ( + "fmt" + "reflect" "sync" ) @@ -9,9 +11,19 @@ var ( defaultUnmarshalers = map[ProofType]ProofUnmarshaler{} ) -// Register sets unmarshaler in map by provided ProofType. -func Register(proofType ProofType, unmarshaler ProofUnmarshaler) { +// Register adds a string representation and unmarshaller for the provided ProofType. +func Register(p Proof) { unmarshalersLk.Lock() defer unmarshalersLk.Unlock() - defaultUnmarshalers[proofType] = unmarshaler + if _, ok := defaultUnmarshalers[p.Type()]; ok { + panic(fmt.Sprintf("fraud: unmarshaler for %s proof is registered", p.Type())) + } + defaultUnmarshalers[p.Type()] = func(data []byte) (Proof, error) { + // the underlying type of `p` is a pointer to a struct and assigning `p` to a new variable is not the + // case, because it could lead to data races. + // So, there is no easier way to create a hard copy of Proof other than using a reflection. + proof := reflect.New(reflect.ValueOf(p).Elem().Type()).Interface().(Proof) + err := proof.UnmarshalBinary(data) + return proof, err + } } diff --git a/fraud/service.go b/fraud/service.go index c1990987e4..a154ba2455 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -82,7 +82,7 @@ func (f *service) processIncoming( ) pubsub.ValidationResult { proof, err := Unmarshal(proofType, msg.Data) if err != nil { - log.Error(err) + log.Errorw("unmarshalling failed", "err", err) if !errors.Is(err, &errNoUnmarshaler{}) { f.pubsub.BlacklistPeer(from) } diff --git a/fraud/service_test.go b/fraud/service_test.go index b9065c5823..55acbe9639 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -2,39 +2,40 @@ package fraud import ( "context" - "errors" + "encoding/hex" "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" - "github.com/libp2p/go-libp2p-core/event" "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" + pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/celestiaorg/celestia-node/ipld" + "github.com/celestiaorg/celestia-node/header" ) func TestService_Subscribe(t *testing.T) { s, _ := createService(t) + proof := newValidProof() + _, err := s.Subscribe(proof.Type()) + require.NoError(t, err) +} - _, err := s.Subscribe(BadEncoding) +func TestService_SubscribeFails(t *testing.T) { + s, _ := createService(t) + proof := newValidProof() + delete(defaultUnmarshalers, proof.Type()) + _, err := s.Subscribe(proof.Type()) require.NoError(t, err) } func TestService_BroadcastFails(t *testing.T) { s, _ := createService(t) - p := CreateBadEncodingProof([]byte("hash"), 0, &ipld.ErrByzantine{ - Index: 0, - Shares: make([]*ipld.ShareWithProof, 0), - }, - ) + p := newValidProof() require.Error(t, s.Broadcast(context.TODO(), p)) } @@ -42,129 +43,70 @@ func TestService_Broadcast(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) t.Cleanup(cancel) - s, store := createService(t) - h, err := store.GetByHeight(ctx, 1) - require.NoError(t, err) - - faultHeader, err := generateByzantineError(ctx, t, h, mdutils.Bserv()) - require.Error(t, err) - var errByz *ipld.ErrByzantine - require.True(t, errors.As(err, &errByz)) + s, _ := createService(t) - subs, err := s.Subscribe(BadEncoding) + proof := newValidProof() + subs, err := s.Subscribe(proof.Type()) require.NoError(t, err) - require.NoError(t, s.Broadcast(ctx, CreateBadEncodingProof([]byte("hash"), uint64(h.Height), errByz))) - p, err := subs.Proof(ctx) + + require.NoError(t, s.Broadcast(ctx, proof)) + _, err = subs.Proof(ctx) require.NoError(t, err) - require.NoError(t, p.Validate(faultHeader)) + require.NoError(t, nil) } -func TestService_BlackListPeer(t *testing.T) { +func TestService_processIncoming(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) // create mock network - net, err := mocknet.FullMeshLinked(3) - require.NoError(t, err) - - // create first fraud service that will broadcast incorrect Fraud Proof - serviceA, store1 := createServiceWithHost(ctx, t, net.Hosts()[0]) - - h, err := store1.GetByHeight(ctx, 1) - require.NoError(t, err) - - // create and break byzantine error - _, err = generateByzantineError(ctx, t, h, mdutils.Bserv()) - require.Error(t, err) - var errByz *ipld.ErrByzantine - require.True(t, errors.As(err, &errByz)) - errByz.Index = 2 - - fserviceA := serviceA.(*service) - require.NotNil(t, fserviceA) - - // create second service that will receive and validate Fraud Proof - serviceB, _ := createServiceWithHost(ctx, t, net.Hosts()[1]) - - fserviceB := serviceB.(*service) - require.NotNil(t, fserviceB) - - blackList, err := pubsub.NewTimeCachedBlacklist(time.Hour * 1) - require.NoError(t, err) - // create pub sub in order to listen for Fraud Proof - psC, err := pubsub.NewGossipSub(ctx, net.Hosts()[2], // -> C - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign), pubsub.WithBlacklist(blackList)) - require.NoError(t, err) - - addrB := host.InfoFromHost(net.Hosts()[1]) // -> B - - serviceC := NewService(psC, store1.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())) - - sub0, err := net.Hosts()[0].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - sub2, err := net.Hosts()[2].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - - // connect peers: A -> B -> C, so A and C are not connected to each other - require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A - require.NoError(t, net.Hosts()[2].Connect(ctx, *addrB)) // host[2] is C - - // wait on both peer identification events - for i := 0; i < 2; i++ { - select { - case <-sub0.Out(): - case <-sub2.Out(): - case <-ctx.Done(): - assert.FailNow(t, "timeout waiting for peers to connect") - } + net, err := mocknet.FullMeshLinked(2) + require.NoError(t, err) + + var tests = []struct { + precondition func() + proof *mockProof + validationResult pubsub.ValidationResult + }{ + { + nil, + newValidProof(), + pubsub.ValidationAccept, + }, + { + nil, + newInvalidProof(), + pubsub.ValidationReject, + }, + { + func() { + delete(defaultUnmarshalers, mockProofType) + }, + newValidProof(), + pubsub.ValidationReject, + }, } - - // subscribe to BEFP - subsA, err := serviceA.Subscribe(BadEncoding) - require.NoError(t, err) - defer subsA.Cancel() - - subsB, err := serviceB.Subscribe(BadEncoding) - require.NoError(t, err) - defer subsB.Cancel() - - subsC, err := serviceC.Subscribe(BadEncoding) - require.NoError(t, err) - defer subsC.Cancel() - - befp := CreateBadEncodingProof([]byte("hash"), uint64(h.Height), errByz) - // deregister validator in order to send Fraud Proof - fserviceA.pubsub.UnregisterTopicValidator(getSubTopic(BadEncoding)) //nolint:errcheck - // create a new validator for serviceB - fserviceB.pubsub.UnregisterTopicValidator(getSubTopic(BadEncoding)) //nolint:errcheck - f := func(ctx context.Context, from peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - msg.ValidatorData = befp - return pubsub.ValidationAccept + for _, test := range tests { + bin, err := test.proof.MarshalBinary() + require.NoError(t, err) + // create first fraud service that will broadcast incorrect Fraud Proof + serviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0]) + fserviceA := serviceA.(*service) + require.NotNil(t, fserviceA) + msg := &pubsub.Message{ + Message: &pubsubpb.Message{ + Data: bin, + }, + ReceivedFrom: net.Hosts()[1].ID(), + } + if test.precondition != nil { + test.precondition() + } + res := fserviceA.processIncoming(ctx, test.proof.Type(), net.Hosts()[1].ID(), msg) + require.True(t, res == test.validationResult) } - fserviceB.pubsub.RegisterTopicValidator(getSubTopic(BadEncoding), f) //nolint:errcheck - bin, err := befp.MarshalBinary() - require.NoError(t, err) - topic, ok := fserviceA.topics[BadEncoding] - require.True(t, ok) - // we cannot avoid sleep because it helps to avoid flakiness - time.Sleep(time.Millisecond * 100) - err = topic.Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) - require.NoError(t, err) - - _, err = subsB.Proof(ctx) - require.NoError(t, err) - - newCtx, cancel := context.WithTimeout(ctx, time.Second*1) - t.Cleanup(cancel) - - _, err = subsC.Proof(newCtx) - require.Error(t, err) - require.False(t, blackList.Contains(net.Hosts()[0].ID())) - require.True(t, blackList.Contains(net.Hosts()[1].ID())) - // we cannot avoid sleep because it helps to avoid flakiness - time.Sleep(time.Millisecond * 100) } -func TestService_GossipingOfFaultBEFP(t *testing.T) { +func TestService_ReGossiping(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) t.Cleanup(cancel) // create mock network @@ -172,197 +114,96 @@ func TestService_GossipingOfFaultBEFP(t *testing.T) { require.NoError(t, err) // create first fraud service that will broadcast incorrect Fraud Proof - serviceA, store1 := createServiceWithHost(ctx, t, net.Hosts()[0]) - - h, err := store1.GetByHeight(ctx, 1) + pserviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0]) require.NoError(t, err) + serviceA := pserviceA.(*service) - // create and break byzantine error - _, err = generateByzantineError(ctx, t, h, mdutils.Bserv()) - require.Error(t, err) - var errByz *ipld.ErrByzantine - require.True(t, errors.As(err, &errByz)) - errByz.Index = 2 - - fserviceA := serviceA.(*service) - require.NotNil(t, fserviceA) - - blackList, err := pubsub.NewTimeCachedBlacklist(time.Hour) - require.NoError(t, err) // create pub sub in order to listen for Fraud Proof psB, err := pubsub.NewGossipSub(ctx, net.Hosts()[1], // -> B - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign), pubsub.WithBlacklist(blackList)) + pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) // create second service that will receive and validate Fraud Proof - serviceB := NewService(psB, store1.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())) - fserviceB := serviceB.(*service) - require.NotNil(t, fserviceB) + pserviceB := NewService( + psB, + func(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { + return &header.ExtendedHeader{}, nil + }, + sync.MutexWrap(datastore.NewMapDatastore()), + ) addrB := host.InfoFromHost(net.Hosts()[1]) // -> B // create pub sub in order to listen for Fraud Proof psC, err := pubsub.NewGossipSub(ctx, net.Hosts()[2], // -> C pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) - serviceC := NewService(psC, store1.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())) - - // perform subscriptions - sub0, err := net.Hosts()[0].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - sub2, err := net.Hosts()[2].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) + pserviceC := NewService( + psC, + func(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { + return &header.ExtendedHeader{}, nil + }, + sync.MutexWrap(datastore.NewMapDatastore()), + ) // establish connections // connect peers: A -> B -> C, so A and C are not connected to each other require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A require.NoError(t, net.Hosts()[2].Connect(ctx, *addrB)) // host[2] is C - // wait on both peer identification events - for i := 0; i < 2; i++ { - select { - case <-sub0.Out(): - case <-sub2.Out(): - case <-ctx.Done(): - assert.FailNow(t, "timeout waiting for peers to connect") - } - } + befp := newValidProof() + bin, err := befp.MarshalBinary() + require.NoError(t, err) - // subscribe to BEFP - subsA, err := serviceA.Subscribe(BadEncoding) + subsA, err := pserviceA.Subscribe(mockProofType) require.NoError(t, err) defer subsA.Cancel() - subsB, err := serviceB.Subscribe(BadEncoding) + subsB, err := pserviceB.Subscribe(mockProofType) require.NoError(t, err) defer subsB.Cancel() - subsC, err := serviceC.Subscribe(BadEncoding) + subsC, err := pserviceC.Subscribe(mockProofType) require.NoError(t, err) defer subsC.Cancel() - - // deregister validator in order to send Fraud Proof - fserviceA.pubsub.UnregisterTopicValidator(getSubTopic(BadEncoding)) //nolint:errcheck - // Broadcast BEFP - befp := CreateBadEncodingProof([]byte("hash"), uint64(h.Height), errByz) - bin, err := befp.MarshalBinary() - require.NoError(t, err) // we cannot avoid sleep because it helps to avoid flakiness time.Sleep(time.Millisecond * 100) - err = fserviceA.topics[BadEncoding].Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) + + err = serviceA.topics[mockProofType].Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) require.NoError(t, err) newCtx, cancel := context.WithTimeout(ctx, time.Millisecond*100) t.Cleanup(cancel) _, err = subsB.Proof(newCtx) - require.Error(t, err) - require.True(t, blackList.Contains(net.Hosts()[0].ID())) - - proofs, err := serviceC.Get(ctx, BadEncoding) - require.Error(t, err) - require.Nil(t, proofs) - // we cannot avoid sleep because it helps to avoid flakiness - time.Sleep(time.Millisecond * 100) -} - -func TestService_GossipingOfBEFP(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - t.Cleanup(cancel) - // create mock network - net, err := mocknet.FullMeshLinked(3) require.NoError(t, err) - // create first fraud service that will broadcast incorrect Fraud Proof - serviceA, store1 := createServiceWithHost(ctx, t, net.Hosts()[0]) - - h, err := store1.GetByHeight(ctx, 1) - require.NoError(t, err) - - // create and break byzantine error - _, err = generateByzantineError(ctx, t, h, mdutils.Bserv()) - require.Error(t, err) - var errByz *ipld.ErrByzantine - require.True(t, errors.As(err, &errByz)) - - fserviceA := serviceA.(*service) - require.NotNil(t, fserviceA) - - // create pub sub in order to listen for Fraud Proof - psB, err := pubsub.NewGossipSub(ctx, net.Hosts()[1], // -> B - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - // create second service that will receive and validate Fraud Proof - serviceB := NewService(psB, store1.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())) - fserviceB := serviceB.(*service) - require.NotNil(t, fserviceB) - addrB := host.InfoFromHost(net.Hosts()[1]) // -> B - - // create pub sub in order to listen for Fraud Proof - psC, err := pubsub.NewGossipSub(ctx, net.Hosts()[2], // -> C - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - serviceC := NewService(psC, store1.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())) - - // perform subscriptions - sub0, err := net.Hosts()[0].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - sub2, err := net.Hosts()[2].EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) - require.NoError(t, err) - - // establish connections - // connect peers: A -> B -> C, so A and C are not connected to each other - require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A - require.NoError(t, net.Hosts()[2].Connect(ctx, *addrB)) // host[2] is C - - // wait on both peer identification events - for i := 0; i < 2; i++ { - select { - case <-sub0.Out(): - case <-sub2.Out(): - case <-ctx.Done(): - assert.FailNow(t, "timeout waiting for peers to connect") - } - } - - // subscribe to BEFP - subsA, err := serviceA.Subscribe(BadEncoding) - require.NoError(t, err) - defer subsA.Cancel() - - subsB, err := serviceB.Subscribe(BadEncoding) - require.NoError(t, err) - defer subsB.Cancel() - - subsC, err := serviceC.Subscribe(BadEncoding) - require.NoError(t, err) - defer subsC.Cancel() - - // deregister validator in order to send Fraud Proof - fserviceA.pubsub.UnregisterTopicValidator(getSubTopic(BadEncoding)) //nolint:errcheck - // Broadcast BEFP - befp := CreateBadEncodingProof([]byte("hash"), uint64(h.Height), errByz) - bin, err := befp.MarshalBinary() + _, err = subsC.Proof(ctx) require.NoError(t, err) // we cannot avoid sleep because it helps to avoid flakiness time.Sleep(time.Millisecond * 100) - err = fserviceA.topics[BadEncoding].Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) - require.NoError(t, err) +} - newCtx, cancel := context.WithTimeout(ctx, time.Millisecond*100) +func TestService_Get(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) t.Cleanup(cancel) - - p, err := subsB.Proof(newCtx) + proof := newValidProof() + bin, err := proof.MarshalBinary() require.NoError(t, err) - require.NoError(t, p.Validate(h)) + pService, _ := createService(t) + service := pService.(*service) + require.NotNil(t, service) - p, err = subsC.Proof(ctx) - require.NoError(t, err) - require.NoError(t, p.Validate(h)) + // try to fetch proof + _, err = pService.Get(ctx, proof.Type()) + // error is expected here because storage is empty + require.Error(t, err) - proofs, err := serviceC.Get(ctx, BadEncoding) + // create store + store := initStore(proof.Type(), service.ds) + // add proof to storage + require.NoError(t, put(ctx, store, hex.EncodeToString(proof.HeaderHash()), bin)) + // fetch proof + _, err = pService.Get(ctx, proof.Type()) require.NoError(t, err) - require.NoError(t, proofs[0].Validate(h)) - // we cannot avoid sleep because it helps to avoid flakiness - time.Sleep(time.Millisecond * 100) } func createService(t *testing.T) (Service, *mockStore) { diff --git a/fraud/store.go b/fraud/store.go index 9d97419c77..b7ef4600bc 100644 --- a/fraud/store.go +++ b/fraud/store.go @@ -57,10 +57,10 @@ func getAll(ctx context.Context, ds datastore.Datastore, proofType ProofType) ([ return proofs, nil } -func initStore(proofType ProofType, ds datastore.Datastore) datastore.Datastore { - return namespace.Wrap(ds, makeKey(proofType)) +func initStore(topic ProofType, ds datastore.Datastore) datastore.Datastore { + return namespace.Wrap(ds, makeKey(topic)) } -func makeKey(p ProofType) datastore.Key { - return datastore.NewKey(fmt.Sprintf("%s/%s", storePrefix, p)) +func makeKey(topic ProofType) datastore.Key { + return datastore.NewKey(fmt.Sprintf("%s/%s", storePrefix, topic)) } diff --git a/fraud/store_test.go b/fraud/store_test.go index 3510aab9f9..fbd87975f3 100644 --- a/fraud/store_test.go +++ b/fraud/store_test.go @@ -2,79 +2,57 @@ package fraud import ( "context" - "errors" "testing" "time" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" ds_sync "github.com/ipfs/go-datastore/sync" - mdutils "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/ipld" ) func TestStore_Put(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer t.Cleanup(cancel) - bServ := mdutils.Bserv() - _, store := createService(t) - h, err := store.GetByHeight(ctx, 1) - require.NoError(t, err) - faultDAH, err := generateByzantineError(ctx, t, h, bServ) - var errByz *ipld.ErrByzantine - require.True(t, errors.As(err, &errByz)) - p := CreateBadEncodingProof([]byte("hash"), uint64(faultDAH.Height), errByz) + p := newValidProof() bin, err := p.MarshalBinary() require.NoError(t, err) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - badEncodingStore := namespace.Wrap(ds, makeKey(BadEncoding)) - err = put(ctx, badEncodingStore, string(p.HeaderHash()), bin) + store := namespace.Wrap(ds, makeKey(p.Type())) + err = put(ctx, store, string(p.HeaderHash()), bin) require.NoError(t, err) } func TestStore_GetAll(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer t.Cleanup(cancel) - bServ := mdutils.Bserv() - _, store := createService(t) + proof := newValidProof() + bin, err := proof.MarshalBinary() + require.NoError(t, err) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - badEncodingStore := namespace.Wrap(ds, makeKey(BadEncoding)) - faultHeaders := make([]*header.ExtendedHeader, 0) - for i := 0; i < 3; i++ { - h, err := store.GetByHeight(ctx, uint64(i+1)) - require.NoError(t, err) - faultDAH, err := generateByzantineError(ctx, t, h, bServ) - var errByz *ipld.ErrByzantine - require.True(t, errors.As(err, &errByz)) + proofStore := namespace.Wrap(ds, makeKey(proof.Type())) - p := CreateBadEncodingProof(h.Hash(), uint64(faultDAH.Height), errByz) - bin, err := p.MarshalBinary() - require.NoError(t, err) - err = put(ctx, badEncodingStore, string(p.HeaderHash()), bin) - require.NoError(t, err) - faultHeaders = append(faultHeaders, faultDAH) - } - befp, err := getAll(ctx, badEncodingStore, BadEncoding) + err = put(ctx, proofStore, string(proof.HeaderHash()), bin) require.NoError(t, err) - require.NotEmpty(t, befp) - for i := 0; i < len(befp); i++ { - require.NoError(t, befp[i].Validate(faultHeaders[i])) - } + + proofs, err := getAll(ctx, proofStore, proof.Type()) + require.NoError(t, err) + require.NotEmpty(t, proofs) + require.NoError(t, proof.Validate(nil)) + } func Test_GetAllFailed(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer t.Cleanup(cancel) + proof := newValidProof() ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - badEncodingStore := namespace.Wrap(ds, makeKey(BadEncoding)) + store := namespace.Wrap(ds, makeKey(proof.Type())) - proofs, err := getAll(ctx, badEncodingStore, BadEncoding) + proofs, err := getAll(ctx, store, proof.Type()) require.Error(t, err) require.ErrorIs(t, err, datastore.ErrNotFound) require.Nil(t, proofs) diff --git a/fraud/testing.go b/fraud/testing.go index 386f2bceff..3eebbaca47 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -2,6 +2,8 @@ package fraud import ( "context" + "encoding/json" + "errors" "testing" "github.com/ipfs/go-blockservice" @@ -55,6 +57,8 @@ func (m *mockStore) GetByHeight(_ context.Context, height uint64) (*header.Exten return m.headers[int64(height)], nil } +func (m *mockStore) Close() error { return nil } + func generateByzantineError( ctx context.Context, t *testing.T, @@ -66,3 +70,54 @@ func generateByzantineError( _, err := rtrv.Retrieve(ctx, faultHeader.DAH) return faultHeader, err } + +const ( + mockProofType ProofType = "mockProof" +) + +type mockProof struct { + Valid bool +} + +func newValidProof() *mockProof { + return newMockProof(true) +} + +func newInvalidProof() *mockProof { + return newMockProof(false) +} + +func newMockProof(valid bool) *mockProof { + p := &mockProof{valid} + if _, ok := defaultUnmarshalers[p.Type()]; !ok { + Register(&mockProof{}) + } + return p +} + +func (m *mockProof) Type() ProofType { + return mockProofType +} + +func (m *mockProof) HeaderHash() []byte { + return []byte("hash") +} + +func (m *mockProof) Height() uint64 { + return 1 +} + +func (m *mockProof) Validate(*header.ExtendedHeader) error { + if m.Valid != true { + return errors.New("mockProof: proof is not valid") + } + return nil +} + +func (m *mockProof) MarshalBinary() (data []byte, err error) { + return json.Marshal(m) +} + +func (m *mockProof) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, m) +} From 1d13be57a37e01483bc90e94b02b3988850def2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 11:18:41 +0000 Subject: [PATCH 0042/1008] chore(deps): bump github.com/ipfs/go-cid from 0.2.0 to 0.3.0 Bumps [github.com/ipfs/go-cid](https://github.com/ipfs/go-cid) from 0.2.0 to 0.3.0. - [Release notes](https://github.com/ipfs/go-cid/releases) - [Commits](https://github.com/ipfs/go-cid/compare/v0.2.0...v0.3.0) --- updated-dependencies: - dependency-name: github.com/ipfs/go-cid dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index f6cd5e2013..86a55b16d4 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ipfs/go-bitswap v0.8.0 github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.4.0 - github.com/ipfs/go-cid v0.2.0 + github.com/ipfs/go-cid v0.3.0 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 diff --git a/go.sum b/go.sum index 83e7ccfac7..bc1021a6ea 100644 --- a/go.sum +++ b/go.sum @@ -619,8 +619,9 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.2.0 h1:01JTiihFq9en9Vz0lc0VDWvZe/uBonGpzo4THP0vcQ0= github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= +github.com/ipfs/go-cid v0.3.0 h1:gT6Cbs6YePaBNc7l6v5EXt0xTMup1jGV5EU1N+QLVpY= +github.com/ipfs/go-cid v0.3.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= From 58f80bac46a1e9dd239625f26c3b9e1a84941c2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 11:18:26 +0000 Subject: [PATCH 0043/1008] chore(deps): bump github.com/ipfs/go-merkledag from 0.6.0 to 0.7.0 Bumps [github.com/ipfs/go-merkledag](https://github.com/ipfs/go-merkledag) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/ipfs/go-merkledag/releases) - [Commits](https://github.com/ipfs/go-merkledag/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: github.com/ipfs/go-merkledag dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 33 ++++----------------------------- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 86a55b16d4..21d8c94879 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/ipfs/go-ipfs-routing v0.2.1 github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 - github.com/ipfs/go-merkledag v0.6.0 + github.com/ipfs/go-merkledag v0.7.0 github.com/libp2p/go-libp2p v0.20.3 github.com/libp2p/go-libp2p-core v0.17.0 github.com/libp2p/go-libp2p-kad-dht v0.16.0 @@ -164,7 +164,7 @@ require ( github.com/ipfs/go-metrics-interface v0.0.1 // indirect github.com/ipfs/go-peertaskqueue v0.7.0 // indirect github.com/ipfs/go-verifcid v0.0.1 // indirect - github.com/ipld/go-codec-dagpb v1.3.0 // indirect + github.com/ipld/go-codec-dagpb v1.3.1 // indirect github.com/ipld/go-ipld-prime v0.16.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect diff --git a/go.sum b/go.sum index bc1021a6ea..1fa59ed9ce 100644 --- a/go.sum +++ b/go.sum @@ -393,7 +393,6 @@ github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -488,7 +487,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= @@ -602,13 +600,11 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= github.com/ipfs/go-bitswap v0.8.0 h1:UEV7kogQu2iGggkE9GhLykDrRCUpsNnpu2NODww/srw= github.com/ipfs/go-bitswap v0.8.0/go.mod h1:/h8sBij8UVEaNWl8ABzpLRA5Y1cttdNUnpeGo2AA/LQ= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= -github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= github.com/ipfs/go-blockservice v0.4.0 h1:7MUijAW5SqdsqEW/EhnNFRJXVF8mGU5aGhZ3CQaCWbY= github.com/ipfs/go-blockservice v0.4.0/go.mod h1:kRjO3wlGW9mS1aKuiCeGhx9K1DagQ10ACpVO59qgAx4= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -653,17 +649,14 @@ github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82l github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= -github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= -github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= github.com/ipfs/go-ipfs-exchange-interface v0.2.0 h1:8lMSJmKogZYNo2jjhUs0izT+dck05pqUw4mWNW9Pw6Y= github.com/ipfs/go-ipfs-exchange-interface v0.2.0/go.mod h1:z6+RhJuDQbqKguVyslSOuVDhqF9JtTrO3eptSAiW2/Y= -github.com/ipfs/go-ipfs-exchange-offline v0.2.0/go.mod h1:HjwBeW0dvZvfOMwDP0TSKXIHf2s+ksdP4E3MLDRtLKY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-exchange-offline v0.3.0/go.mod h1:MOdJ9DChbb5u37M1IcbrRB02e++Z7521fMxqCNRrz9s= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= @@ -699,17 +692,16 @@ github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72g github.com/ipfs/go-log/v2 v2.5.0/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= -github.com/ipfs/go-merkledag v0.6.0 h1:oV5WT2321tS4YQVOPgIrWHvJ0lJobRTerU+i9nmUCuA= -github.com/ipfs/go-merkledag v0.6.0/go.mod h1:9HSEwRd5sV+lbykiYP+2NC/3o6MZbKNaa4hfNcH5iH0= +github.com/ipfs/go-merkledag v0.7.0 h1:PHdWOGwx+J2uRAuP9Mu+bz89ulmf3W2QmbSS/N6O29U= +github.com/ipfs/go-merkledag v0.7.0/go.mod h1:/1cuN4VbcDn/xbVMAqjPUwejJYr8W9SvizmyYLU/B7k= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-peertaskqueue v0.7.0 h1:VyO6G4sbzX80K58N60cCaHsSsypbUNs1GjO5seGNsQ0= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= -github.com/ipld/go-codec-dagpb v1.3.0 h1:czTcaoAuNNyIYWs6Qe01DJ+sEX7B+1Z0LcXjSatMGe8= -github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= +github.com/ipld/go-codec-dagpb v1.3.1 h1:yVNlWRQexCa54ln3MSIiUN++ItH7pdhBFhh0hSgZu1w= +github.com/ipld/go-codec-dagpb v1.3.1/go.mod h1:ErNNglIi5KMur/MfFE/svtgQthzVvf+43MrzLbpcIZY= github.com/ipld/go-ipld-prime v0.9.0/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= -github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/ipld/go-ipld-prime v0.16.0 h1:RS5hhjB/mcpeEPJvfyj0qbOj/QL+/j05heZ0qa97dVo= github.com/ipld/go-ipld-prime v0.16.0/go.mod h1:axSCuOCBPqrH+gvXr2w9uAOulJqBPhHPT2PjoiiU1qA= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= @@ -812,7 +804,6 @@ github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZk github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= -github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= github.com/libp2p/go-libp2p v0.18.0/go.mod h1:+veaZ9z1SZQhmc5PW78jvnnxZ89Mgvmh4cggO11ETmw= github.com/libp2p/go-libp2p v0.20.3 h1:tjjDNfp7FqdI/7v1rXtB/BtELaPlAThL2uzlj18kcrw= @@ -860,7 +851,6 @@ github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJB github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= -github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= github.com/libp2p/go-libp2p-core v0.10.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= @@ -874,7 +864,6 @@ github.com/libp2p/go-libp2p-core v0.17.0/go.mod h1:h/iAbFij28ASmI+tvXfjoipg1g2N3 github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= -github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.16.0 h1:epVRYl3O8dn47uV3wVD2+IobEvBPapEMVj4sWlvwQHU= github.com/libp2p/go-libp2p-kad-dht v0.16.0/go.mod h1:YYLlG8AbpWVGbI/zFeSbiGT0n0lluH7IG0sHeounyWA= @@ -887,7 +876,6 @@ github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3 github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= -github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5BcKwrrb2VmOYb3M= github.com/libp2p/go-libp2p-mplex v0.6.0/go.mod h1:i3usuPrBbh9FD2fLZjGpotyNkwr42KStYZQY7BeTiu4= @@ -896,7 +884,6 @@ github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnI github.com/libp2p/go-libp2p-nat v0.1.0/go.mod h1:DQzAG+QbDYjN1/C3B6vXucLtz3u9rEonLVPtZVzQqks= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= -github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-noise v0.3.0/go.mod h1:JNjHbociDJKHD64KTkzGnzqJ0FEV5gHJa6AB00kbCNQ= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= @@ -907,7 +894,6 @@ github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnq github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= -github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0= github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= @@ -915,7 +901,6 @@ github.com/libp2p/go-libp2p-peerstore v0.7.1 h1:7FpALlqR+3+oOBXdzm3AVt0vjMYLW1b7 github.com/libp2p/go-libp2p-peerstore v0.7.1/go.mod h1:cdUWTHro83vpg6unCpGUr8qJoX3e93Vy8o97u5ppIM0= github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= -github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc= github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= @@ -939,7 +924,6 @@ github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaT github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= -github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= github.com/libp2p/go-libp2p-swarm v0.8.0/go.mod h1:sOMp6dPuqco0r0GHTzfVheVBh6UEL0L1lXUZ5ot2Fvc= github.com/libp2p/go-libp2p-swarm v0.10.0/go.mod h1:71ceMcV6Rg/0rIQ97rsZWMzto1l9LnNquef+efcRbmA= @@ -967,7 +951,6 @@ github.com/libp2p/go-libp2p-tls v0.3.1/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5o github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= -github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= github.com/libp2p/go-libp2p-transport-upgrader v0.5.0/go.mod h1:Rc+XODlB3yce7dvFV4q/RmyJGsFcCZRkeZMu/Zdg0mo= @@ -994,7 +977,6 @@ github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTW github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= -github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= github.com/libp2p/go-mplex v0.6.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= @@ -1028,7 +1010,6 @@ github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7z github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= -github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= github.com/libp2p/go-reuseport-transport v0.1.0 h1:C3PHeHjmnz8m6f0uydObj02tMEoi7CyD1zuN7xQT8gc= github.com/libp2p/go-reuseport-transport v0.1.0/go.mod h1:vev0C0uMkzriDY59yFHD9v+ujJvYmDQVLowvAjEOmfw= @@ -1043,7 +1024,6 @@ github.com/libp2p/go-stream-muxer-multistream v0.4.0/go.mod h1:nb+dGViZleRP4XcyH github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= -github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= github.com/libp2p/go-tcp-transport v0.4.0/go.mod h1:0y52Rwrn4076xdJYu/51/qJIdxz+EWDAOG2S45sV3VI= @@ -1052,7 +1032,6 @@ github.com/libp2p/go-tcp-transport v0.5.1 h1:edOOs688VLZAozWC7Kj5/6HHXKNwi9M6wgR github.com/libp2p/go-tcp-transport v0.5.1/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= -github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= github.com/libp2p/go-ws-transport v0.6.0/go.mod h1:dXqtI9e2JV9FtF1NOtWVZSKXh5zXvnuwPXfj8GPBbYU= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= @@ -1071,7 +1050,6 @@ github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enE github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= @@ -1085,8 +1063,6 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= -github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.5/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= @@ -1632,7 +1608,6 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= From 783777b88c3d362392a205dd965109659453f4bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 11:18:50 +0000 Subject: [PATCH 0044/1008] chore(deps): bump google.golang.org/grpc from 1.48.0 to 1.49.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.48.0 to 1.49.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.48.0...v1.49.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 21d8c94879..2abdc358b1 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( go.uber.org/fx v1.18.1 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f - google.golang.org/grpc v1.48.0 + google.golang.org/grpc v1.49.0 ) require ( diff --git a/go.sum b/go.sum index 1fa59ed9ce..a7b629dc66 100644 --- a/go.sum +++ b/go.sum @@ -2144,8 +2144,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From ab0bdbc102a31483afa5c271eb6c2d2b78a0bdc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 11:18:11 +0000 Subject: [PATCH 0045/1008] chore(deps): bump github.com/multiformats/go-base32 from 0.0.4 to 0.1.0 Bumps [github.com/multiformats/go-base32](https://github.com/multiformats/go-base32) from 0.0.4 to 0.1.0. - [Release notes](https://github.com/multiformats/go-base32/releases) - [Commits](https://github.com/multiformats/go-base32/compare/v0.0.4...v0.1.0) --- updated-dependencies: - dependency-name: github.com/multiformats/go-base32 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2abdc358b1..bed22316e6 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/libp2p/go-libp2p-record v0.1.3 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 github.com/mitchellh/go-homedir v1.1.0 - github.com/multiformats/go-base32 v0.0.4 + github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.6.0 github.com/multiformats/go-multihash v0.2.0 github.com/raulk/go-watchdog v1.3.0 diff --git a/go.sum b/go.sum index a7b629dc66..9dd78ea472 100644 --- a/go.sum +++ b/go.sum @@ -1142,8 +1142,9 @@ github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= -github.com/multiformats/go-base32 v0.0.4 h1:+qMh4a2f37b4xTNs6mqitDinryCI+tfO2dRVMN9mjSE= github.com/multiformats/go-base32 v0.0.4/go.mod h1:jNLFzjPZtp3aIARHbJRZIaPuspdH0J6q39uUM5pnABM= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= From 190e4d022ee2a14ebcf9b7db267b61e7f45f00af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 13:21:50 +0000 Subject: [PATCH 0046/1008] chore(deps): bump github.com/libp2p/go-libp2p-kad-dht Bumps [github.com/libp2p/go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht) from 0.16.0 to 0.17.0. - [Release notes](https://github.com/libp2p/go-libp2p-kad-dht/releases) - [Commits](https://github.com/libp2p/go-libp2p-kad-dht/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: github.com/libp2p/go-libp2p-kad-dht dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 45 ++++++++++---------- go.sum | 129 +++++++++++++++++++++------------------------------------ 2 files changed, 70 insertions(+), 104 deletions(-) diff --git a/go.mod b/go.mod index bed22316e6..63cf39a265 100644 --- a/go.mod +++ b/go.mod @@ -33,9 +33,9 @@ require ( github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/ipfs/go-merkledag v0.7.0 - github.com/libp2p/go-libp2p v0.20.3 - github.com/libp2p/go-libp2p-core v0.17.0 - github.com/libp2p/go-libp2p-kad-dht v0.16.0 + github.com/libp2p/go-libp2p v0.21.0 + github.com/libp2p/go-libp2p-core v0.19.1 + github.com/libp2p/go-libp2p-kad-dht v0.17.0 github.com/libp2p/go-libp2p-peerstore v0.7.1 github.com/libp2p/go-libp2p-pubsub v0.7.0 github.com/libp2p/go-libp2p-record v0.1.3 @@ -80,8 +80,7 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/btcsuite/btcd v0.22.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/celestiaorg/go-leopard v0.1.0 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -89,7 +88,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/confio/ics23/go v0.7.0 // indirect - github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 // indirect + github.com/containerd/cgroups v1.0.4 // indirect github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect @@ -109,7 +108,7 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect - github.com/elastic/gosigar v0.12.0 // indirect + github.com/elastic/gosigar v0.14.2 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/flynn/noise v1.0.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect @@ -122,7 +121,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect - github.com/godbus/dbus/v5 v5.0.4 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -150,7 +149,7 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204 // indirect + github.com/huin/goupnp v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/go-ipfs-delay v0.0.1 // indirect @@ -172,8 +171,8 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/klauspost/compress v1.15.6 // indirect - github.com/klauspost/cpuid/v2 v2.0.12 // indirect - github.com/koron/go-ssdp v0.0.2 // indirect + github.com/klauspost/cpuid/v2 v2.0.14 // indirect + github.com/koron/go-ssdp v0.0.3 // indirect github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect @@ -182,22 +181,23 @@ require ( github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect github.com/libp2p/go-libp2p-loggables v0.1.0 // indirect - github.com/libp2p/go-libp2p-resource-manager v0.3.0 // indirect + github.com/libp2p/go-libp2p-resource-manager v0.5.1 // indirect github.com/libp2p/go-msgio v0.2.0 // indirect github.com/libp2p/go-nat v0.1.0 // indirect github.com/libp2p/go-netroute v0.2.0 // indirect github.com/libp2p/go-openssl v0.0.7 // indirect github.com/libp2p/go-reuseport v0.2.0 // indirect github.com/libp2p/go-yamux/v3 v3.1.2 // indirect - github.com/lucas-clemente/quic-go v0.27.1 // indirect + github.com/lucas-clemente/quic-go v0.28.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect - github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect - github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect + github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect + github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect + github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/miekg/dns v1.1.43 // indirect + github.com/miekg/dns v1.1.50 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect @@ -210,12 +210,12 @@ require ( github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.0.3 // indirect - github.com/multiformats/go-multicodec v0.4.1 // indirect + github.com/multiformats/go-multibase v0.1.1 // indirect + github.com/multiformats/go-multicodec v0.5.0 // indirect github.com/multiformats/go-multistream v0.3.3 // indirect github.com/multiformats/go-varint v0.0.6 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/onsi/ginkgo v1.16.4 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect @@ -227,7 +227,7 @@ require ( github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.34.0 // indirect + github.com/prometheus/common v0.35.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect @@ -249,7 +249,6 @@ require ( github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect - github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect go.etcd.io/bbolt v1.3.6 // indirect @@ -263,13 +262,13 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect + golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1 // indirect - golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect + golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/api v0.81.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect diff --git a/go.sum b/go.sum index 9dd78ea472..145f32d154 100644 --- a/go.sum +++ b/go.sum @@ -142,12 +142,12 @@ github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dm github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= @@ -155,7 +155,6 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= @@ -224,8 +223,9 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= -github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 h1:7grrpcfCtbZLsjtB0DgMuzs1umsJmpzaHMZ6cO6iAWw= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= +github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -311,8 +311,9 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elastic/gosigar v0.12.0 h1:AsdhYCJlTudhfOYQyFNgx+fIVTfrDO0V1ST0vHgiapU= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= +github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4= +github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -383,8 +384,9 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -589,8 +591,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204 h1:+EYBkW+dbi3F/atB+LSQZSWh7+HNrV3A/N0y6DSoy9k= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= +github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -615,11 +617,9 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.2.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-cid v0.3.0 h1:gT6Cbs6YePaBNc7l6v5EXt0xTMup1jGV5EU1N+QLVpY= github.com/ipfs/go-cid v0.3.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= -github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= @@ -634,7 +634,6 @@ github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= -github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-badger v0.2.7/go.mod h1:02rnztVKA4aZwDuaRPTf8mpqcKmXP7mLl6JPxd14JHA= @@ -642,7 +641,6 @@ github.com/ipfs/go-ds-badger v0.3.0/go.mod h1:1ke6mXNqeV8K3y5Ak2bAA0osoTfmxUdupV github.com/ipfs/go-ds-badger2 v0.1.3 h1:Zo9JicXJ1DmXTN4KOw7oPXkspZ0AWHcAFCP1tQKnegg= github.com/ipfs/go-ds-badger2 v0.1.3/go.mod h1:TPhhljfrgewjbtuL/tczP8dNrBYwwk+SdPYbms/NO9w= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= -github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.5.0/go.mod h1:d3XG9RUDzQ6V4SHi8+Xgj9j1XuEk1z82lquxrVbml/Q= @@ -758,13 +756,14 @@ github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRs github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= +github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= -github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o= github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs= +github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= +github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -791,7 +790,6 @@ github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0 github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= -github.com/libp2p/go-conn-security-multistream v0.3.0 h1:9UCIKlBL1hC9u7nkMXpD1nkc/T53PKMAn3/k9ivBAVc= github.com/libp2p/go-conn-security-multistream v0.3.0/go.mod h1:EEP47t4fw/bTelVmEzIDqSe69hO/ip52xBEhZMLWAHM= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= @@ -805,11 +803,10 @@ github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xS github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.15.0/go.mod h1:8Ljmwon0cZZYKrOCjFeLwQEK8bqR42dOheUZ1kSKhP0= -github.com/libp2p/go-libp2p v0.18.0/go.mod h1:+veaZ9z1SZQhmc5PW78jvnnxZ89Mgvmh4cggO11ETmw= -github.com/libp2p/go-libp2p v0.20.3 h1:tjjDNfp7FqdI/7v1rXtB/BtELaPlAThL2uzlj18kcrw= github.com/libp2p/go-libp2p v0.20.3/go.mod h1:I+vndVanE/p/SjFbnA+BEmmfAUEpWxrdXZeyQ1Dus5c= +github.com/libp2p/go-libp2p v0.21.0 h1:s9yYScuIFY33FOOzwTXbc8QqbvsRyKIWFf0FCSJKrfM= +github.com/libp2p/go-libp2p v0.21.0/go.mod h1:zvcA6/C4mr5/XQarRICh+L1SN9dAHHlSWDq4x5VYxg4= github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo= -github.com/libp2p/go-libp2p-asn-util v0.1.0/go.mod h1:wu+AnM9Ii2KgO5jMmS1rz9dvzTdj8BXqsPR9HR0XB7I= github.com/libp2p/go-libp2p-asn-util v0.2.0 h1:rg3+Os8jbnO5DxkC7K/Utdi+DkY3q/d1/1q+8WeNAsw= github.com/libp2p/go-libp2p-asn-util v0.2.0/go.mod h1:WoaWxbHKBymSN41hWSq/lGKJEca7TNm58+gGJi2WsLI= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= @@ -820,11 +817,9 @@ github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfha github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= -github.com/libp2p/go-libp2p-blankhost v0.3.0/go.mod h1:urPC+7U01nCGgJ3ZsV8jdwTp6Ji9ID0dMTvq+aJ+nZU= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= -github.com/libp2p/go-libp2p-circuit v0.6.0 h1:rw/HlhmUB3OktS/Ygz6+2XABOmHKzZpPUuMNUMosj8w= github.com/libp2p/go-libp2p-circuit v0.6.0/go.mod h1:kB8hY+zCpMeScyvFrKrGicRdid6vNXbunKE4rXATZ0M= github.com/libp2p/go-libp2p-connmgr v0.2.4 h1:TMS0vc0TCBomtQJyWr7fYxcVYYhx+q/2gF++G5Jkl/w= github.com/libp2p/go-libp2p-connmgr v0.2.4/go.mod h1:YV0b/RIm8NGPnnNWM7hG9Q38OeQiQfKhHCCs1++ufn0= @@ -854,20 +849,18 @@ github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJB github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-core v0.9.0/go.mod h1:ESsbz31oC3C1AvMJoGx26RTuCkNhmkSRCqZ0kQtJ2/8= github.com/libp2p/go-libp2p-core v0.10.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= -github.com/libp2p/go-libp2p-core v0.11.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= github.com/libp2p/go-libp2p-core v0.12.0/go.mod h1:ECdxehoYosLYHgDDFa2N4yE8Y7aQRAMf0sX9mf2sbGg= github.com/libp2p/go-libp2p-core v0.14.0/go.mod h1:tLasfcVdTXnixsLB0QYaT1syJOhsbrhG7q6pGrHtBg8= -github.com/libp2p/go-libp2p-core v0.15.1/go.mod h1:agSaboYM4hzB1cWekgVReqV5M4g5M+2eNNejV+1EEhs= github.com/libp2p/go-libp2p-core v0.16.1/go.mod h1:O3i/7y+LqUb0N+qhzXjBjjpchgptWAVMG1Voegk7b4c= -github.com/libp2p/go-libp2p-core v0.17.0 h1:QGU8mlxHytwTc4pq/aVQX9VDoAPiCHxfe/oOSwF+YDg= github.com/libp2p/go-libp2p-core v0.17.0/go.mod h1:h/iAbFij28ASmI+tvXfjoipg1g2N33O4UN6LIb6QfoU= +github.com/libp2p/go-libp2p-core v0.19.1 h1:zaZQQCeCrFMtxFa1wHy6AhsVynyNmZAvwgWqSSPT3WE= +github.com/libp2p/go-libp2p-core v0.19.1/go.mod h1:2uLhmmqDiFY+dw+70KkBLeKvvsJHGWUINRDdeV1ip7k= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.1/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= -github.com/libp2p/go-libp2p-kad-dht v0.16.0 h1:epVRYl3O8dn47uV3wVD2+IobEvBPapEMVj4sWlvwQHU= -github.com/libp2p/go-libp2p-kad-dht v0.16.0/go.mod h1:YYLlG8AbpWVGbI/zFeSbiGT0n0lluH7IG0sHeounyWA= -github.com/libp2p/go-libp2p-kbucket v0.3.1/go.mod h1:oyjT5O7tS9CQurok++ERgc46YLwEpuGoFq9ubvoUOio= +github.com/libp2p/go-libp2p-kad-dht v0.17.0 h1:HWEjqjNVDuf8yuccuswGy1vYGzB0v4Z+yQ4DMDMSIqk= +github.com/libp2p/go-libp2p-kad-dht v0.17.0/go.mod h1:zeE26Xo+PY7sS2AgkBQQcBnJEazMT26KGZLUFttl+rk= github.com/libp2p/go-libp2p-kbucket v0.4.7 h1:spZAcgxifvFZHBD8tErvppbnNiKA5uokDu3CV7axu70= github.com/libp2p/go-libp2p-kbucket v0.4.7/go.mod h1:XyVo99AfQH0foSf176k4jY1xUJ2+jUJIZCSDm7r2YKk= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= @@ -878,41 +871,32 @@ github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-mplex v0.5.0/go.mod h1:eLImPJLkj3iG5t5lq68w3Vm5NAQ5BcKwrrb2VmOYb3M= -github.com/libp2p/go-libp2p-mplex v0.6.0/go.mod h1:i3usuPrBbh9FD2fLZjGpotyNkwr42KStYZQY7BeTiu4= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= -github.com/libp2p/go-libp2p-nat v0.1.0/go.mod h1:DQzAG+QbDYjN1/C3B6vXucLtz3u9rEonLVPtZVzQqks= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.2.2/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= -github.com/libp2p/go-libp2p-noise v0.3.0/go.mod h1:JNjHbociDJKHD64KTkzGnzqJ0FEV5gHJa6AB00kbCNQ= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= -github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.8/go.mod h1:gGiPlXdz7mIHd2vfAsHzBNAMqSDkt2UBFwgcITgw1lA= -github.com/libp2p/go-libp2p-peerstore v0.4.0/go.mod h1:rDJUFyzEWPpXpEwywkcTYYzDHlwza8riYMaUzaN6hX0= github.com/libp2p/go-libp2p-peerstore v0.6.0/go.mod h1:DGEmKdXrcYpK9Jha3sS7MhqYdInxJy84bIPtSu65bKc= github.com/libp2p/go-libp2p-peerstore v0.7.1 h1:7FpALlqR+3+oOBXdzm3AVt0vjMYLW1b7jM03E4iEHlw= github.com/libp2p/go-libp2p-peerstore v0.7.1/go.mod h1:cdUWTHro83vpg6unCpGUr8qJoX3e93Vy8o97u5ppIM0= -github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-quic-transport v0.11.2/go.mod h1:wlanzKtIh6pHrq+0U3p3DY9PJfGqxMgPaGKaK5LifwQ= -github.com/libp2p/go-libp2p-quic-transport v0.13.0/go.mod h1:39/ZWJ1TW/jx1iFkKzzUg00W6tDJh73FC0xYudjr7Hc= github.com/libp2p/go-libp2p-quic-transport v0.16.0/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= -github.com/libp2p/go-libp2p-quic-transport v0.16.1 h1:N/XqYXHurphPLDfXYhll8NyqzdZYQqAF4GIr7+SmLV8= -github.com/libp2p/go-libp2p-quic-transport v0.16.1/go.mod h1:1BXjVMzr+w7EkPfiHkKnwsWjPjtfaNT0q8RS3tGDvEQ= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk= github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0= github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= -github.com/libp2p/go-libp2p-resource-manager v0.1.5/go.mod h1:wJPNjeE4XQlxeidwqVY5G6DLOKqFK33u2n8blpl0I6Y= -github.com/libp2p/go-libp2p-resource-manager v0.3.0 h1:2+cYxUNi33tcydsVLt6K5Fv2E3OTiVeafltecAj15E0= github.com/libp2p/go-libp2p-resource-manager v0.3.0/go.mod h1:K+eCkiapf+ey/LADO4TaMpMTP9/Qde/uLlrnRqV4PLQ= +github.com/libp2p/go-libp2p-resource-manager v0.5.1 h1:jm0mdqn7yfh7wbUzlj948BYZX0KZ3RW7OqerkGQ5rYY= +github.com/libp2p/go-libp2p-resource-manager v0.5.1/go.mod h1:CggtV6EZb+Y0dGh41q5ezO4udcVKyhcEFpydHD8EMe0= github.com/libp2p/go-libp2p-routing-helpers v0.2.3 h1:xY61alxJ6PurSi+MXbywZpelvuU4U4p/gPTxjqCqTzY= github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= @@ -925,10 +909,7 @@ github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHv github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.5.3/go.mod h1:NBn7eNW2lu568L7Ns9wdFrOhgRlkRnIDg0FLKbuu3i8= -github.com/libp2p/go-libp2p-swarm v0.8.0/go.mod h1:sOMp6dPuqco0r0GHTzfVheVBh6UEL0L1lXUZ5ot2Fvc= github.com/libp2p/go-libp2p-swarm v0.10.0/go.mod h1:71ceMcV6Rg/0rIQ97rsZWMzto1l9LnNquef+efcRbmA= -github.com/libp2p/go-libp2p-swarm v0.10.2 h1:UaXf+CTq6Ns1N2V1EgqJ9Q3xaRsiN7ImVlDMpirMAWw= -github.com/libp2p/go-libp2p-swarm v0.10.2/go.mod h1:Pdkq0QU5a+qu+oyqIV3bknMsnzk9lnNyKvB9acJ5aZs= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= @@ -940,24 +921,17 @@ github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotl github.com/libp2p/go-libp2p-testing v0.4.2/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-testing v0.5.0/go.mod h1:QBk8fqIL1XNcno/l3/hhaIEn4aLRijpYOR+zVjjlh+A= github.com/libp2p/go-libp2p-testing v0.7.0/go.mod h1:OLbdn9DbgdMwv00v+tlp1l3oe2Cl+FAjoWIA2pa0X6E= -github.com/libp2p/go-libp2p-testing v0.8.0/go.mod h1:gRdsNxQSxAZowTgcLY7CC33xPmleZzoBpqSYbWenqPc= -github.com/libp2p/go-libp2p-testing v0.9.2 h1:dCpODRtRaDZKF8HXT9qqqgON+OMEB423Knrgeod8j84= github.com/libp2p/go-libp2p-testing v0.9.2/go.mod h1:Td7kbdkWqYTJYQGTwzlgXwaqldraIanyjuRiAbK/XQU= +github.com/libp2p/go-libp2p-testing v0.11.0 h1:+R7FRl/U3Y00neyBSM2qgDzqz3HkWH24U9nMlascHL4= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-tls v0.2.0/go.mod h1:twrp2Ci4lE2GYspA1AnlYm+boYjqVruxDKJJj7s6xrc= github.com/libp2p/go-libp2p-tls v0.3.0/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5oFlg+wo2FxJDY= -github.com/libp2p/go-libp2p-tls v0.3.1 h1:lsE2zYte+rZCEOHF72J1Fg3XK3dGQyKvI6i5ehJfEp0= -github.com/libp2p/go-libp2p-tls v0.3.1/go.mod h1:fwF5X6PWGxm6IDRwF3V8AVCCj/hOd5oFlg+wo2FxJDY= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.3/go.mod h1:bpkldbOWXMrXhpZbSV1mQxTrefOg2Fi+k1ClDSA4ppw= github.com/libp2p/go-libp2p-transport-upgrader v0.4.6/go.mod h1:JE0WQuQdy+uLZ5zOaI3Nw9dWGYJIA7mywEtP2lMvnyk= -github.com/libp2p/go-libp2p-transport-upgrader v0.5.0/go.mod h1:Rc+XODlB3yce7dvFV4q/RmyJGsFcCZRkeZMu/Zdg0mo= github.com/libp2p/go-libp2p-transport-upgrader v0.7.0/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= -github.com/libp2p/go-libp2p-transport-upgrader v0.7.1 h1:MSMe+tUfxpC9GArTz7a4G5zQKQgGh00Vio87d3j3xIg= -github.com/libp2p/go-libp2p-transport-upgrader v0.7.1/go.mod h1:GIR2aTRp1J5yjVlkUoFqMkdobfob6RnAwYg/RZPhrzg= -github.com/libp2p/go-libp2p-xor v0.1.0/go.mod h1:LSTM5yRnjGZbWNTA/hRwq2gGFrvRIbQJscoIL/u6InY= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= @@ -967,9 +941,6 @@ github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelN github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= github.com/libp2p/go-libp2p-yamux v0.8.0/go.mod h1:yTkPgN2ib8FHyU1ZcVD7aelzyAqXXwEPbyx+aSKm9h8= -github.com/libp2p/go-libp2p-yamux v0.8.1/go.mod h1:rUozF8Jah2dL9LLGyBaBeTQeARdwhefMCTQVQt6QobE= -github.com/libp2p/go-libp2p-yamux v0.8.2 h1:6GKWntresp0TFxMP/oSoH96nV8XKJRdynXsdp43dn0Y= -github.com/libp2p/go-libp2p-yamux v0.8.2/go.mod h1:rUozF8Jah2dL9LLGyBaBeTQeARdwhefMCTQVQt6QobE= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= @@ -979,12 +950,10 @@ github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3 github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.4.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= -github.com/libp2p/go-mplex v0.6.0/go.mod h1:y26Lx+wNVtMYMaPu300Cbot5LkEZ4tJaNYeHeT9dh6E= github.com/libp2p/go-mplex v0.7.0/go.mod h1:rW8ThnRcYWft/Jb2jeORBmPd6xuG3dGxWN/W168L9EU= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= -github.com/libp2p/go-msgio v0.1.0/go.mod h1:eNlv2vy9V2X/kNldcZ+SShFE++o2Yjxwx6RAYsmgJnE= github.com/libp2p/go-msgio v0.2.0 h1:W6shmB+FeynDrUVl2dgFQvzfBZcXiyqY4VmpQLu9FqU= github.com/libp2p/go-msgio v0.2.0/go.mod h1:dBVM1gW3Jk9XqHkU4eKdGvVHdLa51hoGfll6jMJMSlY= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= @@ -1011,7 +980,6 @@ github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtI github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.5/go.mod h1:TC62hhPc8qs5c/RoXDZG6YmjK+/YWUPC0yYmeUecbjc= -github.com/libp2p/go-reuseport-transport v0.1.0 h1:C3PHeHjmnz8m6f0uydObj02tMEoi7CyD1zuN7xQT8gc= github.com/libp2p/go-reuseport-transport v0.1.0/go.mod h1:vev0C0uMkzriDY59yFHD9v+ujJvYmDQVLowvAjEOmfw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= @@ -1019,21 +987,16 @@ github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2L github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= -github.com/libp2p/go-stream-muxer-multistream v0.4.0 h1:HsM/9OdtqnIzjVXcxTXjmqKrj3gJ8kacaOJwJS1ipaY= github.com/libp2p/go-stream-muxer-multistream v0.4.0/go.mod h1:nb+dGViZleRP4XcyHuZSVrJCBl55nRBOMmiSL/dyziw= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-tcp-transport v0.2.7/go.mod h1:lue9p1b3VmZj1MhhEGB/etmvF/nBQ0X9CW2DutBT3MM= github.com/libp2p/go-tcp-transport v0.2.8/go.mod h1:64rSfVidkYPLqbzpcN2IwHY4pmgirp67h++hZ/rcndQ= -github.com/libp2p/go-tcp-transport v0.4.0/go.mod h1:0y52Rwrn4076xdJYu/51/qJIdxz+EWDAOG2S45sV3VI= github.com/libp2p/go-tcp-transport v0.5.0/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= -github.com/libp2p/go-tcp-transport v0.5.1 h1:edOOs688VLZAozWC7Kj5/6HHXKNwi9M6wgRmmLa8M6Q= -github.com/libp2p/go-tcp-transport v0.5.1/go.mod h1:UPPL0DIjQqiWRwVAb+CEQlaAG0rp/mCqJfIhFcLHc4Y= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.5.0/go.mod h1:I2juo1dNTbl8BKSBYo98XY85kU2xds1iamArLvl8kNg= -github.com/libp2p/go-ws-transport v0.6.0/go.mod h1:dXqtI9e2JV9FtF1NOtWVZSKXh5zXvnuwPXfj8GPBbYU= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= @@ -1043,7 +1006,6 @@ github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/h github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/libp2p/go-yamux/v3 v3.0.1/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= -github.com/libp2p/go-yamux/v3 v3.0.2/go.mod h1:s2LsDhHbh+RfCsQoICSYt58U2f8ijtPANFD8BmE74Bo= github.com/libp2p/go-yamux/v3 v3.1.2 h1:lNEy28MBk1HavUAlzKgShp+F6mn/ea1nDYWftZhFW9Q= github.com/libp2p/go-yamux/v3 v3.1.2/go.mod h1:jeLEQgLXqE2YqX1ilAClIfCMDY+0uXQUKmmb/qp0gT4= github.com/libp2p/zeroconf/v2 v2.0.0/go.mod h1:J85R/d9joD8u8F9aHM8pBXygtG9W02enEwS+wWeL6yo= @@ -1051,10 +1013,10 @@ github.com/libp2p/zeroconf/v2 v2.1.1/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.21.2/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q= -github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0= github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= -github.com/lucas-clemente/quic-go v0.27.1 h1:sOw+4kFSVrdWOYmUjufQ9GBVPqZ+tu+jMtXxXNmRJyk= github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI= +github.com/lucas-clemente/quic-go v0.28.0 h1:9eXVRgIkMQQyiyorz/dAaOYIx3TFzXsIFkNFz4cxuJM= +github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -1070,11 +1032,15 @@ github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtU github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= -github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc= github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= +github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ= +github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI= -github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y= github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= +github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ= +github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -1096,8 +1062,9 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1142,7 +1109,6 @@ github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= -github.com/multiformats/go-base32 v0.0.4/go.mod h1:jNLFzjPZtp3aIARHbJRZIaPuspdH0J6q39uUM5pnABM= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= @@ -1180,12 +1146,14 @@ github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysj github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= -github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= +github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.2.0/go.mod h1:/y4YVwkfMyry5kFbMTbLJKErhycTIftytRV+llXdyS4= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= -github.com/multiformats/go-multicodec v0.4.1 h1:BSJbf+zpghcZMZrwTYBGwy0CPcVZGWiC72Cp8bBd4R4= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= +github.com/multiformats/go-multicodec v0.5.0 h1:EgU6cBe/D7WRwQb1KmnBvU7lrcFGMggZVTPtOW9dDHs= +github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= @@ -1233,8 +1201,9 @@ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1324,8 +1293,8 @@ github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= -github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= +github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE= +github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1475,7 +1444,6 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc= -github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= github.com/warpfork/go-testmark v0.3.0 h1:Q81c4u7hT+BR5kNfNQhEF0VT2pmL7+Kk0wD+ORYl7iA= github.com/warpfork/go-testmark v0.3.0/go.mod h1:jhEf8FVxd+F17juRubpmut64NEG6I2rgkUhlcqqXwE0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= @@ -1489,7 +1457,6 @@ github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1 github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= -github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= @@ -1524,7 +1491,6 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw= go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 h1:ggqApEjDKczicksfvZUCxuvoyDmR6Sbm56LwiK8DVR0= @@ -1543,7 +1509,6 @@ go.opentelemetry.io/otel/sdk v1.9.0 h1:LNXp1vrr83fNXTHgU8eO89mhzxb/bbWAsHG6fNf3q go.opentelemetry.io/otel/sdk v1.9.0/go.mod h1:AEZc8nt5bd2F7BC24J5R0mrjYnpEgYHyTcM/vrSple4= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= -go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/otel/trace v1.9.0 h1:oZaCNJUjWcg60VXWee8lJKlqhPbXAPB51URuR47pQYc= go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -1616,7 +1581,6 @@ golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1715,6 +1679,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1722,10 +1687,10 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220517181318-183a9ca12b87/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= +golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1957,6 +1922,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1 h1:NHLFZ56qCjD+0hYY3kE5Wl40Z7q4Gn9Ln/7YU0lsGko= golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1964,8 +1930,9 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= From f1517c07d44560095415db5436ca59e2cd4eeca7 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 5 Sep 2022 06:15:32 -0700 Subject: [PATCH 0047/1008] feat: adding pre-commit hook and makefile installation script (#1013) * feat: adding pre-commit hook and makefile installation script * renaming git hook install script * Update .githooks/pre-commit Co-authored-by: Hlib Kanunnikov * Update Makefile Co-authored-by: Hlib Kanunnikov * feat: ignoring golangci-lint if no go files are staged Co-authored-by: Hlib Kanunnikov --- .githooks/pre-commit | 38 ++++++++++++++++++++++++++++++++++++++ Makefile | 6 ++++++ 2 files changed, 44 insertions(+) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000000..6a4c26bea2 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,38 @@ +#!/bin/sh + +set -eu -o pipefail + +STAGED_GO_FILES=$(git diff --cached --name-only -- '*.go') +STAGED_MD_FILES=$(git diff --cached --name-only -- '*.md') + +if [[ $STAGED_GO_FILES == "" ]] && [[ $STAGED_MD_FILES == "" ]]; then + echo "--> Found no go or markdown files, skipping linting" +elif [[ $STAGED_GO_FILES == "" ]]; then + echo "--> Found markdown files, linting" + if ! command -v markdownlint &> /dev/null ; then + echo "markdownlint is not installed of available in the PATH" >&2 + echo "please check https://github.com/igorshubovych/markdownlint-cli" >&2 + exit 1 + fi + markdownlint --config .markdownlint.yaml '**/*.md' +else + echo "--> Found go files, running make lint" + if ! command -v golangci-lint &> /dev/null ; then + echo "golangci-lint not installed or available in the PATH" >&2 + echo "please check https://github.com/golangci/golangci-lint" >&2 + exit 1 + fi + make lint +fi + +if go mod tidy -v 2>&1 | grep -q 'updates to go.mod needed'; then + exit 1 +fi + +git diff --exit-code go.* &> /dev/null + +if [ $? -eq 1 ]; then + echo "go.mod or go.sum differs, please re-add it to your commit" + + exit 1 +fi diff --git a/Makefile b/Makefile index 5deab12d50..0f0fc2f5d2 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,12 @@ help: Makefile @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' .PHONY: help +## install: Install git-hooks from .githooks directory. +install-hooks: + @echo "--> Installing git hooks" + @git config core.hooksPath .githooks +.PHONY: init-hooks + ## build: Build celestia-node binary. build: @echo "--> Building Celestia" From 5ae17afd227b370e8709a27f29491fef66c3f514 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Wed, 7 Sep 2022 17:40:49 +0300 Subject: [PATCH 0048/1008] fraud: implement fraud sync (#917) * fraud: add fraud message * fraud: implement fraud sync logic * fraud: start syncing proofs once node checks the local storage * doc: update an adr * fraud: add peer cache to avoid multiple requests to the same peer * fraud: join topics for all available proof types --- das/daser_test.go | 3 +- docs/adr/adr-006-fraud-service.md | 13 +- fraud/bad_encoding_test.go | 2 +- fraud/interface.go | 5 +- fraud/pb/proof.pb.go | 620 +++++++++++++++++++++++++++++- fraud/pb/proof.proto | 13 + fraud/registry.go | 11 + fraud/requester.go | 55 +++ fraud/service.go | 163 +++++--- fraud/service_test.go | 104 +++-- fraud/store.go | 5 + fraud/store_test.go | 15 + fraud/sync.go | 148 +++++++ node/components.go | 4 +- node/services/service.go | 34 +- node/tests/fraud_test.go | 79 +++- 16 files changed, 1161 insertions(+), 113 deletions(-) create mode 100644 fraud/requester.go create mode 100644 fraud/sync.go diff --git a/das/daser_test.go b/das/daser_test.go index 14a49bba35..60f88fbfd1 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -272,7 +272,8 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { mockGet, shareServ, sub, _ := createDASerSubcomponents(t, bServ, 15, 15, avail) // create fraud service and break one header - f := fraud.NewService(ps, mockGet.GetByHeight, ds) + f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false) + require.NoError(t, f.Start(ctx)) mockGet.headers[1] = header.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() diff --git a/docs/adr/adr-006-fraud-service.md b/docs/adr/adr-006-fraud-service.md index 29f2c2c4c8..fa88f8c81d 100644 --- a/docs/adr/adr-006-fraud-service.md +++ b/docs/adr/adr-006-fraud-service.md @@ -197,7 +197,7 @@ Both full and light nodes should stop `DAS`, `Syncer` and `SubmitTx` services. 1. Valid BadEncodingFraudProofs should be stored on the disk using `FraudStore` interface: -### BEFP storage +### Fraud storage BEFP storage will be created on first subscription to Bad Encoding Fraud Proof. BEFP will be stored in datastore once it will be received, using `fraud/badEncodingProof` path and the corresponding block hash as the key: @@ -215,6 +215,17 @@ func getAll(ctx context.Context, ds datastore.Datastore) ([][]byte, error) In case if response error will be empty (and not ```datastore.ErrNotFound```), then a BEFP has been already added to storage and the node should be halted. +### Fraud sync + +The main purpose of FraudSync is to deliver fraud proofs to nodes that were started after a BEFP appears. Since full nodes create the BEFP during reconstruction, FraudSync is mainly implemented for light nodes: + +- Once a light node checks that its local fraud storage is empty, it starts waiting for new connections with the remote peers(full/bridge nodes) using `share/discovery`. +- The light node will send 5 requests to newly connected peers to get a fraud proof. +- If a fraud proof is received from a remote peer, then it should be validated and propagated across all local subscriptions in order to stop the respective services. + +NOTE: if a received fraud proof ends up being invalid, then the remote peer will be added to the black list. +Both full/light nodes register a stream handler for handling fraud proof requests. + ### Bridge node behaviour Bridge nodes will behave as light nodes do by subscribing to BEFP fraud sub and listening for BEFPs. If a BEFP is received, it will similarly shut down all dependent services, including broadcasting new `ExtendedHeader`s to the network. diff --git a/fraud/bad_encoding_test.go b/fraud/bad_encoding_test.go index 5af32c7680..ec736d57af 100644 --- a/fraud/bad_encoding_test.go +++ b/fraud/bad_encoding_test.go @@ -16,7 +16,7 @@ func TestFraudProofValidation(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer t.Cleanup(cancel) bServ := mdutils.Bserv() - _, store := createService(t) + _, store := createService(t, false) h, err := store.GetByHeight(ctx, 1) require.NoError(t, err) diff --git a/fraud/interface.go b/fraud/interface.go index e592ffd7f2..9fa8cc3d2b 100644 --- a/fraud/interface.go +++ b/fraud/interface.go @@ -17,7 +17,7 @@ type headerFetcher func(context.Context, uint64) (*header.ExtendedHeader, error) type ProofUnmarshaler func([]byte) (Proof, error) // Service encompasses the behavior necessary to subscribe and broadcast -// Fraud Proofs within the network. +// fraud proofs within the network. type Service interface { Subscriber Broadcaster @@ -39,8 +39,9 @@ type Subscriber interface { Subscribe(ProofType) (Subscription, error) } -// Getter encompasses the behavior to fetch stored FraudProofs. +// Getter encompasses the behavior to fetch stored fraud proofs. type Getter interface { + // Get fetches fraud proofs from the disk by its type. Get(context.Context, ProofType) ([]Proof, error) } diff --git a/fraud/pb/proof.pb.go b/fraud/pb/proof.pb.go index 389725e5ba..ddea926701 100644 --- a/fraud/pb/proof.pb.go +++ b/fraud/pb/proof.pb.go @@ -124,31 +124,180 @@ func (m *BadEncoding) GetAxis() Axis { return Axis_ROW } +type FraudMessageRequest struct { + RequestedProofType []string `protobuf:"bytes,1,rep,name=RequestedProofType,proto3" json:"RequestedProofType,omitempty"` +} + +func (m *FraudMessageRequest) Reset() { *m = FraudMessageRequest{} } +func (m *FraudMessageRequest) String() string { return proto.CompactTextString(m) } +func (*FraudMessageRequest) ProtoMessage() {} +func (*FraudMessageRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_318cb87a8bb2d394, []int{1} +} +func (m *FraudMessageRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FraudMessageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FraudMessageRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FraudMessageRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_FraudMessageRequest.Merge(m, src) +} +func (m *FraudMessageRequest) XXX_Size() int { + return m.Size() +} +func (m *FraudMessageRequest) XXX_DiscardUnknown() { + xxx_messageInfo_FraudMessageRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_FraudMessageRequest proto.InternalMessageInfo + +func (m *FraudMessageRequest) GetRequestedProofType() []string { + if m != nil { + return m.RequestedProofType + } + return nil +} + +type ProofResponse struct { + Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"` + Value [][]byte `protobuf:"bytes,2,rep,name=Value,proto3" json:"Value,omitempty"` +} + +func (m *ProofResponse) Reset() { *m = ProofResponse{} } +func (m *ProofResponse) String() string { return proto.CompactTextString(m) } +func (*ProofResponse) ProtoMessage() {} +func (*ProofResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_318cb87a8bb2d394, []int{2} +} +func (m *ProofResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ProofResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ProofResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ProofResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProofResponse.Merge(m, src) +} +func (m *ProofResponse) XXX_Size() int { + return m.Size() +} +func (m *ProofResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ProofResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ProofResponse proto.InternalMessageInfo + +func (m *ProofResponse) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *ProofResponse) GetValue() [][]byte { + if m != nil { + return m.Value + } + return nil +} + +type FraudMessageResponse struct { + Proofs []*ProofResponse `protobuf:"bytes,1,rep,name=Proofs,proto3" json:"Proofs,omitempty"` +} + +func (m *FraudMessageResponse) Reset() { *m = FraudMessageResponse{} } +func (m *FraudMessageResponse) String() string { return proto.CompactTextString(m) } +func (*FraudMessageResponse) ProtoMessage() {} +func (*FraudMessageResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_318cb87a8bb2d394, []int{3} +} +func (m *FraudMessageResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FraudMessageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FraudMessageResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FraudMessageResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_FraudMessageResponse.Merge(m, src) +} +func (m *FraudMessageResponse) XXX_Size() int { + return m.Size() +} +func (m *FraudMessageResponse) XXX_DiscardUnknown() { + xxx_messageInfo_FraudMessageResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_FraudMessageResponse proto.InternalMessageInfo + +func (m *FraudMessageResponse) GetProofs() []*ProofResponse { + if m != nil { + return m.Proofs + } + return nil +} + func init() { proto.RegisterEnum("fraud.pb.Axis", Axis_name, Axis_value) proto.RegisterType((*BadEncoding)(nil), "fraud.pb.BadEncoding") + proto.RegisterType((*FraudMessageRequest)(nil), "fraud.pb.FraudMessageRequest") + proto.RegisterType((*ProofResponse)(nil), "fraud.pb.ProofResponse") + proto.RegisterType((*FraudMessageResponse)(nil), "fraud.pb.FraudMessageResponse") } func init() { proto.RegisterFile("fraud/pb/proof.proto", fileDescriptor_318cb87a8bb2d394) } var fileDescriptor_318cb87a8bb2d394 = []byte{ - // 252 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2b, 0x4a, 0x2c, - 0x4d, 0xd1, 0x2f, 0x48, 0xd2, 0x2f, 0x28, 0xca, 0xcf, 0x4f, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0xe2, 0x00, 0x8b, 0xea, 0x15, 0x24, 0x49, 0x09, 0x67, 0x16, 0xe4, 0x80, 0xa5, 0x8b, 0x33, - 0x12, 0x8b, 0x52, 0x21, 0xd2, 0x4a, 0xcb, 0x19, 0xb9, 0xb8, 0x9d, 0x12, 0x53, 0x5c, 0xf3, 0x92, - 0xf3, 0x53, 0x32, 0xf3, 0xd2, 0x85, 0xe4, 0xb8, 0xb8, 0x3c, 0x52, 0x13, 0x53, 0x52, 0x8b, 0x3c, - 0x12, 0x8b, 0x33, 0x24, 0x18, 0x15, 0x18, 0x35, 0x78, 0x82, 0x90, 0x44, 0x84, 0xc4, 0xb8, 0xd8, - 0x3c, 0x52, 0x33, 0xd3, 0x33, 0x4a, 0x24, 0x98, 0x14, 0x18, 0x35, 0x58, 0x82, 0xa0, 0x3c, 0x21, - 0x35, 0x2e, 0xb6, 0x60, 0x90, 0xb1, 0xc5, 0x12, 0xcc, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0x7c, 0x7a, - 0x20, 0xdb, 0xf4, 0x0a, 0x92, 0xf4, 0xc0, 0xc2, 0x41, 0x50, 0x59, 0x21, 0x11, 0x2e, 0x56, 0xcf, - 0xbc, 0x94, 0xd4, 0x0a, 0x09, 0x16, 0x05, 0x46, 0x0d, 0xde, 0x20, 0x08, 0x47, 0x48, 0x89, 0x8b, - 0xc5, 0xb1, 0x22, 0xb3, 0x58, 0x82, 0x55, 0x81, 0x51, 0x83, 0xcf, 0x88, 0x4f, 0x0f, 0xe6, 0x66, - 0xbd, 0xc4, 0x8a, 0xcc, 0xe2, 0x20, 0xb0, 0x9c, 0x96, 0x04, 0x17, 0x0b, 0x88, 0x27, 0xc4, 0xce, - 0xc5, 0x1c, 0xe4, 0x1f, 0x2e, 0xc0, 0x00, 0x62, 0x38, 0xfb, 0xfb, 0x08, 0x30, 0x3a, 0x49, 0x9c, - 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, - 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x12, 0x1b, 0xd8, 0x93, 0xc6, 0x80, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xe8, 0xc8, 0xdd, 0xab, 0x1b, 0x01, 0x00, 0x00, + // 350 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0xcd, 0x4a, 0xeb, 0x40, + 0x18, 0xcd, 0x34, 0x69, 0xee, 0xed, 0xd7, 0x1f, 0xca, 0xb4, 0xdc, 0x3b, 0xb8, 0x08, 0x21, 0x0b, + 0x19, 0x5c, 0x4c, 0xa1, 0xae, 0x5c, 0x5a, 0xa9, 0x56, 0x50, 0x2a, 0xa3, 0xe8, 0x7a, 0x62, 0xa6, + 0x6d, 0xa0, 0x24, 0x63, 0xa6, 0x85, 0xfa, 0x16, 0xbe, 0x85, 0xaf, 0xe2, 0xb2, 0x4b, 0x97, 0xd2, + 0xbe, 0x88, 0xcc, 0x34, 0xa2, 0x05, 0x77, 0xe7, 0x27, 0x39, 0xe7, 0x7c, 0x0c, 0x74, 0x27, 0x85, + 0x58, 0x26, 0x3d, 0x15, 0xf7, 0x54, 0x91, 0xe7, 0x13, 0xa6, 0x8a, 0x7c, 0x91, 0xe3, 0xbf, 0x56, + 0x65, 0x2a, 0x3e, 0xe8, 0xa4, 0x6a, 0x6e, 0x6d, 0x3d, 0x13, 0x85, 0xdc, 0xd9, 0xd1, 0x2b, 0x82, + 0xfa, 0x40, 0x24, 0xc3, 0xec, 0x31, 0x4f, 0xd2, 0x6c, 0x8a, 0x03, 0x80, 0x91, 0x14, 0x89, 0x2c, + 0x46, 0x42, 0xcf, 0x08, 0x0a, 0x11, 0x6d, 0xf0, 0x1f, 0x0a, 0xfe, 0x07, 0xfe, 0x48, 0xa6, 0xd3, + 0xd9, 0x82, 0x54, 0x42, 0x44, 0x3d, 0x5e, 0x32, 0x7c, 0x08, 0xfe, 0xad, 0x89, 0xd5, 0xc4, 0x0d, + 0x5d, 0x5a, 0xef, 0xb7, 0x98, 0x69, 0x63, 0x2a, 0x66, 0x56, 0xe6, 0xa5, 0x8b, 0xbb, 0x50, 0xbd, + 0xcc, 0x12, 0xb9, 0x22, 0x5e, 0x88, 0x68, 0x93, 0xef, 0x08, 0x8e, 0xc0, 0x3b, 0x5d, 0xa5, 0x9a, + 0x54, 0x43, 0x44, 0x5b, 0xfd, 0x16, 0xfb, 0xda, 0xcc, 0xc4, 0x2a, 0xd5, 0xdc, 0x7a, 0xd1, 0x10, + 0x3a, 0xe7, 0x46, 0xbe, 0x96, 0x5a, 0x8b, 0xa9, 0xe4, 0xf2, 0x69, 0x29, 0xf5, 0x02, 0x33, 0xc0, + 0x25, 0x94, 0xc9, 0x8d, 0xb9, 0xfb, 0xee, 0x59, 0x49, 0x82, 0x42, 0x97, 0xd6, 0xf8, 0x2f, 0x4e, + 0x74, 0x02, 0x4d, 0x4b, 0xb8, 0xd4, 0x2a, 0xcf, 0xb4, 0xc4, 0x18, 0xbc, 0xf2, 0x17, 0x44, 0x6b, + 0xdc, 0x62, 0xb3, 0xf2, 0x5e, 0xcc, 0x97, 0x92, 0x54, 0x42, 0x97, 0x36, 0xf8, 0x8e, 0x44, 0x17, + 0xd0, 0xdd, 0x5f, 0x50, 0x26, 0xf4, 0xc0, 0xb7, 0x91, 0xda, 0xd6, 0xd6, 0xfb, 0xff, 0xbf, 0xf7, + 0xef, 0x55, 0xf1, 0xf2, 0xb3, 0x23, 0x02, 0x9e, 0x39, 0x0c, 0xff, 0x01, 0x97, 0x8f, 0x1f, 0xda, + 0x8e, 0x01, 0x67, 0xe3, 0xab, 0x36, 0x1a, 0x90, 0xb7, 0x4d, 0x80, 0xd6, 0x9b, 0x00, 0x7d, 0x6c, + 0x02, 0xf4, 0xb2, 0x0d, 0x9c, 0xf5, 0x36, 0x70, 0xde, 0xb7, 0x81, 0x13, 0xfb, 0xf6, 0xbd, 0x8e, + 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x5b, 0x6e, 0x3b, 0x05, 0xe6, 0x01, 0x00, 0x00, } func (m *BadEncoding) Marshal() (dAtA []byte, err error) { @@ -210,6 +359,114 @@ func (m *BadEncoding) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *FraudMessageRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FraudMessageRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FraudMessageRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RequestedProofType) > 0 { + for iNdEx := len(m.RequestedProofType) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.RequestedProofType[iNdEx]) + copy(dAtA[i:], m.RequestedProofType[iNdEx]) + i = encodeVarintProof(dAtA, i, uint64(len(m.RequestedProofType[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ProofResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProofResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ProofResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Value) > 0 { + for iNdEx := len(m.Value) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Value[iNdEx]) + copy(dAtA[i:], m.Value[iNdEx]) + i = encodeVarintProof(dAtA, i, uint64(len(m.Value[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Type) > 0 { + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarintProof(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *FraudMessageResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FraudMessageResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FraudMessageResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Proofs) > 0 { + for iNdEx := len(m.Proofs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Proofs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProof(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintProof(dAtA []byte, offset int, v uint64) int { offset -= sovProof(v) base := offset @@ -249,6 +506,55 @@ func (m *BadEncoding) Size() (n int) { return n } +func (m *FraudMessageRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.RequestedProofType) > 0 { + for _, s := range m.RequestedProofType { + l = len(s) + n += 1 + l + sovProof(uint64(l)) + } + } + return n +} + +func (m *ProofResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + if l > 0 { + n += 1 + l + sovProof(uint64(l)) + } + if len(m.Value) > 0 { + for _, b := range m.Value { + l = len(b) + n += 1 + l + sovProof(uint64(l)) + } + } + return n +} + +func (m *FraudMessageResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Proofs) > 0 { + for _, e := range m.Proofs { + l = e.Size() + n += 1 + l + sovProof(uint64(l)) + } + } + return n +} + func sovProof(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -430,6 +736,286 @@ func (m *BadEncoding) Unmarshal(dAtA []byte) error { } return nil } +func (m *FraudMessageRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FraudMessageRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FraudMessageRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestedProofType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProof + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProof + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestedProofType = append(m.RequestedProofType, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProof(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProof + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProofResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProofResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProofResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProof + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProof + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Type = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthProof + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthProof + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = append(m.Value, make([]byte, postIndex-iNdEx)) + copy(m.Value[len(m.Value)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProof(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProof + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FraudMessageResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FraudMessageResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FraudMessageResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proofs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProof + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProof + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProof + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proofs = append(m.Proofs, &ProofResponse{}) + if err := m.Proofs[len(m.Proofs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProof(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProof + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipProof(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/fraud/pb/proof.proto b/fraud/pb/proof.proto index 873bfa316c..e68d52193e 100644 --- a/fraud/pb/proof.proto +++ b/fraud/pb/proof.proto @@ -16,3 +16,16 @@ message BadEncoding { uint32 Index = 4; axis Axis = 5; } + +message FraudMessageRequest { + repeated string RequestedProofType = 1; +} + +message ProofResponse { + string Type = 1; + repeated bytes Value = 2; +} + +message FraudMessageResponse { + repeated ProofResponse Proofs= 1; +} diff --git a/fraud/registry.go b/fraud/registry.go index ed2a492cce..ab6110505e 100644 --- a/fraud/registry.go +++ b/fraud/registry.go @@ -27,3 +27,14 @@ func Register(p Proof) { return proof, err } } + +// getRegisteredProofTypes returns all available proofTypes. +func getRegisteredProofTypes() []ProofType { + unmarshalersLk.Lock() + defer unmarshalersLk.Unlock() + proofs := make([]ProofType, 0, len(defaultUnmarshalers)) + for proof := range defaultUnmarshalers { + proofs = append(proofs, proof) + } + return proofs +} diff --git a/fraud/requester.go b/fraud/requester.go new file mode 100644 index 0000000000..1da9444bbe --- /dev/null +++ b/fraud/requester.go @@ -0,0 +1,55 @@ +package fraud + +import ( + "context" + "time" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + + "github.com/celestiaorg/go-libp2p-messenger/serde" + + pb "github.com/celestiaorg/celestia-node/fraud/pb" +) + +const ( + // writeDeadline sets timeout for sending messages to the stream + writeDeadline = time.Second * 5 + // readDeadline sets timeout for reading messages from the stream + readDeadline = time.Minute +) + +func requestProofs( + ctx context.Context, + host host.Host, + pid peer.ID, + proofTypes []string, +) ([]*pb.ProofResponse, error) { + msg := &pb.FraudMessageRequest{RequestedProofType: proofTypes} + stream, err := host.NewStream(ctx, pid, fraudProtocolID) + if err != nil { + return nil, err + } + + if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + log.Warn(err) + } + _, err = serde.Write(stream, msg) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, err + } + if err = stream.CloseWrite(); err != nil { + log.Warn(err) + } + if err = stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { + log.Warn(err) + } + resp := &pb.FraudMessageResponse{} + _, err = serde.Read(stream, resp) + if err != nil { + stream.Reset() //nolint:errcheck + return nil, err + } + return resp.Proofs, stream.Close() +} diff --git a/fraud/service.go b/fraud/service.go index a154ba2455..82ca1298de 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -1,26 +1,33 @@ package fraud import ( + "bytes" "context" "encoding/hex" "errors" "fmt" "sync" - "time" "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/protocol" pubsub "github.com/libp2p/go-libp2p-pubsub" -) -const ( - // fetchHeaderTimeout duration of GetByHeight request to fetch an ExtendedHeader. - fetchHeaderTimeout = time.Minute * 2 + "github.com/celestiaorg/celestia-node/params" ) -// service is responsible for validating and propagating Fraud Proofs. +// fraudRequests is the amount of external requests that will be tried to get fraud proofs from other peers. +const fraudRequests = 5 + +var fraudProtocolID = protocol.ID(fmt.Sprintf("/fraud/v0.0.1/%s", params.DefaultNetwork())) + +// ProofService is responsible for validating and propagating Fraud Proofs. // It implements the Service interface. -type service struct { +type ProofService struct { + ctx context.Context + cancel context.CancelFunc + topicsLk sync.RWMutex topics map[ProofType]*pubsub.Topic @@ -28,30 +35,71 @@ type service struct { stores map[ProofType]datastore.Datastore pubsub *pubsub.PubSub + host host.Host getter headerFetcher ds datastore.Datastore + + syncerEnabled bool +} + +func NewProofService( + p *pubsub.PubSub, + host host.Host, + getter headerFetcher, + ds datastore.Datastore, + syncerEnabled bool, +) *ProofService { + return &ProofService{ + pubsub: p, + host: host, + getter: getter, + topics: make(map[ProofType]*pubsub.Topic), + stores: make(map[ProofType]datastore.Datastore), + ds: ds, + syncerEnabled: syncerEnabled, + } +} + +// registerProofTopics registers proofTypes as pubsub topics to be joined. +func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { + for _, proofType := range proofTypes { + t, err := join(f.pubsub, proofType, f.processIncoming) + if err != nil { + return err + } + f.topicsLk.Lock() + f.topics[proofType] = t + f.topicsLk.Unlock() + } + return nil } -func NewService(p *pubsub.PubSub, getter headerFetcher, ds datastore.Datastore) Service { - return &service{ - pubsub: p, - getter: getter, - topics: make(map[ProofType]*pubsub.Topic), - stores: make(map[ProofType]datastore.Datastore), - ds: ds, +// Start joins fraud proofs topics, sets the stream handler for fraudProtocolID and starts syncing if syncer is enabled. +func (f *ProofService) Start(context.Context) error { + f.ctx, f.cancel = context.WithCancel(context.Background()) + if err := f.registerProofTopics(getRegisteredProofTypes()...); err != nil { + return err } + f.host.SetStreamHandler(fraudProtocolID, f.handleFraudMessageRequest) + if f.syncerEnabled { + go f.syncFraudProofs(f.ctx) + } + return nil +} + +// Stop removes the stream handler and cancels the underlying ProofService +func (f *ProofService) Stop(context.Context) error { + f.host.RemoveStreamHandler(fraudProtocolID) + f.cancel() + return nil } -func (f *service) Subscribe(proofType ProofType) (_ Subscription, err error) { +func (f *ProofService) Subscribe(proofType ProofType) (_ Subscription, err error) { f.topicsLk.Lock() defer f.topicsLk.Unlock() t, ok := f.topics[proofType] if !ok { - t, err = join(f.pubsub, proofType, f.processIncoming) - if err != nil { - return nil, err - } - f.topics[proofType] = t + return nil, fmt.Errorf("topic for %s does not exist", proofType) } subs, err := t.Subscribe() if err != nil { @@ -60,7 +108,7 @@ func (f *service) Subscribe(proofType ProofType) (_ Subscription, err error) { return &subscription{subs}, nil } -func (f *service) Broadcast(ctx context.Context, p Proof) error { +func (f *ProofService) Broadcast(ctx context.Context, p Proof) error { bin, err := p.MarshalBinary() if err != nil { return err @@ -74,12 +122,15 @@ func (f *service) Broadcast(ctx context.Context, p Proof) error { return t.Publish(ctx, bin) } -func (f *service) processIncoming( +// processIncoming encompasses the logic for validating fraud proofs. +func (f *ProofService) processIncoming( ctx context.Context, proofType ProofType, from peer.ID, msg *pubsub.Message, ) pubsub.ValidationResult { + // unmarshal message to the Proof. + // Peer will be added to black list if unmarshalling fails. proof, err := Unmarshal(proofType, msg.Data) if err != nil { log.Errorw("unmarshalling failed", "err", err) @@ -88,21 +139,22 @@ func (f *service) processIncoming( } return pubsub.ValidationReject } + // check the fraud proof locally and ignore if it has been already stored locally. + if f.verifyLocal(ctx, proofType, hex.EncodeToString(proof.HeaderHash()), msg.Data) { + return pubsub.ValidationIgnore + } + + msg.ValidatorData = proof - newCtx, cancel := context.WithTimeout(ctx, fetchHeaderTimeout) - extHeader, err := f.getter(newCtx, proof.Height()) - defer cancel() + // fetch extended header in order to verify the fraud proof. + extHeader, err := f.getter(ctx, proof.Height()) if err != nil { - // Timeout means there is a problem with the network. - // As we cannot prove or discard Fraud Proof, user must restart the node. - if errors.Is(err, context.DeadlineExceeded) { - log.Errorw("failed to fetch header. Timeout reached.") - // TODO(@vgonkivs): add handling for this case. As we are not able to verify fraud proof. - } log.Errorw("failed to fetch header to verify a fraud proof", "err", err, "proofType", proof.Type(), "height", proof.Height()) return pubsub.ValidationIgnore } + // validate the fraud proof. + // Peer will be added to black list if the validation fails. err = proof.Validate(extHeader) if err != nil { log.Errorw("proof validation err: ", @@ -110,12 +162,17 @@ func (f *service) processIncoming( f.pubsub.BlacklistPeer(from) return pubsub.ValidationReject } - log.Warnw("received fraud proof", "proofType", proof.Type(), - "height", proof.Height(), - "hash", hex.EncodeToString(extHeader.DAH.Hash()), - "from", from.String(), - ) - msg.ValidatorData = proof + + // add the fraud proof to storage. + err = f.put(ctx, proof.Type(), hex.EncodeToString(proof.HeaderHash()), msg.Data) + if err != nil { + log.Errorw("failed to store fraud proof", "err", err) + } + + return pubsub.ValidationAccept +} + +func (f *ProofService) Get(ctx context.Context, proofType ProofType) ([]Proof, error) { f.storesLk.Lock() store, ok := f.stores[proofType] if !ok { @@ -123,15 +180,12 @@ func (f *service) processIncoming( f.stores[proofType] = store } f.storesLk.Unlock() - err = put(ctx, store, string(proof.HeaderHash()), msg.Data) - if err != nil { - log.Error(err) - } - log.Warn("Shutting down services...") - return pubsub.ValidationAccept + + return getAll(ctx, store, proofType) } -func (f *service) Get(ctx context.Context, proofType ProofType) ([]Proof, error) { +// put adds a fraud proof to the local storage. +func (f *ProofService) put(ctx context.Context, proofType ProofType, hash string, data []byte) error { f.storesLk.Lock() store, ok := f.stores[proofType] if !ok { @@ -139,6 +193,25 @@ func (f *service) Get(ctx context.Context, proofType ProofType) ([]Proof, error) f.stores[proofType] = store } f.storesLk.Unlock() + return put(ctx, store, hash, data) +} - return getAll(ctx, store, proofType) +// verifyLocal checks if a fraud proof has been stored locally. +func (f *ProofService) verifyLocal(ctx context.Context, proofType ProofType, hash string, data []byte) bool { + f.storesLk.RLock() + storage, ok := f.stores[proofType] + f.storesLk.RUnlock() + if !ok { + return false + } + + proof, err := getByHash(ctx, storage, hash) + if err != nil { + if !errors.Is(err, datastore.ErrNotFound) { + log.Error(err) + } + return false + } + + return bytes.Equal(proof, data) } diff --git a/fraud/service_test.go b/fraud/service_test.go index 55acbe9639..ec750e68ec 100644 --- a/fraud/service_test.go +++ b/fraud/service_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/sync" "github.com/libp2p/go-libp2p-core/host" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -19,33 +20,37 @@ import ( ) func TestService_Subscribe(t *testing.T) { - s, _ := createService(t) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) + t.Cleanup(cancel) + s, _ := createService(t, false) proof := newValidProof() + require.NoError(t, s.Start(ctx)) _, err := s.Subscribe(proof.Type()) require.NoError(t, err) } func TestService_SubscribeFails(t *testing.T) { - s, _ := createService(t) + s, _ := createService(t, false) proof := newValidProof() - delete(defaultUnmarshalers, proof.Type()) _, err := s.Subscribe(proof.Type()) - require.NoError(t, err) + require.Error(t, err) } func TestService_BroadcastFails(t *testing.T) { - s, _ := createService(t) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) + t.Cleanup(cancel) + s, _ := createService(t, false) p := newValidProof() - require.Error(t, s.Broadcast(context.TODO(), p)) + require.Error(t, s.Broadcast(ctx, p)) } func TestService_Broadcast(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) t.Cleanup(cancel) - s, _ := createService(t) - + s, _ := createService(t, false) proof := newValidProof() + require.NoError(t, s.Start(ctx)) subs, err := s.Subscribe(proof.Type()) require.NoError(t, err) @@ -89,9 +94,7 @@ func TestService_processIncoming(t *testing.T) { bin, err := test.proof.MarshalBinary() require.NoError(t, err) // create first fraud service that will broadcast incorrect Fraud Proof - serviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0]) - fserviceA := serviceA.(*service) - require.NotNil(t, fserviceA) + service, _ := createServiceWithHost(ctx, t, net.Hosts()[0], false) msg := &pubsub.Message{ Message: &pubsubpb.Message{ Data: bin, @@ -101,7 +104,8 @@ func TestService_processIncoming(t *testing.T) { if test.precondition != nil { test.precondition() } - res := fserviceA.processIncoming(ctx, test.proof.Type(), net.Hosts()[1].ID(), msg) + require.NoError(t, service.Start(ctx)) + res := service.processIncoming(ctx, test.proof.Type(), net.Hosts()[1].ID(), msg) require.True(t, res == test.validationResult) } } @@ -114,21 +118,21 @@ func TestService_ReGossiping(t *testing.T) { require.NoError(t, err) // create first fraud service that will broadcast incorrect Fraud Proof - pserviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0]) + pserviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0], false) require.NoError(t, err) - serviceA := pserviceA.(*service) - // create pub sub in order to listen for Fraud Proof psB, err := pubsub.NewGossipSub(ctx, net.Hosts()[1], // -> B pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) // create second service that will receive and validate Fraud Proof - pserviceB := NewService( + pserviceB := NewProofService( psB, + net.Hosts()[1], func(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { return &header.ExtendedHeader{}, nil }, sync.MutexWrap(datastore.NewMapDatastore()), + false, ) addrB := host.InfoFromHost(net.Hosts()[1]) // -> B @@ -136,14 +140,15 @@ func TestService_ReGossiping(t *testing.T) { psC, err := pubsub.NewGossipSub(ctx, net.Hosts()[2], // -> C pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) - pserviceC := NewService( + pserviceC := NewProofService( psC, + net.Hosts()[2], func(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { return &header.ExtendedHeader{}, nil }, sync.MutexWrap(datastore.NewMapDatastore()), + false, ) - // establish connections // connect peers: A -> B -> C, so A and C are not connected to each other require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) // host[0] is A @@ -152,7 +157,9 @@ func TestService_ReGossiping(t *testing.T) { befp := newValidProof() bin, err := befp.MarshalBinary() require.NoError(t, err) - + require.NoError(t, pserviceA.Start(ctx)) + require.NoError(t, pserviceB.Start(ctx)) + require.NoError(t, pserviceC.Start(ctx)) subsA, err := pserviceA.Subscribe(mockProofType) require.NoError(t, err) defer subsA.Cancel() @@ -167,7 +174,7 @@ func TestService_ReGossiping(t *testing.T) { // we cannot avoid sleep because it helps to avoid flakiness time.Sleep(time.Millisecond * 100) - err = serviceA.topics[mockProofType].Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) + err = pserviceA.topics[mockProofType].Publish(ctx, bin, pubsub.WithReadiness(pubsub.MinTopicSize(1))) require.NoError(t, err) newCtx, cancel := context.WithTimeout(ctx, time.Millisecond*100) @@ -188,17 +195,14 @@ func TestService_Get(t *testing.T) { proof := newValidProof() bin, err := proof.MarshalBinary() require.NoError(t, err) - pService, _ := createService(t) - service := pService.(*service) - require.NotNil(t, service) - + pService, _ := createService(t, false) // try to fetch proof _, err = pService.Get(ctx, proof.Type()) // error is expected here because storage is empty require.Error(t, err) // create store - store := initStore(proof.Type(), service.ds) + store := initStore(proof.Type(), pService.ds) // add proof to storage require.NoError(t, put(ctx, store, hex.EncodeToString(proof.HeaderHash()), bin)) // fetch proof @@ -206,26 +210,58 @@ func TestService_Get(t *testing.T) { require.NoError(t, err) } -func createService(t *testing.T) (Service, *mockStore) { +func TestService_Sync(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + t.Cleanup(cancel) + // create mock network + net, err := mocknet.FullMeshLinked(2) + require.NoError(t, err) + + pserviceA, _ := createServiceWithHost(ctx, t, net.Hosts()[0], false) + pserviceB, _ := createServiceWithHost(ctx, t, net.Hosts()[1], true) + proof := newValidProof() + require.NoError(t, pserviceA.Start(ctx)) + require.NoError(t, pserviceB.Start(ctx)) + subs, err := pserviceB.Subscribe(mockProofType) + require.NoError(t, err) + bin, err := proof.MarshalBinary() + require.NoError(t, err) + store := namespace.Wrap(pserviceA.ds, makeKey(mockProofType)) + require.NoError(t, put(ctx, store, string(proof.HeaderHash()), bin)) + + addrB := host.InfoFromHost(net.Hosts()[1]) + require.NoError(t, net.Hosts()[0].Connect(ctx, *addrB)) + + _, err = subs.Proof(ctx) + require.NoError(t, err) +} + +func createService(t *testing.T, enabledSyncer bool) (*ProofService, *mockStore) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) t.Cleanup(cancel) // create mock network net, err := mocknet.FullMeshLinked(1) require.NoError(t, err) - // create pubsub for host - ps, err := pubsub.NewGossipSub(ctx, net.Hosts()[0], - pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) - require.NoError(t, err) - store := createStore(t, 10) - return NewService(ps, store.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())), store + return createServiceWithHost(ctx, t, net.Hosts()[0], enabledSyncer) } -func createServiceWithHost(ctx context.Context, t *testing.T, host host.Host) (Service, *mockStore) { +func createServiceWithHost( + ctx context.Context, + t *testing.T, + host host.Host, + enabledSyncer bool, +) (*ProofService, *mockStore) { // create pubsub for host ps, err := pubsub.NewGossipSub(ctx, host, pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign)) require.NoError(t, err) store := createStore(t, 10) - return NewService(ps, store.GetByHeight, sync.MutexWrap(datastore.NewMapDatastore())), store + return NewProofService( + ps, + host, + store.GetByHeight, + sync.MutexWrap(datastore.NewMapDatastore()), + enabledSyncer, + ), store } diff --git a/fraud/store.go b/fraud/store.go index b7ef4600bc..ff812c47c3 100644 --- a/fraud/store.go +++ b/fraud/store.go @@ -30,6 +30,11 @@ func query(ctx context.Context, ds datastore.Datastore, q q.Query) ([]q.Entry, e return results.Rest() } +// getByHash fetches a fraud proof by its hash from local storage. +func getByHash(ctx context.Context, ds datastore.Datastore, hash string) ([]byte, error) { + return ds.Get(ctx, datastore.NewKey(hash)) +} + // getAll queries all Fraud Proofs by their type. func getAll(ctx context.Context, ds datastore.Datastore, proofType ProofType) ([]Proof, error) { entries, err := query(ctx, ds, q.Query{}) diff --git a/fraud/store_test.go b/fraud/store_test.go index fbd87975f3..2556b8cf5e 100644 --- a/fraud/store_test.go +++ b/fraud/store_test.go @@ -57,3 +57,18 @@ func Test_GetAllFailed(t *testing.T) { require.ErrorIs(t, err, datastore.ErrNotFound) require.Nil(t, proofs) } + +func Test_getByHash(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer t.Cleanup(cancel) + + proof := newValidProof() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + store := namespace.Wrap(ds, makeKey(proof.Type())) + bin, err := proof.MarshalBinary() + require.NoError(t, err) + err = put(ctx, store, string(proof.HeaderHash()), bin) + require.NoError(t, err) + _, err = getByHash(ctx, store, string(proof.HeaderHash())) + require.NoError(t, err) +} diff --git a/fraud/sync.go b/fraud/sync.go new file mode 100644 index 0000000000..e710cfccb2 --- /dev/null +++ b/fraud/sync.go @@ -0,0 +1,148 @@ +package fraud + +import ( + "context" + "time" + + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/event" + "github.com/libp2p/go-libp2p-core/network" + "github.com/libp2p/go-libp2p-core/peer" + pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/celestiaorg/go-libp2p-messenger/serde" + + pb "github.com/celestiaorg/celestia-node/fraud/pb" +) + +// syncFraudProofs encompasses the behavior for fetching fraud proofs from other peers. +// syncFraudProofs subscribes to EvtPeerIdentificationCompleted to get newly connected peers +// to request fraud proofs from and request fraud proofs from them. +// After fraud proofs are received, they are published to all local subscriptions for verification +// order to be verified. +func (f *ProofService) syncFraudProofs(ctx context.Context) { + log.Debug("start fetching fraud proofs") + // subscribe to new peer connections that we can request fraud proofs from + sub, err := f.host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) + if err != nil { + log.Error(err) + return + } + defer sub.Close() + f.topicsLk.RLock() + // get proof types from subscribed pubsub topics + proofTypes := make([]string, 0, len(f.topics)) + for proofType := range f.topics { + proofTypes = append(proofTypes, string(proofType)) + } + f.topicsLk.RUnlock() + connStatus := event.EvtPeerIdentificationCompleted{} + // peerCache is used to store discovered peers to avoid sending multiple requests to the same peer + peerCache := make(map[peer.ID]struct{}) + // request proofs from `fraudRequests` many peers + for i := 0; i < fraudRequests; i++ { + select { + case <-ctx.Done(): + return + case e := <-sub.Out(): + connStatus = e.(event.EvtPeerIdentificationCompleted) + } + + // ignore already requested peers or ourselves as a peer + if _, ok := peerCache[connStatus.Peer]; ok || connStatus.Peer == f.host.ID() { + i-- + continue + } + + peerCache[connStatus.Peer] = struct{}{} + // valid peer found, so go send proof requests + go func(pid peer.ID) { + log.Debugw("requesting proofs from peer", "pid", pid) + respProofs, err := requestProofs(ctx, f.host, pid, proofTypes) + if err != nil { + log.Errorw("error while requesting fraud proofs", "err", err, "peer", pid) + return + } + if len(respProofs) == 0 { + log.Debugw("peer did not return any proofs", "pid", pid) + return + } + log.Debugw("got fraud proofs from peer", "pid", connStatus.Peer) + for _, data := range respProofs { + f.topicsLk.RLock() + topic, ok := f.topics[ProofType(data.Type)] + f.topicsLk.RUnlock() + if !ok { + log.Errorf("topic for %s does not exist", ProofType(data.Type)) + continue + } + for _, val := range data.Value { + err = topic.Publish( + ctx, + val, + // broadcast across all local subscriptions in order to verify fraud proof and to stop services + pubsub.WithLocalPublication(true), + // key can be nil because it will not be verified in this case + pubsub.WithSecretKeyAndPeerId(nil, pid), + ) + if err != nil { + log.Error(err) + } + } + } + }(connStatus.Peer) + } +} + +// handleFraudMessageRequest handles an incoming FraudMessageRequest. +func (f *ProofService) handleFraudMessageRequest(stream network.Stream) { + req := &pb.FraudMessageRequest{} + if err := stream.SetReadDeadline(time.Now().Add(readDeadline)); err != nil { + log.Warn(err) + } + _, err := serde.Read(stream, req) + if err != nil { + stream.Reset() //nolint:errcheck + log.Errorw("handling fraud message request failed", "err", err) + return + } + if err = stream.CloseRead(); err != nil { + log.Warn(err) + } + + resp := &pb.FraudMessageResponse{} + resp.Proofs = make([]*pb.ProofResponse, 0, len(req.RequestedProofType)) + // retrieve fraud proofs as provided by the FraudMessageRequest proofTypes. + for _, p := range req.RequestedProofType { + proofs, err := f.Get(f.ctx, ProofType(p)) + if err != nil { + if err != datastore.ErrNotFound { + log.Error(err) + } + continue + } + pbProofs := &pb.ProofResponse{Type: p, Value: make([][]byte, 0, len(proofs))} + for _, proof := range proofs { + bin, err := proof.MarshalBinary() + if err != nil { + log.Error(err) + continue + } + pbProofs.Value = append(pbProofs.Value, bin) + } + resp.Proofs = append(resp.Proofs, pbProofs) + } + + if err = stream.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil { + log.Warn(err) + } + _, err = serde.Write(stream, resp) + if err != nil { + stream.Reset() //nolint:errcheck + log.Errorw("error while writing a response", "err", err) + return + } + if err = stream.CloseWrite(); err != nil { + log.Warnw("error while closing a writer in stream", "err", err) + } +} diff --git a/node/components.go b/node/components.go index 2150dbb72c..4acefc34a6 100644 --- a/node/components.go +++ b/node/components.go @@ -33,6 +33,7 @@ func lightComponents(cfg *Config, store Store) fx.Option { fx.Provide(services.HeaderExchangeP2P(cfg.Services)), fx.Provide(services.LightAvailability(cfg.Services)), fx.Provide(services.CacheAvailability[*share.LightAvailability]), + fxutil.ProvideAs(services.FraudServiceWithSyncer, new(fraud.Service), new(fraud.Subscriber)), fx.Invoke(rpc.Handler), ) } @@ -46,6 +47,7 @@ func bridgeComponents(cfg *Config, store Store) fx.Option { fx.Supply(header.MakeExtendedHeader), fx.Provide(services.FullAvailability(cfg.Services)), fx.Provide(services.CacheAvailability[*share.FullAvailability]), + fxutil.ProvideAs(services.FraudService, new(fraud.Service), new(fraud.Subscriber)), fx.Invoke(func( state *state.Service, share *share.Service, @@ -66,6 +68,7 @@ func fullComponents(cfg *Config, store Store) fx.Option { fx.Provide(services.HeaderExchangeP2P(cfg.Services)), fx.Provide(services.FullAvailability(cfg.Services)), fx.Provide(services.CacheAvailability[*share.FullAvailability]), + fxutil.ProvideAs(services.FraudService, new(fraud.Service), new(fraud.Subscriber)), fx.Invoke(rpc.Handler), ) } @@ -87,7 +90,6 @@ func baseComponents(cfg *Config, store Store) fx.Option { fx.Provide(services.HeaderService), fx.Provide(services.HeaderStore), fx.Invoke(services.HeaderStoreInit(&cfg.Services)), - fxutil.ProvideAs(services.FraudService, new(fraud.Service), new(fraud.Subscriber)), fx.Provide(services.HeaderSyncer), fxutil.ProvideAs(services.P2PSubscriber, new(header.Broadcaster), new(header.Subscriber)), fx.Provide(services.HeaderP2PExchangeServer), diff --git a/node/services/service.go b/node/services/service.go index 226d4842d0..e8274d78ac 100644 --- a/node/services/service.go +++ b/node/services/service.go @@ -161,13 +161,41 @@ func DASer( return das } -// FraudService constructs fraud proof service. +// FraudService constructs a fraud proof service with the syncer disabled. func FraudService( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore header.Store, + ds datastore.Batching, +) (fraud.Service, error) { + return newFraudService(lc, sub, host, hstore, ds, false) +} + +// FraudServiceWithSyncer constructs fraud proof service with enabled syncer. +func FraudServiceWithSyncer( + lc fx.Lifecycle, sub *pubsub.PubSub, + host host.Host, hstore header.Store, ds datastore.Batching, -) fraud.Service { - return fraud.NewService(sub, hstore.GetByHeight, ds) +) (fraud.Service, error) { + return newFraudService(lc, sub, host, hstore, ds, true) +} + +func newFraudService( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore header.Store, + ds datastore.Batching, + isEnabled bool) (fraud.Service, error) { + pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled) + lc.Append(fx.Hook{ + OnStart: pservice.Start, + OnStop: pservice.Stop, + }) + return pservice, nil } // LightAvailability constructs light share availability. diff --git a/node/tests/fraud_test.go b/node/tests/fraud_test.go index 74136155f3..ba64923692 100644 --- a/node/tests/fraud_test.go +++ b/node/tests/fraud_test.go @@ -16,7 +16,7 @@ import ( ) /* - Test-Case: Full Node will propagate BEFP to the network, once ByzantineError will be received from sampling. + Test-Case: Full Node will propagate a fraud proof to the network, once ByzantineError will be received from sampling. Pre-Requisites: - CoreClient is started by swamp. Steps: @@ -24,14 +24,14 @@ import ( 2. Start a BN. 3. Create a Full Node(FN) with a connection to BN as a trusted peer. 4. Start a FN. - 5. Subscribe to BEFP and wait when it will be received. + 5. Subscribe to a fraud proof and wait when it will be received. 6. Check FN is not synced to 15. - Note: 15 is not available because DASer will be stopped before reaching this height due to receiving BEFP. + Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. */ func TestFraudProofBroadcasting(t *testing.T) { sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) - bridge := sw.NewBridgeNode(node.WithHeaderConstructFn(header.FraudMaker(t, 10))) + bridge := sw.NewBridgeNode(node.WithHeaderConstructFn(header.FraudMaker(t, 20))) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) t.Cleanup(cancel) @@ -44,14 +44,14 @@ func TestFraudProofBroadcasting(t *testing.T) { store := node.MockStore(t, node.DefaultConfig(node.Full)) full := sw.NewNodeWithStore(node.Full, store, node.WithTrustedPeers(addrs[0].String())) + err = full.Start(ctx) + require.NoError(t, err) + // subscribe to fraud proof before node starts helps // to prevent flakiness when fraud proof is propagating before subscribing on it subscr, err := full.FraudServ.Subscribe(fraud.BadEncoding) require.NoError(t, err) - err = full.Start(ctx) - require.NoError(t, err) - _, err = subscr.Proof(ctx) require.NoError(t, err) @@ -61,7 +61,7 @@ func TestFraudProofBroadcasting(t *testing.T) { // rework this after https://github.com/celestiaorg/celestia-node/issues/427 t.Cleanup(cancel) - _, err = full.HeaderServ.GetByHeight(newCtx, 15) + _, err = full.HeaderServ.GetByHeight(newCtx, 25) require.ErrorIs(t, err, context.DeadlineExceeded) require.NoError(t, full.Stop(ctx)) @@ -73,3 +73,66 @@ func TestFraudProofBroadcasting(t *testing.T) { require.NoError(t, err) require.NotNil(t, proofs) } + +/* + Test-Case: Light node receives a fraud proof using Fraud Sync + Pre-Requisites: + - CoreClient is started by swamp. + Steps: + 1. Create a Bridge Node(BN) with broken extended header at height 10. + 2. Start a BN. + 3. Create a Full Node(FN) with a connection to BN as a trusted peer. + 4. Start a FN. + 5. Subscribe to a fraud proof and wait when it will be received. + 6. Start LN once a fraud proof is received and verified by FN. + 7. Wait until LN will be connected to FN and fetch a fraud proof. +*/ +func TestFraudProofSyncing(t *testing.T) { + sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) + cfg := node.DefaultConfig(node.Bridge) + cfg.P2P.Bootstrapper = true + const defaultTimeInterval = time.Second * 5 + bridge := sw.NewBridgeNode( + node.WithConfig(cfg), + node.WithRefreshRoutingTablePeriod(defaultTimeInterval), + node.WithDiscoveryInterval(defaultTimeInterval), + node.WithAdvertiseInterval(defaultTimeInterval), + node.WithHeaderConstructFn(header.FraudMaker(t, 10)), + ) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + + err := bridge.Start(ctx) + require.NoError(t, err) + addr := host.InfoFromHost(bridge.Host) + addrs, err := peer.AddrInfoToP2pAddrs(addr) + require.NoError(t, err) + + full := sw.NewFullNode( + node.WithTrustedPeers(addrs[0].String()), + node.WithRefreshRoutingTablePeriod(defaultTimeInterval), + node.WithAdvertiseInterval(defaultTimeInterval), + ) + + ln := sw.NewLightNode( + node.WithBootstrappers([]peer.AddrInfo{*addr}), + node.WithRefreshRoutingTablePeriod(defaultTimeInterval), + node.WithDiscoveryInterval(defaultTimeInterval), + ) + + require.NoError(t, full.Start(ctx)) + subsFn, err := full.FraudServ.Subscribe(fraud.BadEncoding) + require.NoError(t, err) + defer subsFn.Cancel() + _, err = subsFn.Proof(ctx) + require.NoError(t, err) + + require.NoError(t, ln.Start(ctx)) + // internal subscription for the fraud proof is done in order to ensure that light node + // receives the BEFP. + subsLn, err := ln.FraudServ.Subscribe(fraud.BadEncoding) + require.NoError(t, err) + _, err = subsLn.Proof(ctx) + require.NoError(t, err) + subsLn.Cancel() +} From c9ece1d4fd547b425466befdfc85eed791850d3c Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 8 Sep 2022 00:27:32 -0700 Subject: [PATCH 0049/1008] chore: resolving linter errors (#1061) --- fraud/testing.go | 2 +- node/node.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fraud/testing.go b/fraud/testing.go index 3eebbaca47..f65a366355 100644 --- a/fraud/testing.go +++ b/fraud/testing.go @@ -108,7 +108,7 @@ func (m *mockProof) Height() uint64 { } func (m *mockProof) Validate(*header.ExtendedHeader) error { - if m.Valid != true { + if !m.Valid { return errors.New("mockProof: proof is not valid") } return nil diff --git a/node/node.go b/node/node.go index ca7ed014c3..42b71edd53 100644 --- a/node/node.go +++ b/node/node.go @@ -37,6 +37,8 @@ var log = logging.Logger("node") // * Light // * Full type Node struct { + fx.In `ignore-unexported:"true"` + Type Type Network params.Network Bootstrappers params.Bootstrappers @@ -155,7 +157,7 @@ func newNode(opts ...fx.Option) (*Node, error) { node := new(Node) app := fx.New( fx.NopLogger, - fx.Extract(node), + fx.Populate(node), fx.Options(opts...), ) if err := app.Err(); err != nil { From cbf4f374f3240ac88ef18c410b6f2aade3a007b9 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 9 Sep 2022 11:47:52 +0200 Subject: [PATCH 0050/1008] params: Switch default network to `arabica` (#1052) --- params/bootstrap.go | 5 +++++ params/default.go | 2 +- params/genesis.go | 1 + params/network.go | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/params/bootstrap.go b/params/bootstrap.go index 6789387534..7473193109 100644 --- a/params/bootstrap.go +++ b/params/bootstrap.go @@ -29,6 +29,11 @@ func bootstrappersFor(net Network) ([]string, error) { // NOTE: Every time we add a new long-running network, its bootstrap peers have to be added here. var bootstrapList = map[Network][]string{ + Arabica: { + "/dns4/limani.celestia-devops.dev/tcp/2121/p2p/12D3KooWNpRWxpi1APzV6CnwHvdgNRuTUbMNvg4ta2i1fnqYXR7H", + "/dns4/marsellesa.celestia-devops.dev/tcp/2121/p2p/12D3KooWHr2wqFAsMXnPzpFsgxmePgXb8BqpkePebwUgLyZc95bd", + "/dns4/parainem.celestia-devops.dev/tcp/2121/p2p/12D3KooWHX8xpwg8qkP7kLKmKGtgZvmsopvgxc6Fwtu665QC7G8q", + }, Mamaki: { "/dns4/andromeda.celestia-devops.dev/tcp/2121/p2p/12D3KooWKvPXtV1yaQ6e3BRNUHa5Phh8daBwBi3KkGaSSkUPys6D", "/dns4/libra.celestia-devops.dev/tcp/2121/p2p/12D3KooWK5aDotDcLsabBmWDazehQLMsDkRyARm1k7f1zGAXqbt4", diff --git a/params/default.go b/params/default.go index edb8e89d38..68caa39e8f 100644 --- a/params/default.go +++ b/params/default.go @@ -12,7 +12,7 @@ const ( ) // defaultNetwork defines a default network for the Celestia Node. -var defaultNetwork = Mamaki +var defaultNetwork = Arabica // DefaultNetwork returns the network of the current build. func DefaultNetwork() Network { diff --git a/params/genesis.go b/params/genesis.go index f0255e50a3..6880c8dbed 100644 --- a/params/genesis.go +++ b/params/genesis.go @@ -20,6 +20,7 @@ func GenesisFor(net Network) (string, error) { // NOTE: Every time we add a new long-running network, its genesis hash has to be added here. var genesisList = map[Network]string{ + Arabica: "C364B5937805342009408F7D44DBBF43C02AE227F968CF14C5001613B18CE419", Mamaki: "41BBFD05779719E826C4D68C4CCBBC84B2B761EB52BC04CFDE0FF8603C9AA3CA", Private: "", } diff --git a/params/network.go b/params/network.go index a86b9a6bb8..21467c8ebd 100644 --- a/params/network.go +++ b/params/network.go @@ -8,6 +8,8 @@ import ( // NOTE: Every time we add a new long-running network, it has to be added here. const ( + // Arabica testnet. See: celestiaorg/networks. + Arabica Network = "arabica" // Mamaki testnet. See: celestiaorg/networks. Mamaki Network = "mamaki" // Private can be used to set up any private network, including local testing setups. @@ -34,6 +36,7 @@ func (n Network) Validate() error { // networksList is a strict list of all known long-standing networks. var networksList = map[Network]struct{}{ + Arabica: {}, Mamaki: {}, Private: {}, } From 8657edc37d3911d42de447d9d0c93c4676a148c9 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 9 Sep 2022 14:20:54 +0300 Subject: [PATCH 0051/1008] share: change logs level in discovery (#1065) --- service/share/backoff.go | 2 +- service/share/discovery.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/service/share/backoff.go b/service/share/backoff.go index 4b4b95c5af..6cdb04f8ac 100644 --- a/service/share/backoff.go +++ b/service/share/backoff.go @@ -48,7 +48,7 @@ func (b *backoffConnector) Connect(ctx context.Context, p peer.AddrInfo) error { cache := b.connectionData(p.ID) if time.Now().Before(cache.nexttry) { b.cacheLk.Unlock() - return fmt.Errorf("share/discovery: backoff period is not ended for peer=%s", p.ID.String()) + return fmt.Errorf("share/discovery: backoff period has not ended for peer=%s", p.ID.String()) } cache.nexttry = time.Now().Add(cache.backoff.Delay()) b.cacheLk.Unlock() diff --git a/service/share/discovery.go b/service/share/discovery.go index 074bb1d8f7..38e080721b 100644 --- a/service/share/discovery.go +++ b/service/share/discovery.go @@ -71,7 +71,7 @@ func (d *discovery) handlePeerFound(ctx context.Context, topic string, peer peer err = d.connector.Connect(ctx, peer) if err != nil { - log.Warn(err) + log.Debug(err) d.set.Remove(peer.ID) return } From faf5e51ab8efdffb3f3f7fbcf61f44b879413969 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 13 Sep 2022 00:06:58 -0700 Subject: [PATCH 0052/1008] feat(cmd): Provide ability to specify network to user (#1073) * feat(cmd): adding network flag, deprecating CELESTIA_PRIVATE_GENESIS, changing default store extension * fix(cmd): validating network passed with --node.network * feat(cmd): dynamic network list for command hint --- cmd/cel-key/node_types.go | 38 +++++++++++++++++++++----------------- cmd/cel-shed/header.go | 12 ++++++++---- cmd/celestia/bridge.go | 4 ++-- cmd/celestia/full.go | 4 ++-- cmd/celestia/light.go | 4 ++-- cmd/flags_node.go | 32 +++++++++++++++++++++++++++----- params/default.go | 16 +++++++++------- params/genesis.go | 1 - params/network.go | 14 +++++++++++++- 9 files changed, 84 insertions(+), 41 deletions(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index f167ca345f..70c3eed76b 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -2,25 +2,35 @@ package main import ( "fmt" + "strings" "github.com/spf13/cobra" flag "github.com/spf13/pflag" + "github.com/celestiaorg/celestia-node/params" + sdkflags "github.com/cosmos/cosmos-sdk/client/flags" ) var ( - nodeDirKey = "node.type" - - bridgeDir = "~/.celestia-bridge/keys" - fullDir = "~/.celestia-full/keys" - lightDir = "~/.celestia-light/keys" + nodeDirKey = "node.type" + nodeNetworkKey = "node.network" ) func DirectoryFlags() *flag.FlagSet { flags := &flag.FlagSet{} - flags.String(nodeDirKey, "", "Sets key utility to use the node type's directory (e.g. "+ - "~/.celestia-light if --node.type light is passed).") + defaultNetwork := string(params.DefaultNetwork()) + + flags.String( + nodeDirKey, + "", + "Sets key utility to use the node type's directory (e.g. "+ + "~/.celestia-light-"+strings.ToLower(defaultNetwork)+" if --node.type light is passed).") + flags.String( + nodeNetworkKey, + defaultNetwork, + "Sets key utility to use the node network's directory (e.g. "+ + "~/.celestia-light-mynetwork if --node.network MyNetwork is passed).") return flags } @@ -31,17 +41,11 @@ func ParseDirectoryFlags(cmd *cobra.Command) error { return nil } + network := cmd.Flag(nodeNetworkKey).Value.String() switch nodeType { - case "bridge": - if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, bridgeDir); err != nil { - return err - } - case "full": - if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, fullDir); err != nil { - return err - } - case "light": - if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, lightDir); err != nil { + case "bridge", "full", "light": + keyPath := fmt.Sprintf("~/.celestia-%s-%s", nodeType, strings.ToLower(network)) + if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, keyPath); err != nil { return err } default: diff --git a/cmd/cel-shed/header.go b/cmd/cel-shed/header.go index c1c9e7ec99..b2e09f30cd 100644 --- a/cmd/cel-shed/header.go +++ b/cmd/cel-shed/header.go @@ -21,12 +21,12 @@ var headerCmd = &cobra.Command{ } var headerStoreInit = &cobra.Command{ - Use: "store-init [node-type] [height]", + Use: "store-init [node-type] [network] [height]", Short: `Forcefully initialize header store head to be of the given height. Requires the node being stopped. Custom store path is not supported yet.`, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - if len(args) != 2 { + if len(args) != 3 { return fmt.Errorf("not enough arguments") } @@ -35,12 +35,16 @@ Custom store path is not supported yet.`, return fmt.Errorf("invalid node-type") } - height, err := strconv.Atoi(args[1]) + network := args[1] + + height, err := strconv.Atoi(args[2]) if err != nil { return fmt.Errorf("invalid height: %w", err) } - s, err := node.OpenStore(fmt.Sprintf("~/.celestia-%s", strings.ToLower(tp.String()))) + s, err := node.OpenStore( + fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(network)), + ) if err != nil { return err } diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index 04491866e4..e5d73e6d32 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -13,7 +13,7 @@ import ( func init() { bridgeCmd.AddCommand( cmdnode.Init( - cmdnode.NodeFlags(node.Bridge), + cmdnode.NodeFlags(), cmdnode.P2PFlags(), cmdnode.CoreFlags(), cmdnode.MiscFlags(), @@ -21,7 +21,7 @@ func init() { cmdnode.KeyFlags(), ), cmdnode.Start( - cmdnode.NodeFlags(node.Bridge), + cmdnode.NodeFlags(), cmdnode.P2PFlags(), cmdnode.CoreFlags(), cmdnode.MiscFlags(), diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index a30c761898..7cd7c7f3c7 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -14,7 +14,7 @@ import ( func init() { fullCmd.AddCommand( cmdnode.Init( - cmdnode.NodeFlags(node.Full), + cmdnode.NodeFlags(), cmdnode.P2PFlags(), cmdnode.HeadersFlags(), cmdnode.MiscFlags(), @@ -25,7 +25,7 @@ func init() { cmdnode.KeyFlags(), ), cmdnode.Start( - cmdnode.NodeFlags(node.Full), + cmdnode.NodeFlags(), cmdnode.P2PFlags(), cmdnode.HeadersFlags(), cmdnode.MiscFlags(), diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 72bbbfd269..3635a8bd0a 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -14,7 +14,7 @@ import ( func init() { lightCmd.AddCommand( cmdnode.Init( - cmdnode.NodeFlags(node.Light), + cmdnode.NodeFlags(), cmdnode.P2PFlags(), cmdnode.HeadersFlags(), cmdnode.MiscFlags(), @@ -25,7 +25,7 @@ func init() { cmdnode.KeyFlags(), ), cmdnode.Start( - cmdnode.NodeFlags(node.Light), + cmdnode.NodeFlags(), cmdnode.P2PFlags(), cmdnode.HeadersFlags(), cmdnode.MiscFlags(), diff --git a/cmd/flags_node.go b/cmd/flags_node.go index 9d3ad72dfa..f4a5aed8f4 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -9,20 +9,22 @@ import ( flag "github.com/spf13/pflag" "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/params" ) var ( - nodeStoreFlag = "node.store" - nodeConfigFlag = "node.config" + nodeStoreFlag = "node.store" + nodeConfigFlag = "node.config" + nodeNetworkFlag = "node.network" ) // NodeFlags gives a set of hardcoded Node package flags. -func NodeFlags(tp node.Type) *flag.FlagSet { +func NodeFlags() *flag.FlagSet { flags := &flag.FlagSet{} flags.String( nodeStoreFlag, - fmt.Sprintf("~/.celestia-%s", strings.ToLower(tp.String())), + "", "The path to root/home directory of your Celestia Node Store", ) flags.String( @@ -30,13 +32,33 @@ func NodeFlags(tp node.Type) *flag.FlagSet { "", "Path to a customized node config TOML file", ) + flags.String( + nodeNetworkFlag, + "", + "The name of the network to connect to, e.g. "+params.ListProvidedNetworks(), + ) return flags } // ParseNodeFlags parses Node flags from the given cmd and applies values to Env. func ParseNodeFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { - ctx = WithStorePath(ctx, cmd.Flag(nodeStoreFlag).Value.String()) + network := cmd.Flag(nodeNetworkFlag).Value.String() + if network != "" { + err := params.SetDefaultNetwork(params.Network(network)) + if err != nil { + return ctx, err + } + } else { + network = string(params.DefaultNetwork()) + } + + store := cmd.Flag(nodeStoreFlag).Value.String() + if store == "" { + tp := NodeType(ctx) + store = fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(network)) + } + ctx = WithStorePath(ctx, store) nodeConfig := cmd.Flag(nodeConfigFlag).Value.String() if nodeConfig != "" { diff --git a/params/default.go b/params/default.go index 68caa39e8f..0915bc7f67 100644 --- a/params/default.go +++ b/params/default.go @@ -7,8 +7,7 @@ import ( ) const ( - EnvCustomNetwork = "CELESTIA_CUSTOM" - EnvPrivateGenesis = "CELESTIA_PRIVATE_GENESIS" + EnvCustomNetwork = "CELESTIA_CUSTOM" ) // defaultNetwork defines a default network for the Celestia Node. @@ -19,6 +18,14 @@ func DefaultNetwork() Network { return defaultNetwork } +func SetDefaultNetwork(net Network) error { + if err := net.Validate(); err != nil { + return err + } + defaultNetwork = net + return nil +} + func init() { // check if custom network option set // format: CELESTIA_CUSTOM=:: @@ -52,9 +59,4 @@ func init() { bootstrapList[Network(netID)] = bs } } - // check if private network option set - if genesis, ok := os.LookupEnv(EnvPrivateGenesis); ok { - defaultNetwork = Private - genesisList[Private] = strings.ToUpper(genesis) - } } diff --git a/params/genesis.go b/params/genesis.go index 6880c8dbed..8c6c778662 100644 --- a/params/genesis.go +++ b/params/genesis.go @@ -4,7 +4,6 @@ import "fmt" // GenesisFor reports a hash of a genesis block for a given network. // Genesis is strictly defined and can't be modified. -// To run a custom genesis private network use CELESTIA_PRIVATE_GENESIS env var. func GenesisFor(net Network) (string, error) { if err := net.Validate(); err != nil { return "", err diff --git a/params/network.go b/params/network.go index 21467c8ebd..af8bdc9e16 100644 --- a/params/network.go +++ b/params/network.go @@ -13,7 +13,6 @@ const ( // Mamaki testnet. See: celestiaorg/networks. Mamaki Network = "mamaki" // Private can be used to set up any private network, including local testing setups. - // Use CELESTIA_PRIVATE_GENESIS env var to enable Private by specifying its genesis block hash. Private Network = "private" ) @@ -40,3 +39,16 @@ var networksList = map[Network]struct{}{ Mamaki: {}, Private: {}, } + +// ListProvidedNetworks provides a string listing all known long-standing networks for things like command hints. +func ListProvidedNetworks() string { + var networks string + for net := range networksList { + // "private" network isn't really a choosable option, so skip + if net != Private { + networks += string(net) + ", " + } + } + // chop off trailing ", " + return networks[:len(networks)-2] +} From 4618e2ba92a2b53c749598849e63fceb95661b6e Mon Sep 17 00:00:00 2001 From: nitin Date: Tue, 13 Sep 2022 15:31:07 +0530 Subject: [PATCH 0053/1008] cmd/cel-key: Return err if no node type provided (#1081) --- cmd/cel-key/node_types.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 70c3eed76b..5e499360b4 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "strings" @@ -38,7 +39,7 @@ func DirectoryFlags() *flag.FlagSet { func ParseDirectoryFlags(cmd *cobra.Command) error { nodeType := cmd.Flag(nodeDirKey).Value.String() if nodeType == "" { - return nil + return errors.New("no node type provided") } network := cmd.Flag(nodeNetworkKey).Value.String() From 00d80c423b2bfacec22a253ce6af3a534a1be3a7 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Tue, 13 Sep 2022 13:28:00 +0200 Subject: [PATCH 0054/1008] feat|refactor(header/sync): network head determination (#990) * feat(params|header/sync): dirty intergration of block time into Syncer Co-authored-by: Rene * feat(header): new utility funcs for the ExtendedHeader in preparation for Syncer improvements Co-authored-by: Rene * feat|refactor(header/sync): revision of Syncer's objective head deteremination logic The previous version of Syncer struggled with two issues: * On Syncer's start, synchronization didn't start and waited for a gossiped header trigger sync and set a sync target (only when the subjective head was not expired) * This is why Node could wait up to block time second to start syncing * There was no way to request the most recent objective header of the network * I.e. if the user wanted to request the latest possible state, it wasn't able to do that besides waiting for full sync to finish. The new reimplementation fixes these two problems and improves code readability and docs. Mainly, it splits the existing `trustedHead` into two methods `subjectiveHead` and `objectiveHead`. Where the latter now relies on the latest known header timestamp and block time to determine its recency. The new reimplementation fixes these two problems and improves code readability and docs. Mainly, it splits the existing trustedHead into two methods subjectiveHead and objectiveHead. Where the latter now relies on the latest known header timestamp and block time to determine its recency. If the header is not recent, we request it from the trusted peer(s), assuming it's always synced. * docs(header/sync): add TODO for potential optimization * refactor(header/sync): rework Syncer lifecycling Mainly, allow Start to error so that subsequent Stop does not panic. While also make lifecycle logic less confusing and less error-prone * fix(node): do not fail the Start for Syncer if it is not initialized, so that Node tests does not fail * fix(header/sync): use RWLock for sync ranges Going further, there wiil be multiple readers that should not block each other * fix(header/sync): ensure objective head is requested only once when many at any moment * chore(header/sync): cleanup syncing code and update the tests to use WaitSync * chore(header/sync): documentation, logging and code dispoisiton improvements * Terminology change from the 'objective head' to the 'network head' consistent over docs and logs * More logs for unhappy cases + more information for extisting logs * Extracttion of head retrieval logic into a separate file * Apply docs suggestions from @renaynay and @liamsi Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Ismail Khoffi * Update header/header.go Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Rene Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> Co-authored-by: Ismail Khoffi --- header/header.go | 10 +++ header/sync/ranges.go | 18 ++-- header/sync/sync.go | 173 +++++++++++---------------------------- header/sync/sync_head.go | 167 +++++++++++++++++++++++++++++++++++++ header/sync/sync_test.go | 74 +++++++++++++++-- header/verify.go | 5 ++ node/rpc_test.go | 4 +- node/services/service.go | 15 +++- params/network.go | 6 +- 9 files changed, 328 insertions(+), 144 deletions(-) create mode 100644 header/sync/sync_head.go diff --git a/header/header.go b/header/header.go index c0240caf29..6d0a7f1ec6 100644 --- a/header/header.go +++ b/header/header.go @@ -82,6 +82,16 @@ func (eh *ExtendedHeader) LastHeader() bts.HexBytes { return eh.RawHeader.LastBlockID.Hash } +// IsBefore returns whether the given header is of a higher height. +func (eh *ExtendedHeader) IsBefore(h *ExtendedHeader) bool { + return eh.Height < h.Height +} + +// Equals returns whether the hash and height of the given header match. +func (eh *ExtendedHeader) Equals(header *ExtendedHeader) bool { + return eh.Height == header.Height && bytes.Equal(eh.Hash(), header.Hash()) +} + // ValidateBasic performs *basic* validation to check for missed/incorrect fields. func (eh *ExtendedHeader) ValidateBasic() error { err := eh.RawHeader.ValidateBasic() diff --git a/header/sync/ranges.go b/header/sync/ranges.go index 7b5db2444a..98b0a4b6b4 100644 --- a/header/sync/ranges.go +++ b/header/sync/ranges.go @@ -9,14 +9,14 @@ import ( // ranges keeps non-overlapping and non-adjacent header ranges which are used to cache headers (in ascending order). // This prevents unnecessary / duplicate network requests for additional headers during sync. type ranges struct { + lk sync.RWMutex ranges []*headerRange - lk sync.Mutex // no need for RWMutex as there is only one reader } // Head returns the highest ExtendedHeader in all ranges if any. func (rs *ranges) Head() *header.ExtendedHeader { - rs.lk.Lock() - defer rs.lk.Unlock() + rs.lk.RLock() + defer rs.lk.RUnlock() ln := len(rs.ranges) if ln == 0 { @@ -97,9 +97,9 @@ func (rs *ranges) First() (*headerRange, bool) { } type headerRange struct { - start uint64 - lk sync.Mutex // no need for RWMutex as there is only one reader + lk sync.RWMutex headers []*header.ExtendedHeader + start uint64 } func newRange(h *header.ExtendedHeader) *headerRange { @@ -118,15 +118,15 @@ func (r *headerRange) Append(h ...*header.ExtendedHeader) { // Empty reports if range is empty. func (r *headerRange) Empty() bool { - r.lk.Lock() - defer r.lk.Unlock() + r.lk.RLock() + defer r.lk.RUnlock() return len(r.headers) == 0 } // Head reports the head of range if any. func (r *headerRange) Head() *header.ExtendedHeader { - r.lk.Lock() - defer r.lk.Unlock() + r.lk.RLock() + defer r.lk.RUnlock() ln := len(r.headers) if ln == 0 { return nil diff --git a/header/sync/sync.go b/header/sync/sync.go index 20a404ecae..3a809b54b8 100644 --- a/header/sync/sync.go +++ b/header/sync/sync.go @@ -7,8 +7,6 @@ import ( "time" logging "github.com/ipfs/go-log/v2" - - pubsub "github.com/libp2p/go-libp2p-pubsub" tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/celestia-node/header" @@ -17,56 +15,73 @@ import ( var log = logging.Logger("header/sync") // Syncer implements efficient synchronization for headers. +// +// Subjective header - the latest known header that is not expired (within trusting period) +// Network header - the latest header received from the network + // // There are two main processes running in Syncer: // 1. Main syncing loop(s.syncLoop) -// * Performs syncing from the subjective(local chain view) header up to the latest known trusted header +// * Performs syncing from the subjective header up to the network head // * Syncs by requesting missing headers from Exchange or -// * By accessing cache of pending and verified headers -// 2. Receives new headers from PubSub subnetwork (s.processIncoming) -// * Usually, a new header is adjacent to the trusted head and if so, it is simply appended to the local store, -// incrementing the subjective height and making it the new latest known trusted header. -// * Or, if it receives a header further in the future, -// * verifies against the latest known trusted header -// * adds the header to pending cache(making it the latest known trusted header) +// * By accessing cache of pending network headers received from PubSub +// 2. Receives new headers from PubSub subnetwork (s.incomingNetHead) +// * Once received, tries to append it to the store +// * Or, if not adjacent to head of the store, +// * verifies against the latest known subjective header +// * adds the header to pending cache, thereby making it the latest known subjective header // * and triggers syncing loop to catch up to that point. type Syncer struct { sub header.Subscriber exchange header.Exchange store header.Store + // blockTime provides a reference point for the Syncer to determine + // whether its subjective head is outdated + blockTime time.Duration + // stateLk protects state which represents the current or latest sync stateLk sync.RWMutex state State // signals to start syncing triggerSync chan struct{} - // pending keeps ranges of valid headers received from the network awaiting to be appended to store + // pending keeps ranges of valid new network headers awaiting to be appended to store pending ranges - // cancel cancels syncLoop's context + // netReqLk ensures only one network head is requested at any moment + netReqLk sync.RWMutex + + // controls lifecycle for syncLoop + ctx context.Context cancel context.CancelFunc } // NewSyncer creates a new instance of Syncer. -func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscriber) *Syncer { +func NewSyncer(exchange header.Exchange, store header.Store, sub header.Subscriber, blockTime time.Duration) *Syncer { return &Syncer{ sub: sub, exchange: exchange, store: store, + blockTime: blockTime, triggerSync: make(chan struct{}, 1), // should be buffered } } // Start starts the syncing routine. -func (s *Syncer) Start(context.Context) error { - err := s.sub.AddValidator(s.processIncoming) +func (s *Syncer) Start(ctx context.Context) error { + s.ctx, s.cancel = context.WithCancel(context.Background()) + // register validator for header subscriptions + // syncer does not subscribe itself and syncs headers together with validation + err := s.sub.AddValidator(s.incomingNetHead) if err != nil { return err } - - ctx, cancel := context.WithCancel(context.Background()) - go s.syncLoop(ctx) - s.wantSync() - s.cancel = cancel + // get the latest head and set it as syncing target + _, err = s.networkHead(ctx) + if err != nil { + return err + } + // start syncLoop only if Start is errorless + go s.syncLoop() return nil } @@ -119,35 +134,6 @@ func (s *Syncer) State() State { return state } -// trustedHead returns the latest known trusted header that is within the trusting period. -func (s *Syncer) trustedHead(ctx context.Context) (*header.ExtendedHeader, error) { - // check pending for trusted header and return it if applicable - // NOTE: Pending cannot be expired, guaranteed - pendHead := s.pending.Head() - if pendHead != nil { - return pendHead, nil - } - - sbj, err := s.store.Head(ctx) - if err != nil { - return nil, err - } - - // check if our subjective header is not expired and use it - if !sbj.IsExpired() { - return sbj, nil - } - - // otherwise, request head from a trustedPeer or, in other words, do automatic subjective initialization - objHead, err := s.exchange.Head(ctx) - if err != nil { - return nil, err - } - - s.pending.Add(objHead) - return objHead, nil -} - // wantSync will trigger the syncing loop (non-blocking). func (s *Syncer) wantSync() { select { @@ -157,100 +143,37 @@ func (s *Syncer) wantSync() { } // syncLoop controls syncing process. -func (s *Syncer) syncLoop(ctx context.Context) { +func (s *Syncer) syncLoop() { for { select { case <-s.triggerSync: - s.sync(ctx) - case <-ctx.Done(): + s.sync(s.ctx) + case <-s.ctx.Done(): return } } } -// sync ensures we are synced up to any trusted header. +// sync ensures we are synced from the Store's head up to the new subjective head func (s *Syncer) sync(ctx context.Context) { - trstHead, err := s.trustedHead(ctx) - if err != nil { - log.Errorw("getting trusted head", "err", err) + newHead := s.pending.Head() + if newHead == nil { return } - s.syncTo(ctx, trstHead) -} - -// processIncoming processes new processIncoming Headers, validates them and stores/caches if applicable. -func (s *Syncer) processIncoming(ctx context.Context, maybeHead *header.ExtendedHeader) pubsub.ValidationResult { - // 1. Try to append. If header is not adjacent/from future - try it for pending cache below - _, err := s.store.Append(ctx, maybeHead) - switch err { - case nil: - // a happy case where we append adjacent header correctly - return pubsub.ValidationAccept - case header.ErrNonAdjacent: - // not adjacent, so try to cache it after verifying - default: - var verErr *header.VerifyError - if errors.As(err, &verErr) { - return pubsub.ValidationReject - } - - log.Errorw("appending header", - "height", maybeHead.Height, - "hash", maybeHead.Hash().String(), - "err", err) - // might be a storage error or something else, but we can still try to continue processing 'maybeHead' - } - - // 2. Get known trusted head, so we can verify maybeHead - trstHead, err := s.trustedHead(ctx) - if err != nil { - log.Errorw("getting trusted head", "err", err) - return pubsub.ValidationIgnore // we don't know if header is invalid so ignore - } - - // 3. Filter out maybeHead if behind trusted - if maybeHead.Height <= trstHead.Height { - log.Warnw("received known header", - "height", maybeHead.Height, - "hash", maybeHead.Hash()) - return pubsub.ValidationIgnore // we don't know if header is invalid so ignore - } - - // 4. Verify maybeHead against trusted - err = trstHead.VerifyNonAdjacent(maybeHead) - var verErr *header.VerifyError - if errors.As(err, &verErr) { - log.Errorw("invalid header", - "height_of_invalid", maybeHead.Height, - "hash_of_invalid", maybeHead.Hash(), - "height_of_trusted", trstHead.Height, - "hash_of_trusted", trstHead.Hash(), - "reason", verErr.Reason) - return pubsub.ValidationReject - } - - // 5. Save verified header to pending cache - // NOTE: Pending cache can't be DOSed as we verify above each header against a trusted one. - s.pending.Add(maybeHead) - // and trigger sync to catch-up - s.wantSync() - log.Infow("pending head", - "height", maybeHead.Height, - "hash", maybeHead.Hash()) - return pubsub.ValidationAccept -} - -// syncTo requests headers from locally stored head up to the new head. -func (s *Syncer) syncTo(ctx context.Context, newHead *header.ExtendedHeader) { head, err := s.store.Head(ctx) if err != nil { log.Errorw("getting head during sync", "err", err) return } - if head.Height == newHead.Height { - return + if head.Height >= newHead.Height { + log.Warnw("sync attempt to an already synced header", + "synced_height", head.Height, + "attempted_height", newHead.Height, + ) + log.Warn("PLEASE REPORT THIS AS A BUG") + return // should never happen, but just in case } log.Infow("syncing headers", diff --git a/header/sync/sync_head.go b/header/sync/sync_head.go new file mode 100644 index 0000000000..fb5754f691 --- /dev/null +++ b/header/sync/sync_head.go @@ -0,0 +1,167 @@ +package sync + +import ( + "context" + "errors" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + + "github.com/celestiaorg/celestia-node/header" +) + +// subjectiveHead returns the latest known local header that is not expired(within trusting period). +// If the header is expired, it is retrieved from a trusted peer without validation; +// in other words, an automatic subjective initialization is performed. +func (s *Syncer) subjectiveHead(ctx context.Context) (*header.ExtendedHeader, error) { + // pending head is the latest known subjective head Syncer syncs to, so try to get it + // NOTES: + // * Empty when no sync is in progress + // * Pending cannot be expired, guaranteed + pendHead := s.pending.Head() + if pendHead != nil { + return pendHead, nil + } + // if empty, get subjective head out of the store + netHead, err := s.store.Head(ctx) + if err != nil { + return nil, err + } + // check if our subjective header is not expired and use it + if !netHead.IsExpired() { + return netHead, nil + } + log.Infow("subjective header expired", "height", netHead.Height) + // otherwise, request network head from a trusted peer + netHead, err = s.exchange.Head(ctx) + if err != nil { + return nil, err + } + // and set as the new subjective head without validation, + // or, in other words, do 'automatic subjective initialization' + s.newNetHead(ctx, netHead, true) + switch { + default: + log.Infow("subjective initialization finished", "height", netHead.Height) + return netHead, nil + case netHead.IsExpired(): + log.Warnw("subjective initialization with an expired header", "height", netHead.Height) + case !netHead.IsRecent(s.blockTime): + log.Warnw("subjective initialization with an old header", "height", netHead.Height) + } + log.Warn("trusted peer is out of sync") + return netHead, nil +} + +// networkHead returns the latest network header. +// Known subjective head is considered network head if it is recent enough(now-timestamp<=blocktime). +// Otherwise, network header is requested from a trusted peer and set as the new subjective head, +// assuming that trusted peer is always synced. +func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error) { + sbjHead, err := s.subjectiveHead(ctx) + if err != nil { + return nil, err + } + // if subjective header is recent enough (relative to the network's block time) - just use it + if sbjHead.IsRecent(s.blockTime) { + return sbjHead, nil + } + // otherwise, request head from a trusted peer, as we assume it is fully synced + // + // the lock construction here ensures only one routine requests at a time + // while others wait via Rlock + if !s.netReqLk.TryLock() { + s.netReqLk.RLock() + defer s.netReqLk.RUnlock() + return s.subjectiveHead(ctx) + } + defer s.netReqLk.Unlock() + // TODO(@Wondertan): Here is another potential networking optimization: + // * From sbjHead's timestamp and current time predict the time to the next header(TNH) + // * If now >= TNH && now <= TNH + (THP) header propagation time + // * Wait for header to arrive instead of requesting it + // * This way we don't request as we know the new network header arrives exactly + netHead, err := s.exchange.Head(ctx) + if err != nil { + return nil, err + } + // process netHead returned from the trusted peer and validate against the subjective head + // NOTE: We could trust the netHead like we do during 'automatic subjective initialization' + // but in this case our subjective head is not expired, so we should verify maybeHead + // and only if it is valid, set it as new head + s.newNetHead(ctx, netHead, false) + // maybeHead was either accepted or rejected as the new subjective + // anyway return most current known subjective head + return s.subjectiveHead(ctx) +} + +// incomingNetHead processes new gossiped network headers. +func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHeader) pubsub.ValidationResult { + // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network header + _, err := s.store.Append(ctx, netHead) + switch err { + case nil: + // a happy case where we appended maybe head directly, so accept + return pubsub.ValidationAccept + case header.ErrNonAdjacent: + // not adjacent, maybe we've missed a few headers or its from the past + default: + var verErr *header.VerifyError + if errors.As(err, &verErr) { + return pubsub.ValidationReject + } + // might be a storage error or something else, but we can still try to continue processing netHead + log.Errorw("appending network header", + "height", netHead.Height, + "hash", netHead.Hash().String(), + "err", err) + } + // try as new head + return s.newNetHead(ctx, netHead, false) +} + +// newNetHead sets the network header as the new subjective head with preceding validation(per request). +func (s *Syncer) newNetHead(ctx context.Context, netHead *header.ExtendedHeader, trust bool) pubsub.ValidationResult { + // validate netHead against subjective head + if !trust { + if res := s.validate(ctx, netHead); res != pubsub.ValidationAccept { + // netHead was either ignored or rejected + return res + } + } + // and if valid, set it as new subjective head + s.pending.Add(netHead) + s.wantSync() + log.Infow("new network head", "height", netHead.Height, "hash", netHead.Hash()) + return pubsub.ValidationAccept +} + +// validate checks validity of the given header against the subjective head. +func (s *Syncer) validate(ctx context.Context, new *header.ExtendedHeader) pubsub.ValidationResult { + sbjHead, err := s.subjectiveHead(ctx) + if err != nil { + log.Errorw("getting subjective head during validation", "err", err) + return pubsub.ValidationIgnore // local error, so ignore + } + // ignore header if it's from the past + if !sbjHead.IsBefore(new) { + log.Warnw("received known network header", + "current_height", sbjHead.Height, + "header_height", new.Height, + "header_hash", new.Hash()) + return pubsub.ValidationIgnore + } + // perform verification + err = sbjHead.VerifyNonAdjacent(new) + var verErr *header.VerifyError + if errors.As(err, &verErr) { + log.Errorw("invalid network header", + "height_of_invalid", new.Height, + "hash_of_invalid", new.Hash(), + "height_of_subjective", sbjHead.Height, + "hash_of_subjective", sbjHead.Hash(), + "reason", verErr.Reason) + return pubsub.ValidationReject + } + // and accept if the header is good + return pubsub.ValidationAccept +} diff --git a/header/sync/sync_test.go b/header/sync/sync_test.go index 12e160ef6d..4d65a576da 100644 --- a/header/sync/sync_test.go +++ b/header/sync/sync_test.go @@ -8,12 +8,15 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/bytes" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/local" "github.com/celestiaorg/celestia-node/header/store" ) +var blockTime = 30 * time.Second + func TestSyncSimpleRequestingHead(t *testing.T) { // this way we force local head of Syncer to expire, so it requests a new one from trusted peer header.TrustingPeriod = time.Microsecond @@ -33,11 +36,12 @@ func TestSyncSimpleRequestingHead(t *testing.T) { require.NoError(t, err) localStore := store.NewTestStore(ctx, t, head) - syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}) + syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, blockTime) err = syncer.Start(ctx) require.NoError(t, err) - _, err = localStore.GetByHeight(ctx, 100) + time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing + err = syncer.WaitSync(ctx) require.NoError(t, err) exp, err := remoteStore.Head(ctx) @@ -67,7 +71,7 @@ func TestSyncCatchUp(t *testing.T) { remoteStore := store.NewTestStore(ctx, t, head) localStore := store.NewTestStore(ctx, t, head) - syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}) + syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, blockTime) // 1. Initial sync err := syncer.Start(ctx) require.NoError(t, err) @@ -77,10 +81,11 @@ func TestSyncCatchUp(t *testing.T) { require.NoError(t, err) // 3. syncer rcvs header from the future and starts catching-up - res := syncer.processIncoming(ctx, suite.GenExtendedHeaders(1)[0]) + res := syncer.incomingNetHead(ctx, suite.GenExtendedHeaders(1)[0]) assert.Equal(t, pubsub.ValidationAccept, res) - _, err = localStore.GetByHeight(ctx, 102) + time.Sleep(time.Millisecond * 10) // needs some to realize it is syncing + err = syncer.WaitSync(ctx) require.NoError(t, err) exp, err := remoteStore.Head(ctx) @@ -111,7 +116,7 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { remoteStore := store.NewTestStore(ctx, t, head) localStore := store.NewTestStore(ctx, t, head) - syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}) + syncer := NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, blockTime) err := syncer.Start(ctx) require.NoError(t, err) @@ -153,3 +158,60 @@ func TestSyncPendingRangesWithMisses(t *testing.T) { assert.Equal(t, exp.Height, have.Height) assert.Empty(t, syncer.pending.Head()) // assert all cache from pending is used } + +// Test that only one objective header is requested at a time +func TestSyncer_OnlyOneRecentRequest(t *testing.T) { + blockTime := time.Nanosecond // so that we always request recent + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + t.Cleanup(cancel) + + suite := header.NewTestSuite(t, 3) + store := store.NewTestStore(ctx, t, suite.Head()) + newHead := suite.GenExtendedHeader() + exchange := &exchangeCountingHead{header: newHead} + syncer := NewSyncer(exchange, store, &header.DummySubscriber{}, blockTime) + + res := make(chan *header.ExtendedHeader) + for i := 0; i < 10; i++ { + go func() { + head, err := syncer.networkHead(ctx) + if err != nil { + panic(err) + } + select { + case res <- head: + case <-ctx.Done(): + return + } + }() + } + + for i := 0; i < 10; i++ { + head := <-res + assert.True(t, exchange.header.Equals(head)) + } + assert.Equal(t, 1, exchange.counter) +} + +type exchangeCountingHead struct { + header *header.ExtendedHeader + counter int +} + +func (e *exchangeCountingHead) Head(context.Context) (*header.ExtendedHeader, error) { + e.counter++ + time.Sleep(time.Millisecond * 100) // simulate requesting something + return e.header, nil +} + +func (e *exchangeCountingHead) Get(ctx context.Context, bytes bytes.HexBytes) (*header.ExtendedHeader, error) { + panic("implement me") +} + +func (e *exchangeCountingHead) GetByHeight(ctx context.Context, u uint64) (*header.ExtendedHeader, error) { + panic("implement me") +} + +func (e *exchangeCountingHead) GetRangeByHeight(c context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { + panic("implement me") +} diff --git a/header/verify.go b/header/verify.go index deb497645a..f4f5af877b 100644 --- a/header/verify.go +++ b/header/verify.go @@ -26,6 +26,11 @@ func (eh *ExtendedHeader) IsExpired() bool { return !expirationTime.After(time.Now()) } +// IsRecent checks if header is recent against the given blockTime. +func (eh *ExtendedHeader) IsRecent(blockTime time.Duration) bool { + return time.Since(eh.Time) <= blockTime // TODO @renaynay: should we allow for a 5-10 block drift here? +} + // VerifyNonAdjacent validates non-adjacent untrusted header against trusted 'eh'. func (eh *ExtendedHeader) VerifyNonAdjacent(untrst *ExtendedHeader) error { if err := eh.verify(untrst); err != nil { diff --git a/node/rpc_test.go b/node/rpc_test.go index 7a8b78fad1..adcf14dbed 100644 --- a/node/rpc_test.go +++ b/node/rpc_test.go @@ -15,6 +15,8 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/local" @@ -242,7 +244,7 @@ func setupHeaderService(ctx context.Context, t *testing.T) *service.Service { _, err := localStore.Append(ctx, suite.GenExtendedHeaders(5)...) require.NoError(t, err) // create syncer - syncer := sync.NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}) + syncer := sync.NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, params.BlockTime) return service.NewHeaderService(syncer, nil, nil, nil, localStore) } diff --git a/node/services/service.go b/node/services/service.go index e8274d78ac..5ecc0f22fa 100644 --- a/node/services/service.go +++ b/node/services/service.go @@ -34,11 +34,22 @@ func HeaderSyncer( sub header.Subscriber, fservice fraud.Service, ) (*sync.Syncer, error) { - syncer := sync.NewSyncer(ex, store, sub) + syncer := sync.NewSyncer(ex, store, sub, params.BlockTime) lifecycleCtx := fxutil.WithLifecycle(ctx, lc) lc.Append(fx.Hook{ OnStart: func(startCtx context.Context) error { - return FraudLifecycle(startCtx, lifecycleCtx, fraud.BadEncoding, fservice, syncer.Start, syncer.Stop) + start := func(ctx context.Context) error { + err := syncer.Start(ctx) + switch err { + default: + return err + case header.ErrNoHead: + log.Warnw("Syncer running on uninitialized Store - headers won't be synced") + case nil: + } + return nil + } + return FraudLifecycle(startCtx, lifecycleCtx, fraud.BadEncoding, fservice, start, syncer.Stop) }, OnStop: syncer.Stop, }) diff --git a/params/network.go b/params/network.go index af8bdc9e16..d0d8b2e8e4 100644 --- a/params/network.go +++ b/params/network.go @@ -2,6 +2,7 @@ package params import ( "errors" + "time" "github.com/libp2p/go-libp2p-core/peer" ) @@ -14,12 +15,15 @@ const ( Mamaki Network = "mamaki" // Private can be used to set up any private network, including local testing setups. Private Network = "private" + // BlockTime is a network block time. + // TODO @renaynay @Wondertan (#790) + BlockTime = time.Second * 30 ) // Network is a type definition for DA network run by Celestia Node. type Network string -// Bootstrappers is a type definition for nodes that will be used as bootstrappers +// Bootstrappers is a type definition for nodes that will be used as bootstrappers. type Bootstrappers []peer.AddrInfo // ErrInvalidNetwork is thrown when unknown network is used. From f83983ca58948482ced6cabd0f93b1d2ba10009a Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 13 Sep 2022 06:58:46 -0700 Subject: [PATCH 0055/1008] fix(cel-key): reading from correct directory (#1087) --- cmd/cel-key/node_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cel-key/node_types.go b/cmd/cel-key/node_types.go index 5e499360b4..bf85418cd4 100644 --- a/cmd/cel-key/node_types.go +++ b/cmd/cel-key/node_types.go @@ -45,7 +45,7 @@ func ParseDirectoryFlags(cmd *cobra.Command) error { network := cmd.Flag(nodeNetworkKey).Value.String() switch nodeType { case "bridge", "full", "light": - keyPath := fmt.Sprintf("~/.celestia-%s-%s", nodeType, strings.ToLower(network)) + keyPath := fmt.Sprintf("~/.celestia-%s-%s/keys", nodeType, strings.ToLower(network)) if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, keyPath); err != nil { return err } From 29554319af84f6da40fd13a84debca2567390de2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:51:30 +0200 Subject: [PATCH 0056/1008] refactor(service/state): Fix log to return correct height of balance request (#1089) --- service/state/core_access.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/service/state/core_access.go b/service/state/core_access.go index cb2d8b9e91..129b808a16 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -19,8 +19,9 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/x/payment" apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/nmt/namespace" + + "github.com/celestiaorg/celestia-node/header" ) var log = logging.Logger("state") @@ -163,7 +164,7 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*B value := result.Response.Value // if the value returned is empty, the account balance does not yet exist if len(value) == 0 { - log.Errorf("balance for account %s does not exist at block height %d", addr.String(), head.Height) + log.Errorf("balance for account %s does not exist at block height %d", addr.String(), head.Height-1) return &Balance{ Denom: app.BondDenom, Amount: sdktypes.NewInt(0), From c48c42b6a93876449ce15005e9e94ca4290d2d98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 11:14:35 +0000 Subject: [PATCH 0057/1008] chore(deps): bump github.com/multiformats/go-multiaddr Bumps [github.com/multiformats/go-multiaddr](https://github.com/multiformats/go-multiaddr) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/multiformats/go-multiaddr/releases) - [Commits](https://github.com/multiformats/go-multiaddr/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: github.com/multiformats/go-multiaddr dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 63cf39a265..d82e8b8877 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/libp2p/go-libp2p-routing-helpers v0.2.3 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 - github.com/multiformats/go-multiaddr v0.6.0 + github.com/multiformats/go-multiaddr v0.7.0 github.com/multiformats/go-multihash v0.2.0 github.com/raulk/go-watchdog v1.3.0 github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum index 145f32d154..385be7d327 100644 --- a/go.sum +++ b/go.sum @@ -1127,8 +1127,8 @@ github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9x github.com/multiformats/go-multiaddr v0.4.0/go.mod h1:YcpyLH8ZPudLxQlemYBPhSm0/oCXAT8Z4mzFpyoPyRc= github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= -github.com/multiformats/go-multiaddr v0.6.0 h1:qMnoOPj2s8xxPU5kZ57Cqdr0hHhARz7mFsPMIiYNqzg= -github.com/multiformats/go-multiaddr v0.6.0/go.mod h1:F4IpaKZuPP360tOMn2Tpyu0At8w23aRyVqeK0DbFeGM= +github.com/multiformats/go-multiaddr v0.7.0 h1:gskHcdaCyPtp9XskVwtvEeQOG465sCohbQIirSyqxrc= +github.com/multiformats/go-multiaddr v0.7.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= From 8f8abf07c79ccfcaf79e174bd11375a229791756 Mon Sep 17 00:00:00 2001 From: rachid Date: Thu, 15 Sep 2022 12:53:50 +0200 Subject: [PATCH 0058/1008] command typo 9009 => 9090 grpc port --- cmd/flags_core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/flags_core.go b/cmd/flags_core.go index 93dd7f8b98..f1a555ea26 100644 --- a/cmd/flags_core.go +++ b/cmd/flags_core.go @@ -26,7 +26,7 @@ func CoreFlags() *flag.FlagSet { coreFlag, "", "Indicates node to connect to the given core node. "+ - "Example: , 127.0.0.1. Assumes RPC port 26657 and gRPC port 9009 as default unless otherwise specified.", + "Example: , 127.0.0.1. Assumes RPC port 26657 and gRPC port 9090 as default unless otherwise specified.", ) flags.String( coreRPCFlag, From a6cb7357ec3afab00227f45323fd2259cf1ed9f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 19:29:51 +0000 Subject: [PATCH 0059/1008] chore(deps): bump go.opentelemetry.io/otel/sdk from 1.9.0 to 1.10.0 Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d82e8b8877..2a1b98681b 100644 --- a/go.mod +++ b/go.mod @@ -49,13 +49,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 github.com/tendermint/tendermint v0.35.4 - go.opentelemetry.io/otel v1.9.0 + go.opentelemetry.io/otel v1.10.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 go.opentelemetry.io/otel/metric v0.31.0 - go.opentelemetry.io/otel/sdk v1.9.0 + go.opentelemetry.io/otel/sdk v1.10.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 - go.opentelemetry.io/otel/trace v1.9.0 + go.opentelemetry.io/otel/trace v1.10.0 go.uber.org/fx v1.18.1 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f diff --git a/go.sum b/go.sum index 385be7d327..612dafe98a 100644 --- a/go.sum +++ b/go.sum @@ -1491,8 +1491,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw= -go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= +go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 h1:ggqApEjDKczicksfvZUCxuvoyDmR6Sbm56LwiK8DVR0= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= @@ -1505,12 +1505,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 h1:FAF9l8 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0/go.mod h1:smUdtylgc0YQiUr2PuifS4hBXhAS5xtR6WQhxP1wiNA= go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.9.0 h1:LNXp1vrr83fNXTHgU8eO89mhzxb/bbWAsHG6fNf3qWo= -go.opentelemetry.io/otel/sdk v1.9.0/go.mod h1:AEZc8nt5bd2F7BC24J5R0mrjYnpEgYHyTcM/vrSple4= +go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= +go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk= -go.opentelemetry.io/otel/trace v1.9.0 h1:oZaCNJUjWcg60VXWee8lJKlqhPbXAPB51URuR47pQYc= -go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= +go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= From ba2d4c727cb516629e61418b81eb38da45823ab8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Sep 2022 12:22:09 +0000 Subject: [PATCH 0060/1008] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2a1b98681b..9c0f3386f8 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/tendermint/tendermint v0.35.4 go.opentelemetry.io/otel v1.10.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 go.opentelemetry.io/otel/metric v0.31.0 go.opentelemetry.io/otel/sdk v1.10.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 @@ -253,10 +253,10 @@ require ( github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 // indirect - go.opentelemetry.io/proto/otlp v0.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.15.0 // indirect go.uber.org/multierr v1.8.0 // indirect diff --git a/go.sum b/go.sum index 612dafe98a..7216c31394 100644 --- a/go.sum +++ b/go.sum @@ -1493,16 +1493,16 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 h1:ggqApEjDKczicksfvZUCxuvoyDmR6Sbm56LwiK8DVR0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 h1:H0+xwv4shKw0gfj/ZqR13qO2N/dBQogB1OcRjJjV39Y= go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0/go.mod h1:nkenGD8vcvs0uN6WhR90ZVHQlgDsRmXicnNadMnk+XQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 h1:MuEG0gG27QZQrqhNl0f7vQ5Nl03OQfFeDAqWkGt+1zM= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0/go.mod h1:52qtPFDDaa0FaSyyzPnxWMehx2SZv0xuobTlNEZA2JA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 h1:NN90Cuna0CnBg8YNu1Q0V35i2E8LDByFOwHRCq/ZP9I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0/go.mod h1:0EsCXjZAiiZGnLdEUXM9YjCKuuLZMYyglh2QDXcYKVA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 h1:FAF9l8Wjxi9Ad2k/vLTfHZyzXYX72C62wBGpV3G6AIo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0/go.mod h1:smUdtylgc0YQiUr2PuifS4hBXhAS5xtR6WQhxP1wiNA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 h1:S8DedULB3gp93Rh+9Z+7NTEv+6Id/KYS7LDyipZ9iCE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0/go.mod h1:5WV40MLWwvWlGP7Xm8g3pMcg0pKOUY609qxJn8y7LmM= go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= @@ -1512,8 +1512,8 @@ go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNA go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= -go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= From 1803ca09de789bde7d211949b02f2b9df7f7a4b0 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 15 Sep 2022 06:33:34 -0700 Subject: [PATCH 0061/1008] feat(rpc): implementing staking reads and replacing sdk.Address (#1060) * feat: staking reads + linting errors * feat: rewriting to not take delAddr as a parameter * refactor(rpc): renaming local variables * refactor(rpc): using AccAddress and ValAddress instead of the generic Address type --- service/rpc/endpoints.go | 8 +++ service/rpc/state.go | 119 ++++++++++++++++++++++++++++++++--- service/state/core_access.go | 97 +++++++++++++++++----------- service/state/interface.go | 30 ++++++--- service/state/service.go | 36 +++++++++-- service/state/state.go | 7 ++- 6 files changed, 235 insertions(+), 62 deletions(-) diff --git a/service/rpc/endpoints.go b/service/rpc/endpoints.go index 1d5fea4f2a..657b926e15 100644 --- a/service/rpc/endpoints.go +++ b/service/rpc/endpoints.go @@ -18,6 +18,14 @@ func (h *Handler) RegisterEndpoints(rpc *Server) { rpc.RegisterHandlerFunc(cancelUnbondingEndpoint, h.handleCancelUnbonding, http.MethodPost) rpc.RegisterHandlerFunc(beginRedelegationEndpoint, h.handleRedelegation, http.MethodPost) + // staking queries + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryDelegationEndpoint, addrKey), h.handleQueryDelegation, + http.MethodGet) + rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}", queryUnbondingEndpoint, addrKey), h.handleQueryUnbonding, + http.MethodGet) + rpc.RegisterHandlerFunc(queryRedelegationsEndpoint, h.handleQueryRedelegations, + http.MethodPost) + // share endpoints rpc.RegisterHandlerFunc(fmt.Sprintf("%s/{%s}/height/{%s}", namespacedSharesEndpoint, nIDKey, heightKey), h.handleSharesByNamespaceRequest, http.MethodGet) diff --git a/service/rpc/state.go b/service/rpc/state.go index ef45d353a8..392c42fc48 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -13,14 +13,17 @@ import ( ) const ( - balanceEndpoint = "/balance" - submitTxEndpoint = "/submit_tx" - submitPFDEndpoint = "/submit_pfd" - transferEndpoint = "/transfer" - delegationEndpoint = "/delegate" - undelegationEndpoint = "/begin_unbonding" - cancelUnbondingEndpoint = "/cancel_unbond" - beginRedelegationEndpoint = "/begin_redelegate" + balanceEndpoint = "/balance" + submitTxEndpoint = "/submit_tx" + submitPFDEndpoint = "/submit_pfd" + transferEndpoint = "/transfer" + delegationEndpoint = "/delegate" + undelegationEndpoint = "/begin_unbonding" + cancelUnbondingEndpoint = "/cancel_unbond" + beginRedelegationEndpoint = "/begin_redelegate" + queryDelegationEndpoint = "/query_delegation" + queryUnbondingEndpoint = "/query_unbonding" + queryRedelegationsEndpoint = "/query_redelegations" ) var addrKey = "address" @@ -75,6 +78,11 @@ type cancelUnbondRequest struct { GasLimit uint64 `json:"gas_limit"` } +type queryRedelegationsRequest struct { + From string `json:"from"` + To string `json:"to"` +} + func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { var ( bal *state.Balance @@ -348,3 +356,98 @@ func (h *Handler) handleRedelegation(w http.ResponseWriter, r *http.Request) { log.Errorw("writing response", "endpoint", beginRedelegationEndpoint, "err", err) } } + +func (h *Handler) handleQueryDelegation(w http.ResponseWriter, r *http.Request) { + // read and parse request + vars := mux.Vars(r) + addrStr, exists := vars[addrKey] + if !exists { + writeError(w, http.StatusBadRequest, queryDelegationEndpoint, errors.New("address not specified")) + return + } + + // convert address to Address type + addr, err := types.ValAddressFromBech32(addrStr) + if err != nil { + writeError(w, http.StatusBadRequest, queryDelegationEndpoint, err) + return + } + delegation, err := h.state.QueryDelegation(r.Context(), addr) + if err != nil { + writeError(w, http.StatusInternalServerError, queryDelegationEndpoint, err) + return + } + resp, err := json.Marshal(delegation) + if err != nil { + writeError(w, http.StatusInternalServerError, queryDelegationEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", queryDelegationEndpoint, "err", err) + } +} + +func (h *Handler) handleQueryUnbonding(w http.ResponseWriter, r *http.Request) { + // read and parse request + vars := mux.Vars(r) + addrStr, exists := vars[addrKey] + if !exists { + writeError(w, http.StatusBadRequest, queryUnbondingEndpoint, errors.New("address not specified")) + return + } + + // convert address to Address type + addr, err := types.ValAddressFromBech32(addrStr) + if err != nil { + writeError(w, http.StatusBadRequest, queryUnbondingEndpoint, err) + return + } + unbonding, err := h.state.QueryUnbonding(r.Context(), addr) + if err != nil { + writeError(w, http.StatusInternalServerError, queryUnbondingEndpoint, err) + return + } + resp, err := json.Marshal(unbonding) + if err != nil { + writeError(w, http.StatusInternalServerError, queryUnbondingEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", queryUnbondingEndpoint, "err", err) + } +} + +func (h *Handler) handleQueryRedelegations(w http.ResponseWriter, r *http.Request) { + var req queryRedelegationsRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + writeError(w, http.StatusBadRequest, queryRedelegationsEndpoint, err) + return + } + srcValAddr, err := types.ValAddressFromBech32(req.From) + if err != nil { + writeError(w, http.StatusBadRequest, queryRedelegationsEndpoint, err) + return + } + dstValAddr, err := types.ValAddressFromBech32(req.To) + if err != nil { + writeError(w, http.StatusBadRequest, queryRedelegationsEndpoint, err) + return + } + unbonding, err := h.state.QueryRedelegations(r.Context(), srcValAddr, dstValAddr) + if err != nil { + writeError(w, http.StatusInternalServerError, queryRedelegationsEndpoint, err) + return + } + resp, err := json.Marshal(unbonding) + if err != nil { + writeError(w, http.StatusInternalServerError, queryRedelegationsEndpoint, err) + return + } + _, err = w.Write(resp) + if err != nil { + log.Errorw("writing response", "endpoint", queryRedelegationsEndpoint, "err", err) + } +} diff --git a/service/state/core_access.go b/service/state/core_access.go index 129b808a16..006c943e3c 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -32,8 +32,9 @@ type CoreAccessor struct { signer *apptypes.KeyringSigner getter header.Getter - queryCli banktypes.QueryClient - rpcCli rpcclient.ABCIClient + queryCli banktypes.QueryClient + stakingCli stakingtypes.QueryClient + rpcCli rpcclient.ABCIClient coreConn *grpc.ClientConn coreIP string @@ -74,6 +75,9 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { // create the query client queryCli := banktypes.NewQueryClient(ca.coreConn) ca.queryCli = queryCli + // create the staking query client + stakingCli := stakingtypes.NewQueryClient(ca.coreConn) + ca.stakingCli = stakingCli // create ABCI query client cli, err := http.New(fmt.Sprintf("http://%s:%s", ca.coreIP, ca.rpcPort), "/websocket") if err != nil { @@ -132,7 +136,7 @@ func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { return ca.BalanceForAddress(ctx, addr) } -func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { +func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr AccAddress) (*Balance, error) { head, err := ca.getter.Head(ctx) if err != nil { return nil, err @@ -215,20 +219,16 @@ func (ca *CoreAccessor) SubmitTxWithBroadcastMode( func (ca *CoreAccessor) Transfer( ctx context.Context, - addr Address, + addr AccAddress, amount Int, gasLim uint64, ) (*TxResponse, error) { - to, ok := addr.(sdktypes.AccAddress) - if !ok { - return nil, fmt.Errorf("state: unsupported address type") - } from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err } coins := sdktypes.NewCoins(sdktypes.NewCoin(app.BondDenom, amount)) - msg := banktypes.NewMsgSend(from, to, coins) + msg := banktypes.NewMsgSend(from, addr, coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err @@ -238,21 +238,17 @@ func (ca *CoreAccessor) Transfer( func (ca *CoreAccessor) CancelUnbondingDelegation( ctx context.Context, - valAddr Address, + valAddr ValAddress, amount, height Int, gasLim uint64, ) (*TxResponse, error) { - validator, ok := valAddr.(sdktypes.ValAddress) - if !ok { - return nil, fmt.Errorf("state: unsupported address type") - } from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err } coins := sdktypes.NewCoin(app.BondDenom, amount) - msg := stakingtypes.NewMsgCancelUnbondingDelegation(from, validator, height.Int64(), coins) + msg := stakingtypes.NewMsgCancelUnbondingDelegation(from, valAddr, height.Int64(), coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err @@ -263,24 +259,16 @@ func (ca *CoreAccessor) CancelUnbondingDelegation( func (ca *CoreAccessor) BeginRedelegate( ctx context.Context, srcValAddr, - dstValAddr Address, + dstValAddr ValAddress, amount Int, gasLim uint64, ) (*TxResponse, error) { - srcValidator, ok := srcValAddr.(sdktypes.ValAddress) - if !ok { - return nil, fmt.Errorf("state: unsupported address type") - } - dstValidator, ok := dstValAddr.(sdktypes.ValAddress) - if !ok { - return nil, fmt.Errorf("state: unsupported address type") - } from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err } coins := sdktypes.NewCoin(app.BondDenom, amount) - msg := stakingtypes.NewMsgBeginRedelegate(from, srcValidator, dstValidator, coins) + msg := stakingtypes.NewMsgBeginRedelegate(from, srcValAddr, dstValAddr, coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err @@ -290,20 +278,16 @@ func (ca *CoreAccessor) BeginRedelegate( func (ca *CoreAccessor) Undelegate( ctx context.Context, - delAddr Address, + delAddr ValAddress, amount Int, gasLim uint64, ) (*TxResponse, error) { - delegate, ok := delAddr.(sdktypes.ValAddress) - if !ok { - return nil, fmt.Errorf("state: unsupported address type") - } from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err } coins := sdktypes.NewCoin(app.BondDenom, amount) - msg := stakingtypes.NewMsgUndelegate(from, delegate, coins) + msg := stakingtypes.NewMsgUndelegate(from, delAddr, coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err @@ -313,23 +297,62 @@ func (ca *CoreAccessor) Undelegate( func (ca *CoreAccessor) Delegate( ctx context.Context, - delAddr Address, + delAddr ValAddress, amount Int, gasLim uint64, ) (*TxResponse, error) { - delegate, ok := delAddr.(sdktypes.ValAddress) - if !ok { - return nil, fmt.Errorf("state: unsupported address type") - } from, err := ca.signer.GetSignerInfo().GetAddress() if err != nil { return nil, err } coins := sdktypes.NewCoin(app.BondDenom, amount) - msg := stakingtypes.NewMsgDelegate(from, delegate, coins) + msg := stakingtypes.NewMsgDelegate(from, delAddr, coins) signedTx, err := ca.constructSignedTx(ctx, msg, apptypes.SetGasLimit(gasLim)) if err != nil { return nil, err } return ca.SubmitTx(ctx, signedTx) } + +func (ca *CoreAccessor) QueryDelegation( + ctx context.Context, + valAddr ValAddress, +) (*stakingtypes.QueryDelegationResponse, error) { + delAddr, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + return ca.stakingCli.Delegation(ctx, &stakingtypes.QueryDelegationRequest{ + DelegatorAddr: delAddr.String(), + ValidatorAddr: valAddr.String(), + }) +} + +func (ca *CoreAccessor) QueryUnbonding( + ctx context.Context, + valAddr ValAddress, +) (*stakingtypes.QueryUnbondingDelegationResponse, error) { + delAddr, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + return ca.stakingCli.UnbondingDelegation(ctx, &stakingtypes.QueryUnbondingDelegationRequest{ + DelegatorAddr: delAddr.String(), + ValidatorAddr: valAddr.String(), + }) +} +func (ca *CoreAccessor) QueryRedelegations( + ctx context.Context, + srcValAddr, + dstValAddr ValAddress, +) (*stakingtypes.QueryRedelegationsResponse, error) { + delAddr, err := ca.signer.GetSignerInfo().GetAddress() + if err != nil { + return nil, err + } + return ca.stakingCli.Redelegations(ctx, &stakingtypes.QueryRedelegationsRequest{ + DelegatorAddr: delAddr.String(), + SrcValidatorAddr: srcValAddr.String(), + DstValidatorAddr: dstValAddr.String(), + }) +} diff --git a/service/state/interface.go b/service/state/interface.go index 9bae4c9fd0..40427c017c 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -1,11 +1,10 @@ package state import ( - "cosmossdk.io/math" - "context" - "github.com/cosmos/cosmos-sdk/types" + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/celestiaorg/nmt/namespace" ) @@ -28,10 +27,10 @@ type Accessor interface { // NOTE: the balance returned is the balance reported by the block right before // the node's current head (head-1). This is due to the fact that for block N, the block's // `AppHash` is the result of applying the previous block's transaction list. - BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) + BalanceForAddress(ctx context.Context, addr AccAddress) (*Balance, error) // Transfer sends the given amount of coins from default wallet of the node to the given account address. - Transfer(ctx context.Context, to types.Address, amount math.Int, gasLimit uint64) (*TxResponse, error) + Transfer(ctx context.Context, to AccAddress, amount math.Int, gasLimit uint64) (*TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in // a block. @@ -40,11 +39,24 @@ type Accessor interface { SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*TxResponse, error) // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. - CancelUnbondingDelegation(ctx context.Context, valAddr Address, amount, height Int, gasLim uint64) (*TxResponse, error) + CancelUnbondingDelegation( + ctx context.Context, + valAddr ValAddress, + amount, + height Int, + gasLim uint64, + ) (*TxResponse, error) // BeginRedelegate sends a user's delegated tokens to a new validator for redelegation. - BeginRedelegate(ctx context.Context, srcValAddr, dstValAddr Address, amount Int, gasLim uint64) (*TxResponse, error) + BeginRedelegate(ctx context.Context, srcValAddr, dstValAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) // Undelegate undelegates a user's delegated tokens, unbonding them from the current validator. - Undelegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) + Undelegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) // Delegate sends a user's liquid tokens to a validator for delegation. - Delegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) + Delegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) + + // QueryDelegation retrieves the delegation information between a delegator and a validator. + QueryDelegation(ctx context.Context, valAddr ValAddress) (*types.QueryDelegationResponse, error) + // QueryUnbonding retrieves the unbonding status between a delegator and a validator. + QueryUnbonding(ctx context.Context, valAddr ValAddress) (*types.QueryUnbondingDelegationResponse, error) + // QueryRedelegations retrieves the status of the redelegations between a delegator and a validator. + QueryRedelegations(ctx context.Context, srcValAddr, dstValAddr ValAddress) (*types.QueryRedelegationsResponse, error) } diff --git a/service/state/service.go b/service/state/service.go index e4307641f2..ca945afe5a 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -3,6 +3,8 @@ package state import ( "context" + "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/nmt/namespace" ) @@ -39,7 +41,7 @@ func (s *Service) Balance(ctx context.Context) (*Balance, error) { return s.accessor.Balance(ctx) } -func (s *Service) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { +func (s *Service) BalanceForAddress(ctx context.Context, addr AccAddress) (*Balance, error) { return s.accessor.BalanceForAddress(ctx, addr) } @@ -47,13 +49,13 @@ func (s *Service) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) { return s.accessor.SubmitTx(ctx, tx) } -func (s *Service) Transfer(ctx context.Context, to Address, amount Int, gasLimit uint64) (*TxResponse, error) { +func (s *Service) Transfer(ctx context.Context, to AccAddress, amount Int, gasLimit uint64) (*TxResponse, error) { return s.accessor.Transfer(ctx, to, amount, gasLimit) } func (s *Service) CancelUnbondingDelegation( ctx context.Context, - valAddr Address, + valAddr ValAddress, amount, height Int, gasLim uint64, @@ -64,21 +66,43 @@ func (s *Service) CancelUnbondingDelegation( func (s *Service) BeginRedelegate( ctx context.Context, srcValAddr, - dstValAddr Address, + dstValAddr ValAddress, amount Int, gasLim uint64, ) (*TxResponse, error) { return s.accessor.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, gasLim) } -func (s *Service) Undelegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) { +func (s *Service) Undelegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) { return s.accessor.Undelegate(ctx, delAddr, amount, gasLim) } -func (s *Service) Delegate(ctx context.Context, delAddr Address, amount Int, gasLim uint64) (*TxResponse, error) { +func (s *Service) Delegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) { return s.accessor.Delegate(ctx, delAddr, amount, gasLim) } +func (s *Service) QueryDelegation( + ctx context.Context, + valAddr ValAddress, +) (*types.QueryDelegationResponse, error) { + return s.accessor.QueryDelegation(ctx, valAddr) +} + +func (s *Service) QueryUnbonding( + ctx context.Context, + valAddr ValAddress, +) (*types.QueryUnbondingDelegationResponse, error) { + return s.accessor.QueryUnbonding(ctx, valAddr) +} + +func (s *Service) QueryRedelegations( + ctx context.Context, + srcValAddr, + dstValAddr ValAddress, +) (*types.QueryRedelegationsResponse, error) { + return s.accessor.QueryRedelegations(ctx, srcValAddr, dstValAddr) +} + func (s *Service) Start(context.Context) error { s.ctx, s.cancel = context.WithCancel(context.Background()) return nil diff --git a/service/state/state.go b/service/state/state.go index a7bab14bff..a4720b6987 100644 --- a/service/state/state.go +++ b/service/state/state.go @@ -15,8 +15,11 @@ type Tx = coretypes.Tx // TxResponse is an alias to the TxResponse type from Cosmos-SDK. type TxResponse = sdk.TxResponse -// Address is an alias to the Address type from Cosmos-SDK. -type Address = sdk.Address +// ValAddress is an alias to the ValAddress type from Cosmos-SDK. +type ValAddress = sdk.ValAddress + +// AccAddress is an alias to the AccAddress type from Cosmos-SDK. +type AccAddress = sdk.AccAddress // Int is an alias to the Int type from Cosmos-SDK. type Int = math.Int From b37a8446a187b36cc07fc55db3dc650a628a0538 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 19 Sep 2022 13:03:42 +0200 Subject: [PATCH 0062/1008] refactor(service/state): `CoreAccessor` relies on `header.Head` instead of `header.Getter` (#1088) * refactor(service/state): Remove unused header.Getter from state service and lint core_access.go imports * refactor(header): Extract Head method from Getter into its own interface `Head` * feat(header/sync): Syncer implements header.Head * doc(header/sync): document Head method better * refactor(service/state): CoreAccessor takes header.Head instead of header.Getter --- header/interface.go | 11 +++++++++-- header/sync/sync_head.go | 6 ++++++ node/state/core.go | 9 +++++---- node/state/state.go | 4 +--- service/state/core_access.go | 4 ++-- service/state/service.go | 6 +----- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/header/interface.go b/header/interface.go index b8debed8e0..8b10aaf4f8 100644 --- a/header/interface.go +++ b/header/interface.go @@ -104,8 +104,7 @@ type Store interface { // Getter contains the behavior necessary for a component to retrieve // headers that have been processed during header sync. type Getter interface { - // Head returns the ExtendedHeader of the chain head. - Head(context.Context) (*ExtendedHeader, error) + Head // Get returns the ExtendedHeader corresponding to the given hash. Get(context.Context, tmbytes.HexBytes) (*ExtendedHeader, error) @@ -116,3 +115,11 @@ type Getter interface { // GetRangeByHeight returns the given range [from:to) of ExtendedHeaders. GetRangeByHeight(ctx context.Context, from, to uint64) ([]*ExtendedHeader, error) } + +// Head contains the behavior necessary for a component to retrieve +// the chain head. Note that "chain head" is subjective to the component +// reporting it. +type Head interface { + // Head returns the latest known header. + Head(context.Context) (*ExtendedHeader, error) +} diff --git a/header/sync/sync_head.go b/header/sync/sync_head.go index fb5754f691..2bed57b18d 100644 --- a/header/sync/sync_head.go +++ b/header/sync/sync_head.go @@ -9,6 +9,12 @@ import ( "github.com/celestiaorg/celestia-node/header" ) +// Head returns the Syncer's latest known header. It calls 'networkHead' in order to +// either return or eagerly fetch the most recent header. +func (s *Syncer) Head(ctx context.Context) (*header.ExtendedHeader, error) { + return s.networkHead(ctx) +} + // subjectiveHead returns the latest known local header that is not expired(within trusting period). // If the header is expired, it is retrieved from a trusted peer without validation; // in other words, an automatic subjective initialization is performed. diff --git a/node/state/core.go b/node/state/core.go index 804c7f07d2..6a9d645c1a 100644 --- a/node/state/core.go +++ b/node/state/core.go @@ -4,7 +4,8 @@ import ( "go.uber.org/fx" apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - "github.com/celestiaorg/celestia-node/header" + + "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/service/state" ) @@ -14,9 +15,9 @@ func CoreAccessor( coreIP, coreRPC, coreGRPC string, -) func(fx.Lifecycle, *apptypes.KeyringSigner, header.Store) (state.Accessor, error) { - return func(lc fx.Lifecycle, signer *apptypes.KeyringSigner, getter header.Store) (state.Accessor, error) { - ca := state.NewCoreAccessor(signer, getter, coreIP, coreRPC, coreGRPC) +) func(fx.Lifecycle, *apptypes.KeyringSigner, *sync.Syncer) (state.Accessor, error) { + return func(lc fx.Lifecycle, signer *apptypes.KeyringSigner, syncer *sync.Syncer) (state.Accessor, error) { + ca := state.NewCoreAccessor(signer, syncer, coreIP, coreRPC, coreGRPC) lc.Append(fx.Hook{ OnStart: ca.Start, OnStop: ca.Stop, diff --git a/node/state/state.go b/node/state/state.go index a3ac29f23b..b57ead9c7b 100644 --- a/node/state/state.go +++ b/node/state/state.go @@ -7,7 +7,6 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/node/core" "github.com/celestiaorg/celestia-node/node/key" @@ -32,10 +31,9 @@ func Service( ctx context.Context, lc fx.Lifecycle, accessor state.Accessor, - store header.Store, fservice fraud.Service, ) *state.Service { - serv := state.NewService(accessor, store) + serv := state.NewService(accessor) lifecycleCtx := fxutil.WithLifecycle(ctx, lc) lc.Append(fx.Hook{ OnStart: func(startCtx context.Context) error { diff --git a/service/state/core_access.go b/service/state/core_access.go index 006c943e3c..af40140bd6 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -30,7 +30,7 @@ var log = logging.Logger("state") // with a celestia-core node. type CoreAccessor struct { signer *apptypes.KeyringSigner - getter header.Getter + getter header.Head queryCli banktypes.QueryClient stakingCli stakingtypes.QueryClient @@ -47,7 +47,7 @@ type CoreAccessor struct { // connection. func NewCoreAccessor( signer *apptypes.KeyringSigner, - getter header.Getter, + getter header.Head, coreIP, rpcPort string, grpcPort string, diff --git a/service/state/service.go b/service/state/service.go index ca945afe5a..9100d2ec9b 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -5,7 +5,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/nmt/namespace" ) @@ -16,15 +15,12 @@ type Service struct { cancel context.CancelFunc accessor Accessor - - getter header.Getter } // NewService constructs a new state Service. -func NewService(accessor Accessor, getter header.Getter) *Service { +func NewService(accessor Accessor) *Service { return &Service{ accessor: accessor, - getter: getter, } } From 658139da12dae71fdc5c58b7308ca9338906632b Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 5 Aug 2022 20:58:46 +0300 Subject: [PATCH 0063/1008] das: Daser parallelization - add pool of workers - resume DAS'er and workers upon restart from last checkpoint - save checkpoint periodically - prioritise headers of newly produced blocks over old ones --- das/checkpoint.go | 50 ++++ das/checkpoint_store.go | 47 ---- das/checkpoint_store_test.go | 25 -- das/checkpoint_test.go | 40 +++ das/coordinator.go | 131 +++++++++ das/coordinator_test.go | 524 +++++++++++++++++++++++++++++++++++ das/daser.go | 334 ++++++---------------- das/daser_test.go | 330 ++++------------------ das/doc.go | 15 +- das/done.go | 31 +++ das/state.go | 249 ++++++++++++++--- das/state_test.go | 85 ++++++ das/stats.go | 32 +++ das/store.go | 101 +++++++ das/subscriber.go | 37 +++ das/worker.go | 99 +++++++ go.mod | 2 +- node/rpc_test.go | 4 +- service/rpc/das.go | 20 +- 19 files changed, 1503 insertions(+), 653 deletions(-) create mode 100644 das/checkpoint.go delete mode 100644 das/checkpoint_store.go delete mode 100644 das/checkpoint_store_test.go create mode 100644 das/checkpoint_test.go create mode 100644 das/coordinator.go create mode 100644 das/coordinator_test.go create mode 100644 das/done.go create mode 100644 das/state_test.go create mode 100644 das/stats.go create mode 100644 das/store.go create mode 100644 das/subscriber.go create mode 100644 das/worker.go diff --git a/das/checkpoint.go b/das/checkpoint.go new file mode 100644 index 0000000000..860c72cb35 --- /dev/null +++ b/das/checkpoint.go @@ -0,0 +1,50 @@ +package das + +import ( + "fmt" +) + +type checkpoint struct { + SampleFrom uint64 `json:"sample_from"` + NetworkHead uint64 `json:"network_head"` + // Failed will be prioritized on restart + Failed map[uint64]int `json:"failed,omitempty"` + // Workers will resume on restart from previous state + Workers []workerCheckpoint `json:"workers,omitempty"` +} + +// workerCheckpoint will be used to resume worker on restart +type workerCheckpoint struct { + From uint64 `json:"from"` + To uint64 `json:"to"` +} + +func newCheckpoint(stats SamplingStats) checkpoint { + workers := make([]workerCheckpoint, 0, len(stats.Workers)) + for _, w := range stats.Workers { + workers = append(workers, workerCheckpoint{ + From: w.Curr, + To: w.To, + }) + } + return checkpoint{ + SampleFrom: stats.CatchupHead + 1, + NetworkHead: stats.NetworkHead, + Failed: stats.Failed, + Workers: workers, + } +} + +func (c checkpoint) String() string { + str := fmt.Sprintf("SampleFrom: %v, NetworkHead: %v", c.SampleFrom, c.NetworkHead) + + if len(c.Workers) > 0 { + str += fmt.Sprintf(", Workers: %v", len(c.Workers)) + } + + if len(c.Failed) > 0 { + str += fmt.Sprintf("\nFailed: %v", c.Failed) + } + + return str +} diff --git a/das/checkpoint_store.go b/das/checkpoint_store.go deleted file mode 100644 index 7552c20bb2..0000000000 --- a/das/checkpoint_store.go +++ /dev/null @@ -1,47 +0,0 @@ -package das - -import ( - "context" - "encoding/binary" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/namespace" -) - -var ( - storePrefix = datastore.NewKey("das") - checkpointKey = datastore.NewKey("checkpoint") -) - -// wrapCheckpointStore wraps the given datastore.Datastore with the `das` -// prefix. The checkpoint store stores/loads the DASer's checkpoint to/from -// disk using the checkpointKey. The checkpoint is stored as a uint64 -// representation of the height of the latest successfully DASed header. -func wrapCheckpointStore(ds datastore.Datastore) datastore.Datastore { - return namespace.Wrap(ds, storePrefix) -} - -// loadCheckpoint loads the DAS checkpoint height from disk and returns it. -// If there is no known checkpoint, it returns height 0. -func loadCheckpoint(ctx context.Context, ds datastore.Datastore) (int64, error) { - checkpoint, err := ds.Get(ctx, checkpointKey) - if err != nil { - // if no checkpoint was found, return checkpoint as - // 0 since DASer begins sampling on checkpoint+1 - if err == datastore.ErrNotFound { - log.Debug("checkpoint not found, starting sampling at block height 1") - return 0, nil - } - - return 0, err - } - return int64(binary.BigEndian.Uint64(checkpoint)), err -} - -// storeCheckpoint stores the given DAS checkpoint to disk. -func storeCheckpoint(ctx context.Context, ds datastore.Datastore, checkpoint int64) error { - buf := make([]byte, 8) - binary.BigEndian.PutUint64(buf, uint64(checkpoint)) - - return ds.Put(ctx, checkpointKey, buf) -} diff --git a/das/checkpoint_store_test.go b/das/checkpoint_store_test.go deleted file mode 100644 index 243436e1b7..0000000000 --- a/das/checkpoint_store_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package das - -import ( - "context" - "testing" - "time" - - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCheckpointStore(t *testing.T) { - ds := wrapCheckpointStore(sync.MutexWrap(datastore.NewMapDatastore())) - checkpoint := int64(5) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer t.Cleanup(cancel) - err := storeCheckpoint(ctx, ds, checkpoint) - require.NoError(t, err) - got, err := loadCheckpoint(ctx, ds) - require.NoError(t, err) - - assert.Equal(t, checkpoint, got) -} diff --git a/das/checkpoint_test.go b/das/checkpoint_test.go new file mode 100644 index 0000000000..3035e8fae5 --- /dev/null +++ b/das/checkpoint_test.go @@ -0,0 +1,40 @@ +package das + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckpointStore(t *testing.T) { + ds := newCheckpointStore(sync.MutexWrap(datastore.NewMapDatastore())) + failed := make(map[uint64]int) + failed[2] = 1 + failed[3] = 2 + cp := checkpoint{ + SampleFrom: 1, + NetworkHead: 6, + Failed: failed, + Workers: []workerCheckpoint{ + { + From: 1, + To: 2, + }, + { + From: 5, + To: 10, + }, + }, + } + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer t.Cleanup(cancel) + assert.NoError(t, ds.store(ctx, cp)) + got, err := ds.load(ctx) + require.NoError(t, err) + assert.Equal(t, cp, got) +} diff --git a/das/coordinator.go b/das/coordinator.go new file mode 100644 index 0000000000..0611453338 --- /dev/null +++ b/das/coordinator.go @@ -0,0 +1,131 @@ +package das + +import ( + "context" + "sync" + + "github.com/celestiaorg/celestia-node/header" +) + +// samplingCoordinator runs and coordinates sampling workers and updates current sampling state +type samplingCoordinator struct { + concurrencyLimit int + getter header.Getter + sampleFn sampleFn + + state coordinatorState + + // resultCh fans-in sampling results from worker to coordinator + resultCh chan result + // updHeadCh signals to update network head header height + updHeadCh chan uint64 + // waitCh signals to block coordinator for external access to state + waitCh chan *sync.WaitGroup + + workersWg sync.WaitGroup + done +} + +// result will carry errors to coordinator after worker finishes the job +type result struct { + job + failed []uint64 + err error +} + +func newSamplingCoordinator( + concurrency int, + samplingRange uint64, + getter header.Getter, + sample sampleFn) *samplingCoordinator { + return &samplingCoordinator{ + concurrencyLimit: concurrency, + getter: getter, + sampleFn: sample, + state: newCoordinatorState(samplingRange), + resultCh: make(chan result), + updHeadCh: make(chan uint64), + waitCh: make(chan *sync.WaitGroup), + done: newDone("sampling coordinator"), + } +} + +func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { + sc.state.resumeFromCheckpoint(cp) + // resume workers + for _, wk := range cp.Workers { + sc.runWorker(ctx, sc.state.newJob(wk.From, wk.To)) + } + + for { + for !sc.concurrencyLimitReached() { + next, found := sc.state.nextJob() + if !found { + break + } + sc.runWorker(ctx, next) + } + + select { + case max := <-sc.updHeadCh: + sc.state.updateHead(max) + case res := <-sc.resultCh: + sc.state.handleResult(res) + case wg := <-sc.waitCh: + wg.Wait() + case <-ctx.Done(): + sc.workersWg.Wait() + sc.indicateDone() + return + } + } +} + +// runWorker runs job in separate worker go-routine +func (sc *samplingCoordinator) runWorker(ctx context.Context, j job) { + w := newWorker(j) + sc.state.putInProgress(j.id, w.getState) + + // launch worker go-routine + sc.workersWg.Add(1) + go func() { + defer sc.workersWg.Done() + w.run(ctx, sc.getter, sc.sampleFn, sc.resultCh) + }() +} + +// listen notifies the coordinator about a new network head received via subscription. +func (sc *samplingCoordinator) listen(ctx context.Context, height uint64) { + select { + case sc.updHeadCh <- height: + case <-ctx.Done(): + } +} + +// stats pauses the coordinator to get stats in a concurrently safe manner +func (sc *samplingCoordinator) stats(ctx context.Context) (SamplingStats, error) { + var wg sync.WaitGroup + wg.Add(1) + defer wg.Done() + + select { + case sc.waitCh <- &wg: + case <-ctx.Done(): + return SamplingStats{}, ctx.Err() + } + + return sc.state.unsafeStats(), nil +} + +func (sc *samplingCoordinator) getCheckpoint(ctx context.Context) (checkpoint, error) { + stats, err := sc.stats(ctx) + if err != nil { + return checkpoint{}, err + } + return newCheckpoint(stats), nil +} + +// concurrencyLimitReached indicates whether concurrencyLimit has been reached +func (sc *samplingCoordinator) concurrencyLimitReached() bool { + return len(sc.state.inProgress) >= sc.concurrencyLimit +} diff --git a/das/coordinator_test.go b/das/coordinator_test.go new file mode 100644 index 0000000000..e6883b41be --- /dev/null +++ b/das/coordinator_test.go @@ -0,0 +1,524 @@ +package das + +import ( + "context" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/celestiaorg/celestia-node/header" + + "github.com/stretchr/testify/assert" +) + +func TestCoordinator(t *testing.T) { + concurrency := 10 + samplingRange := uint64(10) + networkHead := uint64(500) + sampleFrom := uint64(genesisHeight) + timeoutDelay := 125 * time.Second + + t.Run("test run", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + + sampler := newMockSampler(sampleFrom, networkHead) + + coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, onceMiddleWare(sampler.sample)) + go coordinator.run(ctx, sampler.checkpoint) + + // check if all jobs were sampled successfully + assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") + + // wait for coordinator to indicateDone catchup + assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + assert.Emptyf(t, coordinator.state.failed, "failed list should be empty") + + cancel() + stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) + }) + + t.Run("discovered new headers", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + + sampler := newMockSampler(sampleFrom, networkHead) + + coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, sampler.sample) + go coordinator.run(ctx, sampler.checkpoint) + + time.Sleep(50 * time.Millisecond) + // discover new height + for i := 0; i < 200; i++ { + // mess the order by running in go-routine + sampler.discover(ctx, networkHead+uint64(i), coordinator.listen) + } + + // check if all jobs were sampled successfully + assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") + + // wait for coordinator to indicateDone catchup + assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + assert.Emptyf(t, coordinator.state.failed, "failed list should be empty") + + cancel() + stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) + }) + + t.Run("prioritize newly discovered over known", func(t *testing.T) { + sampleFrom := uint64(1) + networkHead := uint64(10) + toBeDiscovered := uint64(20) + samplingRange := uint64(4) + concurrency := 1 + + sampler := newMockSampler(sampleFrom, networkHead) + + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + + // lock worker before start, to not let it indicateDone before discover + lk := newLock(sampleFrom, sampleFrom) + + // expect worker to prioritize newly discovered (20 -> 10) and then old (0 -> 10) + order := newCheckOrder().addInterval(sampleFrom, samplingRange) // worker will pick up first job before discovery + order.addStacks(networkHead+1, toBeDiscovered, samplingRange) + order.addInterval(samplingRange+1, toBeDiscovered) + + // start coordinator + coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, + lk.middleWare( + order.middleWare( + sampler.sample)), + ) + go coordinator.run(ctx, sampler.checkpoint) + + // wait for worker to pick up first job + time.Sleep(50 * time.Millisecond) + + // discover new height + sampler.discover(ctx, toBeDiscovered, coordinator.listen) + + // check if no header were sampled yet + assert.Equal(t, 0, sampler.sampledAmount()) + + // unblock worker + lk.release(sampleFrom) + + // check if all jobs were sampled successfully + assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") + + // wait for coordinator to indicateDone catchup + assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + assert.Emptyf(t, coordinator.state.failed, "failed list should be empty") + + cancel() + stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) + }) + + t.Run("priority routine should not lock other workers", func(t *testing.T) { + networkHead := uint64(20) + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + + sampler := newMockSampler(sampleFrom, networkHead) + + lk := newLock(sampleFrom, networkHead) // lock all workers before start + coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, + lk.middleWare(sampler.sample)) + go coordinator.run(ctx, sampler.checkpoint) + + time.Sleep(50 * time.Millisecond) + // discover new height and lock it + discovered := networkHead + 1 + lk.add(discovered) + sampler.discover(ctx, discovered, coordinator.listen) + + // check if no header were sampled yet + assert.Equal(t, 0, sampler.sampledAmount()) + + // unblock workers to resume sampling + lk.releaseAll(discovered) + + // wait for coordinator to run sample on all headers except discovered + time.Sleep(100 * time.Millisecond) + + // check that only last header is pending + assert.EqualValues(t, int(discovered-sampleFrom), sampler.doneAmount()) + assert.False(t, sampler.heightIsDone(discovered)) + + // release all headers for coordinator + lk.releaseAll() + + // check if all jobs were sampled successfully + assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") + + // wait for coordinator to indicateDone catchup + assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + assert.Emptyf(t, coordinator.state.failed, "failed list is not empty") + + cancel() + stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + assert.Equal(t, sampler.finalState(), newCheckpoint(coordinator.state.unsafeStats())) + }) + + t.Run("failed should be stored", func(t *testing.T) { + sampleFrom := uint64(1) + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + + bornToFail := []uint64{4, 8, 15, 16, 23, 42} + sampler := newMockSampler(sampleFrom, networkHead, bornToFail...) + + coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, onceMiddleWare(sampler.sample)) + go coordinator.run(ctx, sampler.checkpoint) + + // wait for coordinator to indicateDone catchup + assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + + cancel() + stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + + // set failed items in expectedState + expectedState := sampler.finalState() + for _, h := range bornToFail { + expectedState.Failed[h] = 1 + } + assert.Equal(t, expectedState, newCheckpoint(coordinator.state.unsafeStats())) + }) + + t.Run("failed should retry on restart", func(t *testing.T) { + sampleFrom := uint64(50) + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + + failedLastRun := map[uint64]int{4: 1, 8: 2, 15: 1, 16: 1, 23: 1, 42: 1, sampleFrom - 1: 1} + failedAgain := []uint64{16} + + sampler := newMockSampler(sampleFrom, networkHead, failedAgain...) + sampler.checkpoint.Failed = failedLastRun + + coordinator := newSamplingCoordinator(concurrency, samplingRange, getterStub{}, onceMiddleWare(sampler.sample)) + go coordinator.run(ctx, sampler.checkpoint) + + // check if all jobs were sampled successfully + assert.NoError(t, sampler.finished(ctx), "not all headers were sampled") + + // wait for coordinator to indicateDone catchup + assert.NoError(t, coordinator.state.waitCatchUp(ctx)) + + cancel() + stopCtx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + defer cancel() + assert.NoError(t, coordinator.wait(stopCtx)) + + expectedState := sampler.finalState() + expectedState.Failed = make(map[uint64]int) + for _, v := range failedAgain { + expectedState.Failed[v] = failedLastRun[v] + 1 + } + assert.Equal(t, expectedState, newCheckpoint(coordinator.state.unsafeStats())) + }) +} + +func BenchmarkCoordinator(b *testing.B) { + concurrency := 100 + samplingRange := uint64(10) + timeoutDelay := 5 * time.Second + + b.Run("bench run", func(b *testing.B) { + ctx, cancel := context.WithTimeout(context.Background(), timeoutDelay) + coordinator := newSamplingCoordinator(concurrency, samplingRange, newBenchGetter(), + func(ctx context.Context, h *header.ExtendedHeader) error { return nil }) + go coordinator.run(ctx, checkpoint{ + SampleFrom: 1, + NetworkHead: uint64(b.N), + }) + + // wait for coordinator to indicateDone catchup + if err := coordinator.state.waitCatchUp(ctx); err != nil { + b.Error(err) + } + cancel() + }) +} + +// ensures all headers are sampled in range except ones that are born to fail +type mockSampler struct { + lock sync.Mutex + + checkpoint + bornToFail map[uint64]bool + done map[uint64]int + + isFinished bool + finishedCh chan struct{} +} + +func newMockSampler(sampledBefore, sampleTo uint64, bornToFail ...uint64) mockSampler { + failMap := make(map[uint64]bool) + for _, h := range bornToFail { + failMap[h] = true + } + return mockSampler{ + checkpoint: checkpoint{ + SampleFrom: sampledBefore, + NetworkHead: sampleTo, + Failed: make(map[uint64]int), + Workers: make([]workerCheckpoint, 0), + }, + bornToFail: failMap, + done: make(map[uint64]int), + finishedCh: make(chan struct{}), + } +} + +func (m *mockSampler) sample(ctx context.Context, h *header.ExtendedHeader) error { + if err := ctx.Err(); err != nil { + return err + } + + m.lock.Lock() + defer m.lock.Unlock() + + height := uint64(h.Height) + m.done[height]++ + + if len(m.done) > int(m.NetworkHead-m.SampleFrom) && !m.isFinished { + m.isFinished = true + close(m.finishedCh) + } + + if m.bornToFail[height] { + return errors.New("born to fail, sad life") + } + + if height > m.NetworkHead || height < m.SampleFrom { + if m.Failed[height] == 0 { + return fmt.Errorf("header: %v out of range: %v-%v", h, m.SampleFrom, m.NetworkHead) + } + } + return nil +} + +// finished returns when all jobs were sampled successfully +func (m *mockSampler) finished(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-m.finishedCh: + } + return nil +} + +func (m *mockSampler) heightIsDone(h uint64) bool { + m.lock.Lock() + defer m.lock.Unlock() + return m.done[h] != 0 +} + +func (m *mockSampler) doneAmount() int { + m.lock.Lock() + defer m.lock.Unlock() + return len(m.done) +} + +func (m *mockSampler) finalState() checkpoint { + m.lock.Lock() + defer m.lock.Unlock() + + finalState := m.checkpoint + finalState.SampleFrom = finalState.NetworkHead + 1 + return finalState +} + +func (m *mockSampler) discover(ctx context.Context, newHeight uint64, emit func(ctx context.Context, h uint64)) { + m.lock.Lock() + + if newHeight > m.checkpoint.NetworkHead { + m.checkpoint.NetworkHead = newHeight + if m.isFinished { + m.finishedCh = make(chan struct{}) + m.isFinished = false + } + } + m.lock.Unlock() + emit(ctx, newHeight) +} + +func (m *mockSampler) sampledAmount() int { + m.lock.Lock() + defer m.lock.Unlock() + return len(m.done) +} + +// ensures correct order of operations +type checkOrder struct { + lock sync.Mutex + queue []uint64 +} + +func newCheckOrder() *checkOrder { + return &checkOrder{} +} + +func (o *checkOrder) addInterval(start, end uint64) *checkOrder { + o.lock.Lock() + defer o.lock.Unlock() + + if end > start { + for end >= start { + o.queue = append(o.queue, start) + start++ + } + return o + } + + for start >= end { + o.queue = append(o.queue, start) + if start == 0 { + return o + } + start-- + + } + return o +} + +// splits interval into ranges with stackSize length and puts them with reverse order +func (o *checkOrder) addStacks(start, end, stackSize uint64) uint64 { + if start+stackSize-1 < end { + end = o.addStacks(start+stackSize, end, stackSize) + } + if start > end { + start = end + } + o.addInterval(start, end) + return start - 1 +} + +func TestOrder(t *testing.T) { + o := newCheckOrder().addInterval(0, 3).addInterval(3, 0) + assert.Equal(t, []uint64{0, 1, 2, 3, 3, 2, 1, 0}, o.queue) +} + +func TestStack(t *testing.T) { + o := newCheckOrder() + o.addStacks(10, 20, 3) + assert.Equal(t, []uint64{19, 20, 16, 17, 18, 13, 14, 15, 10, 11, 12}, o.queue) +} + +func (o *checkOrder) middleWare(out sampleFn) sampleFn { + return func(ctx context.Context, h *header.ExtendedHeader) error { + o.lock.Lock() + + if len(o.queue) > 0 { + // check last item in queue to be same as input + if o.queue[0] != uint64(h.Height) { + o.lock.Unlock() + return fmt.Errorf("expected height: %v,got: %v", o.queue[0], h) + } + o.queue = o.queue[1:] + } + + o.lock.Unlock() + return out(ctx, h) + } +} + +// blocks operations if item is in lock list +type lock struct { + m sync.Mutex + blockList map[uint64]chan struct{} +} + +func newLock(from, to uint64) *lock { + list := make(map[uint64]chan struct{}) + for from <= to { + list[from] = make(chan struct{}) + from++ + } + return &lock{ + blockList: list, + } +} + +func (l *lock) add(hs ...uint64) { + l.m.Lock() + defer l.m.Unlock() + for _, h := range hs { + l.blockList[h] = make(chan struct{}) + } +} + +func (l *lock) release(hs ...uint64) { + l.m.Lock() + defer l.m.Unlock() + + for _, h := range hs { + if ch, ok := l.blockList[h]; ok { + close(ch) + delete(l.blockList, h) + } + } +} + +func (l *lock) releaseAll(except ...uint64) { + m := make(map[uint64]bool) + for _, h := range except { + m[h] = true + } + + l.m.Lock() + defer l.m.Unlock() + + for h, ch := range l.blockList { + if m[h] { + continue + } + close(ch) + delete(l.blockList, h) + } +} + +func (l *lock) middleWare(out sampleFn) sampleFn { + return func(ctx context.Context, h *header.ExtendedHeader) error { + l.m.Lock() + ch, blocked := l.blockList[uint64(h.Height)] + l.m.Unlock() + if !blocked { + return out(ctx, h) + } + + select { + case <-ch: + return out(ctx, h) + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func onceMiddleWare(out sampleFn) sampleFn { + db := make(map[int64]int) + m := sync.Mutex{} + return func(ctx context.Context, h *header.ExtendedHeader) error { + m.Lock() + db[h.Height]++ + if db[h.Height] > 1 { + m.Unlock() + return fmt.Errorf("header sampled more than once: %v", h.Height) + } + m.Unlock() + return out(ctx, h) + } +} diff --git a/das/daser.go b/das/daser.go index 3676c28950..7124acec4a 100644 --- a/das/daser.go +++ b/das/daser.go @@ -4,62 +4,83 @@ import ( "context" "errors" "fmt" + "sync/atomic" "time" + "github.com/celestiaorg/celestia-node/ipld" + "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/ipld" "github.com/celestiaorg/celestia-node/service/share" ) var log = logging.Logger("das") +// TODO: parameters needs performance testing on real network to define optimal values +const ( + // samplingRange is the maximum amount of headers processed in one job. + samplingRange = 100 + + // concurrencyLimit defines the maximum amount of sampling workers running in parallel. + concurrencyLimit = 16 + + // backgroundStoreInterval is the period of time for background checkpointStore to perform a checkpoint backup. + backgroundStoreInterval = 10 * time.Minute + + // priorityQueueSize defines the size limit of the priority queue + priorityQueueSize = concurrencyLimit * 4 + + // genesisHeight is the height sampling will start from + genesisHeight = 1 +) + // DASer continuously validates availability of data committed to headers. type DASer struct { - ctx context.Context - cancel context.CancelFunc - da share.Availability bcast fraud.Broadcaster hsub header.Subscriber // listens for new headers in the network getter header.Getter // retrieves past headers - cstore datastore.Datastore // checkpoint store - state state + sampler *samplingCoordinator + store checkpointStore + subscriber subscriber - jobsCh chan *catchUpJob - - sampleDn chan struct{} // done signal for sample loop - catchUpDn chan struct{} // done signal for catchUp loop + cancel context.CancelFunc + subscriberDone chan struct{} + running int32 } +type listenFn func(ctx context.Context, height uint64) +type sampleFn func(context.Context, *header.ExtendedHeader) error + // NewDASer creates a new DASer. func NewDASer( da share.Availability, hsub header.Subscriber, getter header.Getter, - cstore datastore.Datastore, + dstore datastore.Datastore, bcast fraud.Broadcaster, ) *DASer { - wrappedDS := wrapCheckpointStore(cstore) - return &DASer{ - da: da, - bcast: bcast, - hsub: hsub, - getter: getter, - cstore: wrappedDS, - jobsCh: make(chan *catchUpJob, 16), - sampleDn: make(chan struct{}), - catchUpDn: make(chan struct{}), + d := &DASer{ + da: da, + bcast: bcast, + hsub: hsub, + getter: getter, + store: newCheckpointStore(dstore), + subscriber: newSubscriber(), + subscriberDone: make(chan struct{}), } + d.sampler = newSamplingCoordinator(concurrencyLimit, samplingRange, getter, d.sample) + + return d } // Start initiates subscription for new ExtendedHeaders and spawns a sampling routine. -func (d *DASer) Start(context.Context) error { - if d.cancel != nil { +func (d *DASer) Start(ctx context.Context) error { + if !atomic.CompareAndSwapInt32(&d.running, 0, 1) { return fmt.Errorf("da: DASer already started") } @@ -68,201 +89,71 @@ func (d *DASer) Start(context.Context) error { return err } - d.ctx, d.cancel = context.WithCancel(context.Background()) - // load latest DASed checkpoint - checkpoint, err := loadCheckpoint(d.ctx, d.cstore) + cp, err := d.store.load(ctx) if err != nil { - return err + log.Warnw("checkpoint not found, initializing with height 1") + + cp = checkpoint{ + SampleFrom: genesisHeight, + NetworkHead: genesisHeight, + } + + // attempt to get head info. No need to handle error, later DASer + // will be able to find new head from subscriber after it is started + if h, err := d.getter.Head(ctx); err == nil { + cp.NetworkHead = uint64(h.Height) + } } - log.Infow("loaded checkpoint", "height", checkpoint) + log.Info("starting DASer from checkpoint: ", cp.String()) + + runCtx, cancel := context.WithCancel(context.Background()) + d.cancel = cancel + + go d.sampler.run(runCtx, cp) + go d.subscriber.run(runCtx, sub, d.sampler.listen) + go d.store.runBackgroundStore(runCtx, backgroundStoreInterval, d.sampler.getCheckpoint) - // kick off catch-up routine manager - go d.catchUpManager(d.ctx, checkpoint) - // kick off sampling routine for recently received headers - go d.sample(d.ctx, sub, checkpoint) return nil } // Stop stops sampling. func (d *DASer) Stop(ctx context.Context) error { - // Stop func can now be invoked twice in one Lifecycle, when: - // * BEFP is received; - // * node is stopping; - // this statement helps avoiding panic on the second Stop. - // If ctx.Err is not nil then it means that Stop was called for the first time. - // NOTE: we are expecting *only* ContextCancelled error here. - if d.ctx.Err() == context.Canceled { + if !atomic.CompareAndSwapInt32(&d.running, 1, 0) { return nil } - d.cancel() - // wait for both sampling routines to exit - for i := 0; i < 2; i++ { - select { - case <-d.catchUpDn: - case <-d.sampleDn: - case <-ctx.Done(): - return ctx.Err() - } - } - d.cancel = nil - return nil -} - -// sample validates availability for each Header received from header subscription. -func (d *DASer) sample(ctx context.Context, sub header.Subscription, checkpoint int64) { - // indicate sampling routine is running - d.indicateRunning() - defer func() { - sub.Cancel() - // indicate sampling routine is stopped - d.indicateStopped() - // send done signal - d.sampleDn <- struct{}{} - }() - - // sampleHeight tracks the last successful height of this routine - sampleHeight := checkpoint - - for { - h, err := sub.NextHeader(ctx) - if err != nil { - if err == context.Canceled { - return - } - - log.Errorw("failed to get next header", "err", err) - continue - } - - // If the next header coming through gossipsub is not adjacent - // to our last DASed header, kick off routine to DAS all headers - // between last DASed header and h. This situation could occur - // either on start or due to network latency/disconnection. - if h.Height > sampleHeight+1 { - // DAS headers between last DASed height up to the current - // header - job := &catchUpJob{ - from: sampleHeight, - to: h.Height - 1, - } - select { - case <-ctx.Done(): - return - case d.jobsCh <- job: - } - } - - err = d.sampleHeader(ctx, h) - if err != nil { - // record error - d.updateSampleState(h, err) - log.Warn("DASer SAMPLING ROUTINE WILL BE STOPPED. IN ORDER TO CONTINUE SAMPLING, " + - "RE-START THE NODE") - return - } - d.updateSampleState(h, nil) - sampleHeight = h.Height + // try to store checkpoint without waiting for coordinator and workers to stop + cp, err := d.sampler.getCheckpoint(ctx) + if err != nil { + log.Error("DASer coordinator checkpoint is unavailable") } -} - -// catchUpJob represents a catch-up job. (from:to] -type catchUpJob struct { - from, to int64 -} -// catchUpManager manages catch-up jobs, performing them one at a time, exiting -// only once context is canceled and storing latest DASed checkpoint to disk. -func (d *DASer) catchUpManager(ctx context.Context, checkpoint int64) { - defer func() { - // store latest DASed checkpoint to disk here to ensure that if DASer is not yet - // fully caught up to network head, it will resume DASing from this checkpoint - // up to current network head - // TODO @renaynay: Implement Share Cache #180 to ensure no duplicate DASing over same - // header - if err := storeCheckpoint(ctx, d.cstore, checkpoint); err != nil { - log.Errorw("storing checkpoint to disk", "height", checkpoint, "err", err) - } - log.Infow("stored checkpoint to disk", "checkpoint", checkpoint) - // signal that catch-up routine finished - d.catchUpDn <- struct{}{} - }() - - for { - select { - case <-ctx.Done(): - return - case job := <-d.jobsCh: - // record details of incoming job - d.recordJobDetails(job) - // perform catchUp routine - height, err := d.catchUp(ctx, job) - // store the height of the last successfully sampled header - checkpoint = height - // exit routine if a catch-up job was unsuccessful - if err != nil { - // record error - d.state.catchUpLk.Lock() - d.state.catchUp.Error = err - d.state.catchUpLk.Unlock() - - log.Errorw("catch-up routine failed", "attempted range: from", job.from, "to", job.to) - log.Warn("DASer CATCH-UP SAMPLING ROUTINE WILL BE STOPPED. IN ORDER TO CONTINUE SAMPLING, " + - "RE-START THE NODE") - return - } - } + if err = d.store.store(ctx, cp); err != nil { + log.Errorw("storing checkpoint to disk", "err", err) } -} -// catchUp starts a sampling routine for headers starting at the next header -// after the `from` height and exits the loop once `to` is reached. (from:to] -func (d *DASer) catchUp(ctx context.Context, job *catchUpJob) (int64, error) { - log.Infow("sampling past headers", "from", job.from, "to", job.to) - - // start sampling from height at checkpoint+1 since the - // checkpoint height is DASed by broader sample routine - for height := job.from + 1; height <= job.to; height++ { - h, err := d.getter.GetByHeight(ctx, uint64(height)) - if err != nil { - if err == context.Canceled { - // report previous height as the last successfully sampled height and - // error as nil since the routine was ordered to stop - return height - 1, nil - } + d.cancel() + if err = d.sampler.wait(ctx); err != nil { + return fmt.Errorf("DASer force quit: %w", err) + } - log.Errorw("failed to get next header", "height", height, "err", err) - // report previous height as the last successfully sampled height - return height - 1, err - } + // save updated checkpoint after sampler and all workers are shut down + if err = d.store.store(ctx, newCheckpoint(d.sampler.state.unsafeStats())); err != nil { + log.Errorw("storing checkpoint to disk", "Err", err) + } - err = d.sampleHeader(ctx, h) - if err != nil { - return h.Height - 1, err - } - d.state.catchUpLk.Lock() - d.state.catchUp.Height = uint64(h.Height) - d.state.catchUpLk.Unlock() + if err = d.store.wait(ctx); err != nil { + return fmt.Errorf("DASer force quit with err: %w", err) } - d.state.catchUpLk.Lock() - d.state.catchUp.End = time.Now() - d.state.catchUpLk.Unlock() - - jobDetails := d.CatchUpRoutineState() - log.Infow("successfully sampled past headers", "from", job.from, - "to", job.to, "finished (s)", jobDetails.Duration()) - // report successful result - return job.to, nil + return d.subscriber.wait(ctx) } -func (d *DASer) sampleHeader(ctx context.Context, h *header.ExtendedHeader) error { - startTime := time.Now() - +func (d *DASer) sample(ctx context.Context, h *header.ExtendedHeader) error { err := d.da.SharesAvailable(ctx, h.DAH) if err != nil { if err == context.Canceled { - return nil + return err } var byzantineErr *ipld.ErrByzantine if errors.As(err, &byzantineErr) { @@ -272,64 +163,15 @@ func (d *DASer) sampleHeader(ctx context.Context, h *header.ExtendedHeader) erro log.Errorw("fraud proof propagating failed", "err", sendErr) } } + log.Errorw("sampling failed", "height", h.Height, "hash", h.Hash(), "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "err", err) - // report previous height as the last successfully sampled height return err } - sampleTime := time.Since(startTime) - log.Infow("sampled header", "height", h.Height, "hash", h.Hash(), - "square width", len(h.DAH.RowsRoots), "finished (s)", sampleTime.Seconds()) - return nil } -// SampleRoutineState reports the current state of the -// DASer's main sampling routine. -func (d *DASer) SampleRoutineState() RoutineState { - d.state.sampleLk.RLock() - state := d.state.sample - d.state.sampleLk.RUnlock() - return state -} - -func (d *DASer) updateSampleState(h *header.ExtendedHeader, err error) { - height := uint64(h.Height) - - d.state.sampleLk.Lock() - defer d.state.sampleLk.Unlock() - d.state.sample.LatestSampledHeight = height - d.state.sample.LatestSampledSquareWidth = uint64(len(h.DAH.RowsRoots)) - d.state.sample.Error = err -} - -func (d *DASer) indicateRunning() { - d.state.sampleLk.Lock() - defer d.state.sampleLk.Unlock() - d.state.sample.IsRunning = true -} - -func (d *DASer) indicateStopped() { - d.state.sampleLk.Lock() - defer d.state.sampleLk.Unlock() - d.state.sample.IsRunning = false -} - -// CatchUpRoutineState reports the current state of the -// DASer's `catchUp` routine. -func (d *DASer) CatchUpRoutineState() JobInfo { - d.state.catchUpLk.RLock() - state := d.state.catchUp - d.state.catchUpLk.RUnlock() - return state -} - -func (d *DASer) recordJobDetails(job *catchUpJob) { - d.state.catchUpLk.Lock() - defer d.state.catchUpLk.Unlock() - d.state.catchUp.ID++ - d.state.catchUp.From = uint64(job.from) - d.state.catchUp.To = uint64(job.to) - d.state.catchUp.Start = time.Now() +func (d *DASer) SamplingStats(ctx context.Context) (SamplingStats, error) { + return d.sampler.stats(ctx) } diff --git a/das/daser_test.go b/das/daser_test.go index 60f88fbfd1..0b1a8a4d6b 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -2,7 +2,6 @@ package das import ( "context" - "sync" "testing" "time" @@ -44,28 +43,19 @@ func TestDASerLifecycle(t *testing.T) { require.NoError(t, err) // load checkpoint and ensure it's at network head - checkpoint, err := loadCheckpoint(ctx, daser.cstore) + checkpoint, err := daser.store.load(ctx) require.NoError(t, err) - // ensure checkpoint is stored at 15 - assert.Equal(t, int64(15), checkpoint) + // ensure checkpoint is stored at 30 + assert.EqualValues(t, 30, checkpoint.SampleFrom-1) }() - // wait for dasing catch-up routine to finish + // wait for dasing catch-up routine to indicateDone select { case <-ctx.Done(): t.Fatal(ctx.Err()) case <-mockGet.doneCh: } // give catch-up routine a second to finish up sampling last header - for { - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - default: - if daser.CatchUpRoutineState().Finished() { - return - } - } - } + assert.NoError(t, daser.sampler.state.waitCatchUp(ctx)) } func TestDASer_Restart(t *testing.T) { @@ -83,7 +73,7 @@ func TestDASer_Restart(t *testing.T) { err := daser.Start(ctx) require.NoError(t, err) - // wait for dasing catch-up routine to finish + // wait for dasing catch-up routine to indicateDone select { case <-ctx.Done(): t.Fatal(ctx.Err()) @@ -98,164 +88,37 @@ func TestDASer_Restart(t *testing.T) { mockGet.doneCh = make(chan struct{}) // reset dummy subscriber mockGet.fillSubWithHeaders(t, sub, bServ, 45, 60) - // manually set mockGet head to trigger stop at 45 + // manually set mockGet head to trigger finished at 45 mockGet.head = int64(45) // restart DASer with new context restartCtx, restartCancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(restartCancel) + daser = NewDASer(shareServ, sub, mockGet, ds, mockService) err = daser.Start(restartCtx) require.NoError(t, err) - // wait for dasing catch-up routine to finish + // wait for dasing catch-up routine to indicateDone select { case <-restartCtx.Done(): t.Fatal(restartCtx.Err()) case <-mockGet.doneCh: } + assert.NoError(t, daser.sampler.state.waitCatchUp(ctx)) err = daser.Stop(restartCtx) require.NoError(t, err) - assert.True(t, daser.CatchUpRoutineState().Finished()) - // load checkpoint and ensure it's at network head - checkpoint, err := loadCheckpoint(ctx, daser.cstore) + checkpoint, err := daser.store.load(ctx) require.NoError(t, err) // ensure checkpoint is stored at 45 - assert.Equal(t, int64(45), checkpoint) -} - -func TestDASer_catchUp(t *testing.T) { - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() - avail := share.TestLightAvailability(bServ) - mockGet, shareServ, _, mockService := createDASerSubcomponents(t, bServ, 5, 0, avail) - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - daser := NewDASer(shareServ, nil, mockGet, ds, mockService) - - type catchUpResult struct { - checkpoint int64 - err error - } - resultCh := make(chan *catchUpResult, 1) - - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - // catch up from height 2 to head - job := &catchUpJob{ - from: 2, - to: mockGet.head, - } - checkpt, err := daser.catchUp(ctx, job) - resultCh <- &catchUpResult{ - checkpoint: checkpt, - err: err, - } - }() - wg.Wait() - - result := <-resultCh - assert.Equal(t, mockGet.head, result.checkpoint) - require.NoError(t, result.err) -} - -// TestDASer_catchUp_oneHeader tests that catchUp works with a from-to -// difference of 1 -func TestDASer_catchUp_oneHeader(t *testing.T) { - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() - avail := share.TestLightAvailability(bServ) - mockGet, shareServ, _, mockService := createDASerSubcomponents(t, bServ, 6, 0, avail) - daser := NewDASer(shareServ, nil, mockGet, ds, mockService) - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - // store checkpoint - err := storeCheckpoint(ctx, daser.cstore, 5) // pick arbitrary height as last checkpoint - require.NoError(t, err) - - checkpoint, err := loadCheckpoint(ctx, daser.cstore) - require.NoError(t, err) - - type catchUpResult struct { - checkpoint int64 - err error - } - resultCh := make(chan *catchUpResult, 1) - - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - job := &catchUpJob{ - from: checkpoint, - to: mockGet.head, - } - checkpt, err := daser.catchUp(ctx, job) - resultCh <- &catchUpResult{ - checkpoint: checkpt, - err: err, - } - }() - wg.Wait() - - result := <-resultCh - assert.Equal(t, mockGet.head, result.checkpoint) - require.NoError(t, result.err) -} - -func TestDASer_catchUp_fails(t *testing.T) { - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() - avail := share.TestLightAvailability(bServ) - mockGet, _, _, mockService := createDASerSubcomponents(t, bServ, 6, 0, avail) - daser := NewDASer(share.NewTestBrokenAvailability(), nil, mockGet, ds, mockService) - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - // store checkpoint - err := storeCheckpoint(ctx, daser.cstore, 5) // pick arbitrary height as last checkpoint - require.NoError(t, err) - - checkpoint, err := loadCheckpoint(ctx, daser.cstore) - require.NoError(t, err) - - type catchUpResult struct { - checkpoint int64 - err error - } - resultCh := make(chan *catchUpResult, 1) - - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - job := &catchUpJob{ - from: checkpoint, - to: mockGet.head, - } - checkpt, err := daser.catchUp(ctx, job) - resultCh <- &catchUpResult{ - checkpoint: checkpt, - err: err, - } - }() - wg.Wait() - - result := <-resultCh - require.ErrorIs(t, result.err, share.ErrNotAvailable) + assert.EqualValues(t, 60, checkpoint.SampleFrom-1) } func TestDASer_stopsAfter_BEFP(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) t.Cleanup(cancel) ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) @@ -282,7 +145,7 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { resultCh := make(chan error) go fraud.OnProof(newCtx, f, fraud.BadEncoding, func(fraud.Proof) { - resultCh <- daser.Stop(ctx) + resultCh <- daser.Stop(newCtx) }) require.NoError(t, daser.Start(newCtx)) @@ -293,107 +156,8 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { case res := <-resultCh: require.NoError(t, res) } - require.True(t, daser.ctx.Err() == context.Canceled) -} - -func TestDASerState(t *testing.T) { - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() - - // 30 headers in the past and 30 headers in the future (so final sample height should be 60) - mockGet, shareServ, sub, fraud := createDASerSubcomponents(t, bServ, 30, 30, share.NewTestSuccessfulAvailability()) - expectedFinalSampleHeight := uint64(60) - expectedFinalSampleWidth := uint64(len(sub.Headers[29].DAH.RowsRoots)) - - ctx, cancel := context.WithTimeout(context.Background(), timeout) - t.Cleanup(cancel) - - daser := NewDASer(shareServ, sub, mockGet, ds, fraud) - err := daser.Start(ctx) - require.NoError(t, err) - defer func() { - // wait for all "future" headers to be sampled - for { - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - default: - if daser.SampleRoutineState().LatestSampledHeight == expectedFinalSampleHeight { - assert.Equal(t, expectedFinalSampleWidth, daser.SampleRoutineState().LatestSampledSquareWidth) - - err := daser.Stop(ctx) - require.NoError(t, err) - assert.False(t, daser.SampleRoutineState().IsRunning) - return - } - } - } - }() - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - case <-mockGet.doneCh: - } - // give catchUp routine a second to exit - for { - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - default: - if daser.CatchUpRoutineState().Finished() { - return - } - } - } -} - -// TestDASerState_WithErrorInCatchUp tests for the case where an -// error has occurred inside the catchUp routine, ensuring the catchUp -// routine exits as expected and reports the error accurately. -func TestDASerState_WithErrorInCatchUp(t *testing.T) { - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - bServ := mdutils.Bserv() - - // 30 headers in the past and 30 headers in the future - // catchUp routine error should occur at height 16 - brokenHeight := 16 - mockGet, shareServ, sub, fraud := createDASerWithFailingAvailability(t, bServ, 30, 30, - int64(brokenHeight)) - - ctx, cancel := context.WithTimeout(context.Background(), timeout) - t.Cleanup(cancel) - - daser := NewDASer(shareServ, sub, mockGet, ds, fraud) - // allow catchUpDn signal to be read twice - daser.catchUpDn = make(chan struct{}, 2) - - err := daser.Start(ctx) - require.NoError(t, err) - defer func() { - err = daser.Stop(ctx) - require.NoError(t, err) - }() - // wait until catchUp routine has fetched the broken header - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - case <-mockGet.brokenHeightCh: - } - // wait for daser to exit catchUp routine - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - case <-daser.catchUpDn: - catchUp := daser.CatchUpRoutineState() - // make sure catchUp routine didn't successfully complete - assert.False(t, catchUp.Finished()) - // ensure that the error has been reported - assert.NotNil(t, catchUp.Error) - assert.Equal(t, uint64(brokenHeight)-1, catchUp.Height) - // send another done signal so `Stop` can read it - daser.catchUpDn <- struct{}{} - return - } + // wait for manager to finish catchup + require.True(t, daser.running == 0) } // createDASerSubcomponents takes numGetter (number of headers @@ -433,23 +197,6 @@ func createMockGetterAndSub( return mockGet, sub } -func createDASerWithFailingAvailability( - t *testing.T, - bServ blockservice.BlockService, - numGetter, - numSub int, - brokenHeight int64, -) (*mockGetter, *share.Service, *header.DummySubscriber, *fraud.DummyService) { - mockGet, sub := createMockGetterAndSub(t, bServ, numGetter, numSub) - mockGet.brokenHeight = brokenHeight - - shareServ := share.NewService(bServ, &share.TestBrokenAvailability{ - Root: mockGet.headers[brokenHeight].DAH, - }) - - return mockGet, shareServ, sub, new(fraud.DummyService) -} - // fillSubWithHeaders generates `num` headers from the future for p2pSub to pipe through to DASer. func (m *mockGetter) fillSubWithHeaders( t *testing.T, @@ -470,7 +217,7 @@ func (m *mockGetter) fillSubWithHeaders( randHeader.Height = int64(i + 1) sub.Headers[index] = randHeader - // also store to mock getter for duplicate fetching + // also checkpointStore to mock getter for duplicate sampling m.headers[int64(i+1)] = randHeader index++ @@ -478,6 +225,7 @@ func (m *mockGetter) fillSubWithHeaders( } type mockGetter struct { + getterStub doneCh chan struct{} // signals all stored headers have been retrieved brokenHeight int64 @@ -510,19 +258,53 @@ func (m *mockGetter) GetByHeight(_ context.Context, height uint64) (*header.Exte defer func() { switch int64(height) { case m.brokenHeight: - close(m.brokenHeightCh) + select { + case <-m.brokenHeightCh: + default: + close(m.brokenHeightCh) + } case m.head: - close(m.doneCh) + select { + case <-m.doneCh: + default: + close(m.doneCh) + } } }() return m.headers[int64(height)], nil } -func (m *mockGetter) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { +type benchGetterStub struct { + getterStub + header *header.ExtendedHeader +} + +func newBenchGetter() benchGetterStub { + return benchGetterStub{header: &header.ExtendedHeader{ + DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}} +} + +func (m benchGetterStub) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { + return m.header, nil +} + +type getterStub struct{} + +func (m getterStub) Head(context.Context) (*header.ExtendedHeader, error) { + return nil, nil +} + +func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { + return &header.ExtendedHeader{ + RawHeader: header.RawHeader{Height: int64(height)}, + DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}, nil +} + +func (m getterStub) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { return nil, nil } -func (m *mockGetter) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { +func (m getterStub) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { return nil, nil } diff --git a/das/doc.go b/das/doc.go index 254ed65dfa..c255530aef 100644 --- a/das/doc.go +++ b/das/doc.go @@ -1,8 +1,8 @@ /* Package das contains the most important functionality provided by celestia-node. It contains logic for running data availability sampling (DAS) routines on block -headers in the network. DAS is the process of verifying the availability of block -data by sampling chunks or shares of those blocks. +headers in the network. DAS is the process of verifying the availability of +block data by sampling chunks or shares of those blocks. Package das can confirm the availability of block data in the network via the Availability interface which is implemented both in `full` and `light` mode. @@ -13,10 +13,11 @@ sufficiently likely that all block data is available as it is assumed that there are enough `light` availability instances active on the network doing sampling over the same block to collectively verify its availability. -The central component of this package is the `DASer`. It performs one basic function: -a sampling loop that performs DAS on new ExtendedHeaders in the network. The DASer kicks -off this loop by loading its last DASed header (`checkpoint`) and kicking off a `catchUp` -loop to DAS all headers between the checkpoint and the current network head. It simultaneously -continues to perform DAS over new ExtendedHeaders received via gossipsub. +The central component of this package is the `samplingCoordinator`. It launches parallel +workers that perform DAS on new ExtendedHeaders in the network. The DASer kicks off this +loop by loading its last DASed headers snapshot (`checkpoint`) and kicking off worker pool +to DAS all headers between the checkpoint and the current network head. It subscribes +to notifications about to new ExtendedHeaders, received via gossipsub. Newly found headers +are being put into higher priority queue and will be sampled by the next available worker. */ package das diff --git a/das/done.go b/das/done.go new file mode 100644 index 0000000000..b2a6073f0a --- /dev/null +++ b/das/done.go @@ -0,0 +1,31 @@ +package das + +import ( + "context" + "fmt" +) + +type done struct { + name string + finished chan struct{} +} + +func newDone(name string) done { + return done{ + name: name, + finished: make(chan struct{}), + } +} + +func (d *done) indicateDone() { + close(d.finished) +} + +func (d *done) wait(ctx context.Context) error { + select { + case <-d.finished: + case <-ctx.Done(): + return fmt.Errorf("%v stuck: %w", d.name, ctx.Err()) + } + return nil +} diff --git a/das/state.go b/das/state.go index 87d728daa3..b75cd1e397 100644 --- a/das/state.go +++ b/das/state.go @@ -1,53 +1,228 @@ package das import ( - "sync" - "time" + "context" ) -// state collects information about the DASer process. Currently, there are -// only two sampling routines: the main sampling routine which performs sampling -// over current network headers, and the `catchUp` routine which performs sampling -// over past headers from the last sampled checkpoint. -type state struct { - sampleLk sync.RWMutex - sample RoutineState // tracks information related to the main sampling routine +// coordinatorState represents the current state of sampling +type coordinatorState struct { + rangeSize uint64 - catchUpLk sync.RWMutex - catchUp JobInfo // tracks information related to the `catchUp` routine + priority []job // list of headers heights that will be sampled with higher priority + inProgress map[int]func() workerState // keeps track of running workers + failed map[uint64]int // stores heights of failed headers with amount of attempt as value + + nextJobID int + next uint64 // all headers before next were sent to workers + networkHead uint64 + + catchUpDone bool // indicates if all headers are sampled + catchUpDoneCh chan struct{} // blocks until all headers are sampled } -// RoutineState contains important information about the state of a -// current sampling routine. -type RoutineState struct { - // reports if an error has occurred during the routine's - // sampling process - Error error `json:"error"` - // tracks the latest successfully sampled height of the routine - LatestSampledHeight uint64 `json:"latest_sampled_height"` - // tracks the square width of the latest successfully sampled - // height of the routine - LatestSampledSquareWidth uint64 `json:"latest_sampled_square_width"` - // tracks whether routine is running - IsRunning bool `json:"is_running"` +// newCoordinatorState initiates state for samplingCoordinator +func newCoordinatorState(samplingRangeSize uint64) coordinatorState { + return coordinatorState{ + rangeSize: samplingRangeSize, + priority: make([]job, 0), + inProgress: make(map[int]func() workerState), + failed: make(map[uint64]int), + nextJobID: 0, + next: genesisHeight, + networkHead: genesisHeight, + catchUpDone: false, + catchUpDoneCh: make(chan struct{}), + } +} + +func (s *coordinatorState) resumeFromCheckpoint(c checkpoint) { + s.next = c.SampleFrom + s.networkHead = c.NetworkHead + // put failed into priority to retry them on restart + for h, count := range c.Failed { + s.failed[h] = count + s.priority = append(s.priority, s.newJob(h, h)) + } +} + +func (s *coordinatorState) handleResult(res result) { + delete(s.inProgress, res.id) + + failedFromWorker := make(map[uint64]bool) + for _, h := range res.failed { + failedFromWorker[h] = true + } + + // check if the worker retried any of the previously failed heights + for h := range s.failed { + if h < res.From || h > res.To { + continue + } + + if !failedFromWorker[h] { + delete(s.failed, h) + } + } + // add newly failed heights + for h := range failedFromWorker { + s.failed[h]++ + } + s.checkDone() } -// JobInfo contains information about a catchUp job. -type JobInfo struct { - Start time.Time `json:"start"` - End time.Time `json:"end"` - Error error `json:"error"` +func (s *coordinatorState) updateHead(last uint64) bool { + // seen this header before + if last <= s.networkHead { + log.Warnf("received head height: %v, which is lower or the same as previously known: %v", last, s.networkHead) + return false + } + + if s.networkHead == genesisHeight { + s.networkHead = last + log.Infow("found first header, starting sampling") + return true + } + + // add most recent headers into priority queue + from := s.networkHead + 1 + for from <= last && len(s.priority) < priorityQueueSize { + s.priority = append(s.priority, s.newJob(from, last)) + from += s.rangeSize + } + + log.Debugw("added recent headers to DASer priority queue", "from_height", s.networkHead, "to_height", last) + s.networkHead = last + s.checkDone() + return true +} + +// nextJob will return header height to be processed and done flag if there is none +func (s *coordinatorState) nextJob() (next job, found bool) { + // all headers were sent to workers. + if s.next > s.networkHead { + return job{}, false + } + + // try to take from priority first + if next, found := s.nextFromPriority(); found { + return next, found + } + + j := s.newJob(s.next, s.networkHead) + + s.next += s.rangeSize + if s.next > s.networkHead { + s.next = s.networkHead + 1 + } - ID uint64 `json:"id"` - Height uint64 `json:"height"` - From uint64 `json:"from"` - To uint64 `json:"to"` + return j, true } -func (ji JobInfo) Finished() bool { - return ji.To == ji.Height +func (s *coordinatorState) nextFromPriority() (job, bool) { + for len(s.priority) > 0 { + next := s.priority[len(s.priority)-1] + s.priority = s.priority[:len(s.priority)-1] + + // this job will be processed next normally, we can skip it + if next.From == s.next { + continue + } + + return next, true + } + return job{}, false +} + +func (s *coordinatorState) putInProgress(jobID int, getState func() workerState) { + s.inProgress[jobID] = getState +} + +func (s *coordinatorState) newJob(from, max uint64) job { + s.nextJobID++ + to := from + s.rangeSize - 1 + if to > max { + to = max + } + return job{ + id: s.nextJobID, + From: from, + To: to, + } +} + +// unsafeStats collects coordinator stats without thread-safety +func (s *coordinatorState) unsafeStats() SamplingStats { + workers := make([]WorkerStats, 0, len(s.inProgress)) + lowestFailedOrInProgress := s.next + failed := make(map[uint64]int) + + // gather worker stats + for _, getStats := range s.inProgress { + wstats := getStats() + var errMsg string + if wstats.Err != nil { + errMsg = wstats.Err.Error() + } + workers = append(workers, WorkerStats{ + Curr: wstats.Curr, + From: wstats.From, + To: wstats.To, + ErrMsg: errMsg, + }) + + for _, h := range wstats.failed { + failed[h]++ + if h < lowestFailedOrInProgress { + lowestFailedOrInProgress = h + } + } + + if wstats.Curr < lowestFailedOrInProgress { + lowestFailedOrInProgress = wstats.Curr + } + } + + // set lowestFailedOrInProgress to minimum failed - 1 + for h, count := range s.failed { + failed[h] += count + if h < lowestFailedOrInProgress { + lowestFailedOrInProgress = h + } + } + + return SamplingStats{ + SampledChainHead: lowestFailedOrInProgress - 1, + CatchupHead: s.next - 1, + NetworkHead: s.networkHead, + Failed: failed, + Workers: workers, + Concurrency: len(workers), + CatchUpDone: s.catchUpDone, + IsRunning: len(workers) > 0 || s.catchUpDone, + } +} + +func (s *coordinatorState) checkDone() { + if len(s.inProgress) == 0 && len(s.priority) == 0 && s.next > s.networkHead { + if !s.catchUpDone { + close(s.catchUpDoneCh) + s.catchUpDone = true + } + return + } + + if s.catchUpDone { + s.catchUpDoneCh = make(chan struct{}) + s.catchUpDone = false + } } -func (ji JobInfo) Duration() time.Duration { - return ji.End.Sub(ji.Start) +// waitCatchUp waits for sampling process to indicateDone catchup +func (s *coordinatorState) waitCatchUp(ctx context.Context) error { + select { + case <-s.catchUpDoneCh: + case <-ctx.Done(): + return ctx.Err() + } + return nil } diff --git a/das/state_test.go b/das/state_test.go new file mode 100644 index 0000000000..f67e9a47af --- /dev/null +++ b/das/state_test.go @@ -0,0 +1,85 @@ +package das + +import ( + "errors" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + + "go.uber.org/multierr" +) + +func Test_coordinatorStats(t *testing.T) { + tests := []struct { + name string + state *coordinatorState + want SamplingStats + }{ + { + "basic", + &coordinatorState{ + inProgress: map[int]func() workerState{ + 1: func() workerState { + return workerState{ + job: job{ + From: 21, + To: 30, + }, + Curr: 25, + failed: []uint64{22}, + Err: errors.New("22: failed"), + } + }, + 2: func() workerState { + return workerState{ + job: job{ + From: 11, + To: 20, + }, + Curr: 15, + failed: []uint64{12, 13}, + Err: multierr.Append(errors.New("12: failed"), errors.New("13: failed")), + } + }, + }, + failed: map[uint64]int{22: 1, 23: 1, 24: 2}, + nextJobID: 0, + next: 31, + networkHead: 100, + }, + SamplingStats{ + SampledChainHead: 11, + CatchupHead: 30, + NetworkHead: 100, + Failed: map[uint64]int{22: 2, 23: 1, 24: 2, 12: 1, 13: 1}, + Workers: []WorkerStats{ + { + Curr: 25, + From: 21, + To: 30, + ErrMsg: "22: failed", + }, + { + Curr: 15, + From: 11, + To: 20, + ErrMsg: "12: failed; 13: failed", + }, + }, + Concurrency: 2, + CatchUpDone: false, + IsRunning: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stats := tt.state.unsafeStats() + sort.Slice(stats.Workers, func(i, j int) bool { + return stats.Workers[i].From > stats.Workers[j].Curr + }) + assert.Equal(t, tt.want, stats, "stats are not equal") + }) + } +} diff --git a/das/stats.go b/das/stats.go new file mode 100644 index 0000000000..3b0a61ea05 --- /dev/null +++ b/das/stats.go @@ -0,0 +1,32 @@ +package das + +// SamplingStats collects information about the DASer process. Currently, there are +// only two sampling routines: the main sampling routine which performs sampling +// over current network headers, and the `catchUp` routine which performs sampling +// over past headers from the last sampled checkpoint. +type SamplingStats struct { + // all headers before SampledChainHead were successfully sampled + SampledChainHead uint64 `json:"head_of_sampled_chain"` + // all headers before CatchupHead were submitted to sampling workers + CatchupHead uint64 `json:"head_of_catchup"` + // NetworkHead is the height of the most recent header in the network + NetworkHead uint64 `json:"network_head_height"` + // Failed contains all skipped header's heights with corresponding try count + Failed map[uint64]int `json:"failed,omitempty"` + // Workers has information about each currently running worker stats + Workers []WorkerStats `json:"workers,omitempty"` + // Concurrency currently running parallel workers + Concurrency int `json:"concurrency"` + // CatchUpDone indicates whether all known headers are sampled + CatchUpDone bool `json:"catch_up_done"` + // IsRunning tracks whether the DASer service is running + IsRunning bool `json:"is_running"` +} + +type WorkerStats struct { + Curr uint64 `json:"current"` + From uint64 `json:"from"` + To uint64 `json:"to"` + + ErrMsg string `json:"error,omitempty"` +} diff --git a/das/store.go b/das/store.go new file mode 100644 index 0000000000..1d63a5083b --- /dev/null +++ b/das/store.go @@ -0,0 +1,101 @@ +package das + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" +) + +var ( + storePrefix = datastore.NewKey("das") + checkpointKey = datastore.NewKey("checkpoint") +) + +// The checkpointStore stores/loads the DASer's checkpoint to/from +// disk using the checkpointKey. The checkpoint is stored as a struct +// representation of the latest successfully DASed state. +type checkpointStore struct { + datastore.Datastore + done +} + +// newCheckpointStore wraps the given datastore.Datastore with the `das` prefix. +func newCheckpointStore(ds datastore.Datastore) checkpointStore { + return checkpointStore{ + namespace.Wrap(ds, storePrefix), + newDone("checkpoint store")} +} + +// load loads the DAS checkpoint from disk and returns it. +func (s *checkpointStore) load(ctx context.Context) (checkpoint, error) { + bs, err := s.Get(ctx, checkpointKey) + if err != nil { + return checkpoint{}, err + } + + cp := checkpoint{} + err = json.Unmarshal(bs, &cp) + return cp, err +} + +// checkpointStore stores the given DAS checkpoint to disk. +func (s *checkpointStore) store(ctx context.Context, cp checkpoint) error { + // checkpointStore latest DASed checkpoint to disk here to ensure that if DASer is not yet + // fully caught up to network head, it will resume DASing from this checkpoint + // up to current network head + bs, err := json.Marshal(cp) + if err != nil { + return fmt.Errorf("marshal checkpoint: %w", err) + } + + if err = s.Put(ctx, checkpointKey, bs); err != nil { + return err + } + + log.Info("stored checkpoint to disk: ", cp.String()) + return nil +} + +// runBackgroundStore periodically saves current sampling state in case of DASer force quit before +// being able to store state on exit. The routine can be disabled by passing storeInterval = 0. +func (s *checkpointStore) runBackgroundStore( + ctx context.Context, + storeInterval time.Duration, + getCheckpoint func(ctx context.Context) (checkpoint, error)) { + defer s.indicateDone() + + // runBackgroundStore could be disabled by setting storeInterval = 0 + if storeInterval == 0 { + log.Info("DASer background checkpointStore is disabled") + return + } + + ticker := time.NewTicker(storeInterval) + defer ticker.Stop() + + var prev uint64 + for { + // blocked by ticker to perform storing only once in a period + select { + case <-ticker.C: + case <-ctx.Done(): + return + } + + cp, err := getCheckpoint(ctx) + if err != nil { + log.Debug("DASer coordinator checkpoint is unavailable") + continue + } + if cp.SampleFrom > prev { + if err = s.store(ctx, cp); err != nil { + log.Errorw("storing checkpoint to disk", "err", err) + } + prev = cp.SampleFrom + } + } +} diff --git a/das/subscriber.go b/das/subscriber.go new file mode 100644 index 0000000000..862cb6a2e4 --- /dev/null +++ b/das/subscriber.go @@ -0,0 +1,37 @@ +package das + +import ( + "context" + + "github.com/celestiaorg/celestia-node/header" +) + +// subscriber subscribes to notifications about new headers in the network to keep +// sampling process up-to-date with current network state. +type subscriber struct { + done +} + +func newSubscriber() subscriber { + return subscriber{newDone("subscriber")} +} + +func (s *subscriber) run(ctx context.Context, sub header.Subscription, emit listenFn) { + defer s.indicateDone() + defer sub.Cancel() + + for { + h, err := sub.NextHeader(ctx) + if err != nil { + if err == context.Canceled { + return + } + + log.Errorw("failed to get next header", "err", err) + continue + } + log.Infow("new header received via subscription", "height", h.Height) + + emit(ctx, uint64(h.Height)) + } +} diff --git a/das/worker.go b/das/worker.go new file mode 100644 index 0000000000..86fec42aa6 --- /dev/null +++ b/das/worker.go @@ -0,0 +1,99 @@ +package das + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "go.uber.org/multierr" + + "github.com/celestiaorg/celestia-node/header" +) + +type worker struct { + lock sync.Mutex + state workerState +} + +// workerState contains important information about the state of a +// current sampling routine. +type workerState struct { + job + + Curr uint64 + Err error + failed []uint64 +} + +// job represents headers interval to be processed by worker +type job struct { + id int + From uint64 + To uint64 +} + +func (w *worker) run( + ctx context.Context, + getter header.Getter, + sample sampleFn, + resultCh chan<- result) { + jobStart := time.Now() + log.Debugw("start sampling worker", "from", w.state.From, "to", w.state.To) + + for curr := w.state.From; curr <= w.state.To; curr++ { + // TODO: get headers in batches + h, err := getter.GetByHeight(ctx, curr) + if err == nil { + err = sample(ctx, h) + } + + if errors.Is(err, context.Canceled) { + // sampling worker will resume upon restart + break + } + w.setResult(curr, err) + } + + if w.state.Curr > w.state.From { + jobTime := time.Since(jobStart) + log.Infow("sampled headers", "from", w.state.From, "to", w.state.Curr, + "finished (s)", jobTime.Seconds()) + } + + select { + case resultCh <- result{ + job: w.state.job, + failed: w.state.failed, + err: w.state.Err, + }: + case <-ctx.Done(): + } +} + +func newWorker(j job) worker { + return worker{ + state: workerState{ + job: j, + Curr: j.From, + failed: make([]uint64, 0), + }, + } +} + +func (w *worker) setResult(curr uint64, err error) { + w.lock.Lock() + defer w.lock.Unlock() + if err != nil { + w.state.failed = append(w.state.failed, curr) + w.state.Err = multierr.Append(w.state.Err, fmt.Errorf("height: %v, err: %w", curr, err)) + } + w.state.Curr = curr +} + +func (w *worker) getState() workerState { + w.lock.Lock() + defer w.lock.Unlock() + return w.state +} diff --git a/go.mod b/go.mod index 9c0f3386f8..d44b1a0572 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.10.0 go.uber.org/fx v1.18.1 + go.uber.org/multierr v1.8.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f google.golang.org/grpc v1.49.0 @@ -259,7 +260,6 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.15.0 // indirect - go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect diff --git a/node/rpc_test.go b/node/rpc_test.go index adcf14dbed..228696bb63 100644 --- a/node/rpc_test.go +++ b/node/rpc_test.go @@ -189,11 +189,11 @@ func TestDASStateRequest(t *testing.T) { err = resp.Body.Close() require.NoError(t, err) }() - dasStateResp := new(rpc.DasStateResponse) + dasStateResp := new(das.SamplingStats) err = json.NewDecoder(resp.Body).Decode(dasStateResp) require.NoError(t, err) // ensure daser is running - assert.True(t, dasStateResp.SampleRoutine.IsRunning) + assert.True(t, dasStateResp.IsRunning) } func setupNodeWithModifiedRPC(t *testing.T) *Node { diff --git a/service/rpc/das.go b/service/rpc/das.go index f6369a144e..c511f4da40 100644 --- a/service/rpc/das.go +++ b/service/rpc/das.go @@ -3,27 +3,19 @@ package rpc import ( "encoding/json" "net/http" - - "github.com/celestiaorg/celestia-node/das" ) const ( dasStateEndpoint = "/daser/state" ) -// DasStateResponse encompasses the fields returned in response -// to a `/daser` request. -type DasStateResponse struct { - SampleRoutine das.RoutineState `json:"sample_routine"` - CatchUpRoutine das.JobInfo `json:"catch_up_routine"` -} - func (h *Handler) handleDASStateRequest(w http.ResponseWriter, r *http.Request) { - dasState := new(DasStateResponse) - dasState.SampleRoutine = h.das.SampleRoutineState() - dasState.CatchUpRoutine = h.das.CatchUpRoutineState() - - resp, err := json.Marshal(dasState) + stats, err := h.das.SamplingStats(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, dasStateEndpoint, err) + return + } + resp, err := json.Marshal(stats) if err != nil { writeError(w, http.StatusInternalServerError, dasStateEndpoint, err) return From 3a1e655466e2f90451bda4cadb3342f163c131ec Mon Sep 17 00:00:00 2001 From: Wondertan Date: Tue, 23 Aug 2022 19:50:09 +0200 Subject: [PATCH 0064/1008] docs(adr): initial unfinished draft for ADR #011 --- docs/adr/adr-11-blocksync-overhaul.md | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 docs/adr/adr-11-blocksync-overhaul.md diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md new file mode 100644 index 0000000000..d90f65d288 --- /dev/null +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -0,0 +1,69 @@ +# ADR #011: BlockSync Overhaul + +## Changelog + +- 23.08.22: Initial unfinished drafy + +## Authors + +- @Wondertan + +> I start to like writing ADRs, step-by-step. Also, there is a trick that helps: imagine like you are talking to a dev +> who just joined the team to onboard him. + +## Context + +### Status Qou + +Current block synchronization is done over Bitswap, traversing NMT trees of rows and columns of data square quadrants. +We know from empirical evidence that it takes more than 200 seconds(~65000 network requests) to download a 4MB block of +256kb shares, which is unacceptable and must be much less than the block time(15/30sec). + +TODO: Simple diagram with problem visualization + +The DASing, on the other hand, shows acceptable metrics for the block sizes we are aiming for initially. In the case of +the same block, a DAS operation takes 50ms * 8(technically 9) blocking requests, which is ~400ms in an ideal scenario +(excluding disk IO). With higher latency and bigger block size(higher NMT trees), the DASIng operation could take much +more(TODO type of the grow, e.g., quadratic/etc.), but is likely(TODO proof?) to be less than a block time. + +Getting data by namespace lies between BlockSync and DASing, where more data equals more requests and more time to +fulfill the requests. + +### Mini Node Offsite 2022 Berlin + +To facilitate and speedup the resolution of the problem we decided to make a team gathering in Berlin for 4 days. With +the help of preliminary preparations by the @Wondertan and invited guest @willscot, we were able to find a solution +to match all the requirements: + +- Sync time less than block time(ideally sub-second) +- Data by namespace less than block time(ideally sub-second) +- Pragmatic timeframe + - We need this done before incentivized testnet + - So we don't have time to redesign protocol from scratch +- Keep Bitswap as it suffices DAS and solves data withholding attack + - Mainly keeping existing Bitswap logic as a fallback mechanism for reconstruction from light nodes case +- Keeping random hash-addressed access to shares for Bitswap to work + +### ADR Goals + +This ADR is intended to outline design decisions for Block syncing mechanism/protocol improvements together with +block data storage. In a nutshell, the decision is to use CAR format(with some modification), together with dagstore +for extended block storage and custom p2p Req/Resp protocol for accessing the block data and data by namespace id +in the happy path. + +// TODO links + +### Alternative Approaches + +#### Block Storage + +- Extended blocks as a set of share blobs and Merkle proofs in global store (*current approach with KVStore*) +- Extended block as a single blob only(computing Merkle proofs) +- Extended block as a single blob and Merkle proofs +- Extended block as a set of DAG/CAR blobs +- Extended block as a single DAG/CAR blob + +#### Block Syncing + +- GraphSync +- Bitswap(current) From 77e7626024abdacd7ba588368aeb8321729bc529 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Thu, 1 Sep 2022 23:03:26 +0200 Subject: [PATCH 0065/1008] docs(adr): block/eds storage design --- docs/adr/adr-11-blocksync-overhaul.md | 207 ++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 11 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index d90f65d288..9f10554343 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -1,8 +1,9 @@ -# ADR #011: BlockSync Overhaul +# ADR #011: Block Data Sync Overhaul ## Changelog -- 23.08.22: Initial unfinished drafy +- 23.08.22: Initial unfinished draft +- 01.09.22: Block/EDS Storage design ## Authors @@ -11,11 +12,18 @@ > I start to like writing ADRs, step-by-step. Also, there is a trick that helps: imagine like you are talking to a dev > who just joined the team to onboard him. +## Glossary + +- LN - Light Node +- FN - Full Node +- BN - Bridge Node +- EDS(Extended Data Square) - plain Block data omitting headers and other metadata. + ## Context ### Status Qou -Current block synchronization is done over Bitswap, traversing NMT trees of rows and columns of data square quadrants. +Current block data synchronization is done over Bitswap, traversing NMT trees of rows and columns of data square quadrants. We know from empirical evidence that it takes more than 200 seconds(~65000 network requests) to download a 4MB block of 256kb shares, which is unacceptable and must be much less than the block time(15/30sec). @@ -32,8 +40,8 @@ fulfill the requests. ### Mini Node Offsite 2022 Berlin To facilitate and speedup the resolution of the problem we decided to make a team gathering in Berlin for 4 days. With -the help of preliminary preparations by the @Wondertan and invited guest @willscot, we were able to find a solution -to match all the requirements: +the help of preliminary preparations by @Wondertan and invited guest @willscott, we were able to find a solution +in 2 days to match the following requirements: - Sync time less than block time(ideally sub-second) - Data by namespace less than block time(ideally sub-second) @@ -44,20 +52,190 @@ to match all the requirements: - Mainly keeping existing Bitswap logic as a fallback mechanism for reconstruction from light nodes case - Keeping random hash-addressed access to shares for Bitswap to work -### ADR Goals +### Decision This ADR is intended to outline design decisions for Block syncing mechanism/protocol improvements together with -block data storage. In a nutshell, the decision is to use CAR format(with some modification), together with dagstore -for extended block storage and custom p2p Req/Resp protocol for accessing the block data and data by namespace id -in the happy path. +block data storage. In a nutshell, the decision is to use ___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ +and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ for ___extended block storage___ +and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by namespace id) in the happy path. + +#### Key Design Decision + +- __FNs/BNs store EDSes as [CAR files](https://ipld.io/specs/transport/car/carv2/).__ CAR format provides an efficient +way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read sequentially +in one read and transferred over the wire. Additionally, [CARv2](https://ipld.io/specs/transport/car/carv2/) introduces +pluggable indexes over the blob allowing efficient random access in one read(if the index is cached in memory). + + - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, however they should not be transferred +in case of EDS, so keeping them separately is a better strategy which `DAGStore` provides out of the box. + +- __FNs/BNs run a single instance of `DAGStore` to manage CARv1 block files.__ + +- __LNs DASing remains untouched__. Both the networking protocol and storage for LNs remains untouched as it fulfills +the requirements. This includes Bitswap as backbone protocol for requesting samples and global Badger KVStore. + +- __FNs/BNs manage a top-level index for _hash_ to _CARv1 block file_ mapping.__ Current DASing for LNs requires FNs/BNs to serve +simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can quickly +serve requests. However, the indexing has a major consequence - data usage, so further, this index will have to be +removed. LNs know which block they sample and can provide this data together with sample request over Bitswap. This +requires us to either facilitate implementation of [Bitswap's auth extention](https://github.com/ipfs/specs/pull/270) +or proposing custom Bitswap message extention. + +- __New libp2p based Exchange protocol is introduced for data/share exchange purposes.__ FNs/BNs servers LN client. + +### Detailed Design + +#### Block/EDS Storage + +The new block storage design is solely additive. Meaning that all the existing storage related components and functionality +are kept with additional components introduced. Altogether, existing and new components will be recomposed to serve the +foundation of our improved block EDS storage subsystem. + +##### `share.EDSStore` + +Mainly, a new addition is `EDSStore` type in `share` pkg, which manages every EDS on the +disk a FNs/BNs keep. Each EDS together with its Merkle Proofs serializes into CARv1 file with a special +traversal algorithm(TODO Explain here or refer as another section). All the serialized CARv1 file blobs are managed in +OS FS via underlying DAGStore. + +The introduced `EDSStore` also maintains a top-level index enabling granular and efficient random access to every share +and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore`(TODO link) interface +implementation to achieve the access. However, this comes with additional storage costs for indices. The main use-case +is randomized sampling over the whole chain of EDS block data and getting data by namespace. + +```go +type EDSStore struct { + cars dagstore.DAGStore + idx index.FullIndexRepo + mounts *mount.Registry + ... +} +``` + +##### `share.EDSStore.Put` + +To write an entire EDS `Put` method is introduced. Internally, it + +- Serializes the EDS into the CARv1(*) //TODO Describe in something like EDStoCAR func and its reverse form +- Wraps it with DAGStore's [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) +- Converts `DataRoot` into the `shard.Key` +- Registers the Mount as a shard on the DAGStore + +NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS +so this is out of scope of the document. + +```go +// Put stores the given data square with DataRoot as key. +// +// The square is verified on the Exchange level and Put only stores the square trusting it. +// Put serializes the full EDS into a CARv1 file internally and mounts it onto the DAGStore. +// The resulting file stores all the shares and NMT Merkle Proofs of the EDS. +func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error +``` + +##### `share.EDSStore.GetCAR` + +To read an EDS as a byte stream `GetCAR` method is introduced. Internally it + +- Converts `DataRoot` into the `shard.Key` +- Gets Mount by Key from `mount.Registry` +- Return Reader from `Mount.Fetch` + +NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` +and closing it when operation is done. + +```go +// GetCAR takes DataRoot and returns a buffered reader to respective EDS serialized as CARv1 file. +// +// The Reader strictly reads the first quadrant(1/4) of EDS omitting all the NMT Merkle proofs. +// Integrity of the store data is not verified. +// +// Caller must Close returned reader after reading. +func (s *Store) GetCAR(context.Context, DataRoot) (io.ReadCloser, error) +``` + +##### `share.EDSStore.Blockstore` -// TODO links +`Blockstore` method return a `Blockstore` interface implementation instance, providing random access over share and NMT +Merkle proof in every stored EDS. It is required for FNs/BNs to serve DAS requests over the Bitswap and for reading data +by namespace. // TODO Link to the section + +There is a [frozen/un-merged implementation](https://github.com/filecoin-project/dagstore/pull/116) of `Blockstore` +over `DAGStore` and CARv2 indexes. + +NOTE: We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs. + +```go +// Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS +// registered on the Store. NOTE: The Blockstore does not store full Celestia Blocks, but IPFS blocks. +// We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes, so that Bitswap can access those. +func (s *Store) Blockstore() blockstore.Blockstore + +``` + +##### `share.EDSStore.Get` + +To read an entire EDS `Get` method is introduced. Internally it: + +- Gets serialized EDS Reader via `GetCAR` +- Deserializes EDS // TODO Link to its own section + - Fills new `rsmt2d.ExtendedDataSquare` one by one with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) +- Recomputes and verifies against DataRoot and returns + +NOTE: It's not necessary, but API ergonomics/symmetry nice-to-have + +```go +// Get reads EDS out of Store by given DataRoot. +// +// It reads only one quadrant(1/4) of the EDS and verifies integrity of the stored data by recomputing it. +func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, error) + +``` + +##### `share.EDSStore.Remove` + +To remove stored EDS `Remove` methods is introduced. Internally it: + +- Converts `DataRoot` into the `shard.Key` +- Removes Mount/Shard from `DAGStore` + - `DAGStore` cleans up indices itself +- Removes FS/File mount of CARv1 file from disk + +NOTE: It's not necessary, but API ergonomics/symmetry nice-to-have + +```go +// Remove removes EDS from Store by given DataRoot. +func (s *Store) Remove(context.Context, DataRoot) error + +``` + +#### Reading Data By Namespace + +Generally stays unchanged with minor edits: + +- `share/ipld.GetByNamespace` is kept to load data from disk only and not from network anymore + - Using `Blockservice` with offline exchange // TODO Links + - Using `Blockstore` provided by `EDSStore` +- `share/ipld.GetByNamespace` is extended to return NMT Merkle proofs + - Similar to `share/ipld.GetProofsForShares` + - Ensure merkle proofs are not duplicated! + +Alternatively, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace` returning +CARv1 Reader with encoded shares and NMT Merkle Proofs. + +##### `node.Store` + +// TODO Elaborate on how FS/File DAGStore mounts work over `node.Store` + `index.FullIndexRepo` + +##### Relations with Node Types + +// TODO Elaborate on node relations ### Alternative Approaches #### Block Storage -- Extended blocks as a set of share blobs and Merkle proofs in global store (*current approach with KVStore*) +- Extended blocks as a set of share blobs and Merkle proofs in global store (_current approach with KVStore_) - Extended block as a single blob only(computing Merkle proofs) - Extended block as a single blob and Merkle proofs - Extended block as a set of DAG/CAR blobs @@ -67,3 +245,10 @@ in the happy path. - GraphSync - Bitswap(current) + +### Considerations + +- EDS to/from CARv2 converting performance +Current sync design assumes two converts from CAR to EDS on the protocol layer and back to CAR when storing the EDS. +Rsmt2d allocates on most operations with individual shares and for bigger blocks during sync this allocs puts significant +pressure on GC. One way to substantially alleviate this is to integrate bytes buffer pool into rmst2d From 7752309f123faffd0ffc25e1475e8488c0400a56 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 2 Sep 2022 13:34:08 +0200 Subject: [PATCH 0066/1008] docs(adr): serde for EDS --- docs/adr/adr-11-blocksync-overhaul.md | 98 ++++++++++++++++++++------- 1 file changed, 72 insertions(+), 26 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 9f10554343..181109099e 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -4,6 +4,7 @@ - 23.08.22: Initial unfinished draft - 01.09.22: Block/EDS Storage design +- 02.09.22: serde for EDS ## Authors @@ -59,21 +60,19 @@ block data storage. In a nutshell, the decision is to use ___[CAR format](https: and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by namespace id) in the happy path. -#### Key Design Decision +#### Key Design Decisions -- __FNs/BNs store EDSes as [CAR files](https://ipld.io/specs/transport/car/carv2/).__ CAR format provides an efficient -way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read sequentially -in one read and transferred over the wire. Additionally, [CARv2](https://ipld.io/specs/transport/car/carv2/) introduces -pluggable indexes over the blob allowing efficient random access in one read(if the index is cached in memory). +- __FNs/BNs store EDSes serialized as [CAR files](https://ipld.io/specs/transport/car).__ CAR format provides an +efficient way to store Merkle DAG data, like EDS with NMT. It packs such DAG data into a single blob which can be read +sequentially in one read and transferred over the wire. Additionally, [CARv2](https://ipld.io/specs/transport/car/carv2/) +introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read +(if the index is cached in memory). - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, however they should not be transferred in case of EDS, so keeping them separately is a better strategy which `DAGStore` provides out of the box. - __FNs/BNs run a single instance of `DAGStore` to manage CARv1 block files.__ -- __LNs DASing remains untouched__. Both the networking protocol and storage for LNs remains untouched as it fulfills -the requirements. This includes Bitswap as backbone protocol for requesting samples and global Badger KVStore. - - __FNs/BNs manage a top-level index for _hash_ to _CARv1 block file_ mapping.__ Current DASing for LNs requires FNs/BNs to serve simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can quickly serve requests. However, the indexing has a major consequence - data usage, so further, this index will have to be @@ -81,7 +80,10 @@ removed. LNs know which block they sample and can provide this data together wit requires us to either facilitate implementation of [Bitswap's auth extention](https://github.com/ipfs/specs/pull/270) or proposing custom Bitswap message extention. -- __New libp2p based Exchange protocol is introduced for data/share exchange purposes.__ FNs/BNs servers LN client. +- __LNs DASing remains untouched__. Both the networking protocol and storage for LNs remains untouched as it fulfills + the requirements. This includes Bitswap as backbone protocol for requesting samples and global Badger KVStore. + +- __New libp2p based Exchange protocol is introduced for data/share exchange purposes.__ FNs/BNs servers LNs clients. ### Detailed Design @@ -89,7 +91,53 @@ or proposing custom Bitswap message extention. The new block storage design is solely additive. Meaning that all the existing storage related components and functionality are kept with additional components introduced. Altogether, existing and new components will be recomposed to serve the -foundation of our improved block EDS storage subsystem. +foundation of our improved block storage subsystem. + +The central data structure representing Celestia block data is EDS(`rsmt2d.ExtendedDataSquare`) and the new storage design +is focused around storing entire EDSes as a whole rather than a set of individual chunks s.t. storage subsystem +can handle storing and streaming/serving blocks of 4mb sizes and more. + +##### EDS Serde + +Storing EDS as a whole requires EDS (de)serialization. For this the [CAR format](https://ipld.io/specs/transport/car) is +chosen. + +###### `share.WriteEDS` + +To write EDS into a stream/file `WriteEDS` is introduced. +// TODO Describe logic + +NOTE: CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file and +there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses shares +and Merkle Proofs in depth-first manner packing them in a CAR file. However, this is incompatible with the requirement +to truncate the CAR file to get strictly the first quadrant out of it without NMT proofs, so serialization must be +different from the utility to support that. + +NOTE2: Alternatively to `WriteEDS`, and `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic +and efficient in some cases, with the cost of more complex implementation. + +```go +// WriteEDS writes whole EDS into given io.Writer as CARv1 file. +// All its shares and recomputed NMT proofs. +func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error +``` + +###### `share.ReadEDS` + +To read EDS out of stream/file `ReadEDS` is introduced. Internally, it + +- Imports EDS with an empty pre-allocated slice. NOTE: Size can be taken from DataRoot +- Wraps given io.Reader with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) +- Reads out blocks one by one and fills up the EDS quadrant via `EDS.SetCell` +- Recomputes and validates via `EDS.Repair` + +```go +// ReadEDS reads EDS quadrant(1/4) from io.Reader CAR file. +// +// It expects strictly first EDS quadrant(top left). +// Returned EDS is guaranteed to be full and valid against DataRoot, otherwise ReadEDS errors. +func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, error) +``` ##### `share.EDSStore` @@ -105,7 +153,7 @@ is randomized sampling over the whole chain of EDS block data and getting data b ```go type EDSStore struct { - cars dagstore.DAGStore + dgstr dagstore.DAGStore idx index.FullIndexRepo mounts *mount.Registry ... @@ -116,10 +164,10 @@ type EDSStore struct { To write an entire EDS `Put` method is introduced. Internally, it -- Serializes the EDS into the CARv1(*) //TODO Describe in something like EDStoCAR func and its reverse form -- Wraps it with DAGStore's [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) -- Converts `DataRoot` into the `shard.Key` -- Registers the Mount as a shard on the DAGStore +- Serializes the EDS into the CARv1 via `share.WriteEDS` +- Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) +- Converts `DataRoot` into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Registers the Mount as a Shard on the `DAGStore` NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS so this is out of scope of the document. @@ -137,9 +185,9 @@ func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error To read an EDS as a byte stream `GetCAR` method is introduced. Internally it -- Converts `DataRoot` into the `shard.Key` -- Gets Mount by Key from `mount.Registry` -- Return Reader from `Mount.Fetch` +- Converts `DataRoot` into the [`shard.Key`]( +- Gets Mount by Key from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) +- Return Reader from [`Mount.Fetch`](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go#L71) NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` and closing it when operation is done. @@ -177,12 +225,10 @@ func (s *Store) Blockstore() blockstore.Blockstore To read an entire EDS `Get` method is introduced. Internally it: -- Gets serialized EDS Reader via `GetCAR` -- Deserializes EDS // TODO Link to its own section - - Fills new `rsmt2d.ExtendedDataSquare` one by one with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) -- Recomputes and verifies against DataRoot and returns +- Gets serialized EDS Reader via `Store.GetCAR` +- Deserializes EDS and validates it via `share.ReadEDS` -NOTE: It's not necessary, but API ergonomics/symmetry nice-to-have +NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have ```go // Get reads EDS out of Store by given DataRoot. @@ -196,12 +242,12 @@ func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, erro To remove stored EDS `Remove` methods is introduced. Internally it: -- Converts `DataRoot` into the `shard.Key` +- Converts `DataRoot` into the [`shard.Key`]( - Removes Mount/Shard from `DAGStore` - `DAGStore` cleans up indices itself -- Removes FS/File mount of CARv1 file from disk +- Removes FS/File Mount of CARv1 file from disk -NOTE: It's not necessary, but API ergonomics/symmetry nice-to-have +NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have ```go // Remove removes EDS from Store by given DataRoot. From 7cf2e6dd93b6995428aa16781f8f21deb9ff8bd6 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 2 Sep 2022 13:48:09 +0200 Subject: [PATCH 0067/1008] docs(adr): improve API comments --- docs/adr/adr-11-blocksync-overhaul.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 181109099e..3928f06e34 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -19,6 +19,7 @@ - FN - Full Node - BN - Bridge Node - EDS(Extended Data Square) - plain Block data omitting headers and other metadata. +- NMT - Namespaced Merkle Tree ## Context @@ -87,6 +88,8 @@ or proposing custom Bitswap message extention. ### Detailed Design +> All the comments on the API definitions should be preserved and potentially improved by implementations. + #### Block/EDS Storage The new block storage design is solely additive. Meaning that all the existing storage related components and functionality @@ -176,8 +179,8 @@ so this is out of scope of the document. // Put stores the given data square with DataRoot as key. // // The square is verified on the Exchange level and Put only stores the square trusting it. -// Put serializes the full EDS into a CARv1 file internally and mounts it onto the DAGStore. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. +// Additionally, the file gets indexed s.t. Store.Blockstore can access them. func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error ``` @@ -247,10 +250,13 @@ To remove stored EDS `Remove` methods is introduced. Internally it: - `DAGStore` cleans up indices itself - Removes FS/File Mount of CARv1 file from disk -NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have +NOTES: + +- It's not necessary, but an API ergonomics/symmetry nice-to-have +- GC logic on the DAGStore has to be investigated so that Removing is correctly implemented ```go -// Remove removes EDS from Store by given DataRoot. +// Remove removes EDS from Store by given DataRoot and cleans up all the indexing. func (s *Store) Remove(context.Context, DataRoot) error ``` From fb4308587705be636c97d5370bc9eaaea8ed02ef Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 2 Sep 2022 14:07:48 +0200 Subject: [PATCH 0068/1008] docs(adr): minor improvements for Key Design Decion and Consideration sections --- docs/adr/adr-11-blocksync-overhaul.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 3928f06e34..6a91224868 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -70,16 +70,16 @@ introduces pluggable indexes over the blob allowing efficient random access to s (if the index is cached in memory). - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, however they should not be transferred -in case of EDS, so keeping them separately is a better strategy which `DAGStore` provides out of the box. - -- __FNs/BNs run a single instance of `DAGStore` to manage CARv1 block files.__ +in case of EDS, so keeping them separately is a better strategy. Also CARv2 takes more space for metadata which is not +needed in our case. - __FNs/BNs manage a top-level index for _hash_ to _CARv1 block file_ mapping.__ Current DASing for LNs requires FNs/BNs to serve simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can quickly -serve requests. However, the indexing has a major consequence - data usage, so further, this index will have to be -removed. LNs know which block they sample and can provide this data together with sample request over Bitswap. This -requires us to either facilitate implementation of [Bitswap's auth extention](https://github.com/ipfs/specs/pull/270) -or proposing custom Bitswap message extention. +serve requests. + +- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CARv1 block files.__ +DAGStore provides both the top-level indexing and CARv2 based indexing per each CARv1 file. In essence, it's an engine +for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. - __LNs DASing remains untouched__. Both the networking protocol and storage for LNs remains untouched as it fulfills the requirements. This includes Bitswap as backbone protocol for requesting samples and global Badger KVStore. @@ -300,7 +300,12 @@ CARv1 Reader with encoded shares and NMT Merkle Proofs. ### Considerations -- EDS to/from CARv2 converting performance -Current sync design assumes two converts from CAR to EDS on the protocol layer and back to CAR when storing the EDS. +- ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares and for bigger blocks during sync this allocs puts significant pressure on GC. One way to substantially alleviate this is to integrate bytes buffer pool into rmst2d + +- ___Disk usage increase from top-level index.___ This is temporary solution. The index will have to be removed. +LNs know which block they sample and can provide DataRoot together with sample request over Bitswap, removing +the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extention](https://github.com/ipfs/specs/pull/270) +or proposing custom Bitswap message extention. Subsequently, the Blockstore implementation provided via `EDSStore` would +have to be changed to take expect DataRoot being passed through the `context.Context`. From f16a6f9f36e192510cb4132d80767440fd7b1b25 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 2 Sep 2022 16:02:16 +0200 Subject: [PATCH 0069/1008] docs(adr): elaborate on how CARv2 files are going to be stored via FS --- docs/adr/adr-11-blocksync-overhaul.md | 68 +++++++++++++++++---------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 6a91224868..2f426a0f4f 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -144,33 +144,55 @@ func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, ##### `share.EDSStore` -Mainly, a new addition is `EDSStore` type in `share` pkg, which manages every EDS on the -disk a FNs/BNs keep. Each EDS together with its Merkle Proofs serializes into CARv1 file with a special -traversal algorithm(TODO Explain here or refer as another section). All the serialized CARv1 file blobs are managed in -OS FS via underlying DAGStore. +To manage every EDS on the disk FNs/BNs keep `EDSStore` type is introduced in `share` pkg. Each EDS together with its +Merkle Proofs serializes into CARv1 file. All the serialized CARv1 file blobs are stored as OS FS files as DAGStore +[Mounts](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go). -The introduced `EDSStore` also maintains a top-level index enabling granular and efficient random access to every share -and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore`(TODO link) interface -implementation to achieve the access. However, this comes with additional storage costs for indices. The main use-case -is randomized sampling over the whole chain of EDS block data and getting data by namespace. +The introduced `EDSStore` also maintains (via DAGStore) a top-level index enabling granular and efficient random access +to every share and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore` interface +implementation to achieve the access. The main use-case is randomized sampling over the whole chain of EDS block data +and getting data by namespace. + +EDSStore constructor should instantiate ```go type EDSStore struct { + basepath string dgstr dagstore.DAGStore - idx index.FullIndexRepo + topIdx index.Inverted + carIdx index.FullIndexRepo mounts *mount.Registry ... } + +// NewEDSStore constructs EDStore over OS directory to store CARv1 files of EDSes and indices for them. +// Datastore is used to keep inverted/top-level index. +func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { + topIdx := index.NewInverted(datastore) + carIdx := index.NewFSRepo(basepath + "/index") + mounts := mount.NewRegistry() + return &EDSStore{ + basepath: basepath, + dgst: dagstore.New(dagstore.Config{...}), + topIdx: index.NewInverted(datastore), + carIdx: index.NewFSRepo(basepath + "/index") + mounts: mounts, + } +} + ``` ##### `share.EDSStore.Put` -To write an entire EDS `Put` method is introduced. Internally, it +To write an entire EDS `Put` method is introduced. Internally it -- Serializes the EDS into the CARv1 via `share.WriteEDS` +- Opens a file under `storepath/DataRoot` path +- Serializes the EDS into the file via `share.WriteEDS` - Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) - Converts `DataRoot` into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) -- Registers the Mount as a Shard on the `DAGStore` +- Registers the `Mount` as a `Shard` on the `DAGStore` + - This register the `Mount` in [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) +passed to `DAGStore`. NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS so this is out of scope of the document. @@ -189,11 +211,11 @@ func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error To read an EDS as a byte stream `GetCAR` method is introduced. Internally it - Converts `DataRoot` into the [`shard.Key`]( -- Gets Mount by Key from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) -- Return Reader from [`Mount.Fetch`](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go#L71) +- Gets Mount by `shard.Key` from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) +- Returns `io.ReadeCloser` from [`Mount.Fetch`](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go#L71) NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` -and closing it when operation is done. +and closing it when operation is done. This has to be confirmed. ```go // GetCAR takes DataRoot and returns a buffered reader to respective EDS serialized as CARv1 file. @@ -228,7 +250,7 @@ func (s *Store) Blockstore() blockstore.Blockstore To read an entire EDS `Get` method is introduced. Internally it: -- Gets serialized EDS Reader via `Store.GetCAR` +- Gets serialized EDS `io.Reader` via `Store.GetCAR` - Deserializes EDS and validates it via `share.ReadEDS` NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have @@ -246,9 +268,9 @@ func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, erro To remove stored EDS `Remove` methods is introduced. Internally it: - Converts `DataRoot` into the [`shard.Key`]( -- Removes Mount/Shard from `DAGStore` - - `DAGStore` cleans up indices itself -- Removes FS/File Mount of CARv1 file from disk +- Removes respective `Mount` and `Shard` from `DAGStore` + - `DAGStore` cleans up indices and `mount.Registery` itself +- Removes `FileMount` file of CARv1 file from disk under `storepath/DataRoot` path NOTES: @@ -275,13 +297,9 @@ Generally stays unchanged with minor edits: Alternatively, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace` returning CARv1 Reader with encoded shares and NMT Merkle Proofs. -##### `node.Store` - -// TODO Elaborate on how FS/File DAGStore mounts work over `node.Store` + `index.FullIndexRepo` - -##### Relations with Node Types +##### EDSStore Directory Path -// TODO Elaborate on node relations +The EDSStore expects a directory to store CAR files and indices to. The path should be gotten based on `node.Store.Path` ### Alternative Approaches From a505d3ca7fb7716c32ae73d029c9df53db084fa5 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 9 Sep 2022 16:38:49 +0200 Subject: [PATCH 0070/1008] docs(adr-11): apply grammar/stylistic suggestions from @distractedm1nd --- docs/adr/adr-11-blocksync-overhaul.md | 42 +++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 2f426a0f4f..f425535bde 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -23,7 +23,7 @@ ## Context -### Status Qou +### Status Quo Current block data synchronization is done over Bitswap, traversing NMT trees of rows and columns of data square quadrants. We know from empirical evidence that it takes more than 200 seconds(~65000 network requests) to download a 4MB block of @@ -36,8 +36,8 @@ the same block, a DAS operation takes 50ms * 8(technically 9) blocking requests, (excluding disk IO). With higher latency and bigger block size(higher NMT trees), the DASIng operation could take much more(TODO type of the grow, e.g., quadratic/etc.), but is likely(TODO proof?) to be less than a block time. -Getting data by namespace lies between BlockSync and DASing, where more data equals more requests and more time to -fulfill the requests. +Getting data by namespace also needs to be improved. The time it takes currently lies between BlockSync and DASing, +where more data equals more requests and more time to fulfill the requests. ### Mini Node Offsite 2022 Berlin @@ -49,9 +49,9 @@ in 2 days to match the following requirements: - Data by namespace less than block time(ideally sub-second) - Pragmatic timeframe - We need this done before incentivized testnet - - So we don't have time to redesign protocol from scratch -- Keep Bitswap as it suffices DAS and solves data withholding attack - - Mainly keeping existing Bitswap logic as a fallback mechanism for reconstruction from light nodes case + - We don't have time to redesign the protocol from scratch +- Keep Bitswap, as it suffices for DAS and solves the data withholding attack + - Existing Bitswap logic kept as a fallback mechanism for the case of reconstruction from light nodes - Keeping random hash-addressed access to shares for Bitswap to work ### Decision @@ -110,11 +110,11 @@ chosen. To write EDS into a stream/file `WriteEDS` is introduced. // TODO Describe logic -NOTE: CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file and -there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses shares -and Merkle Proofs in depth-first manner packing them in a CAR file. However, this is incompatible with the requirement -to truncate the CAR file to get strictly the first quadrant out of it without NMT proofs, so serialization must be -different from the utility to support that. +NOTE: CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file +and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses +shares and Merkle Proofs in depth-first manner packing them in a CAR file. However, this is incompatible with the +requirement of being able to truncate the CAR file to read out __only__ the first quadrant out of it without NMT proofs, +so serialization must be different from the utility to support that. NOTE2: Alternatively to `WriteEDS`, and `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic and efficient in some cases, with the cost of more complex implementation. @@ -127,7 +127,7 @@ func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error ###### `share.ReadEDS` -To read EDS out of stream/file `ReadEDS` is introduced. Internally, it +To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it - Imports EDS with an empty pre-allocated slice. NOTE: Size can be taken from DataRoot - Wraps given io.Reader with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) @@ -135,18 +135,18 @@ To read EDS out of stream/file `ReadEDS` is introduced. Internally, it - Recomputes and validates via `EDS.Repair` ```go -// ReadEDS reads EDS quadrant(1/4) from io.Reader CAR file. -// +// ReadEDS reads an EDS quadrant(1/4) from an io.Reader CAR file.// // It expects strictly first EDS quadrant(top left). -// Returned EDS is guaranteed to be full and valid against DataRoot, otherwise ReadEDS errors. +// The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS errors. func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, error) ``` ##### `share.EDSStore` -To manage every EDS on the disk FNs/BNs keep `EDSStore` type is introduced in `share` pkg. Each EDS together with its -Merkle Proofs serializes into CARv1 file. All the serialized CARv1 file blobs are stored as OS FS files as DAGStore -[Mounts](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go). +To manage every EDS on the disk, FNs/BNs keep an `EDSStore`. The `EDSStore` type is introduced in the `share` pkg. +Each EDS together with its Merkle Proofs serializes into CARv1 file. All the serialized CARv1 file blobs are mounted on +DAGStore via [Local FS Mounts](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#mounts) and registered +as [Shards](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#shards). The introduced `EDSStore` also maintains (via DAGStore) a top-level index enabling granular and efficient random access to every share and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore` interface @@ -218,7 +218,7 @@ NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAc and closing it when operation is done. This has to be confirmed. ```go -// GetCAR takes DataRoot and returns a buffered reader to respective EDS serialized as CARv1 file. +// GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as CARv1 file. // // The Reader strictly reads the first quadrant(1/4) of EDS omitting all the NMT Merkle proofs. // Integrity of the store data is not verified. @@ -250,8 +250,8 @@ func (s *Store) Blockstore() blockstore.Blockstore To read an entire EDS `Get` method is introduced. Internally it: -- Gets serialized EDS `io.Reader` via `Store.GetCAR` -- Deserializes EDS and validates it via `share.ReadEDS` +- Gets a serialized EDS `io.Reader` via `Store.GetCAR` +- Deserializes the EDS and validates it via `share.ReadEDS` NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have From 22406cb68fb8f4b23298b0f79172899b490433fa Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 9 Sep 2022 16:51:36 +0200 Subject: [PATCH 0071/1008] docs(adr-11): clarify CARv2 vs CARv1 --- docs/adr/adr-11-blocksync-overhaul.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index f425535bde..7169ca0762 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -69,17 +69,16 @@ sequentially in one read and transferred over the wire. Additionally, [CARv2](ht introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read (if the index is cached in memory). - - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, however they should not be transferred -in case of EDS, so keeping them separately is a better strategy. Also CARv2 takes more space for metadata which is not -needed in our case. - -- __FNs/BNs manage a top-level index for _hash_ to _CARv1 block file_ mapping.__ Current DASing for LNs requires FNs/BNs to serve -simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can quickly -serve requests. - -- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CARv1 block files.__ -DAGStore provides both the top-level indexing and CARv2 based indexing per each CARv1 file. In essence, it's an engine -for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. +- __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs +to serve simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can +quickly serve requests. + +- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block +files.__ DAGStore provides both the top-level indexing and CARv2 based indexing per each CAR file. In essence, it's an +engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. + - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2 based +indexing as well. Usage of CARv1 keeps only one copy of index, stores/transfers less metadata per EDS and simplifies +reading EDS from file. - __LNs DASing remains untouched__. Both the networking protocol and storage for LNs remains untouched as it fulfills the requirements. This includes Bitswap as backbone protocol for requesting samples and global Badger KVStore. From 9a27afb38f8c8f6927567b5bf52e92466154d999 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 9 Sep 2022 19:04:19 +0200 Subject: [PATCH 0072/1008] docs(adr-11): correct usage of mount.Registry; make all NOTEs italic --- docs/adr/adr-11-blocksync-overhaul.md | 50 ++++++++++++++------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 7169ca0762..575fbfe019 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -69,14 +69,14 @@ sequentially in one read and transferred over the wire. Additionally, [CARv2](ht introduces pluggable indexes over the blob allowing efficient random access to shares and NMT Proofs in one read (if the index is cached in memory). -- __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs -to serve simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can +- __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs +to serve simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can quickly serve requests. -- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block +- __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block files.__ DAGStore provides both the top-level indexing and CARv2 based indexing per each CAR file. In essence, it's an engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. - - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2 based + - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2 based indexing as well. Usage of CARv1 keeps only one copy of index, stores/transfers less metadata per EDS and simplifies reading EDS from file. @@ -109,14 +109,14 @@ chosen. To write EDS into a stream/file `WriteEDS` is introduced. // TODO Describe logic -NOTE: CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file +_NOTE: CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses shares and Merkle Proofs in depth-first manner packing them in a CAR file. However, this is incompatible with the requirement of being able to truncate the CAR file to read out __only__ the first quadrant out of it without NMT proofs, -so serialization must be different from the utility to support that. +so serialization must be different from the utility to support that._ -NOTE2: Alternatively to `WriteEDS`, and `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic -and efficient in some cases, with the cost of more complex implementation. +_NOTE2: Alternatively to `WriteEDS`, and `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic +and efficient in some cases, with the cost of more complex implementation._ ```go // WriteEDS writes whole EDS into given io.Writer as CARv1 file. @@ -152,8 +152,6 @@ to every share and/or Merkle proof over every registered CARv1 file. The `EDSSto implementation to achieve the access. The main use-case is randomized sampling over the whole chain of EDS block data and getting data by namespace. -EDSStore constructor should instantiate - ```go type EDSStore struct { basepath string @@ -170,17 +168,24 @@ func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { topIdx := index.NewInverted(datastore) carIdx := index.NewFSRepo(basepath + "/index") mounts := mount.NewRegistry() + r := mount.NewRegistry() + err = r.Register("fs", &mount.FSMount{FS: os.DirFS(basePath + "/eds/")}) // registration is a must + if err != nil { + panic(err) + } + return &EDSStore{ basepath: basepath, dgst: dagstore.New(dagstore.Config{...}), topIdx: index.NewInverted(datastore), carIdx: index.NewFSRepo(basepath + "/index") mounts: mounts, - } + } } - ``` +_NOTE: EDSStore should have lifecycle methods(Start/Stop)._ + ##### `share.EDSStore.Put` To write an entire EDS `Put` method is introduced. Internally it @@ -190,11 +195,9 @@ To write an entire EDS `Put` method is introduced. Internally it - Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) - Converts `DataRoot` into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) - Registers the `Mount` as a `Shard` on the `DAGStore` - - This register the `Mount` in [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) -passed to `DAGStore`. -NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS -so this is out of scope of the document. +_NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS +so this is out of scope of the document._ ```go // Put stores the given data square with DataRoot as key. @@ -213,8 +216,8 @@ To read an EDS as a byte stream `GetCAR` method is introduced. Internally it - Gets Mount by `shard.Key` from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) - Returns `io.ReadeCloser` from [`Mount.Fetch`](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go#L71) -NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` -and closing it when operation is done. This has to be confirmed. +_NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` +and closing it when operation is done. This has to be confirmed._ ```go // GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as CARv1 file. @@ -235,7 +238,7 @@ by namespace. // TODO Link to the section There is a [frozen/un-merged implementation](https://github.com/filecoin-project/dagstore/pull/116) of `Blockstore` over `DAGStore` and CARv2 indexes. -NOTE: We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs. +_NOTE: We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._ ```go // Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS @@ -252,7 +255,7 @@ To read an entire EDS `Get` method is introduced. Internally it: - Gets a serialized EDS `io.Reader` via `Store.GetCAR` - Deserializes the EDS and validates it via `share.ReadEDS` -NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have +_NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have._ ```go // Get reads EDS out of Store by given DataRoot. @@ -268,13 +271,12 @@ To remove stored EDS `Remove` methods is introduced. Internally it: - Converts `DataRoot` into the [`shard.Key`]( - Removes respective `Mount` and `Shard` from `DAGStore` - - `DAGStore` cleans up indices and `mount.Registery` itself - Removes `FileMount` file of CARv1 file from disk under `storepath/DataRoot` path -NOTES: +_NOTES:_ -- It's not necessary, but an API ergonomics/symmetry nice-to-have -- GC logic on the DAGStore has to be investigated so that Removing is correctly implemented +- _It's not necessary, but an API ergonomics/symmetry nice-to-have_ +- _GC logic on the DAGStore has to be investigated so that Removing is correctly implemented_ ```go // Remove removes EDS from Store by given DataRoot and cleans up all the indexing. From bd1478ef9e97f2da4b9889a12aec2f0befa51031 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 9 Sep 2022 19:29:46 +0200 Subject: [PATCH 0073/1008] docs(adr-11): clarify what is DataRoot and it's usage; minor formatting --- docs/adr/adr-11-blocksync-overhaul.md | 38 +++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 575fbfe019..dc19aa4196 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -20,6 +20,8 @@ - BN - Bridge Node - EDS(Extended Data Square) - plain Block data omitting headers and other metadata. - NMT - Namespaced Merkle Tree +- [DataRoot](https://github.com/celestiaorg/celestia-node/blob/main/service/share/share.go#L35) - alias for +[DAHeader](https://github.com/celestiaorg/celestia-core/blob/v0.34.x-celestia/pkg/da/data_availability_header.go#L29) ## Context @@ -70,8 +72,8 @@ introduces pluggable indexes over the blob allowing efficient random access to s (if the index is cached in memory). - __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs -to serve simple hash to data requests. The top-level index maps any hash to any block CARv1file so that FNs/BNs can -quickly serve requests. +to serve simple hash to data requests. The top-level index maps any hash of Share/NMT Proof to any block CARv1 file so +that FNs/BNs can quickly serve DA requests. - __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block files.__ DAGStore provides both the top-level indexing and CARv2 based indexing per each CAR file. In essence, it's an @@ -128,13 +130,13 @@ func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it -- Imports EDS with an empty pre-allocated slice. NOTE: Size can be taken from DataRoot +- Imports EDS with an empty pre-allocated slice. _NOTE: Size can be taken from DataRoot Row/Col len_ - Wraps given io.Reader with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) - Reads out blocks one by one and fills up the EDS quadrant via `EDS.SetCell` - Recomputes and validates via `EDS.Repair` ```go -// ReadEDS reads an EDS quadrant(1/4) from an io.Reader CAR file.// +// ReadEDS reads an EDS quadrant(1/4) from an io.Reader CAR file. // It expects strictly first EDS quadrant(top left). // The returned EDS is guaranteed to be full and valid against the DataRoot, otherwise ReadEDS errors. func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, error) @@ -190,17 +192,17 @@ _NOTE: EDSStore should have lifecycle methods(Start/Stop)._ To write an entire EDS `Put` method is introduced. Internally it -- Opens a file under `storepath/DataRoot` path +- Opens a file under `storepath/DataRoot.Hash` path - Serializes the EDS into the file via `share.WriteEDS` - Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) -- Converts `DataRoot` into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) - Registers the `Mount` as a `Shard` on the `DAGStore` _NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS so this is out of scope of the document._ ```go -// Put stores the given data square with DataRoot as key. +// Put stores the given data square with DataRoot's hash as key. // // The square is verified on the Exchange level and Put only stores the square trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. @@ -212,7 +214,7 @@ func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error To read an EDS as a byte stream `GetCAR` method is introduced. Internally it -- Converts `DataRoot` into the [`shard.Key`]( +- Converts `DataRoot`'s hash into the [`shard.Key`]( - Gets Mount by `shard.Key` from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) - Returns `io.ReadeCloser` from [`Mount.Fetch`](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go#L71) @@ -269,9 +271,9 @@ func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, erro To remove stored EDS `Remove` methods is introduced. Internally it: -- Converts `DataRoot` into the [`shard.Key`]( +- Converts `DataRoot`'s hash into the [`shard.Key`]( - Removes respective `Mount` and `Shard` from `DAGStore` -- Removes `FileMount` file of CARv1 file from disk under `storepath/DataRoot` path +- Removes `FileMount` file of CARv1 file from disk under `storepath/DataRoot.Hash` path _NOTES:_ @@ -319,12 +321,14 @@ The EDSStore expects a directory to store CAR files and indices to. The path sho ### Considerations -- ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the protocol layer and back to CAR when storing the EDS. -Rsmt2d allocates on most operations with individual shares and for bigger blocks during sync this allocs puts significant -pressure on GC. One way to substantially alleviate this is to integrate bytes buffer pool into rmst2d +- ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the +protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares and for +bigger blocks during sync this allocs puts significant pressure on GC. One way to substantially alleviate this is to +integrate bytes buffer pool into rmst2d. - ___Disk usage increase from top-level index.___ This is temporary solution. The index will have to be removed. -LNs know which block they sample and can provide DataRoot together with sample request over Bitswap, removing -the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extention](https://github.com/ipfs/specs/pull/270) -or proposing custom Bitswap message extention. Subsequently, the Blockstore implementation provided via `EDSStore` would -have to be changed to take expect DataRoot being passed through the `context.Context`. +LNs know which block they sample and can provide `DataRoot`'s hash together with sample request over Bitswap, removing +the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extention +](https://github.com/ipfs/specs/pull/270) or proposing custom Bitswap message extention. Subsequently, the Blockstore +implementation provided via `EDSStore` would have to be changed to take expect `DataRoot`'s hash being passed through the +`context.Context`. From 38de9c9ad2f22c70b92155c31a216f5607307dc7 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Fri, 9 Sep 2022 19:46:28 +0200 Subject: [PATCH 0074/1008] docs(adr-11): additional clarification notes --- docs/adr/adr-11-blocksync-overhaul.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index dc19aa4196..307c322813 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -20,7 +20,7 @@ - BN - Bridge Node - EDS(Extended Data Square) - plain Block data omitting headers and other metadata. - NMT - Namespaced Merkle Tree -- [DataRoot](https://github.com/celestiaorg/celestia-node/blob/main/service/share/share.go#L35) - alias for +- [DataRoot](https://github.com/celestiaorg/celestia-node/blob/main/service/share/share.go#L35) - alias for [DAHeader](https://github.com/celestiaorg/celestia-core/blob/v0.34.x-celestia/pkg/da/data_availability_header.go#L29) ## Context @@ -72,7 +72,7 @@ introduces pluggable indexes over the blob allowing efficient random access to s (if the index is cached in memory). - __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs -to serve simple hash to data requests. The top-level index maps any hash of Share/NMT Proof to any block CARv1 file so +to serve simple hash to data requests. The top-level index maps any hash of Share/NMT Proof to any block CARv1 file so that FNs/BNs can quickly serve DA requests. - __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block @@ -221,6 +221,8 @@ To read an EDS as a byte stream `GetCAR` method is introduced. Internally it _NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` and closing it when operation is done. This has to be confirmed._ +_NOTE2: The returned `io.Reader` represents actual EDS exchanged over the wire_ + ```go // GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as CARv1 file. // @@ -242,6 +244,8 @@ over `DAGStore` and CARv2 indexes. _NOTE: We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._ +_NOTE: NOTE: The Blockstore does not store full Celestia Blocks, but IPFS blocks._ + ```go // Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS // registered on the Store. NOTE: The Blockstore does not store full Celestia Blocks, but IPFS blocks. @@ -321,14 +325,14 @@ The EDSStore expects a directory to store CAR files and indices to. The path sho ### Considerations -- ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the -protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares and for -bigger blocks during sync this allocs puts significant pressure on GC. One way to substantially alleviate this is to +- ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the +protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares and for +bigger blocks during sync this allocs puts significant pressure on GC. One way to substantially alleviate this is to integrate bytes buffer pool into rmst2d. - ___Disk usage increase from top-level index.___ This is temporary solution. The index will have to be removed. LNs know which block they sample and can provide `DataRoot`'s hash together with sample request over Bitswap, removing the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extention -](https://github.com/ipfs/specs/pull/270) or proposing custom Bitswap message extention. Subsequently, the Blockstore +](https://github.com/ipfs/specs/pull/270) or proposing custom Bitswap message extention. Subsequently, the Blockstore implementation provided via `EDSStore` would have to be changed to take expect `DataRoot`'s hash being passed through the `context.Context`. From 56e87654b791b3156da83576d6dbb311d8016749 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 12 Sep 2022 11:59:15 +0200 Subject: [PATCH 0075/1008] docs(adr-11): Has method; empty block and EDS deduplication; GetCar method clarifications --- docs/adr/adr-11-blocksync-overhaul.md | 41 ++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 307c322813..c3b3bb5ae5 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -31,8 +31,6 @@ Current block data synchronization is done over Bitswap, traversing NMT trees of We know from empirical evidence that it takes more than 200 seconds(~65000 network requests) to download a 4MB block of 256kb shares, which is unacceptable and must be much less than the block time(15/30sec). -TODO: Simple diagram with problem visualization - The DASing, on the other hand, shows acceptable metrics for the block sizes we are aiming for initially. In the case of the same block, a DAS operation takes 50ms * 8(technically 9) blocking requests, which is ~400ms in an ideal scenario (excluding disk IO). With higher latency and bigger block size(higher NMT trees), the DASIng operation could take much @@ -75,6 +73,9 @@ introduces pluggable indexes over the blob allowing efficient random access to s to serve simple hash to data requests. The top-level index maps any hash of Share/NMT Proof to any block CARv1 file so that FNs/BNs can quickly serve DA requests. +- __FNs/BNs addresses EDSes by `DataRoot.Hash`.__ The only alternative is by height, however, it does not allow block +data deduplication in case EDSes are equal and couples Data layer with Header layer. + - __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block files.__ DAGStore provides both the top-level indexing and CARv2 based indexing per each CAR file. In essence, it's an engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. @@ -117,7 +118,7 @@ shares and Merkle Proofs in depth-first manner packing them in a CAR file. Howev requirement of being able to truncate the CAR file to read out __only__ the first quadrant out of it without NMT proofs, so serialization must be different from the utility to support that._ -_NOTE2: Alternatively to `WriteEDS`, and `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic +_NOTE2: Alternatively to `WriteEDS`, an `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic and efficient in some cases, with the cost of more complex implementation._ ```go @@ -216,12 +217,13 @@ To read an EDS as a byte stream `GetCAR` method is introduced. Internally it - Converts `DataRoot`'s hash into the [`shard.Key`]( - Gets Mount by `shard.Key` from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) -- Returns `io.ReadeCloser` from [`Mount.Fetch`](https://github.com/filecoin-project/dagstore/blob/master/mount/mount.go#L71) +- Acquires `ShardAccessor` and returns `io.ReadCloser` from it -_NOTE: It might be necessary to acquire EDS mount via `DAGStore` keeping `ShardAccessor` -and closing it when operation is done. This has to be confirmed._ +_NOTES:_ -_NOTE2: The returned `io.Reader` represents actual EDS exchanged over the wire_ +- _`DAGStores`'s `ShardAccessor` has to be extended to return a `io.ReadCloser`. Currently, it only returns +a `Blockstore` of the CAR_ +- _The returned `io.Reader` represents actual EDS exchanged over the wire which should be limited to return a first quadrant_ ```go // GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as CARv1 file. @@ -271,6 +273,21 @@ func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, erro ``` +##### `share.EDSStore.Has` + +To check if EDSStore keeps an EDS `Has` method is introduced. Internally it: + +- Converts `DataRoot`'s hash into the [`shard.Key`]( +- Tries to acquire `ShardAccessor` and on success closes it returning postive. + +_NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have._ + +```go +// Has checks if EDS exists by given DataRoot. +func (s *Store) Has(context.Context, DataRoot) (bool, error) + +``` + ##### `share.EDSStore.Remove` To remove stored EDS `Remove` methods is introduced. Internally it: @@ -304,6 +321,16 @@ Generally stays unchanged with minor edits: Alternatively, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace` returning CARv1 Reader with encoded shares and NMT Merkle Proofs. +#### EDS Deduplication + +Addressing EDS by DataRoot allows us to deduplicate equal EDSes. EDS equality is every unlikely to happen in practice, +beside empty Block case, which always produces the same EDS. + +#### Empty Block/EDS + +The empty block is valid and small EDS. It can happen on early stage of the network. Its body is constant and to avoid +transferring it over the wire the `EDSStore` should pre initialized with empty EDS value. + ##### EDSStore Directory Path The EDSStore expects a directory to store CAR files and indices to. The path should be gotten based on `node.Store.Path` From e15603209cf1d57f1f01f0628b26677571cdf56c Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 12 Sep 2022 14:32:48 +0200 Subject: [PATCH 0076/1008] docs(adr-11): mention that the ADR is part 1 only --- docs/adr/adr-11-blocksync-overhaul.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index c3b3bb5ae5..6380fa847d 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -1,4 +1,4 @@ -# ADR #011: Block Data Sync Overhaul +# ADR #011: Block Data Sync Overhaul: Part I - Storage ## Changelog @@ -56,10 +56,10 @@ in 2 days to match the following requirements: ### Decision -This ADR is intended to outline design decisions for Block syncing mechanism/protocol improvements together with -block data storage. In a nutshell, the decision is to use ___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ -and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ for ___extended block storage___ -and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by namespace id) in the happy path. +This ADR is intended to outline design decisions for block data storage. In a nutshell, the decision is to use +___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ +for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by +namespace id) in the happy path. The p2p portion of the document will come in the subsequent Part II document. #### Key Design Decisions From 4a43052c19d7595fad8e76cedf113884784f5964 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Mon, 12 Sep 2022 20:04:53 +0200 Subject: [PATCH 0077/1008] docs(adr-11): clarifications for Remove method; minor formatting issues --- docs/adr/adr-11-blocksync-overhaul.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index 6380fa847d..a26f11b955 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -56,9 +56,9 @@ in 2 days to match the following requirements: ### Decision -This ADR is intended to outline design decisions for block data storage. In a nutshell, the decision is to use -___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ -for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by +This ADR is intended to outline design decisions for block data storage. In a nutshell, the decision is to use +___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ +for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by namespace id) in the happy path. The p2p portion of the document will come in the subsequent Part II document. #### Key Design Decisions @@ -215,8 +215,7 @@ func (s *Store) Put(context.Context, DataRoot, *rsmt2d.ExtendedDataSquare) error To read an EDS as a byte stream `GetCAR` method is introduced. Internally it -- Converts `DataRoot`'s hash into the [`shard.Key`]( -- Gets Mount by `shard.Key` from [`mount.Registry`](https://github.com/filecoin-project/dagstore/blob/master/mount/registry.go#L22) +- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) - Acquires `ShardAccessor` and returns `io.ReadCloser` from it _NOTES:_ @@ -277,8 +276,9 @@ func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, erro To check if EDSStore keeps an EDS `Has` method is introduced. Internally it: -- Converts `DataRoot`'s hash into the [`shard.Key`]( -- Tries to acquire `ShardAccessor` and on success closes it returning postive. +- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Checks if [`GetShardInfo`](https://github.com/filecoin-project/dagstore/blob/master/dagstore.go#L483) does not return +[ErrShardUnknown](https://github.com/filecoin-project/dagstore/blob/eac7733212fdd7c80be5078659f7450b3956d2a6/dagstore.go#L55) _NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have._ @@ -290,11 +290,12 @@ func (s *Store) Has(context.Context, DataRoot) (bool, error) ##### `share.EDSStore.Remove` -To remove stored EDS `Remove` methods is introduced. Internally it: +To remove stored EDS `Remove` method is introduced. Internally it: -- Converts `DataRoot`'s hash into the [`shard.Key`]( -- Removes respective `Mount` and `Shard` from `DAGStore` -- Removes `FileMount` file of CARv1 file from disk under `storepath/DataRoot.Hash` path +- Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) +- Destroys `Shard` via `DAGStore` + - Internally removes it's `Mount` as well +- Removes CARv1 file from disk under `storepath/DataRoot.Hash` path _NOTES:_ From 283566ecf6490d5e2a4dff9776ac54aba96856d4 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 14 Sep 2022 17:56:00 +0200 Subject: [PATCH 0078/1008] docs(adr-11): describe WriteEDS; fix all TODOs and fix formatting --- docs/adr/adr-11-blocksync-overhaul.md | 76 +++++++++++++++------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index a26f11b955..a30f2ba1ab 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -3,8 +3,7 @@ ## Changelog - 23.08.22: Initial unfinished draft -- 01.09.22: Block/EDS Storage design -- 02.09.22: serde for EDS +- 14.09.22: First version finished ## Authors @@ -18,8 +17,9 @@ - LN - Light Node - FN - Full Node - BN - Bridge Node -- EDS(Extended Data Square) - plain Block data omitting headers and other metadata. -- NMT - Namespaced Merkle Tree +- [EDS(Extended Data Square)](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L10) - plain Block +data omitting headers and other block metadata. +- [NMT](https://github.com/celestiaorg/nmt) - Namespaced Merkle Tree - [DataRoot](https://github.com/celestiaorg/celestia-node/blob/main/service/share/share.go#L35) - alias for [DAHeader](https://github.com/celestiaorg/celestia-core/blob/v0.34.x-celestia/pkg/da/data_availability_header.go#L29) @@ -33,8 +33,7 @@ We know from empirical evidence that it takes more than 200 seconds(~65000 netwo The DASing, on the other hand, shows acceptable metrics for the block sizes we are aiming for initially. In the case of the same block, a DAS operation takes 50ms * 8(technically 9) blocking requests, which is ~400ms in an ideal scenario -(excluding disk IO). With higher latency and bigger block size(higher NMT trees), the DASIng operation could take much -more(TODO type of the grow, e.g., quadratic/etc.), but is likely(TODO proof?) to be less than a block time. +(excluding disk IO). Getting data by namespace also needs to be improved. The time it takes currently lies between BlockSync and DASing, where more data equals more requests and more time to fulfill the requests. @@ -92,8 +91,6 @@ reading EDS from file. > All the comments on the API definitions should be preserved and potentially improved by implementations. -#### Block/EDS Storage - The new block storage design is solely additive. Meaning that all the existing storage related components and functionality are kept with additional components introduced. Altogether, existing and new components will be recomposed to serve the foundation of our improved block storage subsystem. @@ -102,23 +99,37 @@ The central data structure representing Celestia block data is EDS(`rsmt2d.Exten is focused around storing entire EDSes as a whole rather than a set of individual chunks s.t. storage subsystem can handle storing and streaming/serving blocks of 4mb sizes and more. -##### EDS Serde +#### EDS Serde Storing EDS as a whole requires EDS (de)serialization. For this the [CAR format](https://ipld.io/specs/transport/car) is chosen. -###### `share.WriteEDS` +##### `share.WriteEDS` + +To write EDS into a stream/file `WriteEDS` is introduced. Internally, it -To write EDS into a stream/file `WriteEDS` is introduced. -// TODO Describe logic +- [Re-imports](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L41) EDS similarly to +[`ipld.ImportShares`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L51-L65) + - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline +exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) and in-memory [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) + - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L56) which saves to the +`Blockstore` only NMT Merkle proofs(no shares) _NOTE: `len(node.Links()) == 2`_ + - Actual shares are written further in a special way +- Creates and [writes](https://github.com/ipld/go-car/blob/master/car.go#L86) header [`CARv1Header`](https://github.com/ipld/go-car/blob/master/car.go#L30) + - Fills up `Roots` field with `EDS.RowRoots/EDS.ColRoots` roots converted into CIDs +- Iterates over shares in quadrant-by-quadrant order via `EDS.GetCell` + - [Writes](https://github.com/ipld/go-car/blob/master/car.go#L118) the shares in row-by-row order +- Iterates over in-memory Blockstore and [writes]((https://github.com/ipld/go-car/blob/master/car.go#L118)) NMT Merkle +proofs stored in it -_NOTE: CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file +_NOTES:_ + +- _CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses shares and Merkle Proofs in depth-first manner packing them in a CAR file. However, this is incompatible with the requirement of being able to truncate the CAR file to read out __only__ the first quadrant out of it without NMT proofs, so serialization must be different from the utility to support that._ - -_NOTE2: Alternatively to `WriteEDS`, an `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic +- _Alternatively to `WriteEDS`, an `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic and efficient in some cases, with the cost of more complex implementation._ ```go @@ -127,13 +138,14 @@ and efficient in some cases, with the cost of more complex implementation._ func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error ``` -###### `share.ReadEDS` +##### `share.ReadEDS` To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it - Imports EDS with an empty pre-allocated slice. _NOTE: Size can be taken from DataRoot Row/Col len_ - Wraps given io.Reader with [`BlockReader`](https://github.com/ipld/go-car/blob/master/v2/block_reader.go#L16) - Reads out blocks one by one and fills up the EDS quadrant via `EDS.SetCell` + - In total there should be shares-in-quadrant amount of reads. - Recomputes and validates via `EDS.Repair` ```go @@ -143,7 +155,7 @@ To read an EDS out of a stream/file, `ReadEDS` is introduced. Internally, it func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, error) ``` -##### `share.EDSStore` +#### `share.EDSStore` To manage every EDS on the disk, FNs/BNs keep an `EDSStore`. The `EDSStore` type is introduced in the `share` pkg. Each EDS together with its Merkle Proofs serializes into CARv1 file. All the serialized CARv1 file blobs are mounted on @@ -236,16 +248,18 @@ func (s *Store) GetCAR(context.Context, DataRoot) (io.ReadCloser, error) ##### `share.EDSStore.Blockstore` -`Blockstore` method return a `Blockstore` interface implementation instance, providing random access over share and NMT -Merkle proof in every stored EDS. It is required for FNs/BNs to serve DAS requests over the Bitswap and for reading data -by namespace. // TODO Link to the section +`Blockstore` method return a [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) +interface implementation instance, providing random access over share and NMT Merkle proof in every stored EDS. It is +required for FNs/BNs to serve DAS requests over the Bitswap and for [reading data by namespace](#reading-data-by-namespace). There is a [frozen/un-merged implementation](https://github.com/filecoin-project/dagstore/pull/116) of `Blockstore` over `DAGStore` and CARv2 indexes. -_NOTE: We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._ +_NOTES:_ -_NOTE: NOTE: The Blockstore does not store full Celestia Blocks, but IPFS blocks._ +- _We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._ +- _The Blockstore does not store full Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS +blocks._ ```go // Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS @@ -313,13 +327,13 @@ func (s *Store) Remove(context.Context, DataRoot) error Generally stays unchanged with minor edits: - `share/ipld.GetByNamespace` is kept to load data from disk only and not from network anymore - - Using `Blockservice` with offline exchange // TODO Links - - Using `Blockstore` provided by `EDSStore` + - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) + - Using [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) provided by `EDSStore` - `share/ipld.GetByNamespace` is extended to return NMT Merkle proofs - Similar to `share/ipld.GetProofsForShares` - Ensure merkle proofs are not duplicated! -Alternatively, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace` returning +As an extention, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace` returning CARv1 Reader with encoded shares and NMT Merkle Proofs. #### EDS Deduplication @@ -332,25 +346,19 @@ beside empty Block case, which always produces the same EDS. The empty block is valid and small EDS. It can happen on early stage of the network. Its body is constant and to avoid transferring it over the wire the `EDSStore` should pre initialized with empty EDS value. -##### EDSStore Directory Path +#### EDSStore Directory Path -The EDSStore expects a directory to store CAR files and indices to. The path should be gotten based on `node.Store.Path` +The EDSStore on construction expects a directory to store CAR files and indices to. The path should be gotten based +on `node.Store.Path`. ### Alternative Approaches -#### Block Storage - - Extended blocks as a set of share blobs and Merkle proofs in global store (_current approach with KVStore_) - Extended block as a single blob only(computing Merkle proofs) - Extended block as a single blob and Merkle proofs - Extended block as a set of DAG/CAR blobs - Extended block as a single DAG/CAR blob -#### Block Syncing - -- GraphSync -- Bitswap(current) - ### Considerations - ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the From 016c3d8a42ce8960bd48c0f38fa75fec242a4632 Mon Sep 17 00:00:00 2001 From: Wondertan Date: Wed, 14 Sep 2022 18:38:03 +0200 Subject: [PATCH 0079/1008] docs(adr-11): grammar fixes --- docs/adr/adr-11-blocksync-overhaul.md | 146 +++++++++++++------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/docs/adr/adr-11-blocksync-overhaul.md b/docs/adr/adr-11-blocksync-overhaul.md index a30f2ba1ab..98fea28739 100644 --- a/docs/adr/adr-11-blocksync-overhaul.md +++ b/docs/adr/adr-11-blocksync-overhaul.md @@ -3,13 +3,13 @@ ## Changelog - 23.08.22: Initial unfinished draft -- 14.09.22: First version finished +- 14.09.22: The first finished version ## Authors - @Wondertan -> I start to like writing ADRs, step-by-step. Also, there is a trick that helps: imagine like you are talking to a dev +> I start to like writing ADRs step-by-step. Also, there is a trick that helps: imagine like you are talking to a dev > who just joined the team to onboard him. ## Glossary @@ -40,8 +40,8 @@ where more data equals more requests and more time to fulfill the requests. ### Mini Node Offsite 2022 Berlin -To facilitate and speedup the resolution of the problem we decided to make a team gathering in Berlin for 4 days. With -the help of preliminary preparations by @Wondertan and invited guest @willscott, we were able to find a solution +To facilitate and speed up the resolution of the problem, we decided to make the team gathering in Berlin for four days. +With the help of preliminary preparations by @Wondertan and guest @willscott, we were able to find a solution in 2 days to match the following requirements: - Sync time less than block time(ideally sub-second) @@ -55,7 +55,7 @@ in 2 days to match the following requirements: ### Decision -This ADR is intended to outline design decisions for block data storage. In a nutshell, the decision is to use +This ADR intends to outline design decisions for block data storage. In a nutshell, the decision is to use ___[CAR format](https://ipld.io/specs/transport/car/carv2/)___ and ___[Dagstore](https://github.com/filecoin-project/dagstore)___ for ___extended block storage___ and ___custom p2p Req/Resp protocol for block data syncing___(whole block and data by namespace id) in the happy path. The p2p portion of the document will come in the subsequent Part II document. @@ -69,52 +69,50 @@ introduces pluggable indexes over the blob allowing efficient random access to s (if the index is cached in memory). - __FNs/BNs manage a top-level index for _hash_ to _CAR block file_ mapping.__ Current DASing for LNs requires FNs/BNs -to serve simple hash to data requests. The top-level index maps any hash of Share/NMT Proof to any block CARv1 file so +to serve simple hash to data requests. The top-level index maps any Share/NMT Proof hash to any block CARv1 file so that FNs/BNs can quickly serve DA requests. -- __FNs/BNs addresses EDSes by `DataRoot.Hash`.__ The only alternative is by height, however, it does not allow block -data deduplication in case EDSes are equal and couples Data layer with Header layer. +- __FNs/BNs address EDSes by `DataRoot.Hash`.__ The only alternative is by height; however, it does not allow block +data deduplication in case EDSes are equal and couples the Data layer/pkg with the Header layer/pkg. - __FNs/BNs run a single instance of [`DAGStore`](https://github.com/filecoin-project/dagstore) to manage CAR block -files.__ DAGStore provides both the top-level indexing and CARv2 based indexing per each CAR file. In essence, it's an +files.__ DAGStore provides the top-level indexing and CARv2-based indexing per each CAR file. In essence, it's an engine for managing any CAR files with indexing, convenient abstractions, tools, recovery mechanisms, etc. - - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2 based -indexing as well. Usage of CARv1 keeps only one copy of index, stores/transfers less metadata per EDS and simplifies -reading EDS from file. + - __EDSes as _CARv1_ files over _CARv2_.__ CARv2 encodes indexes into the file, while DAGStore maintains CARv2-based +indexing. Usage of CARv1 keeps only one copy of the index, stores/transfers less metadata per EDS, and simplifies +reading EDS from a file. -- __LNs DASing remains untouched__. Both the networking protocol and storage for LNs remains untouched as it fulfills - the requirements. This includes Bitswap as backbone protocol for requesting samples and global Badger KVStore. - -- __New libp2p based Exchange protocol is introduced for data/share exchange purposes.__ FNs/BNs servers LNs clients. +- __LNs DASing remains untouched__. The networking protocol and storage for LNs remain intact as it fulfills the +requirements. Bitswap usage as the backbone protocol for requesting samples and global Badger KVStore remain unaffected. ### Detailed Design > All the comments on the API definitions should be preserved and potentially improved by implementations. -The new block storage design is solely additive. Meaning that all the existing storage related components and functionality -are kept with additional components introduced. Altogether, existing and new components will be recomposed to serve the +The new block storage design is solely additive. All the existing storage-related components and functionality +are kept with additional components introduced. Altogether, existing and new components will be recomposed to serve as the foundation of our improved block storage subsystem. -The central data structure representing Celestia block data is EDS(`rsmt2d.ExtendedDataSquare`) and the new storage design -is focused around storing entire EDSes as a whole rather than a set of individual chunks s.t. storage subsystem -can handle storing and streaming/serving blocks of 4mb sizes and more. +The central data structure representing Celestia block data is EDS(`rsmt2d.ExtendedDataSquare`), and the new storage design +is focused around storing entire EDSes as a whole rather than a set of individual chunks, s.t. storage subsystem +can handle storing and streaming/serving blocks of 4MB and more. #### EDS Serde -Storing EDS as a whole requires EDS (de)serialization. For this the [CAR format](https://ipld.io/specs/transport/car) is +Storing EDS as a whole requires EDS (de)serialization. For this, the [CAR format](https://ipld.io/specs/transport/car) is chosen. ##### `share.WriteEDS` -To write EDS into a stream/file `WriteEDS` is introduced. Internally, it +To write EDS into a stream/file, `WriteEDS` is introduced. Internally, it - [Re-imports](https://github.com/celestiaorg/rsmt2d/blob/master/extendeddatasquare.go#L41) EDS similarly to [`ipld.ImportShares`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L51-L65) - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) and in-memory [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) - - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L56) which saves to the + - With [`NodeVisitor`](https://github.com/celestiaorg/celestia-node/blob/main/ipld/add.go#L56), which saves to the `Blockstore` only NMT Merkle proofs(no shares) _NOTE: `len(node.Links()) == 2`_ - - Actual shares are written further in a special way + - Actual shares are written further in a particular way explained further - Creates and [writes](https://github.com/ipld/go-car/blob/master/car.go#L86) header [`CARv1Header`](https://github.com/ipld/go-car/blob/master/car.go#L30) - Fills up `Roots` field with `EDS.RowRoots/EDS.ColRoots` roots converted into CIDs - Iterates over shares in quadrant-by-quadrant order via `EDS.GetCell` @@ -122,18 +120,18 @@ exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.g - Iterates over in-memory Blockstore and [writes]((https://github.com/ipld/go-car/blob/master/car.go#L118)) NMT Merkle proofs stored in it -_NOTES:_ +___NOTES:___ - _CAR provides [a utility](https://github.com/ipld/go-car/blob/master/car.go#L47) to serialize any DAG into the file and there is a way to serialize EDS into DAG(`share/ipld.ImportShares`). This approach is the simplest and traverses -shares and Merkle Proofs in depth-first manner packing them in a CAR file. However, this is incompatible with the -requirement of being able to truncate the CAR file to read out __only__ the first quadrant out of it without NMT proofs, +shares and Merkle Proofs in a depth-first manner packing them in a CAR file. However, this is incompatible with the +requirement of being able to truncate the CAR file reading out __only__ the first quadrant out of it without NMT proofs, so serialization must be different from the utility to support that._ - _Alternatively to `WriteEDS`, an `EDSReader` could be introduced to make EDS-to-stream handling more idiomatic and efficient in some cases, with the cost of more complex implementation._ ```go -// WriteEDS writes whole EDS into given io.Writer as CARv1 file. +// WriteEDS writes the whole EDS into the given io.Writer as CARv1 file. // All its shares and recomputed NMT proofs. func WriteEDS(context.Context, *rsmt2d.ExtendedDataSquare, io.Writer) error ``` @@ -157,14 +155,14 @@ func ReadEDS(context.Context, io.Reader, DataRoot) (*rsmt2d.ExtendedDataSquare, #### `share.EDSStore` -To manage every EDS on the disk, FNs/BNs keep an `EDSStore`. The `EDSStore` type is introduced in the `share` pkg. -Each EDS together with its Merkle Proofs serializes into CARv1 file. All the serialized CARv1 file blobs are mounted on +FNs/BNs keep an `EDSStore` to manage every EDS on the disk. The `EDSStore` type is introduced in the `share` pkg. +Each EDS together with its Merkle Proofs serializes into a CARv1 file. All the serialized CARv1 file blobs are mounted on DAGStore via [Local FS Mounts](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#mounts) and registered as [Shards](https://github.com/filecoin-project/dagstore/blob/master/docs/design.md#shards). The introduced `EDSStore` also maintains (via DAGStore) a top-level index enabling granular and efficient random access to every share and/or Merkle proof over every registered CARv1 file. The `EDSStore` provides a custom `Blockstore` interface -implementation to achieve the access. The main use-case is randomized sampling over the whole chain of EDS block data +implementation to achieve access. The main use-case is randomized sampling over the whole chain of EDS block data and getting data by namespace. ```go @@ -178,7 +176,7 @@ type EDSStore struct { } // NewEDSStore constructs EDStore over OS directory to store CARv1 files of EDSes and indices for them. -// Datastore is used to keep inverted/top-level index. +// Datastore is used to keep the inverted/top-level index. func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { topIdx := index.NewInverted(datastore) carIdx := index.NewFSRepo(basepath + "/index") @@ -199,23 +197,23 @@ func NewEDSStore(basepath string, ds datastore.Batching) *EDSStore { } ``` -_NOTE: EDSStore should have lifecycle methods(Start/Stop)._ +___NOTE:___ _EDSStore should have lifecycle methods(Start/Stop)._ ##### `share.EDSStore.Put` To write an entire EDS `Put` method is introduced. Internally it -- Opens a file under `storepath/DataRoot.Hash` path +- Opens a file under `Store.Path/DataRoot.Hash` path - Serializes the EDS into the file via `share.WriteEDS` - Wraps it with `DAGStore`'s [FileMount](https://github.com/filecoin-project/dagstore/blob/master/mount/file.go#L10) - Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) - Registers the `Mount` as a `Shard` on the `DAGStore` -_NOTE: Registering on the DAGStore automatically populates top-level index with shares/proofs accessible from stored EDS -so this is out of scope of the document._ +___NOTE:___ _Registering on the DAGStore populates the top-level index with shares/proofs accessible from stored EDS, which is +out of the scope of the document._ ```go -// Put stores the given data square with DataRoot's hash as key. +// Put stores the given data square with DataRoot's hash as a key. // // The square is verified on the Exchange level and Put only stores the square trusting it. // The resulting file stores all the shares and NMT Merkle Proofs of the EDS. @@ -230,16 +228,16 @@ To read an EDS as a byte stream `GetCAR` method is introduced. Internally it - Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) - Acquires `ShardAccessor` and returns `io.ReadCloser` from it -_NOTES:_ +___NOTES:___ -- _`DAGStores`'s `ShardAccessor` has to be extended to return a `io.ReadCloser`. Currently, it only returns -a `Blockstore` of the CAR_ -- _The returned `io.Reader` represents actual EDS exchanged over the wire which should be limited to return a first quadrant_ +- _`DAGStores`'s `ShardAccessor` has to be extended to return an `io.ReadCloser`. Currently, it only returns + a `Blockstore` of the CAR_ +- _The returned `io.Reader` represents actual EDS exchanged over the wire, which should be limited to return the first quadrant_ ```go -// GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as CARv1 file. +// GetCAR takes a DataRoot and returns a buffered reader to the respective EDS serialized as a CARv1 file. // -// The Reader strictly reads the first quadrant(1/4) of EDS omitting all the NMT Merkle proofs. +// The Reader strictly reads the first quadrant(1/4) of EDS, omitting all the NMT Merkle proofs. // Integrity of the store data is not verified. // // Caller must Close returned reader after reading. @@ -248,23 +246,23 @@ func (s *Store) GetCAR(context.Context, DataRoot) (io.ReadCloser, error) ##### `share.EDSStore.Blockstore` -`Blockstore` method return a [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) +`Blockstore` method returns a [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) interface implementation instance, providing random access over share and NMT Merkle proof in every stored EDS. It is required for FNs/BNs to serve DAS requests over the Bitswap and for [reading data by namespace](#reading-data-by-namespace). There is a [frozen/un-merged implementation](https://github.com/filecoin-project/dagstore/pull/116) of `Blockstore` over `DAGStore` and CARv2 indexes. -_NOTES:_ +___NOTES:___ - _We can either use it(and finish if something is missing for our case) or implement custom optimized for our needs._ -- _The Blockstore does not store full Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS +- _The Blockstore does not store whole Celestia Blocks, but IPFS blocks. We represent Merkle proofs and shares in IPFS blocks._ ```go // Blockstore returns an IPFS Blockstore providing access to individual shares/nodes of all EDS -// registered on the Store. NOTE: The Blockstore does not store full Celestia Blocks, but IPFS blocks. -// We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes, so that Bitswap can access those. +// registered on the Store. NOTE: The Blockstore does not store whole Celestia Blocks but IPFS blocks. +// We represent `shares` and NMT Merkle proofs as IPFS blocks and IPLD nodes so Bitswap can access those. func (s *Store) Blockstore() blockstore.Blockstore ``` @@ -276,12 +274,12 @@ To read an entire EDS `Get` method is introduced. Internally it: - Gets a serialized EDS `io.Reader` via `Store.GetCAR` - Deserializes the EDS and validates it via `share.ReadEDS` -_NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have._ +___NOTE:___ _It's unnecessary, but an API ergonomics/symmetry nice-to-have._ ```go // Get reads EDS out of Store by given DataRoot. // -// It reads only one quadrant(1/4) of the EDS and verifies integrity of the stored data by recomputing it. +// It reads only one quadrant(1/4) of the EDS and verifies the integrity of the stored data by recomputing it. func (s *Store) Get(context.Context, DataRoot) (*rsmt2d.ExtendedDataSquare, error) ``` @@ -294,10 +292,10 @@ To check if EDSStore keeps an EDS `Has` method is introduced. Internally it: - Checks if [`GetShardInfo`](https://github.com/filecoin-project/dagstore/blob/master/dagstore.go#L483) does not return [ErrShardUnknown](https://github.com/filecoin-project/dagstore/blob/eac7733212fdd7c80be5078659f7450b3956d2a6/dagstore.go#L55) -_NOTE: It's not necessary, but an API ergonomics/symmetry nice-to-have._ +___NOTE:___ _It's unnecessary, but an API ergonomics/symmetry nice-to-have._ ```go -// Has checks if EDS exists by given DataRoot. +// Has checks if EDS exists by the given DataRoot. func (s *Store) Has(context.Context, DataRoot) (bool, error) ``` @@ -308,16 +306,16 @@ To remove stored EDS `Remove` method is introduced. Internally it: - Converts `DataRoot`'s hash into the [`shard.Key`](https://github.com/filecoin-project/dagstore/blob/master/shard/key.go#L12) - Destroys `Shard` via `DAGStore` - - Internally removes it's `Mount` as well -- Removes CARv1 file from disk under `storepath/DataRoot.Hash` path + - Internally removes its `Mount` as well +- Removes CARv1 file from disk under `Store.Path/DataRoot.Hash` path -_NOTES:_ +___NOTES:___ -- _It's not necessary, but an API ergonomics/symmetry nice-to-have_ +- _It's unnecessary, but an API ergonomics/symmetry nice-to-have_ - _GC logic on the DAGStore has to be investigated so that Removing is correctly implemented_ ```go -// Remove removes EDS from Store by given DataRoot and cleans up all the indexing. +// Remove removes EDS from Store by the given DataRoot and cleans up all the indexing. func (s *Store) Remove(context.Context, DataRoot) error ``` @@ -326,34 +324,34 @@ func (s *Store) Remove(context.Context, DataRoot) error Generally stays unchanged with minor edits: -- `share/ipld.GetByNamespace` is kept to load data from disk only and not from network anymore +- `share/ipld.GetByNamespace` is kept to load data from disk only and not from the network anymore - Using [`Blockservice`](https://github.com/ipfs/go-blockservice/blob/master/blockservice.go#L46) with [offline exchange](https://github.com/ipfs/go-ipfs-exchange-offline/blob/master/offline.go#L16) - Using [`Blockstore`](https://github.com/ipfs/go-ipfs-blockstore/blob/master/blockstore.go#L33) provided by `EDSStore` - `share/ipld.GetByNamespace` is extended to return NMT Merkle proofs - Similar to `share/ipld.GetProofsForShares` - - Ensure merkle proofs are not duplicated! + - Ensure Merkle proofs are not duplicated! -As an extention, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace` returning -CARv1 Reader with encoded shares and NMT Merkle Proofs. +As an extension, `share/ipld.GetByNamespace` can be modified to `share.CARByNamespace`, returning CARv1 Reader with +encoded shares and NMT Merkle Proofs. #### EDS Deduplication -Addressing EDS by DataRoot allows us to deduplicate equal EDSes. EDS equality is every unlikely to happen in practice, +Addressing EDS by DataRoot allows us to deduplicate equal EDSes. EDS equality is very unlikely to happen in practice, beside empty Block case, which always produces the same EDS. #### Empty Block/EDS -The empty block is valid and small EDS. It can happen on early stage of the network. Its body is constant and to avoid -transferring it over the wire the `EDSStore` should pre initialized with empty EDS value. +The empty block is valid and small EDS. It can happen in the early stage of the network. Its body is constant, and to avoid +transferring it over the wire, the `EDSStore` should be pre-initialized with an empty EDS value. #### EDSStore Directory Path -The EDSStore on construction expects a directory to store CAR files and indices to. The path should be gotten based +The EDSStore on construction expects a directory to store CAR files and indices. The path should be gotten based on `node.Store.Path`. ### Alternative Approaches -- Extended blocks as a set of share blobs and Merkle proofs in global store (_current approach with KVStore_) +- Extended blocks as a set of share blobs and Merkle proofs in global Store (_current approach with KVStore_) - Extended block as a single blob only(computing Merkle proofs) - Extended block as a single blob and Merkle proofs - Extended block as a set of DAG/CAR blobs @@ -362,13 +360,13 @@ on `node.Store.Path`. ### Considerations - ___EDS to/from CARv2 converting performance.___ Current sync design assumes two converts from CAR to EDS on the -protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares and for -bigger blocks during sync this allocs puts significant pressure on GC. One way to substantially alleviate this is to -integrate bytes buffer pool into rmst2d. +protocol layer and back to CAR when storing the EDS. Rsmt2d allocates on most operations with individual shares, and for +more giant blocks during sync, these allocations put significant pressure on GC. One way to substantially alleviate this +is to integrate the bytes buffer pool into rmst2d. -- ___Disk usage increase from top-level index.___ This is temporary solution. The index will have to be removed. +- ___Disk usage increases from the top-level index.___ This is a temporary solution. The index will have to be removed. LNs know which block they sample and can provide `DataRoot`'s hash together with sample request over Bitswap, removing -the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extention -](https://github.com/ipfs/specs/pull/270) or proposing custom Bitswap message extention. Subsequently, the Blockstore -implementation provided via `EDSStore` would have to be changed to take expect `DataRoot`'s hash being passed through the +the need for hash-to-eds-file mapping. This requires us to either facilitate implementation of [Bitswap's auth extension +](https://github.com/ipfs/specs/pull/270) or proposing a custom Bitswap message extension. Subsequently, the Blockstore +implementation provided via `EDSStore` would have to be changed to expect `DataRoot`'s hash to be passed through the `context.Context`. From 9b11b993a00a359fff6b65beeed6cfbf9dd2d4c5 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 20 Sep 2022 11:51:26 +0200 Subject: [PATCH 0080/1008] chore(deps): Upgrading to go 1.19 (#1067) * chore(deps): upgrading to go 1.19, applying lint fixes and godoc compatibility changes * fix: using t.TempDir and exiting on pprof server start failure * chore(deps): updating workflows * fix(ci): changing docker golang version to 1.19 --- .github/workflows/docker-build.yml | 2 +- .github/workflows/go-ci.yml | 4 +-- README.md | 2 +- cmd/celestia/cmd_test.go | 7 ++--- cmd/env.go | 4 +++ cmd/flags_misc.go | 8 +++-- core/testing.go | 2 +- docker/Dockerfile | 2 +- go.mod | 2 +- header/doc.go | 50 +++++++++++++++--------------- header/p2p/exchange.go | 2 +- header/sync/sync.go | 25 ++++++++------- header/testing.go | 7 +++-- header/verify.go | 5 +-- ipld/get_shares.go | 9 +++--- ipld/plugin/test_helpers.go | 2 +- ipld/retriever.go | 11 ++++--- ipld/retriever_quadrant.go | 11 ++++--- ipld/test_helpers.go | 4 +-- node/components.go | 7 +++-- node/config.go | 7 +++-- node/init.go | 2 +- node/rpc/component.go | 5 +-- node/rpc_test.go | 4 +-- node/store.go | 4 +-- node/tests/fraud_test.go | 34 ++++++++++---------- node/tests/p2p_test.go | 34 ++++++++++---------- node/tests/swamp/swamp_tx.go | 3 +- service/rpc/server.go | 3 ++ service/rpc/server_test.go | 4 +-- service/share/share.go | 19 ++++++------ service/share/share_test.go | 1 + service/state/doc.go | 13 ++++---- 33 files changed, 160 insertions(+), 139 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 5a4ed89ede..424d319369 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: env: - GO_VERSION: 1.18 + GO_VERSION: 1.19 IMAGE_NAME: ${{ github.repository }} REGISTRY: ghcr.io diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 575fb1e2d8..71e882a48d 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -9,7 +9,7 @@ on: types: [published] env: - GO_VERSION: 1.18 + GO_VERSION: 1.19 concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -30,7 +30,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3.2.0 with: - version: v1.45 + version: v1.49.0 go_mod_tidy_check: name: Go Mod Tidy Check diff --git a/README.md b/README.md index bfb5ca2ae3..084957750e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-avai | Requirement | Notes | |-------------|----------------| -| Go version | 1.18 or higher | +| Go version | 1.19 or higher | ## System Requirements diff --git a/cmd/celestia/cmd_test.go b/cmd/celestia/cmd_test.go index 4d1dee1b08..0994261a74 100644 --- a/cmd/celestia/cmd_test.go +++ b/cmd/celestia/cmd_test.go @@ -3,7 +3,6 @@ package main import ( "bytes" "context" - "io/ioutil" "os" "testing" @@ -12,8 +11,7 @@ import ( func TestLight(t *testing.T) { // Run the tests in a temporary directory - tmpDir, err := ioutil.TempDir("", "light") - require.NoError(t, err, "error creating a temporary test directory") + tmpDir := t.TempDir() testDir, err := os.Getwd() require.NoError(t, err, "error getting the current working directory") err = os.Chdir(tmpDir) @@ -59,8 +57,7 @@ func TestLight(t *testing.T) { func TestBridge(t *testing.T) { // Run the tests in a temporary directory - tmpDir, err := ioutil.TempDir("", "bridge") - require.NoError(t, err, "error creating a temporary test directory") + tmpDir := t.TempDir() testDir, err := os.Getwd() require.NoError(t, err, "error getting the current working directory") err = os.Chdir(tmpDir) diff --git a/cmd/env.go b/cmd/env.go index 2d085a96db..560076de93 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -3,9 +3,13 @@ package cmd import ( "context" + logging "github.com/ipfs/go-log/v2" + "github.com/celestiaorg/celestia-node/node" ) +var log = logging.Logger("cmd") + // NodeType reads the node.Type from the context. func NodeType(ctx context.Context) node.Type { return ctx.Value(nodeTypeKey{}).(node.Type) diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 92c451326c..7a22ed7d05 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -3,7 +3,6 @@ package cmd import ( "context" "fmt" - "log" "net/http" "net/http/pprof" "strings" @@ -145,7 +144,12 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - log.Println(http.ListenAndServe("0.0.0.0:6000", mux)) + err := http.ListenAndServe("0.0.0.0:6000", mux) //nolint:gosec + if err != nil { + log.Fatalw("failed to start pprof server", "err", err) + } else { + log.Info("started pprof server on port 6000") + } }() } diff --git a/core/testing.go b/core/testing.go index 5b48c5454c..f0ddfd58bf 100644 --- a/core/testing.go +++ b/core/testing.go @@ -85,7 +85,7 @@ func RandValidator(randPower bool, minPower int64) (*tmtypes.Validator, tmtypes. privVal := tmtypes.NewMockPV() votePower := minPower if randPower { - // nolint:gosec // G404: Use of weak random number generator + //nolint:gosec // G404: Use of weak random number generator votePower += int64(rand.Uint32()) } pubKey, err := privVal.GetPubKey() diff --git a/docker/Dockerfile b/docker/Dockerfile index a0637eb8ae..99c8c3776d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.18 as builder +FROM --platform=$BUILDPLATFORM golang:1.19 as builder RUN apt-get install make WORKDIR /src COPY go.mod go.sum ./ diff --git a/go.mod b/go.mod index d44b1a0572..8ab20370c0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/celestiaorg/celestia-node -go 1.18 +go 1.19 replace github.com/ipfs/go-verifcid => github.com/celestiaorg/go-verifcid v0.0.1-lazypatch diff --git a/header/doc.go b/header/doc.go index f0696e46da..277027d4f9 100644 --- a/header/doc.go +++ b/header/doc.go @@ -14,34 +14,34 @@ to HeaderSub will receive and validate the ExtendedHeader, and store it, making other dependent services (such as the DataAvailabilitySampler, or DASer) to access. There are 5 main components in the header package: - 1. core.Listener listens for new blocks from the celestia-core network (run by bridge nodes only), - extends them, generates a new ExtendedHeader, and publishes it to the HeaderSub. - 2. p2p.Subscriber listens for new ExtendedHeaders from the Celestia Data Availability (DA) network (via - the HeaderSub) - 3. p2p.Exchange or core.Exchange request ExtendedHeaders from other celestia DA nodes (default for - full and light nodes) or from a celestia-core node connection (bridge nodes only) - 4. Syncer manages syncing of past and recent ExtendedHeaders from either the DA network or a celestia-core - connection (bridge nodes only). - 5. Store manages storing ExtendedHeaders and making them available for access by other dependent services. + 1. core.Listener listens for new blocks from the celestia-core network (run by bridge nodes only), + extends them, generates a new ExtendedHeader, and publishes it to the HeaderSub. + 2. p2p.Subscriber listens for new ExtendedHeaders from the Celestia Data Availability (DA) network (via + the HeaderSub) + 3. p2p.Exchange or core.Exchange request ExtendedHeaders from other celestia DA nodes (default for + full and light nodes) or from a celestia-core node connection (bridge nodes only) + 4. Syncer manages syncing of past and recent ExtendedHeaders from either the DA network or a celestia-core + connection (bridge nodes only). + 5. Store manages storing ExtendedHeaders and making them available for access by other dependent services. For bridge nodes, the general flow of the header Service is as follows: - 1. core.Listener listens for new blocks from the celestia-core connection - 2. core.Listener validates the block and generates the ExtendedHeader, simultaneously storing the - extended block shares to disk - 3. core.Listener publishes the new ExtendedHeader to HeaderSub, notifying all subscribed peers of the new - ExtendedHeader - 4. Syncer is already subscribed to the HeaderSub, so it receives new ExtendedHeaders locally from - the core.Listener and stores them to disk via Store. - 4a. If the celestia-core connection is started simultaneously with the bridge node, then the celestia-core - connection will handle the syncing component, piping every synced block to the core.Listener - 4b. If the celestia-core connection is already synced to the network, the Syncer handles requesting past - headers up to the network head from the celestia-core connection (using core.Exchange rather than p2p.Exchange). + 1. core.Listener listens for new blocks from the celestia-core connection + 2. core.Listener validates the block and generates the ExtendedHeader, simultaneously storing the + extended block shares to disk + 3. core.Listener publishes the new ExtendedHeader to HeaderSub, notifying all subscribed peers of the new + ExtendedHeader + 4. Syncer is already subscribed to the HeaderSub, so it receives new ExtendedHeaders locally from + the core.Listener and stores them to disk via Store. + - If the celestia-core connection is started simultaneously with the bridge node, then the celestia-core + connection will handle the syncing component, piping every synced block to the core.Listener + - If the celestia-core connection is already synced to the network, the Syncer handles requesting past + headers up to the network head from the celestia-core connection (using core.Exchange rather than p2p.Exchange). For full and light nodes, the general flow of the header Service is as follows: - 1. Syncer listens for new ExtendedHeaders from HeaderSub - 2. If there is a gap between the local head of chain and the new, validated network head, the Syncer - kicks off a sync routine to request all ExtendedHeaders between local head and network head. - 3. While the Syncer requests headers between the local head and network head in batches, it appends them to the - subjective chain via Store with the last batched header as the new local head. + 1. Syncer listens for new ExtendedHeaders from HeaderSub + 2. If there is a gap between the local head of chain and the new, validated network head, the Syncer + kicks off a sync routine to request all ExtendedHeaders between local head and network head. + 3. While the Syncer requests headers between the local head and network head in batches, it appends them to the + subjective chain via Store with the last batched header as the new local head. */ package header diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index 26da41f831..de0646cf14 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -124,7 +124,7 @@ func (ex *Exchange) performRequest( return nil, fmt.Errorf("no trusted peers") } - // nolint:gosec // G404: Use of weak random number generator + //nolint:gosec // G404: Use of weak random number generator index := rand.Intn(len(ex.trustedPeers)) stream, err := ex.host.NewStream(ctx, ex.trustedPeers[index], exchangeProtocolID) if err != nil { diff --git a/header/sync/sync.go b/header/sync/sync.go index 3a809b54b8..ab3233ad42 100644 --- a/header/sync/sync.go +++ b/header/sync/sync.go @@ -18,19 +18,20 @@ var log = logging.Logger("header/sync") // // Subjective header - the latest known header that is not expired (within trusting period) // Network header - the latest header received from the network - // // There are two main processes running in Syncer: // 1. Main syncing loop(s.syncLoop) -// * Performs syncing from the subjective header up to the network head -// * Syncs by requesting missing headers from Exchange or -// * By accessing cache of pending network headers received from PubSub -// 2. Receives new headers from PubSub subnetwork (s.incomingNetHead) -// * Once received, tries to append it to the store -// * Or, if not adjacent to head of the store, -// * verifies against the latest known subjective header -// * adds the header to pending cache, thereby making it the latest known subjective header -// * and triggers syncing loop to catch up to that point. +// - Performs syncing from the subjective(local chain view) header up to the latest known trusted header +// - Syncs by requesting missing headers from Exchange or +// - By accessing cache of pending and verified headers +// +// 2. Receives new headers from PubSub subnetwork (s.processIncoming) +// - Usually, a new header is adjacent to the trusted head and if so, it is simply appended to the local store, +// incrementing the subjective height and making it the new latest known trusted header. +// - Or, if it receives a header further in the future, +// verifies against the latest known trusted header +// adds the header to pending cache(making it the latest known trusted header) +// and triggers syncing loop to catch up to that point. type Syncer struct { sub header.Subscriber exchange header.Exchange @@ -237,7 +238,9 @@ func (s *Syncer) processHeaders(ctx context.Context, from, to uint64) (int, erro } // TODO(@Wondertan): Number of headers that can be requested at once. Either make this configurable or, -// find a proper rationale for constant. +// +// find a proper rationale for constant. +// // TODO(@Wondertan): Make configurable var requestSize uint64 = 512 diff --git a/header/testing.go b/header/testing.go index 05e0d7982b..1e76eee828 100644 --- a/header/testing.go +++ b/header/testing.go @@ -1,5 +1,6 @@ // TODO(@Wondertan): Ideally, we should move that into subpackage, so this does not get included into binary of // production code, but that does not matter at the moment. + package header import ( @@ -172,7 +173,7 @@ func RandRawHeader(t *testing.T) *RawHeader { return &RawHeader{ Version: version.Consensus{Block: 11, App: 1}, ChainID: "test", - Height: mrand.Int63(), //nolint:gosec + Height: mrand.Int63(), Time: time.Now(), LastBlockID: RandBlockID(t), LastCommitHash: tmrand.Bytes(32), @@ -196,8 +197,8 @@ func RandBlockID(t *testing.T) types.BlockID { Hash: make([]byte, 32), }, } - mrand.Read(bid.Hash) //nolint:gosec - mrand.Read(bid.PartSetHeader.Hash) //nolint:gosec + mrand.Read(bid.Hash) + mrand.Read(bid.PartSetHeader.Hash) return bid } diff --git a/header/verify.go b/header/verify.go index f4f5af877b..a99198c7ed 100644 --- a/header/verify.go +++ b/header/verify.go @@ -8,6 +8,9 @@ import ( "github.com/tendermint/tendermint/light" ) +// TODO(@Wondertan): We should request TrustingPeriod from the network's state params or +// listen for network params changes to always have a topical value. + // TrustingPeriod is period through which we can trust a header's validators set. // // Should be significantly less than the unbonding period (e.g. unbonding @@ -16,8 +19,6 @@ import ( // More specifically, trusting period + time needed to check headers + time // needed to report and punish misbehavior should be less than the unbonding // period. -// TODO(@Wondertan): We should request it from the network's state params -// or listen for network params changes to always have a topical value. var TrustingPeriod = 168 * time.Hour // IsExpired checks if header is expired against trusting period. diff --git a/ipld/get_shares.go b/ipld/get_shares.go index 1c4ef3b363..ed370222dd 100644 --- a/ipld/get_shares.go +++ b/ipld/get_shares.go @@ -22,8 +22,8 @@ import ( // // NOTE: This value only limits amount of simultaneously running workers that // are spawned as the load increases and are killed, once the load declines. -// TODO(@Wondertan): This assumes we have parallelized DASer implemented. -// Sync the values once it is shipped. +// +// TODO(@Wondertan): This assumes we have parallelized DASer implemented. Sync the values once it is shipped. // TODO(@Wondertan): Allow configuration of values without global state. var NumWorkersLimit = MaxSquareSize * MaxSquareSize / 2 * NumConcurrentSquares @@ -33,8 +33,9 @@ var NumConcurrentSquares = 8 // Global worker pool that globally controls and limits goroutines spawned by // GetShares. -// TODO(@Wondertan): Idle timeout for workers needs to be configured to around block time, -// so that workers spawned between each reconstruction for every new block are reused. +// +// TODO(@Wondertan): Idle timeout for workers needs to be configured to around block time, +// so that workers spawned between each reconstruction for every new block are reused. var pool = workerpool.New(NumWorkersLimit) // GetShares gets shares from either local storage, or, if not found, requests diff --git a/ipld/plugin/test_helpers.go b/ipld/plugin/test_helpers.go index f27f6b92db..0ac7634817 100644 --- a/ipld/plugin/test_helpers.go +++ b/ipld/plugin/test_helpers.go @@ -10,7 +10,7 @@ import ( func RandNamespacedCID(t *testing.T) cid.Cid { raw := make([]byte, nmtHashSize) - _, err := mrand.Read(raw) // nolint:gosec // G404: Use of weak random number generator + _, err := mrand.Read(raw) require.NoError(t, err) id, err := CidFromNamespacedSha256(raw) require.NoError(t, err) diff --git a/ipld/retriever.go b/ipld/retriever.go index 65802e0238..f2aa568d24 100644 --- a/ipld/retriever.go +++ b/ipld/retriever.go @@ -32,11 +32,12 @@ var tracer = otel.Tracer("ipld") // Instead of requesting data 'share by share' it requests data by quadrants // minimizing bandwidth usage in the happy cases. // -// ---- ---- -// | 0 | 1 | -// ---- ---- -// | 2 | 3 | -// ---- ---- +// ---- ---- +// | 0 | 1 | +// ---- ---- +// | 2 | 3 | +// ---- ---- +// // Retriever randomly picks one of the data square quadrants and tries to request them one by one // until it is able to reconstruct the whole square. type Retriever struct { diff --git a/ipld/retriever_quadrant.go b/ipld/retriever_quadrant.go index a35458b66d..d73bd623f8 100644 --- a/ipld/retriever_quadrant.go +++ b/ipld/retriever_quadrant.go @@ -25,8 +25,8 @@ const ( // starting to retrieve another quadrant. // // NOTE: -// * The whole data square must be retrieved in less than block time. -// * We have 4 quadrants from two sources(rows, cols) which equals to 8 in total. +// - The whole data square must be retrieved in less than block time. +// - We have 4 quadrants from two sources(rows, cols) which equals to 8 in total. var RetrieveQuadrantTimeout = blockTime / numQuadrants * 2 type quadrant struct { @@ -95,9 +95,10 @@ func newQuadrants(dah *da.DataAvailabilityHeader) []*quadrant { // index calculates index for a share in a data square slice flattened by rows. // // NOTE: The complexity of the formula below comes from: -// * Goal to avoid share copying -// * Goal to make formula generic for both rows and cols -// * While data square is flattened by rows only +// - Goal to avoid share copying +// - Goal to make formula generic for both rows and cols +// - While data square is flattened by rows only +// // TODO(@Wondertan): This can be simplified by making rsmt2d working over 3D byte slice(not flattened) func (q *quadrant) index(rootIdx, cellIdx int) int { size := len(q.roots) diff --git a/ipld/test_helpers.go b/ipld/test_helpers.go index 1a8967e95b..58c2a3bb10 100644 --- a/ipld/test_helpers.go +++ b/ipld/test_helpers.go @@ -53,14 +53,14 @@ func RandShares(t *testing.T, total int) []Share { shares := make([]Share, total) for i := range shares { nid := make([]byte, ShareSize) - _, err := mrand.Read(nid[:NamespaceSize]) // nolint:gosec // G404: Use of weak random number generator + _, err := mrand.Read(nid[:NamespaceSize]) require.NoError(t, err) shares[i] = nid } sort.Slice(shares, func(i, j int) bool { return bytes.Compare(shares[i], shares[j]) < 0 }) for i := range shares { - _, err := mrand.Read(shares[i][NamespaceSize:]) // nolint:gosec // G404: Use of weak random number generator + _, err := mrand.Read(shares[i][NamespaceSize:]) require.NoError(t, err) } diff --git a/node/components.go b/node/components.go index 4acefc34a6..fad93a0fec 100644 --- a/node/components.go +++ b/node/components.go @@ -133,9 +133,10 @@ func invokeWatchdog(pprofdir string) func(lc fx.Lifecycle) error { } } -// TODO(@Wondetan): We must start watchdog only once. This is needed for tests where we run multiple instance -// of the Node. Ideally, the Node should have some testing options instead, so we can check for it and run without -// such utilities but it does not hurt to run one instance of watchdog per test. +// TODO(@Wondertan): We must start watchdog only once. This is needed for tests where we run multiple instance +// of the Node. Ideally, the Node should have some testing options instead, so we can check for it and run without +// such utilities but it does not hurt to run one instance of watchdog per test. + var onceWatchdog = sync.Once{} var logWatchdog = logging.Logger("watchdog") diff --git a/node/config.go b/node/config.go index aa400f7eaa..e31fe0bf08 100644 --- a/node/config.go +++ b/node/config.go @@ -80,10 +80,11 @@ func LoadConfig(path string) (*Config, error) { return &cfg, cfg.Decode(f) } +// TODO(@Wondertan): We should have a description for each field written into w, +// so users can instantly understand purpose of each field. Ideally, we should have a utility +// program to parse comments from actual sources(*.go files) and generate docs from comments. Hint: use 'ast' package. + // Encode encodes a given Config into w. -// TODO(@Wondertan): We should have a description for each field written into w, -// so users can instantly understand purpose of each field. Ideally, we should have a utility program to parse comments -// from actual sources(*.go files) and generate docs from comments. Hint: use 'ast' package. func (cfg *Config) Encode(w io.Writer) error { return toml.NewEncoder(w).Encode(cfg) } diff --git a/node/init.go b/node/init.go index efedb0b57c..37175a23d7 100644 --- a/node/init.go +++ b/node/init.go @@ -37,7 +37,7 @@ func Init(path string, tp Type, options ...Option) error { } return err } - defer flock.Unlock() // nolint: errcheck + defer flock.Unlock() //nolint: errcheck err = initDir(keysPath(path)) if err != nil { diff --git a/node/rpc/component.go b/node/rpc/component.go index 9c8cc14769..21e4546645 100644 --- a/node/rpc/component.go +++ b/node/rpc/component.go @@ -11,8 +11,9 @@ import ( ) // Server constructs a new RPC Server from the given Config. -// TODO @renaynay @Wondertan: this component is meant to be removed on implementation -// of https://github.com/celestiaorg/celestia-node/pull/506. +// +// TODO(@renaynay/@Wondertan): this component is meant to be removed on implementation +// of https://github.com/celestiaorg/celestia-node/pull/506. func Server(cfg rpc.Config) func(lc fx.Lifecycle) *rpc.Server { return func(lc fx.Lifecycle) *rpc.Server { serv := rpc.NewServer(cfg) diff --git a/node/rpc_test.go b/node/rpc_test.go index 228696bb63..eb21527a24 100644 --- a/node/rpc_test.go +++ b/node/rpc_test.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "strconv" "testing" @@ -169,7 +169,7 @@ func TestAvailabilityRequest(t *testing.T) { require.NoError(t, err) }() - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) require.NoError(t, err) availResp := new(rpc.AvailabilityResponse) diff --git a/node/store.go b/node/store.go index a548af6a4b..8b7995fbe2 100644 --- a/node/store.go +++ b/node/store.go @@ -64,7 +64,7 @@ func OpenStore(path string) (Store, error) { ok := IsInit(path) if !ok { - flock.Unlock() // nolint: errcheck + flock.Unlock() //nolint: errcheck return nil, ErrNotInited } @@ -160,7 +160,7 @@ func (f *fsStore) Datastore() (_ datastore.Batching, err error) { } func (f *fsStore) Close() error { - defer f.dirLock.Unlock() // nolint: errcheck + defer f.dirLock.Unlock() //nolint: errcheck return f.data.Close() } diff --git a/node/tests/fraud_test.go b/node/tests/fraud_test.go index ba64923692..e1f96b0bb2 100644 --- a/node/tests/fraud_test.go +++ b/node/tests/fraud_test.go @@ -16,17 +16,19 @@ import ( ) /* - Test-Case: Full Node will propagate a fraud proof to the network, once ByzantineError will be received from sampling. - Pre-Requisites: - - CoreClient is started by swamp. - Steps: +Test-Case: Full Node will propagate a fraud proof to the network, once ByzantineError will be received from sampling. + +Pre-Requisites: + - CoreClient is started by swamp. + +Steps: 1. Create a Bridge Node(BN) with broken extended header at height 10. 2. Start a BN. 3. Create a Full Node(FN) with a connection to BN as a trusted peer. 4. Start a FN. 5. Subscribe to a fraud proof and wait when it will be received. 6. Check FN is not synced to 15. - Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. + Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. */ func TestFraudProofBroadcasting(t *testing.T) { sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) @@ -75,17 +77,17 @@ func TestFraudProofBroadcasting(t *testing.T) { } /* - Test-Case: Light node receives a fraud proof using Fraud Sync - Pre-Requisites: - - CoreClient is started by swamp. - Steps: - 1. Create a Bridge Node(BN) with broken extended header at height 10. - 2. Start a BN. - 3. Create a Full Node(FN) with a connection to BN as a trusted peer. - 4. Start a FN. - 5. Subscribe to a fraud proof and wait when it will be received. - 6. Start LN once a fraud proof is received and verified by FN. - 7. Wait until LN will be connected to FN and fetch a fraud proof. +Test-Case: Light node receives a fraud proof using Fraud Sync +Pre-Requisites: +- CoreClient is started by swamp. +Steps: +1. Create a Bridge Node(BN) with broken extended header at height 10. +2. Start a BN. +3. Create a Full Node(FN) with a connection to BN as a trusted peer. +4. Start a FN. +5. Subscribe to a fraud proof and wait when it will be received. +6. Start LN once a fraud proof is received and verified by FN. +7. Wait until LN will be connected to FN and fetch a fraud proof. */ func TestFraudProofSyncing(t *testing.T) { sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) diff --git a/node/tests/p2p_test.go b/node/tests/p2p_test.go index ef95ed2dd2..39cdc64725 100644 --- a/node/tests/p2p_test.go +++ b/node/tests/p2p_test.go @@ -50,15 +50,15 @@ func TestUseBridgeNodeAsBootstraper(t *testing.T) { } /* - Test-Case: Add peer to blacklist - Steps: - 1. Create a Full Node(BN) - 2. Start a FN - 3. Create a Light Node(LN) - 5. Start a LN - 6. Explicitly block FN id by LN - 7. Check FN is allowed to dial with LN - 8. Check LN is not allowed to dial with FN +Test-Case: Add peer to blacklist +Steps: +1. Create a Full Node(BN) +2. Start a FN +3. Create a Light Node(LN) +5. Start a LN +6. Explicitly block FN id by LN +7. Check FN is allowed to dial with LN +8. Check LN is not allowed to dial with FN */ func TestAddPeerToBlackList(t *testing.T) { sw := swamp.NewSwamp(t) @@ -79,14 +79,14 @@ func TestAddPeerToBlackList(t *testing.T) { /* Test-Case: Connect Full And Light using Bridge node as a bootstrapper Steps: -1. Create a Bridge Node(BN) -2. Start a BN -3. Create full/light nodes with bridge node as bootsrapped peer -4. Start full/light nodes -5. Ensure that nodes are connected to bridge -6. Wait until light will find full node -7. Check that full and light nodes are connected to each other -8. Stop FN and ensure that it's not connected to LN + 1. Create a Bridge Node(BN) + 2. Start a BN + 3. Create full/light nodes with bridge node as bootsrapped peer + 4. Start full/light nodes + 5. Ensure that nodes are connected to bridge + 6. Wait until light will find full node + 7. Check that full and light nodes are connected to each other + 8. Stop FN and ensure that it's not connected to LN */ func TestBootstrapNodesFromBridgeNode(t *testing.T) { sw := swamp.NewSwamp(t) diff --git a/node/tests/swamp/swamp_tx.go b/node/tests/swamp/swamp_tx.go index e1e0420dcd..15a8238b37 100644 --- a/node/tests/swamp/swamp_tx.go +++ b/node/tests/swamp/swamp_tx.go @@ -10,8 +10,7 @@ import ( ) // SubmitData submits given data in the block. -// TODO(@Wondertan): This must be a real PFD using celestia-app, once we able to run App -// in the Swamp. +// TODO(@Wondertan): This must be a real PFD using celestia-app, once we can run App in the Swamp. func (s *Swamp) SubmitData(ctx context.Context, data []byte) error { result, err := s.CoreClient.BroadcastTxSync(ctx, append([]byte("key="), data...)) if err != nil { diff --git a/service/rpc/server.go b/service/rpc/server.go index 14844f376c..8551c95132 100644 --- a/service/rpc/server.go +++ b/service/rpc/server.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "net/http" + "time" "github.com/gorilla/mux" ) @@ -30,6 +31,8 @@ func NewServer(cfg Config) *Server { } server.srv = &http.Server{ Handler: server, + // the amount of time allowed to read request headers. set to the default 2 seconds + ReadHeaderTimeout: 2 * time.Second, } return server } diff --git a/service/rpc/server_test.go b/service/rpc/server_test.go index 943d779c2c..8b077bb637 100644 --- a/service/rpc/server_test.go +++ b/service/rpc/server_test.go @@ -3,7 +3,7 @@ package rpc import ( "context" "fmt" - "io/ioutil" + "io" "net/http" "testing" @@ -32,7 +32,7 @@ func TestServer(t *testing.T) { resp, err := http.Get(url) require.NoError(t, err) - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) require.NoError(t, err) t.Cleanup(func() { resp.Body.Close() diff --git a/service/share/share.go b/service/share/share.go index 85124bc7e6..fc2433733e 100644 --- a/service/share/share.go +++ b/service/share/share.go @@ -37,15 +37,16 @@ type Root = da.DataAvailabilityHeader // Service provides access to any data square or block share on the network. // // All Get methods provided on Service follow the following flow: -// * Check local storage for the requested Share. -// * If exists -// * Load from disk -// * Return -// * If not -// * Find provider on the network -// * Fetch the Share from the provider -// * Store the Share -// * Return +// 1. Check local storage for the requested Share. +// 2. If exists +// * Load from disk +// * Return +// 3. If not +// * Find provider on the network +// * Fetch the Share from the provider +// * Store the Share +// * Return +// // TODO(@Wondertan): Simple thread safety for Start and Stop would not hurt. type Service struct { Availability diff --git a/service/share/share_test.go b/service/share/share_test.go index 9985ab0914..3cf47e5fff 100644 --- a/service/share/share_test.go +++ b/service/share/share_test.go @@ -292,5 +292,6 @@ func TestSharesRoundTrip(t *testing.T) { } // this is a sample block from devnet-2 which originally showed the issue with share ordering +// //go:embed "testdata/block-825320.json" var sampleBlock string diff --git a/service/state/doc.go b/service/state/doc.go index 79f8e6a106..5d27b50f64 100644 --- a/service/state/doc.go +++ b/service/state/doc.go @@ -7,11 +7,10 @@ // celestia network. // // `Accessor` will contain three different implementations: -// 1. Implementation over a gRPC connection with a celestia-core node -// called `CoreAccess`. -// 2. Implementation over a libp2p stream with a state-providing node. -// 3. Implementation over a local running instance of the -// celestia-application (this feature will be implemented in *Full* -// nodes). -// +// 1. Implementation over a gRPC connection with a celestia-core node +// called `CoreAccess`. +// 2. Implementation over a libp2p stream with a state-providing node. +// 3. Implementation over a local running instance of the +// celestia-application (this feature will be implemented in *Full* +// nodes). package state From 2e63b802449f8064e1f1037f581c1f30e5f8983b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:52:34 +0000 Subject: [PATCH 0081/1008] chore(deps): bump go.opentelemetry.io/otel/metric from 0.31.0 to 0.32.0 Bumps [go.opentelemetry.io/otel/metric](https://github.com/open-telemetry/opentelemetry-go) from 0.31.0 to 0.32.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/metric/v0.31.0...metric/v0.32.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/metric dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8ab20370c0..79c9877805 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( go.opentelemetry.io/otel v1.10.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 - go.opentelemetry.io/otel/metric v0.31.0 + go.opentelemetry.io/otel/metric v0.32.0 go.opentelemetry.io/otel/sdk v1.10.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.10.0 diff --git a/go.sum b/go.sum index 7216c31394..f1f3b08014 100644 --- a/go.sum +++ b/go.sum @@ -1503,8 +1503,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXo go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 h1:S8DedULB3gp93Rh+9Z+7NTEv+6Id/KYS7LDyipZ9iCE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0/go.mod h1:5WV40MLWwvWlGP7Xm8g3pMcg0pKOUY609qxJn8y7LmM= -go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= -go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= +go.opentelemetry.io/otel/metric v0.32.0 h1:lh5KMDB8xlMM4kwE38vlZJ3rZeiWrjw3As1vclfC01k= +go.opentelemetry.io/otel/metric v0.32.0/go.mod h1:PVDNTt297p8ehm949jsIzd+Z2bIZJYQQG/uuHTeWFHY= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= From f5460d1d21282c0e818ab02a8e4c60ff26d03ec7 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 20 Sep 2022 18:29:43 +0200 Subject: [PATCH 0082/1008] refactor(service/state): Use DefaultProofRuntime instead of ibc-go `VerifyMembership` endpoint (#1131) * refactor(service/state): Use DefaultProofRuntime instead of importing ibc-gos VerifyMembership API * go.sum * chore(service/state): lint --- go.mod | 1 - go.sum | 2 -- service/state/core_access.go | 17 ++++++----------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 79c9877805..124f0e451f 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/celestiaorg/rsmt2d v0.6.0 github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20220418184507-c53157dd63f6 github.com/cosmos/cosmos-sdk/api v0.1.0 - github.com/cosmos/ibc-go/v4 v4.0.0 github.com/dgraph-io/badger/v2 v2.2007.4 github.com/gammazero/workerpool v1.1.3 github.com/gogo/protobuf v1.3.3 diff --git a/go.sum b/go.sum index f1f3b08014..331398b736 100644 --- a/go.sum +++ b/go.sum @@ -251,8 +251,6 @@ github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4 github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.0 h1:sgyrjqOkycXiN7Tuupuo4QAldKFg7Sipyfeg/IL7cps= github.com/cosmos/iavl v0.19.0/go.mod h1:l5h9pAB3m5fihB3pXVgwYqdY8aBsMagqz7T0MUjxZeA= -github.com/cosmos/ibc-go/v4 v4.0.0 h1:kqbbRDFB9bNLVfxImZ1wIsXZKjbS5NZ5xvnv/MoX138= -github.com/cosmos/ibc-go/v4 v4.0.0/go.mod h1:qxfXOjgJKDzdJeTTDFowrFxxI1KD3x1k0pZifdf9fOg= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= diff --git a/service/state/core_access.go b/service/state/core_access.go index af40140bd6..ec867c31c3 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/api/tendermint/abci" + "github.com/cosmos/cosmos-sdk/store/rootmulti" sdktypes "github.com/cosmos/cosmos-sdk/types" sdktx "github.com/cosmos/cosmos-sdk/types/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - proofutils "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" logging "github.com/ipfs/go-log/v2" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client/http" @@ -178,19 +178,14 @@ func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr AccAddress) if !ok { return nil, fmt.Errorf("cannot convert %s into sdktypes.Int", string(value)) } - // convert proofs into a more digestible format - merkleproof, err := proofutils.ConvertProofs(result.Response.GetProofOps()) - if err != nil { - return nil, err - } - root := proofutils.NewMerkleRoot(head.AppHash) - // VerifyMembership expects the path as: - // []string{, } - path := proofutils.NewMerklePath(banktypes.StoreKey, string(prefixedAccountKey)) - err = merkleproof.VerifyMembership(proofutils.GetSDKSpecs(), root, path, value) + // verify balance + path := fmt.Sprintf("/%s/%s", banktypes.StoreKey, string(prefixedAccountKey)) + prt := rootmulti.DefaultProofRuntime() + err = prt.VerifyValue(result.Response.GetProofOps(), head.AppHash, path, value) if err != nil { return nil, err } + return &Balance{ Denom: app.BondDenom, Amount: coin, From 7af20221dfa750952bc00f801f380a0d6d5dae5b Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 14 Sep 2022 22:35:17 +0300 Subject: [PATCH 0083/1008] ci: remove @liamsi from global CODEOWNERS and add @johnadler and @liamsi as code owner for adrs --- .github/CODEOWNERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6253ffe97c..782d0dcfab 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,4 +7,6 @@ # global owners are only requested if there isn't a more specific # codeowner specified below. For this reason, the global codeowners # are often repeated in package-level definitions. -* @liamsi @renaynay @Wondertan @vgonkivs @distractedm1nd +* @renaynay @Wondertan @vgonkivs @distractedm1nd + +docs/adr @adlerjohn @liamsi From b2cebbc4e42c0749521e075227265311cb402593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 17:06:49 +0000 Subject: [PATCH 0084/1008] chore(deps): bump github.com/ipfs/go-cid from 0.3.0 to 0.3.2 Bumps [github.com/ipfs/go-cid](https://github.com/ipfs/go-cid) from 0.3.0 to 0.3.2. - [Release notes](https://github.com/ipfs/go-cid/releases) - [Commits](https://github.com/ipfs/go-cid/compare/v0.3.0...v0.3.2) --- updated-dependencies: - dependency-name: github.com/ipfs/go-cid dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 124f0e451f..b018889df1 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/ipfs/go-bitswap v0.8.0 github.com/ipfs/go-block-format v0.0.3 github.com/ipfs/go-blockservice v0.4.0 - github.com/ipfs/go-cid v0.3.0 + github.com/ipfs/go-cid v0.3.2 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.2.0 diff --git a/go.sum b/go.sum index 331398b736..07f0e18a55 100644 --- a/go.sum +++ b/go.sum @@ -615,8 +615,8 @@ github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67Fexh github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= -github.com/ipfs/go-cid v0.3.0 h1:gT6Cbs6YePaBNc7l6v5EXt0xTMup1jGV5EU1N+QLVpY= -github.com/ipfs/go-cid v0.3.0/go.mod h1:P+HXFDF4CVhaVayiEb4wkAy7zBHxBwsJyt0Y5U6MLro= +github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= +github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= From edc5644ee6c6c17a8ffa6895d02ee80a5504c51c Mon Sep 17 00:00:00 2001 From: nitin Date: Tue, 13 Sep 2022 16:43:13 +0530 Subject: [PATCH 0085/1008] added Table of Content inside README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 084957750e..d5c5acac44 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +# Table of Contents +- [Celestia Node](#celestia-node) +- [Minimum requirements](#minimum-requirements) +- [System Requirements](#system-requirements) +- [Installation](#installation) +- [API docs](#api-docs) +- [Node types](#node-types) +- [Run a node](#run-a-node) +- [Package-specific documentation](#package-specific-documentation) +- [Code of Conduct](#code-of-conduct) # Celestia Node [![Go Reference](https://pkg.go.dev/badge/github.com/celestiaorg/celestia-node.svg)](https://pkg.go.dev/github.com/celestiaorg/celestia-node) From 57b1769d7276f2b97e9210b96cd6bb8cd3055145 Mon Sep 17 00:00:00 2001 From: nitin Date: Thu, 15 Sep 2022 14:14:45 +0530 Subject: [PATCH 0086/1008] added Table of Contents inside README --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d5c5acac44..561fe8d166 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Table of Contents + - [Celestia Node](#celestia-node) - [Minimum requirements](#minimum-requirements) - [System Requirements](#system-requirements) @@ -8,6 +9,7 @@ - [Run a node](#run-a-node) - [Package-specific documentation](#package-specific-documentation) - [Code of Conduct](#code-of-conduct) + # Celestia Node [![Go Reference](https://pkg.go.dev/badge/github.com/celestiaorg/celestia-node.svg)](https://pkg.go.dev/github.com/celestiaorg/celestia-node) @@ -34,9 +36,9 @@ Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-avai See the official docs page for system requirements per node type: -* [Bridge](https://docs.celestia.org/nodes/bridge-node#hardware-requirements) -* [Light](https://docs.celestia.org/nodes/light-node#hardware-requirements) -* [Full](https://docs.celestia.org/nodes/full-storage-node#hardware-requirements) +- [Bridge](https://docs.celestia.org/nodes/bridge-node#hardware-requirements) +- [Light](https://docs.celestia.org/nodes/light-node#hardware-requirements) +- [Full](https://docs.celestia.org/nodes/full-storage-node#hardware-requirements) ## Installation @@ -55,9 +57,9 @@ Celestia-node public API is documented [here](https://docs.celestia.org/develope ## Node types -* **Bridge** nodes - relay blocks from the celestia consensus network to the celestia data availability (DA) network -* **Full** nodes - fully reconstruct and store blocks by sampling the DA network for shares -* **Light** nodes - verify the availability of block data by sampling the DA network for shares +- **Bridge** nodes - relay blocks from the celestia consensus network to the celestia data availability (DA) network +- **Full** nodes - fully reconstruct and store blocks by sampling the DA network for shares +- **Light** nodes - verify the availability of block data by sampling the DA network for shares More information can be found [here](https://github.com/celestiaorg/celestia-node/blob/main/docs/adr/adr-003-march2022-testnet.md#legend). @@ -75,9 +77,9 @@ celestia start ## Package-specific documentation -* [Header](./service/header/doc.go) -* [Share](./service/share/doc.go) -* [DAS](./das/doc.go) +- [Header](./service/header/doc.go) +- [Share](./service/share/doc.go) +- [DAS](./das/doc.go) ## Code of Conduct From 5dc3e160dfc829475d1e4831f666f635ea5aa5fc Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 20 Sep 2022 12:16:18 +0200 Subject: [PATCH 0087/1008] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 561fe8d166..c4853e81c1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,3 @@ -# Table of Contents - -- [Celestia Node](#celestia-node) -- [Minimum requirements](#minimum-requirements) -- [System Requirements](#system-requirements) -- [Installation](#installation) -- [API docs](#api-docs) -- [Node types](#node-types) -- [Run a node](#run-a-node) -- [Package-specific documentation](#package-specific-documentation) -- [Code of Conduct](#code-of-conduct) - # Celestia Node [![Go Reference](https://pkg.go.dev/badge/github.com/celestiaorg/celestia-node.svg)](https://pkg.go.dev/github.com/celestiaorg/celestia-node) @@ -26,6 +14,18 @@ The DA network wraps the celestia-core consensus network by listening for blocks Continue reading [here](https://blog.celestia.org/celestia-mvp-release-data-availability-sampling-light-clients) if you want to learn more about DAS and how it enables secure and scalable access to Celestia chain data. +## Table of Contents + +- [Celestia Node](#celestia-node) +- [Minimum requirements](#minimum-requirements) +- [System Requirements](#system-requirements) +- [Installation](#installation) +- [API docs](#api-docs) +- [Node types](#node-types) +- [Run a node](#run-a-node) +- [Package-specific documentation](#package-specific-documentation) +- [Code of Conduct](#code-of-conduct) + ## Minimum requirements | Requirement | Notes | From 02b2b33440888dccd30f918ced05339932b818d2 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Wed, 21 Sep 2022 15:36:00 -0400 Subject: [PATCH 0088/1008] Add Protobuf section to CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57bdd95dfb..27aa2b75fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,3 +123,7 @@ package](https://golang.org/pkg/testing/). If you're adding or removing a function, please check there's a `TestType_Method` test for it. Run: `make test` + +## Protobuf + +If your PR modifies `*.proto` files, you will need to regenerate protobuf files with `make pb-gen`. Note this command assumes you have installed [protoc](https://grpc.io/docs/protoc-installation/). From a036bc6a0ef121d15c394d44900da0ec16077b80 Mon Sep 17 00:00:00 2001 From: Martichou Date: Mon, 12 Sep 2022 22:27:52 +0200 Subject: [PATCH 0089/1008] header/store: use an atomic.Pointer instead of Lock We can take advantage of the nature of atomic Pointer here by not Locking at all and thus avoid any potential "micro"-waiting while the lock is already taken. Signed-off-by: Martichou --- header/store/store.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/header/store/store.go b/header/store/store.go index 0aab86257c..9a447ed355 100644 --- a/header/store/store.go +++ b/header/store/store.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "sync" + "sync/atomic" logging "github.com/ipfs/go-log/v2" @@ -53,13 +53,12 @@ type store struct { // writing to datastore // - writeLk sync.Mutex // queue of headers to be written writes chan []*header.ExtendedHeader // signals when writes are finished writesDn chan struct{} // writeHead maintains the current write head - writeHead *header.ExtendedHeader + writeHead atomic.Pointer[header.ExtendedHeader] // pending keeps headers pending to be written in one batch pending *batch } @@ -257,14 +256,10 @@ func (s *store) Append(ctx context.Context, headers ...*header.ExtendedHeader) ( if lh == 0 { return 0, nil } - // taking the ownership of append - // mainly, this is need to avoid race conditions for s.writeHead - s.writeLk.Lock() - defer s.writeLk.Unlock() - // take current write head to verify headers against var err error - head := s.writeHead + // take current write head to verify headers against + head := s.writeHead.Load() if head == nil { head, err = s.Head(ctx) if err != nil { @@ -301,8 +296,9 @@ func (s *store) Append(ctx context.Context, headers ...*header.ExtendedHeader) ( select { case s.writes <- verified: ln := len(verified) - s.writeHead = verified[ln-1] - log.Infow("new head", "height", s.writeHead.Height, "hash", s.writeHead.Hash()) + s.writeHead.Store(verified[ln-1]) + wh := s.writeHead.Load() + log.Infow("new head", "height", wh.Height, "hash", wh.Hash()) // we return an error here after writing, // as there might be an invalid header in between of a given range return ln, err From a25200ecb3ac8734cf0c7237435ad4aece095836 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Wed, 21 Sep 2022 20:51:52 -0400 Subject: [PATCH 0090/1008] Remove Changelog section from CONTRIBUTING.md There is no CHANGELOG_PENDING.md file in this repo --- CONTRIBUTING.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57bdd95dfb..52c41cf2a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,21 +46,6 @@ pkg: Concise title of PR service/header: Remove race in core_listener ``` -## Changelog - -Every *notable* fix, improvement, feature, or breaking change should be made in a -pull-request that includes an update to the `CHANGELOG_PENDING.md` file. - -Changelog entries should be formatted as follows: - -```md -- [module/pkg: Some description about the change #xxx](link to PR) [@contributor](link to contributer github) -``` - -Here, `module` is the part of the code that changed (typically a -top-level Go package), `xxx` is the pull-request number, and `contributor` -is the author/s of the change. - ## Branching Model and Release The main development branch is `main`. From 71a02b38b039b260c41ca49e6dd722e9be03bf94 Mon Sep 17 00:00:00 2001 From: Rootul Patel Date: Wed, 21 Sep 2022 21:05:49 -0400 Subject: [PATCH 0091/1008] chore: remove deprecated linters --- .golangci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d43d74c587..599e5bdea1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,6 @@ run: linters: enable: - bodyclose - - deadcode - depguard - dogsled - dupl @@ -33,13 +32,11 @@ linters: # - scopelint - deprecated since v1.39. exportloopref will be used instead - exportloopref - staticcheck - - structcheck - stylecheck - typecheck - unconvert # - unparam - unused - - varcheck # - whitespace # - wsl # - gocognit From e232910653c0aad2078a5075bbc022ede4b46c47 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 22 Sep 2022 12:48:55 +0300 Subject: [PATCH 0092/1008] header/p2p: remove ExtendedHeaderRequest struct (#1141) --- header/p2p/request.go | 40 ---------------------------------------- header/p2p/serde.go | 27 --------------------------- 2 files changed, 67 deletions(-) delete mode 100644 header/p2p/request.go delete mode 100644 header/p2p/serde.go diff --git a/header/p2p/request.go b/header/p2p/request.go deleted file mode 100644 index 74082a36b1..0000000000 --- a/header/p2p/request.go +++ /dev/null @@ -1,40 +0,0 @@ -package p2p - -import ( - "fmt" - - p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" -) - -// ExtendedHeaderRequest is the packet format for nodes to request ExtendedHeaders -// from the network. -type ExtendedHeaderRequest struct { - Origin uint64 // block height from which to request ExtendedHeaders - Amount uint64 // amount of desired ExtendedHeaders starting from Origin, syncing in ascending order -} - -// MarshalBinary marshals ExtendedHeaderRequest to binary. -func (ehr *ExtendedHeaderRequest) MarshalBinary() ([]byte, error) { - return MarshalExtendedHeaderRequest(ehr) -} - -func (ehr *ExtendedHeaderRequest) UnmarshalBinary(data []byte) error { - if ehr == nil { - return fmt.Errorf("header: cannot UnmarshalBinary - nil ExtendedHeader") - } - - out, err := UnmarshalExtendedHeaderRequest(data) - if err != nil { - return err - } - - *ehr = *out - return nil -} - -func (ehr *ExtendedHeaderRequest) ToProto() *p2p_pb.ExtendedHeaderRequest { - return &p2p_pb.ExtendedHeaderRequest{ - Origin: ehr.Origin, - Amount: ehr.Amount, - } -} diff --git a/header/p2p/serde.go b/header/p2p/serde.go deleted file mode 100644 index 4658867bbb..0000000000 --- a/header/p2p/serde.go +++ /dev/null @@ -1,27 +0,0 @@ -package p2p - -import p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" - -// MarshalExtendedHeaderRequest serializes the given ExtendedHeaderRequest to bytes using protobuf. -// Paired with UnmarshalExtendedHeaderRequest. -func MarshalExtendedHeaderRequest(in *ExtendedHeaderRequest) ([]byte, error) { - out := &p2p_pb.ExtendedHeaderRequest{ - Origin: in.Origin, - Amount: in.Amount, - } - return out.Marshal() -} - -// UnmarshalExtendedHeaderRequest deserializes given data into a new ExtendedHeader using protobuf. -// Paired with MarshalExtendedHeaderRequest. -func UnmarshalExtendedHeaderRequest(data []byte) (*ExtendedHeaderRequest, error) { - in := &p2p_pb.ExtendedHeaderRequest{} - err := in.Unmarshal(data) - if err != nil { - return nil, err - } - return &ExtendedHeaderRequest{ - Origin: in.Origin, - Amount: in.Amount, - }, nil -} From 8bce8d023f9d0a1929e56885e439655717aea4e4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 22 Sep 2022 14:23:12 +0200 Subject: [PATCH 0093/1008] feat(rpc): Allowing for `ValAddr` on `/transfer` and `/balance/$ADDRESS` (#1102) * feat(rpc): Allowing for ValAddr on /transfer and /balance/ADDRESS * refactor(rpc): error variables in rpc/state.go * chore(lint): fixing ineffassign and making const from addrKey --- service/rpc/state.go | 46 +++++++++++++++++++++++++----------- service/state/core_access.go | 2 +- service/state/interface.go | 2 +- service/state/service.go | 2 +- service/state/state.go | 3 +++ 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/service/rpc/state.go b/service/rpc/state.go index 392c42fc48..ad4d9a47fe 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -26,7 +26,13 @@ const ( queryRedelegationsEndpoint = "/query_redelegations" ) -var addrKey = "address" +const addrKey = "address" + +var ( + ErrInvalidAddressFormat = errors.New("address must be a valid account or validator address") + ErrMissingAddress = errors.New("address not specified") + ErrInvalidAmount = errors.New("amount must be greater than zero") +) // submitTxRequest represents a request to submit a raw transaction type submitTxRequest struct { @@ -78,6 +84,7 @@ type cancelUnbondRequest struct { GasLimit uint64 `json:"gas_limit"` } +// queryRedelegationsRequest represents a request to query redelegations type queryRedelegationsRequest struct { From string `json:"from"` To string `json:"to"` @@ -93,10 +100,16 @@ func (h *Handler) handleBalanceRequest(w http.ResponseWriter, r *http.Request) { addrStr, exists := vars[addrKey] if exists { // convert address to Address type - addr, addrerr := types.AccAddressFromBech32(addrStr) - if addrerr != nil { - writeError(w, http.StatusBadRequest, balanceEndpoint, addrerr) - return + var addr state.AccAddress + addr, err = types.AccAddressFromBech32(addrStr) + if err != nil { + // first check if it is a validator address and can be converted + valAddr, err := types.ValAddressFromBech32(addrStr) + if err != nil { + writeError(w, http.StatusBadRequest, balanceEndpoint, ErrInvalidAddressFormat) + return + } + addr = valAddr.Bytes() } bal, err = h.state.BalanceForAddress(r.Context(), addr) } else { @@ -190,13 +203,18 @@ func (h *Handler) handleTransfer(w http.ResponseWriter, r *http.Request) { return } if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, transferEndpoint, errors.New("amount must be greater than 0")) + writeError(w, http.StatusBadRequest, transferEndpoint, ErrInvalidAmount) return } addr, err := types.AccAddressFromBech32(req.To) if err != nil { - writeError(w, http.StatusBadRequest, transferEndpoint, err) - return + // first check if it is a validator address and can be converted + valAddr, err := types.ValAddressFromBech32(req.To) + if err != nil { + writeError(w, http.StatusBadRequest, transferEndpoint, ErrInvalidAddressFormat) + return + } + addr = valAddr.Bytes() } amount := types.NewInt(req.Amount) @@ -224,7 +242,7 @@ func (h *Handler) handleDelegation(w http.ResponseWriter, r *http.Request) { return } if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, delegationEndpoint, errors.New("amount must be greater than 0")) + writeError(w, http.StatusBadRequest, delegationEndpoint, ErrInvalidAmount) return } addr, err := types.ValAddressFromBech32(req.To) @@ -258,7 +276,7 @@ func (h *Handler) handleUndelegation(w http.ResponseWriter, r *http.Request) { return } if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, undelegationEndpoint, errors.New("amount must be greater than 0")) + writeError(w, http.StatusBadRequest, undelegationEndpoint, ErrInvalidAmount) return } addr, err := types.ValAddressFromBech32(req.From) @@ -292,7 +310,7 @@ func (h *Handler) handleCancelUnbonding(w http.ResponseWriter, r *http.Request) return } if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, errors.New("amount must be greater than 0")) + writeError(w, http.StatusBadRequest, cancelUnbondingEndpoint, ErrInvalidAmount) return } addr, err := types.ValAddressFromBech32(req.From) @@ -326,7 +344,7 @@ func (h *Handler) handleRedelegation(w http.ResponseWriter, r *http.Request) { return } if req.Amount <= 0 { - writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, errors.New("amount must be greater than 0")) + writeError(w, http.StatusBadRequest, beginRedelegationEndpoint, ErrInvalidAmount) return } srcAddr, err := types.ValAddressFromBech32(req.From) @@ -362,7 +380,7 @@ func (h *Handler) handleQueryDelegation(w http.ResponseWriter, r *http.Request) vars := mux.Vars(r) addrStr, exists := vars[addrKey] if !exists { - writeError(w, http.StatusBadRequest, queryDelegationEndpoint, errors.New("address not specified")) + writeError(w, http.StatusBadRequest, queryDelegationEndpoint, ErrMissingAddress) return } @@ -393,7 +411,7 @@ func (h *Handler) handleQueryUnbonding(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) addrStr, exists := vars[addrKey] if !exists { - writeError(w, http.StatusBadRequest, queryUnbondingEndpoint, errors.New("address not specified")) + writeError(w, http.StatusBadRequest, queryUnbondingEndpoint, ErrMissingAddress) return } diff --git a/service/state/core_access.go b/service/state/core_access.go index ec867c31c3..24228674e0 100644 --- a/service/state/core_access.go +++ b/service/state/core_access.go @@ -136,7 +136,7 @@ func (ca *CoreAccessor) Balance(ctx context.Context) (*Balance, error) { return ca.BalanceForAddress(ctx, addr) } -func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr AccAddress) (*Balance, error) { +func (ca *CoreAccessor) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { head, err := ca.getter.Head(ctx) if err != nil { return nil, err diff --git a/service/state/interface.go b/service/state/interface.go index 40427c017c..023c76d86e 100644 --- a/service/state/interface.go +++ b/service/state/interface.go @@ -27,7 +27,7 @@ type Accessor interface { // NOTE: the balance returned is the balance reported by the block right before // the node's current head (head-1). This is due to the fact that for block N, the block's // `AppHash` is the result of applying the previous block's transaction list. - BalanceForAddress(ctx context.Context, addr AccAddress) (*Balance, error) + BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) // Transfer sends the given amount of coins from default wallet of the node to the given account address. Transfer(ctx context.Context, to AccAddress, amount math.Int, gasLimit uint64) (*TxResponse, error) diff --git a/service/state/service.go b/service/state/service.go index 9100d2ec9b..35e635c665 100644 --- a/service/state/service.go +++ b/service/state/service.go @@ -37,7 +37,7 @@ func (s *Service) Balance(ctx context.Context) (*Balance, error) { return s.accessor.Balance(ctx) } -func (s *Service) BalanceForAddress(ctx context.Context, addr AccAddress) (*Balance, error) { +func (s *Service) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { return s.accessor.BalanceForAddress(ctx, addr) } diff --git a/service/state/state.go b/service/state/state.go index a4720b6987..987a783239 100644 --- a/service/state/state.go +++ b/service/state/state.go @@ -15,6 +15,9 @@ type Tx = coretypes.Tx // TxResponse is an alias to the TxResponse type from Cosmos-SDK. type TxResponse = sdk.TxResponse +// Address is an alias to the Address type from Cosmos-SDK. +type Address = sdk.Address + // ValAddress is an alias to the ValAddress type from Cosmos-SDK. type ValAddress = sdk.ValAddress From 565b2b8aa94d56090f84ad6ed54684491f577325 Mon Sep 17 00:00:00 2001 From: Viacheslav Gonkivskyi Date: Thu, 22 Sep 2022 13:21:27 +0300 Subject: [PATCH 0094/1008] bugfix(fraud): close stream properly --- fraud/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fraud/sync.go b/fraud/sync.go index e710cfccb2..21f5fe4fb4 100644 --- a/fraud/sync.go +++ b/fraud/sync.go @@ -142,7 +142,7 @@ func (f *ProofService) handleFraudMessageRequest(stream network.Stream) { log.Errorw("error while writing a response", "err", err) return } - if err = stream.CloseWrite(); err != nil { - log.Warnw("error while closing a writer in stream", "err", err) + if err = stream.Close(); err != nil { + log.Errorw("error while closing a writer in stream", "err", err) } } From cec6a2d6535870910478cac27f44c19dcd6316af Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 23 Sep 2022 10:45:12 +0200 Subject: [PATCH 0095/1008] node/state: Refactor node pkg -> nodebuilder and split services into modules (#997) * refactor: implement better node pkg structuring for node/state pkg * refactor: rename node/config pkg to node/node * refactor(node/state): Use fx annotation from #923 * refactor(node/state): use constructor from state pkg directly * fix: review suggestions * refactor(node): state module improvements and PR review in the form of a PR (#90) * refactor(node): state module improvements * Move settings, opts and config out of `node/node` back to `node` * `state` pkg module improvements * Absorb Config and options from `node/key` pkg * Don't use core.Config on Module constructor and take from the DI instead * Use node Type and Config on Module constructor * first steps towarads `core` pkg modulazation * chore(swamp): fix linter by shortening import alias * refactor: moving header to subpackage * refactor: moving share components to share subpackage * chore: basic linting * refactor/chore: renaming services and methods to satisfy linter * refactor: moving node construction to node/module.go * refactor: rpc and core subpackages * refactor: building node in /node/module.go, rpc and core fixes for tests * refactor: modularized daser and simplified node/module.go * refactor: passing configs by ref to actually modify node.Config, moving p2p opts, removing config_opts, renaming files to match new scheme * refactor(node): move some global node options to their respective node modules * refactor: unexporting moduleOpts and fields * refactor: variadic options for modules * refactor: removing lifecycle hooks * refactor: renaming node to nodebuilder * refactor: implementing review comments * fix: renaming test directories to nodebuilder * refactor: config setters separated from options, writing config to env for persisting options during initialization * refactor: minor review comments (cleaning clutter to get to the juicy stuff) * refactor: exporting share.Discovery, providing it directly, removing Full and Light availability constructors in nodebuilder/share * refactor: providing header service directly in module * refactor: making InitStore invoke directly without returning a function, removing dead code * fix: fixing issue where flags do not persist when init is called twice Co-authored-by: renaynay * chore: removing dead code * refactor: deferring setting node config during flag parsing * refactor: collapsing switch in DefaultConfig * refactor: removing nodebuilder/daser/daser.go * refactor: fx lifecycle in for core/remote * chore: imports * refactor: removing all setters, moving checks to construction * refactor: adding cfg.ValidateBasic to each nodebuilder module * refactor: moving core sanity checks to cfg.ValidateBasic and populating default conf * fix: replacing IP on sanity check * renaming to sanitizeIP Co-authored-by @Wondertan Co-authored-by: Ryan Co-authored-by: renaynay * Fix issues with config persistence / invoking validation on config (#95) * fix config * make sure to save config before starting node, otherwise all overrides are lost * chore: lint * refactor|test(nodebuilder/tests): update fraud tests to use new nodebuilder configuration functionality * refactor(nodebuilder/header): adapt Fraud (with syncer) to refactoring changes * refactor(nodebuilder/header): restore logic that syncer fails with Warn if no chain head * fix(tests): fixing TestFraudProofSyncing * fix(cmd): not persisting config with flags on start * refactor(nodebuilder): consistent baseComponents, adding back daser proxy constructor, renaming Loader to ConfigLoader * lint: fixing goimports after rebase * refactor: implement better node pkg structuring for node/state pkg * refactor: rename node/config pkg to node/node * refactor(node/state): Use fx annotation from #923 * refactor(node/state): use constructor from state pkg directly * fix: review suggestions * refactor(node): state module improvements and PR review in the form of a PR (#90) * refactor(node): state module improvements * Move settings, opts and config out of `node/node` back to `node` * `state` pkg module improvements * Absorb Config and options from `node/key` pkg * Don't use core.Config on Module constructor and take from the DI instead * Use node Type and Config on Module constructor * first steps towarads `core` pkg modulazation * chore(swamp): fix linter by shortening import alias * refactor: moving header to subpackage * refactor: moving share components to share subpackage * chore: basic linting * refactor/chore: renaming services and methods to satisfy linter * refactor: moving node construction to node/module.go * refactor: rpc and core subpackages * refactor: building node in /node/module.go, rpc and core fixes for tests * refactor: modularized daser and simplified node/module.go * refactor: passing configs by ref to actually modify node.Config, moving p2p opts, removing config_opts, renaming files to match new scheme * refactor(node): move some global node options to their respective node modules * refactor: unexporting moduleOpts and fields * refactor: variadic options for modules * refactor: removing lifecycle hooks * refactor: renaming node to nodebuilder * refactor: implementing review comments * fix: renaming test directories to nodebuilder * refactor: config setters separated from options, writing config to env for persisting options during initialization * refactor: minor review comments (cleaning clutter to get to the juicy stuff) * refactor: exporting share.Discovery, providing it directly, removing Full and Light availability constructors in nodebuilder/share * refactor: providing header service directly in module * refactor: making InitStore invoke directly without returning a function, removing dead code * fix: fixing issue where flags do not persist when init is called twice Co-authored-by: renaynay * chore: removing dead code * refactor: deferring setting node config during flag parsing * refactor: collapsing switch in DefaultConfig * refactor: removing nodebuilder/daser/daser.go * refactor: fx lifecycle in for core/remote * chore: imports * refactor: removing all setters, moving checks to construction * refactor: adding cfg.ValidateBasic to each nodebuilder module * refactor: moving core sanity checks to cfg.ValidateBasic and populating default conf * fix: replacing IP on sanity check * renaming to sanitizeIP Co-authored-by @Wondertan Co-authored-by: Ryan Co-authored-by: renaynay * Fix issues with config persistence / invoking validation on config (#95) * fix config * make sure to save config before starting node, otherwise all overrides are lost * chore: lint * refactor|test(nodebuilder/tests): update fraud tests to use new nodebuilder configuration functionality * refactor(nodebuilder/header): adapt Fraud (with syncer) to refactoring changes * refactor(nodebuilder/header): restore logic that syncer fails with Warn if no chain head * fix(tests): fixing TestFraudProofSyncing * fix(cmd): not persisting config with flags on start * refactor(nodebuilder): consistent baseComponents, adding back daser proxy constructor, renaming Loader to ConfigLoader * lint: fixing goimports after rebase * chore(docs): removing multiline todo from godoc comment for new gofmt compliance * fix(test): Fixing rpc test from rebase * refactor(nodebuilder/fraud): Move fraud-related constructors into separate module * refactor(service/rpc): Wrap port parsing error * refactor(nodebuilder|service): Wrap config error messages with pkg name * refactor(nodebuilder): cfg.ValidateBasic -> cfg.Validate * refactor(nodebuilder): Move refresh routing period check into config validation and fix err check in rpc/config * refactor(das | nodebuilder/p2p): apply suggestions from @Wondertan approval review * fix(cmd): replace logger in cmd after rebase/merge conflict issues * refactor(cmd): set context with config at end of parsing, not in every individual flag parsing func * refactor(cmd): simplify returns in flags_p2p Co-authored-by: Ryan Co-authored-by: Hlib Kanunnikov Co-authored-by: renaynay --- Makefile | 10 +- cmd/cel-shed/header.go | 8 +- cmd/celestia/bridge.go | 18 +- cmd/celestia/full.go | 20 +- cmd/celestia/light.go | 21 +- cmd/env.go | 34 +- cmd/flags_core.go | 52 +-- cmd/flags_header.go | 39 ++- cmd/flags_key.go | 9 +- cmd/flags_misc.go | 4 +- cmd/flags_node.go | 21 +- cmd/flags_p2p.go | 18 +- cmd/flags_rpc.go | 11 +- cmd/init.go | 4 +- cmd/start.go | 8 +- node/components.go | 142 -------- node/config_opts.go | 103 ------ node/core/core.go | 76 ----- node/p2p/bitswap.go | 68 ---- node/p2p/pubsub.go | 56 ---- node/p2p/routing.go | 75 ----- node/services/service.go | 315 ------------------ node/settings.go | 117 ------- node/state/core.go | 27 -- node/state/keyring.go | 74 ---- node/state/state.go | 46 --- {node => nodebuilder}/config.go | 60 ++-- {node => nodebuilder}/config_test.go | 6 +- nodebuilder/core/config.go | 57 ++++ nodebuilder/core/core.go | 9 + nodebuilder/core/module.go | 56 ++++ nodebuilder/core/opts.go | 19 ++ nodebuilder/daser/daser.go | 20 ++ nodebuilder/daser/module.go | 37 ++ nodebuilder/fraud/fraud.go | 80 +++++ nodebuilder/fraud/module.go | 29 ++ .../services => nodebuilder/header}/config.go | 28 +- nodebuilder/header/header.go | 50 +++ nodebuilder/header/module.go | 107 ++++++ nodebuilder/header/opts.go | 16 + {node => nodebuilder}/init.go | 31 +- {node => nodebuilder}/init_test.go | 18 +- nodebuilder/module.go | 46 +++ {node => nodebuilder}/node.go | 35 +- {node => nodebuilder/node}/type.go | 0 {node => nodebuilder}/node_bridge_test.go | 14 +- {node => nodebuilder}/node_light_test.go | 15 +- {node => nodebuilder}/node_test.go | 16 +- {node => nodebuilder}/p2p/addrs.go | 0 nodebuilder/p2p/bitswap.go | 66 ++++ node/p2p/p2p.go => nodebuilder/p2p/config.go | 30 +- {node => nodebuilder}/p2p/host.go | 58 ++-- {node => nodebuilder}/p2p/identity.go | 0 {node => nodebuilder}/p2p/ipld.go | 0 {node => nodebuilder}/p2p/misc.go | 42 ++- nodebuilder/p2p/module.go | 42 +++ nodebuilder/p2p/opts.go | 36 ++ nodebuilder/p2p/pubsub.go | 54 +++ nodebuilder/p2p/routing.go | 69 ++++ nodebuilder/rpc/module.go | 56 ++++ .../component.go => nodebuilder/rpc/rpc.go | 17 - {node => nodebuilder}/rpc_test.go | 40 +-- nodebuilder/settings.go | 27 ++ nodebuilder/share/config.go | 37 ++ nodebuilder/share/module.go | 71 ++++ nodebuilder/share/share.go | 35 ++ .../key/key.go => nodebuilder/state/config.go | 7 +- nodebuilder/state/core.go | 15 + nodebuilder/state/keyring.go | 72 ++++ nodebuilder/state/module.go | 57 ++++ nodebuilder/state/opts.go | 13 + {node => nodebuilder}/store.go | 2 +- {node => nodebuilder}/store_mem.go | 2 +- {node => nodebuilder}/store_test.go | 10 +- {node => nodebuilder}/testing.go | 27 +- {node => nodebuilder}/tests/README.md | 0 {node => nodebuilder}/tests/fraud_test.go | 75 +++-- {node => nodebuilder}/tests/p2p_test.go | 82 +++-- .../tests/reconstruct_test.go | 59 ++-- {node => nodebuilder}/tests/swamp/config.go | 0 .../tests/swamp/img/test_swamp.svg | 0 {node => nodebuilder}/tests/swamp/swamp.go | 70 ++-- {node => nodebuilder}/tests/swamp/swamp_tx.go | 0 {node => nodebuilder}/tests/sync_test.go | 42 ++- nodebuilder/watchdog.go | 49 +++ service/rpc/config.go | 17 + service/share/discovery.go | 12 +- service/share/full_availability.go | 4 +- service/share/light_availability.go | 4 +- 89 files changed, 1767 insertions(+), 1657 deletions(-) delete mode 100644 node/components.go delete mode 100644 node/config_opts.go delete mode 100644 node/core/core.go delete mode 100644 node/p2p/bitswap.go delete mode 100644 node/p2p/pubsub.go delete mode 100644 node/p2p/routing.go delete mode 100644 node/services/service.go delete mode 100644 node/settings.go delete mode 100644 node/state/core.go delete mode 100644 node/state/keyring.go delete mode 100644 node/state/state.go rename {node => nodebuilder}/config.go (51%) rename {node => nodebuilder}/config_test.go (74%) create mode 100644 nodebuilder/core/config.go create mode 100644 nodebuilder/core/core.go create mode 100644 nodebuilder/core/module.go create mode 100644 nodebuilder/core/opts.go create mode 100644 nodebuilder/daser/daser.go create mode 100644 nodebuilder/daser/module.go create mode 100644 nodebuilder/fraud/fraud.go create mode 100644 nodebuilder/fraud/module.go rename {node/services => nodebuilder/header}/config.go (68%) create mode 100644 nodebuilder/header/header.go create mode 100644 nodebuilder/header/module.go create mode 100644 nodebuilder/header/opts.go rename {node => nodebuilder}/init.go (78%) rename {node => nodebuilder}/init_test.go (69%) create mode 100644 nodebuilder/module.go rename {node => nodebuilder}/node.go (85%) rename {node => nodebuilder/node}/type.go (100%) rename {node => nodebuilder}/node_bridge_test.go (66%) rename {node => nodebuilder}/node_light_test.go (70%) rename {node => nodebuilder}/node_test.go (80%) rename {node => nodebuilder}/p2p/addrs.go (100%) create mode 100644 nodebuilder/p2p/bitswap.go rename node/p2p/p2p.go => nodebuilder/p2p/config.go (80%) rename {node => nodebuilder}/p2p/host.go (53%) rename {node => nodebuilder}/p2p/identity.go (100%) rename {node => nodebuilder}/p2p/ipld.go (100%) rename {node => nodebuilder}/p2p/misc.go (67%) create mode 100644 nodebuilder/p2p/module.go create mode 100644 nodebuilder/p2p/opts.go create mode 100644 nodebuilder/p2p/pubsub.go create mode 100644 nodebuilder/p2p/routing.go create mode 100644 nodebuilder/rpc/module.go rename node/rpc/component.go => nodebuilder/rpc/rpc.go (56%) rename {node => nodebuilder}/rpc_test.go (84%) create mode 100644 nodebuilder/settings.go create mode 100644 nodebuilder/share/config.go create mode 100644 nodebuilder/share/module.go create mode 100644 nodebuilder/share/share.go rename node/key/key.go => nodebuilder/state/config.go (63%) create mode 100644 nodebuilder/state/core.go create mode 100644 nodebuilder/state/keyring.go create mode 100644 nodebuilder/state/module.go create mode 100644 nodebuilder/state/opts.go rename {node => nodebuilder}/store.go (99%) rename {node => nodebuilder}/store_mem.go (97%) rename {node => nodebuilder}/store_test.go (80%) rename {node => nodebuilder}/testing.go (68%) rename {node => nodebuilder}/tests/README.md (100%) rename {node => nodebuilder}/tests/fraud_test.go (63%) rename {node => nodebuilder}/tests/p2p_test.go (76%) rename {node => nodebuilder}/tests/reconstruct_test.go (73%) rename {node => nodebuilder}/tests/swamp/config.go (100%) rename {node => nodebuilder}/tests/swamp/img/test_swamp.svg (100%) rename {node => nodebuilder}/tests/swamp/swamp.go (81%) rename {node => nodebuilder}/tests/swamp/swamp_tx.go (100%) rename {node => nodebuilder}/tests/sync_test.go (81%) create mode 100644 nodebuilder/watchdog.go diff --git a/Makefile b/Makefile index 0f0fc2f5d2..b9e1f50cf2 100644 --- a/Makefile +++ b/Makefile @@ -82,25 +82,25 @@ lint: ## test-unit: Running unit tests test-unit: @echo "--> Running unit tests" - @go test -v `go list ./... | grep -v node/tests` -covermode=atomic -coverprofile=coverage.out + @go test -v `go list ./... | grep -v nodebuilder/tests` -covermode=atomic -coverprofile=coverage.out .PHONY: test-unit ## test-unit-race: Running unit tests with data race detector test-unit-race: @echo "--> Running unit tests with data race detector" - @go test -v -race `go list ./... | grep -v node/tests` + @go test -v -race `go list ./... | grep -v nodebuilder/tests` .PHONY: test-unit-race -## test-swamp: Running swamp tests located in node/tests +## test-swamp: Running swamp tests located in nodebuilder/tests test-swamp: @echo "--> Running swamp tests" - @go test -v ./node/tests + @go test -v ./nodebuilder/tests .PHONY: test-swamp ## test-swamp: Running swamp tests with data race detector located in node/tests test-swamp-race: @echo "--> Running swamp tests with data race detector" - @go test -v -race ./node/tests + @go test -v -race ./nodebuilder/tests .PHONY: test-swamp-race ## test-all: Running both unit and swamp tests diff --git a/cmd/cel-shed/header.go b/cmd/cel-shed/header.go index b2e09f30cd..ea2584405d 100644 --- a/cmd/cel-shed/header.go +++ b/cmd/cel-shed/header.go @@ -8,7 +8,8 @@ import ( "github.com/spf13/cobra" "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) func init() { @@ -42,9 +43,8 @@ Custom store path is not supported yet.`, return fmt.Errorf("invalid height: %w", err) } - s, err := node.OpenStore( - fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(network)), - ) + s, err := nodebuilder.OpenStore(fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), + strings.ToLower(network))) if err != nil { return err } diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index e5d73e6d32..6ca7633085 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on @@ -48,28 +48,28 @@ var bridgeCmd = &cobra.Command{ return err } - ctx, err = cmdnode.ParseP2PFlags(ctx, cmd) - if err != nil { - return err - } + cfg := cmdnode.NodeConfig(ctx) - ctx, err = cmdnode.ParseCoreFlags(ctx, cmd) + err = cmdnode.ParseP2PFlags(cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) + err = cmdnode.ParseCoreFlags(cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseRPCFlags(ctx, cmd) + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) if err != nil { return err } - ctx = cmdnode.ParseKeyFlags(ctx, cmd) + cmdnode.ParseRPCFlags(cmd, &cfg) + cmdnode.ParseKeyFlags(cmd, &cfg) + // set config + ctx = cmdnode.WithNodeConfig(ctx, &cfg) cmd.SetContext(ctx) return nil }, diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index 7cd7c7f3c7..ec4e4fea3d 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on @@ -55,33 +55,33 @@ var fullCmd = &cobra.Command{ return err } - ctx, err = cmdnode.ParseP2PFlags(ctx, cmd) - if err != nil { - return err - } + cfg := cmdnode.NodeConfig(ctx) - ctx, err = cmdnode.ParseCoreFlags(ctx, cmd) + err = cmdnode.ParseP2PFlags(cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd) + err = cmdnode.ParseCoreFlags(cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) + ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseRPCFlags(ctx, cmd) + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) if err != nil { return err } - ctx = cmdnode.ParseKeyFlags(ctx, cmd) + cmdnode.ParseRPCFlags(cmd, &cfg) + cmdnode.ParseKeyFlags(cmd, &cfg) + // set config + ctx = cmdnode.WithNodeConfig(ctx, &cfg) cmd.SetContext(ctx) return nil }, diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index 3635a8bd0a..a93e58a829 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" cmdnode "github.com/celestiaorg/celestia-node/cmd" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) // NOTE: We should always ensure that the added Flags below are parsed somewhere, like in the PersistentPreRun func on @@ -50,38 +50,39 @@ var lightCmd = &cobra.Command{ ctx = cmdnode.WithNodeType(ctx, node.Light) + // loads existing config into the environment ctx, err = cmdnode.ParseNodeFlags(ctx, cmd) if err != nil { return err } - ctx, err = cmdnode.ParseP2PFlags(ctx, cmd) - if err != nil { - return err - } + cfg := cmdnode.NodeConfig(ctx) - ctx, err = cmdnode.ParseCoreFlags(ctx, cmd) + err = cmdnode.ParseP2PFlags(cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd) + err = cmdnode.ParseCoreFlags(cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) + ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd, &cfg) if err != nil { return err } - ctx, err = cmdnode.ParseRPCFlags(ctx, cmd) + ctx, err = cmdnode.ParseMiscFlags(ctx, cmd) if err != nil { return err } - ctx = cmdnode.ParseKeyFlags(ctx, cmd) + cmdnode.ParseRPCFlags(cmd, &cfg) + cmdnode.ParseKeyFlags(cmd, &cfg) + // set config + ctx = cmdnode.WithNodeConfig(ctx, &cfg) cmd.SetContext(ctx) return nil }, diff --git a/cmd/env.go b/cmd/env.go index 560076de93..d16f45d41c 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -4,13 +4,15 @@ import ( "context" logging "github.com/ipfs/go-log/v2" + "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) var log = logging.Logger("cmd") -// NodeType reads the node.Type from the context. +// NodeType reads the node type from the context. func NodeType(ctx context.Context) node.Type { return ctx.Value(nodeTypeKey{}).(node.Type) } @@ -20,7 +22,17 @@ func StorePath(ctx context.Context) string { return ctx.Value(storePathKey{}).(string) } -// WithNodeType sets Node Type in the given context. +// NodeConfig reads the node config from the context. +func NodeConfig(ctx context.Context) nodebuilder.Config { + cfg, ok := ctx.Value(configKey{}).(nodebuilder.Config) + if !ok { + nodeType := NodeType(ctx) + cfg = *nodebuilder.DefaultConfig(nodeType) + } + return cfg +} + +// WithNodeType sets the node type in the given context. func WithNodeType(ctx context.Context, tp node.Type) context.Context { return context.WithValue(ctx, nodeTypeKey{}, tp) } @@ -30,23 +42,29 @@ func WithStorePath(ctx context.Context, storePath string) context.Context { return context.WithValue(ctx, storePathKey{}, storePath) } -// NodeOptions returns node options parsed from Environment(Flags, ENV vars, etc) -func NodeOptions(ctx context.Context) []node.Option { - options, ok := ctx.Value(optionsKey{}).([]node.Option) +// NodeOptions returns config options parsed from Environment(Flags, ENV vars, etc) +func NodeOptions(ctx context.Context) []fx.Option { + options, ok := ctx.Value(optionsKey{}).([]fx.Option) if !ok { - return []node.Option{} + return []fx.Option{} } return options } // WithNodeOptions add new options to Env. -func WithNodeOptions(ctx context.Context, opts ...node.Option) context.Context { +func WithNodeOptions(ctx context.Context, opts ...fx.Option) context.Context { options := NodeOptions(ctx) return context.WithValue(ctx, optionsKey{}, append(options, opts...)) } +// WithNodeConfig sets the node config in the Env. +func WithNodeConfig(ctx context.Context, config *nodebuilder.Config) context.Context { + return context.WithValue(ctx, configKey{}, *config) +} + type ( optionsKey struct{} + configKey struct{} storePathKey struct{} nodeTypeKey struct{} ) diff --git a/cmd/flags_core.go b/cmd/flags_core.go index f1a555ea26..1d41669251 100644 --- a/cmd/flags_core.go +++ b/cmd/flags_core.go @@ -1,15 +1,12 @@ package cmd import ( - "context" "fmt" - "strconv" - "strings" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -38,54 +35,27 @@ func CoreFlags() *flag.FlagSet { "9090", "Set a custom gRPC port for the core node connection. The --core.ip flag must also be provided.", ) - return flags } // ParseCoreFlags parses Core flags from the given cmd and applies values to Env. -func ParseCoreFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { +func ParseCoreFlags( + cmd *cobra.Command, + cfg *nodebuilder.Config, +) error { coreIP := cmd.Flag(coreFlag).Value.String() if coreIP == "" { if cmd.Flag(coreGRPCFlag).Changed || cmd.Flag(coreRPCFlag).Changed { - return ctx, fmt.Errorf("cannot specify RPC/gRPC ports without specifying an IP address for --core.ip") + return fmt.Errorf("cannot specify RPC/gRPC ports without specifying an IP address for --core.ip") } - return ctx, nil - } - // sanity check given core ip addr and strip leading protocol - ip, err := sanityCheckIP(coreIP) - if err != nil { - return ctx, err + return nil } rpc := cmd.Flag(coreRPCFlag).Value.String() - // sanity check rpc endpoint - _, err = strconv.Atoi(rpc) - if err != nil { - return ctx, err - } - ctx = WithNodeOptions(ctx, node.WithRemoteCoreIP(ip), node.WithRemoteCorePort(rpc)) - grpc := cmd.Flag(coreGRPCFlag).Value.String() - // sanity check gRPC endpoint - _, err = strconv.Atoi(grpc) - if err != nil { - return ctx, err - } - ctx = WithNodeOptions(ctx, node.WithGRPCPort(grpc)) - return ctx, nil -} -// sanityCheckIP trims leading protocol scheme and port from the given -// IP address if present. -func sanityCheckIP(ip string) (string, error) { - original := ip - ip = strings.TrimPrefix(ip, "http://") - ip = strings.TrimPrefix(ip, "https://") - ip = strings.TrimPrefix(ip, "tcp://") - ip = strings.TrimSuffix(ip, "/") - ip = strings.Split(ip, ":")[0] - if ip == "" { - return "", fmt.Errorf("invalid IP addr given: %s", original) - } - return ip, nil + cfg.Core.IP = coreIP + cfg.Core.RPCPort = rpc + cfg.Core.GRPCPort = grpc + return nil } diff --git a/cmd/flags_header.go b/cmd/flags_header.go index d32226d471..7e5c2afc64 100644 --- a/cmd/flags_header.go +++ b/cmd/flags_header.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -28,11 +28,11 @@ func HeadersFlags() *flag.FlagSet { } // ParseHeadersFlags parses Header package flags from the given cmd and applies values to Env. -func ParseHeadersFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { - if ctx, err := ParseTrustedHashFlags(ctx, cmd); err != nil { +func ParseHeadersFlags(ctx context.Context, cmd *cobra.Command, cfg *nodebuilder.Config) (context.Context, error) { + if ctx, err := ParseTrustedHashFlags(ctx, cmd, cfg); err != nil { return ctx, err } - if ctx, err := ParseTrustedPeerFlags(ctx, cmd); err != nil { + if ctx, err := ParseTrustedPeerFlags(ctx, cmd, cfg); err != nil { return ctx, err } @@ -47,12 +47,18 @@ func TrustedPeersFlags() *flag.FlagSet { nil, "Multiaddresses of a reliable peers to fetch headers from. (Format: multiformats.io/multiaddr)", ) - return flags } // ParseTrustedPeerFlags parses Header package flags from the given cmd and applies values to Env. -func ParseTrustedPeerFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { +func ParseTrustedPeerFlags( + ctx context.Context, + cmd *cobra.Command, + cfg *nodebuilder.Config, +) (setCtx context.Context, err error) { + defer func() { + setCtx = WithNodeConfig(ctx, cfg) + }() tpeers, err := cmd.Flags().GetStringSlice(headersTrustedPeersFlag) if err != nil { return ctx, err @@ -64,10 +70,8 @@ func ParseTrustedPeerFlags(ctx context.Context, cmd *cobra.Command) (context.Con return ctx, fmt.Errorf("cmd: while parsing '%s' with peer addr '%s': %w", headersTrustedPeersFlag, tpeer, err) } } - - ctx = WithNodeOptions(ctx, node.WithTrustedPeers(tpeers...)) - - return ctx, nil + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, tpeers...) + return } // TrustedHashFlags returns a set of flags related to configuring a `TrustedHash`. @@ -79,12 +83,18 @@ func TrustedHashFlags() *flag.FlagSet { "", "Hex encoded header hash. Used to subjectively initialize header synchronization", ) - return flags } // ParseTrustedHashFlags parses Header package flags from the given cmd and applies values to Env. -func ParseTrustedHashFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { +func ParseTrustedHashFlags( + ctx context.Context, + cmd *cobra.Command, + cfg *nodebuilder.Config, +) (setCtx context.Context, err error) { + defer func() { + setCtx = WithNodeConfig(ctx, cfg) + }() hash := cmd.Flag(headersTrustedHashFlag).Value.String() if hash != "" { _, err := hex.DecodeString(hash) @@ -92,8 +102,7 @@ func ParseTrustedHashFlags(ctx context.Context, cmd *cobra.Command) (context.Con return ctx, fmt.Errorf("cmd: while parsing '%s': %w", headersTrustedHashFlag, err) } - ctx = WithNodeOptions(ctx, node.WithTrustedHash(hash)) + cfg.Header.TrustedHash = hash } - - return ctx, nil + return } diff --git a/cmd/flags_key.go b/cmd/flags_key.go index 86253ce8be..1470430a5a 100644 --- a/cmd/flags_key.go +++ b/cmd/flags_key.go @@ -4,9 +4,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "context" - - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) var keyringAccNameFlag = "keyring.accname" @@ -19,10 +17,9 @@ func KeyFlags() *flag.FlagSet { return flags } -func ParseKeyFlags(ctx context.Context, cmd *cobra.Command) context.Context { +func ParseKeyFlags(cmd *cobra.Command, cfg *nodebuilder.Config) { keyringAccName := cmd.Flag(keyringAccNameFlag).Value.String() if keyringAccName != "" { - return WithNodeOptions(ctx, node.WithKeyringAccName(keyringAccName)) + cfg.State.KeyringAccName = keyringAccName } - return ctx } diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index 7a22ed7d05..c1337c8a43 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -23,7 +23,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "github.com/celestiaorg/celestia-node/logs" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -228,7 +228,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e } global.SetMeterProvider(pusher) - ctx = WithNodeOptions(ctx, node.WithMetrics(true)) + ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(true)) } return ctx, err diff --git a/cmd/flags_node.go b/cmd/flags_node.go index f4a5aed8f4..6fe91ab499 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -3,12 +3,14 @@ package cmd import ( "context" "fmt" + "path/filepath" "strings" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/params" ) @@ -62,13 +64,24 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e nodeConfig := cmd.Flag(nodeConfigFlag).Value.String() if nodeConfig != "" { - cfg, err := node.LoadConfig(nodeConfig) + // try to load config from given path + cfg, err := nodebuilder.LoadConfig(nodeConfig) if err != nil { return ctx, fmt.Errorf("cmd: while parsing '%s': %w", nodeConfigFlag, err) } - ctx = WithNodeOptions(ctx, node.WithConfig(cfg)) + ctx = WithNodeConfig(ctx, cfg) + } else { + // check if config already exists at the store path and load it + path := StorePath(ctx) + expanded, err := homedir.Expand(filepath.Clean(path)) + if err != nil { + return ctx, err + } + cfg, err := nodebuilder.LoadConfig(filepath.Join(expanded, "config.toml")) + if err == nil { + ctx = WithNodeConfig(ctx, cfg) + } } - return ctx, nil } diff --git a/cmd/flags_p2p.go b/cmd/flags_p2p.go index 9a2d8f6a33..bb0bba941c 100644 --- a/cmd/flags_p2p.go +++ b/cmd/flags_p2p.go @@ -1,14 +1,13 @@ package cmd import ( - "context" "fmt" "github.com/multiformats/go-multiaddr" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -32,21 +31,24 @@ Peers must bidirectionally point to each other. (Format: multiformats.io/multiad } // ParseP2PFlags parses P2P flags from the given cmd and applies values to Env. -func ParseP2PFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { +func ParseP2PFlags( + cmd *cobra.Command, + cfg *nodebuilder.Config, +) error { mutualPeers, err := cmd.Flags().GetStringSlice(p2pMutualFlag) if err != nil { - return ctx, err + return err } for _, peer := range mutualPeers { - _, err := multiaddr.NewMultiaddr(peer) + _, err = multiaddr.NewMultiaddr(peer) if err != nil { - return ctx, fmt.Errorf("cmd: while parsing '%s': %w", p2pMutualFlag, err) + return fmt.Errorf("cmd: while parsing '%s': %w", p2pMutualFlag, err) } } if len(mutualPeers) != 0 { - ctx = WithNodeOptions(ctx, node.WithMutualPeers(mutualPeers)) + cfg.P2P.MutualPeers = mutualPeers } - return ctx, nil + return nil } diff --git a/cmd/flags_rpc.go b/cmd/flags_rpc.go index f43fc7f725..629d89a0d1 100644 --- a/cmd/flags_rpc.go +++ b/cmd/flags_rpc.go @@ -4,9 +4,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "context" - - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -33,14 +31,13 @@ func RPCFlags() *flag.FlagSet { } // ParseRPCFlags parses RPC flags from the given cmd and applies values to Env. -func ParseRPCFlags(ctx context.Context, cmd *cobra.Command) (context.Context, error) { +func ParseRPCFlags(cmd *cobra.Command, cfg *nodebuilder.Config) { addr := cmd.Flag(addrFlag).Value.String() if addr != "" { - ctx = WithNodeOptions(ctx, node.WithRPCAddress(addr)) + cfg.RPC.Address = addr } port := cmd.Flag(portFlag).Value.String() if port != "" { - ctx = WithNodeOptions(ctx, node.WithRPCPort(port)) + cfg.RPC.Port = port } - return ctx, nil } diff --git a/cmd/init.go b/cmd/init.go index 7f9d2f6776..5eaa465701 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) // Init constructs a CLI command to initialize Celestia Node of any type with the given flags. @@ -16,7 +16,7 @@ func Init(fsets ...*flag.FlagSet) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - return node.Init(StorePath(ctx), NodeType(ctx), NodeOptions(ctx)...) + return nodebuilder.Init(NodeConfig(ctx), StorePath(ctx), NodeType(ctx)) }, } for _, set := range fsets { diff --git a/cmd/start.go b/cmd/start.go index c1a81c9ac7..84918147f2 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/node" + "github.com/celestiaorg/celestia-node/nodebuilder" ) // Start constructs a CLI command to start Celestia Node daemon of any type with the given flags. @@ -22,12 +22,14 @@ Options passed on start override configuration options only on start and are not RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - store, err := node.OpenStore(StorePath(ctx)) + store, err := nodebuilder.OpenStore(StorePath(ctx)) if err != nil { return err } + // override config with all modifiers passed on start + cfg := NodeConfig(ctx) - nd, err := node.New(NodeType(ctx), store, NodeOptions(ctx)...) + nd, err := nodebuilder.NewWithConfig(NodeType(ctx), store, &cfg, NodeOptions(ctx)...) if err != nil { return err } diff --git a/node/components.go b/node/components.go deleted file mode 100644 index fad93a0fec..0000000000 --- a/node/components.go +++ /dev/null @@ -1,142 +0,0 @@ -package node - -import ( - "context" - "sync" - "time" - - logging "github.com/ipfs/go-log/v2" - "github.com/raulk/go-watchdog" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fxutil" - nodecore "github.com/celestiaorg/celestia-node/node/core" - "github.com/celestiaorg/celestia-node/node/p2p" - "github.com/celestiaorg/celestia-node/node/rpc" - "github.com/celestiaorg/celestia-node/node/services" - statecomponents "github.com/celestiaorg/celestia-node/node/state" - "github.com/celestiaorg/celestia-node/params" - headerServ "github.com/celestiaorg/celestia-node/service/header" - rpcServ "github.com/celestiaorg/celestia-node/service/rpc" - "github.com/celestiaorg/celestia-node/service/share" - "github.com/celestiaorg/celestia-node/service/state" -) - -// lightComponents keeps all the components as DI options required to build a Light Node. -func lightComponents(cfg *Config, store Store) fx.Option { - return fx.Options( - fx.Supply(Light), - baseComponents(cfg, store), - fx.Provide(services.DASer), - fx.Provide(services.HeaderExchangeP2P(cfg.Services)), - fx.Provide(services.LightAvailability(cfg.Services)), - fx.Provide(services.CacheAvailability[*share.LightAvailability]), - fxutil.ProvideAs(services.FraudServiceWithSyncer, new(fraud.Service), new(fraud.Subscriber)), - fx.Invoke(rpc.Handler), - ) -} - -// bridgeComponents keeps all the components as DI options required to build a Bridge Node. -func bridgeComponents(cfg *Config, store Store) fx.Option { - return fx.Options( - fx.Supply(Bridge), - baseComponents(cfg, store), - nodecore.Components(cfg.Core), - fx.Supply(header.MakeExtendedHeader), - fx.Provide(services.FullAvailability(cfg.Services)), - fx.Provide(services.CacheAvailability[*share.FullAvailability]), - fxutil.ProvideAs(services.FraudService, new(fraud.Service), new(fraud.Subscriber)), - fx.Invoke(func( - state *state.Service, - share *share.Service, - header *headerServ.Service, - rpcSrv *rpcServ.Server, - ) { - rpc.Handler(state, share, header, rpcSrv, nil) - }), - ) -} - -// fullComponents keeps all the components as DI options required to build a Full Node. -func fullComponents(cfg *Config, store Store) fx.Option { - return fx.Options( - fx.Supply(Full), - baseComponents(cfg, store), - fx.Provide(services.DASer), - fx.Provide(services.HeaderExchangeP2P(cfg.Services)), - fx.Provide(services.FullAvailability(cfg.Services)), - fx.Provide(services.CacheAvailability[*share.FullAvailability]), - fxutil.ProvideAs(services.FraudService, new(fraud.Service), new(fraud.Subscriber)), - fx.Invoke(rpc.Handler), - ) -} - -// baseComponents keeps all the common components shared between different Node types. -func baseComponents(cfg *Config, store Store) fx.Option { - return fx.Options( - fx.Provide(params.DefaultNetwork), - fx.Provide(params.BootstrappersFor), - fx.Provide(context.Background), - fx.Supply(cfg), - fx.Supply(store.Config), - fx.Provide(store.Datastore), - fx.Provide(store.Keystore), - // share components - fx.Invoke(share.EnsureEmptySquareExists), - fx.Provide(services.ShareService), - // header components - fx.Provide(services.HeaderService), - fx.Provide(services.HeaderStore), - fx.Invoke(services.HeaderStoreInit(&cfg.Services)), - fx.Provide(services.HeaderSyncer), - fxutil.ProvideAs(services.P2PSubscriber, new(header.Broadcaster), new(header.Subscriber)), - fx.Provide(services.HeaderP2PExchangeServer), - // p2p components - fx.Invoke(invokeWatchdog(store.Path())), - p2p.Components(cfg.P2P), - // state components - statecomponents.Components(cfg.Core, cfg.Key), - // RPC components - fx.Provide(rpc.Server(cfg.RPC)), - ) -} - -// invokeWatchdog starts the memory watchdog that helps to prevent some of OOMs by forcing GCing -// It also collects heap profiles in the given directory when heap grows to more than 90% of memory usage -func invokeWatchdog(pprofdir string) func(lc fx.Lifecycle) error { - return func(lc fx.Lifecycle) (errOut error) { - onceWatchdog.Do(func() { - // to get watchdog information logged out - watchdog.Logger = logWatchdog - // these set up heap pprof auto capturing on disk when threshold hit 90% usage - watchdog.HeapProfileDir = pprofdir - watchdog.HeapProfileMaxCaptures = 10 - watchdog.HeapProfileThreshold = 0.9 - - policy := watchdog.NewWatermarkPolicy(0.50, 0.60, 0.70, 0.85, 0.90, 0.925, 0.95) - err, stop := watchdog.SystemDriven(0, time.Second*5, policy) - if err != nil { - errOut = err - return - } - - lc.Append(fx.Hook{ - OnStop: func(context.Context) error { - stop() - return nil - }, - }) - }) - return - } -} - -// TODO(@Wondertan): We must start watchdog only once. This is needed for tests where we run multiple instance -// of the Node. Ideally, the Node should have some testing options instead, so we can check for it and run without -// such utilities but it does not hurt to run one instance of watchdog per test. - -var onceWatchdog = sync.Once{} - -var logWatchdog = logging.Logger("watchdog") diff --git a/node/config_opts.go b/node/config_opts.go deleted file mode 100644 index 02eb33e43e..0000000000 --- a/node/config_opts.go +++ /dev/null @@ -1,103 +0,0 @@ -package node - -import "time" - -// WithRemoteCoreIP configures Node to connect to the given remote Core IP. -func WithRemoteCoreIP(ip string) Option { - return func(sets *settings) { - sets.cfg.Core.IP = ip - } -} - -// WithRemoteCorePort configures Node to connect to the given remote Core port. -func WithRemoteCorePort(port string) Option { - return func(sets *settings) { - sets.cfg.Core.RPCPort = port - } -} - -// WithGRPCPort configures Node to connect to given gRPC port -// for state-related queries. -func WithGRPCPort(port string) Option { - return func(sets *settings) { - sets.cfg.Core.GRPCPort = port - } -} - -// WithRPCPort configures Node to expose the given port for RPC -// queries. -func WithRPCPort(port string) Option { - return func(sets *settings) { - sets.cfg.RPC.Port = port - } -} - -// WithRPCAddress configures Node to listen on the given address for RPC -// queries. -func WithRPCAddress(addr string) Option { - return func(sets *settings) { - sets.cfg.RPC.Address = addr - } -} - -// WithTrustedHash sets TrustedHash to the Config. -func WithTrustedHash(hash string) Option { - return func(sets *settings) { - sets.cfg.Services.TrustedHash = hash - } -} - -// WithTrustedPeers appends new "trusted peers" to the Config. -func WithTrustedPeers(addr ...string) Option { - return func(sets *settings) { - sets.cfg.Services.TrustedPeers = append(sets.cfg.Services.TrustedPeers, addr...) - } -} - -// WithPeersLimit overrides default peer limit for peers found during discovery. -func WithPeersLimit(limit uint) Option { - return func(sets *settings) { - sets.cfg.Services.PeersLimit = limit - } -} - -// WithDiscoveryInterval sets interval between discovery sessions. -func WithDiscoveryInterval(interval time.Duration) Option { - return func(sets *settings) { - if interval <= 0 { - return - } - sets.cfg.Services.DiscoveryInterval = interval - } -} - -// WithAdvertiseInterval sets interval between advertises. -func WithAdvertiseInterval(interval time.Duration) Option { - return func(sets *settings) { - if interval <= 0 { - return - } - sets.cfg.Services.AdvertiseInterval = interval - } -} - -// WithConfig sets the entire custom config. -func WithConfig(custom *Config) Option { - return func(sets *settings) { - sets.cfg = custom - } -} - -// WithMutualPeers sets the `MutualPeers` field in the config. -func WithMutualPeers(addrs []string) Option { - return func(sets *settings) { - sets.cfg.P2P.MutualPeers = addrs - } -} - -// WithKeyringAccName sets the `KeyringAccName` field in the key config. -func WithKeyringAccName(name string) Option { - return func(sets *settings) { - sets.cfg.Key.KeyringAccName = name - } -} diff --git a/node/core/core.go b/node/core/core.go deleted file mode 100644 index 3d2fcdf979..0000000000 --- a/node/core/core.go +++ /dev/null @@ -1,76 +0,0 @@ -package core - -import ( - "context" - "fmt" - - "github.com/ipfs/go-blockservice" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/libs/fxutil" - - "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-node/header" - headercore "github.com/celestiaorg/celestia-node/header/core" -) - -// Config combines all configuration fields for managing the relationship with a Core node. -type Config struct { - IP string - RPCPort string - GRPCPort string -} - -// DefaultConfig returns default configuration for managing the -// node's connection to a Celestia-Core endpoint. -func DefaultConfig() Config { - return Config{} -} - -// Components collects all the components and services related to managing the relationship with the Core node. -func Components(cfg Config) fx.Option { - return fx.Options( - fx.Provide(core.NewBlockFetcher), - fxutil.ProvideAs(headercore.NewExchange, new(header.Exchange)), - fx.Invoke(HeaderListener), - fx.Provide(func(lc fx.Lifecycle) (core.Client, error) { - if cfg.IP == "" { - return nil, fmt.Errorf("no celestia-core endpoint given") - } - client, err := RemoteClient(cfg) - if err != nil { - return nil, err - } - lc.Append(fx.Hook{ - OnStart: func(context.Context) error { - return client.Start() - }, - OnStop: func(context.Context) error { - return client.Stop() - }, - }) - - return client, err - }), - ) -} - -func HeaderListener( - lc fx.Lifecycle, - ex *core.BlockFetcher, - bcast header.Broadcaster, - bServ blockservice.BlockService, - construct header.ConstructFn, -) *headercore.Listener { - cl := headercore.NewListener(bcast, ex, bServ, construct) - lc.Append(fx.Hook{ - OnStart: cl.Start, - OnStop: cl.Stop, - }) - return cl -} - -// RemoteClient provides a constructor for core.Client over RPC. -func RemoteClient(cfg Config) (core.Client, error) { - return core.NewRemote(cfg.IP, cfg.RPCPort) -} diff --git a/node/p2p/bitswap.go b/node/p2p/bitswap.go deleted file mode 100644 index 3777f1dea1..0000000000 --- a/node/p2p/bitswap.go +++ /dev/null @@ -1,68 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - - "github.com/ipfs/go-bitswap" - "github.com/ipfs/go-bitswap/network" - "github.com/ipfs/go-datastore" - blockstore "github.com/ipfs/go-ipfs-blockstore" - exchange "github.com/ipfs/go-ipfs-exchange-interface" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/protocol" - routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/libs/fxutil" - nparams "github.com/celestiaorg/celestia-node/params" -) - -const ( - // default size of bloom filter in blockStore - defaultBloomFilterSize = 512 << 10 - // default amount of hash functions defined for bloom filter - defaultBloomFilterHashes = 7 - // default size of arc cache in blockStore - defaultARCCacheSize = 64 << 10 -) - -// DataExchange provides a constructor for IPFS block's DataExchange over BitSwap. -func DataExchange(cfg Config) func(bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { - return func(params bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { - ctx := fxutil.WithLifecycle(params.Ctx, params.Lc) - bs, err := blockstore.CachedBlockstore( - ctx, - blockstore.NewBlockstore(params.Ds), - blockstore.CacheOpts{ - HasBloomFilterSize: defaultBloomFilterSize, - HasBloomFilterHashes: defaultBloomFilterHashes, - HasARCCacheSize: defaultARCCacheSize, - }, - ) - if err != nil { - return nil, nil, err - } - prefix := protocol.ID(fmt.Sprintf("/celestia/%s", params.Net)) - return bitswap.New( - ctx, - network.NewFromIpfsHost(params.Host, &routinghelpers.Null{}, network.Prefix(prefix)), - bs, - bitswap.ProvideEnabled(false), - // NOTE: These below ar required for our protocol to work reliably. - // See https://github.com/celestiaorg/celestia-node/issues/732 - bitswap.SetSendDontHaves(false), - bitswap.SetSimulateDontHavesOnTimeout(false), - ), bs, nil - } -} - -type bitSwapParams struct { - fx.In - - Ctx context.Context - Net nparams.Network - Lc fx.Lifecycle - Host host.Host - Ds datastore.Batching -} diff --git a/node/p2p/pubsub.go b/node/p2p/pubsub.go deleted file mode 100644 index b87137fccc..0000000000 --- a/node/p2p/pubsub.go +++ /dev/null @@ -1,56 +0,0 @@ -package p2p - -import ( - "context" - - "github.com/libp2p/go-libp2p-core/host" - pubsub "github.com/libp2p/go-libp2p-pubsub" - pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" - "go.uber.org/fx" - "golang.org/x/crypto/blake2b" - - "github.com/celestiaorg/celestia-node/libs/fxutil" -) - -// PubSub provides a constructor for PubSub protocol with GossipSub routing. -func PubSub(cfg Config) func(pubSubParams) (*pubsub.PubSub, error) { - return func(params pubSubParams) (*pubsub.PubSub, error) { - fpeers, err := cfg.mutualPeers() - if err != nil { - return nil, err - } - - // TODO(@Wondertan) for PubSub options: - // * Hash-based MsgId function. - // * Validate default peer scoring params for our use-case. - // * Strict subscription filter - // * For different network types(mainnet/testnet/devnet) we should have different network topic names. - // * Hardcode positive score for bootstrap peers - // * Bootstrappers should only gossip and PX - // * Peers should trust boostrappers, so peerscore for them should always be high. - opts := []pubsub.Option{ - pubsub.WithPeerExchange(cfg.PeerExchange || cfg.Bootstrapper), - pubsub.WithDirectPeers(fpeers), - pubsub.WithMessageIdFn(hashMsgID), - } - - return pubsub.NewGossipSub( - fxutil.WithLifecycle(params.Ctx, params.Lc), - params.Host, - opts..., - ) - } -} - -func hashMsgID(m *pubsub_pb.Message) string { - hash := blake2b.Sum256(m.Data) - return string(hash[:]) -} - -type pubSubParams struct { - fx.In - - Ctx context.Context - Lc fx.Lifecycle - Host host.Host -} diff --git a/node/p2p/routing.go b/node/p2p/routing.go deleted file mode 100644 index a077eb7d67..0000000000 --- a/node/p2p/routing.go +++ /dev/null @@ -1,75 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - - "github.com/ipfs/go-datastore" - logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/protocol" - "github.com/libp2p/go-libp2p-core/routing" - dht "github.com/libp2p/go-libp2p-kad-dht" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/libs/fxutil" - nparams "github.com/celestiaorg/celestia-node/params" -) - -var log = logging.Logger("node/p2p") - -// ContentRouting constructs nil content routing, -// as for our use-case existing ContentRouting mechanisms, e.g DHT, are unsuitable -func ContentRouting(r routing.PeerRouting) routing.ContentRouting { - return r.(*dht.IpfsDHT) -} - -// PeerRouting provides constructor for PeerRouting over DHT. -// Basically, this provides a way to discover peer addresses by respecting public keys. -func PeerRouting(cfg Config) func(routingParams) (routing.PeerRouting, error) { - return func(params routingParams) (routing.PeerRouting, error) { - if cfg.RoutingTableRefreshPeriod <= 0 { - cfg.RoutingTableRefreshPeriod = defaultRoutingRefreshPeriod - log.Warnf("routingTableRefreshPeriod is not valid. restoring to default value: %d", cfg.RoutingTableRefreshPeriod) - } - opts := []dht.Option{ - dht.Mode(dht.ModeAuto), - dht.BootstrapPeers(params.Peers...), - dht.ProtocolPrefix(protocol.ID(fmt.Sprintf("/celestia/%s", params.Net))), - dht.Datastore(params.DataStore), - dht.RoutingTableRefreshPeriod(cfg.RoutingTableRefreshPeriod), - } - - if cfg.Bootstrapper { - // override options for bootstrapper - opts = append(opts, - dht.Mode(dht.ModeServer), // it must accept incoming connections - dht.BootstrapPeers(), // no bootstrappers for a bootstrapper ¯\_(ツ)_/¯ - ) - } - - d, err := dht.New(fxutil.WithLifecycle(params.Ctx, params.Lc), params.Host, opts...) - if err != nil { - return nil, err - } - params.Lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return d.Bootstrap(ctx) - }, - OnStop: func(context.Context) error { - return d.Close() - }, - }) - return d, nil - } -} - -type routingParams struct { - fx.In - - Ctx context.Context - Net nparams.Network - Peers nparams.Bootstrappers - Lc fx.Lifecycle - Host HostBase - DataStore datastore.Batching -} diff --git a/node/services/service.go b/node/services/service.go deleted file mode 100644 index 5ecc0f22fa..0000000000 --- a/node/services/service.go +++ /dev/null @@ -1,315 +0,0 @@ -package services - -import ( - "context" - - "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/host" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/libp2p/go-libp2p-core/peerstore" - "github.com/libp2p/go-libp2p-core/routing" - pubsub "github.com/libp2p/go-libp2p-pubsub" - routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" - "github.com/celestiaorg/celestia-node/libs/fxutil" - "github.com/celestiaorg/celestia-node/params" - headerservice "github.com/celestiaorg/celestia-node/service/header" - "github.com/celestiaorg/celestia-node/service/share" -) - -// HeaderSyncer creates a new Syncer. -func HeaderSyncer( - ctx context.Context, - lc fx.Lifecycle, - ex header.Exchange, - store header.Store, - sub header.Subscriber, - fservice fraud.Service, -) (*sync.Syncer, error) { - syncer := sync.NewSyncer(ex, store, sub, params.BlockTime) - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - lc.Append(fx.Hook{ - OnStart: func(startCtx context.Context) error { - start := func(ctx context.Context) error { - err := syncer.Start(ctx) - switch err { - default: - return err - case header.ErrNoHead: - log.Warnw("Syncer running on uninitialized Store - headers won't be synced") - case nil: - } - return nil - } - return FraudLifecycle(startCtx, lifecycleCtx, fraud.BadEncoding, fservice, start, syncer.Stop) - }, - OnStop: syncer.Stop, - }) - - return syncer, nil -} - -// P2PSubscriber creates a new p2p.Subscriber. -func P2PSubscriber(lc fx.Lifecycle, sub *pubsub.PubSub) (*p2p.Subscriber, *p2p.Subscriber) { - p2pSub := p2p.NewSubscriber(sub) - lc.Append(fx.Hook{ - OnStart: p2pSub.Start, - OnStop: p2pSub.Stop, - }) - return p2pSub, p2pSub -} - -// HeaderService creates a new header.Service. -func HeaderService( - syncer *sync.Syncer, - sub header.Subscriber, - p2pServer *p2p.ExchangeServer, - ex header.Exchange, - store header.Store, -) *headerservice.Service { - return headerservice.NewHeaderService(syncer, sub, p2pServer, ex, store) -} - -// HeaderExchangeP2P constructs new Exchange for headers. -func HeaderExchangeP2P(cfg Config) func(params.Bootstrappers, host.Host) (header.Exchange, error) { - return func(bpeers params.Bootstrappers, host host.Host) (header.Exchange, error) { - peers, err := cfg.trustedPeers(bpeers) - if err != nil { - return nil, err - } - ids := make([]peer.ID, len(peers)) - for index, peer := range peers { - ids[index] = peer.ID - host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) - } - return p2p.NewExchange(host, ids), nil - } -} - -// HeaderP2PExchangeServer creates a new header/p2p.ExchangeServer. -func HeaderP2PExchangeServer(lc fx.Lifecycle, host host.Host, store header.Store) *p2p.ExchangeServer { - p2pServ := p2p.NewExchangeServer(host, store) - lc.Append(fx.Hook{ - OnStart: p2pServ.Start, - OnStop: p2pServ.Stop, - }) - - return p2pServ -} - -// HeaderStore creates and initializes new header.Store. -func HeaderStore(lc fx.Lifecycle, ds datastore.Batching) (header.Store, error) { - store, err := store.NewStore(ds) - if err != nil { - return nil, err - } - lc.Append(fx.Hook{ - OnStart: store.Start, - OnStop: store.Stop, - }) - return store, nil -} - -// HeaderStoreInit initializes the store. -func HeaderStoreInit(cfg *Config) func(context.Context, params.Network, header.Store, header.Exchange) error { - return func(ctx context.Context, net params.Network, s header.Store, ex header.Exchange) error { - trustedHash, err := cfg.trustedHash(net) - if err != nil { - return err - } - - err = store.Init(ctx, s, ex, trustedHash) - if err != nil { - // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. - // This is due to requesting step of initialization, which fetches initial Header by trusted hash from - // the network. The step can't be done during unit tests and fixing it would require either - // * Having some test/dev/offline mode for Node that mocks out all the networking - // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step - log.Errorf("initializing store failed: %s", err) - } - - return nil - } -} - -// ShareService constructs new share.Service. -func ShareService(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Availability) *share.Service { - service := share.NewService(bServ, avail) - lc.Append(fx.Hook{ - OnStart: service.Start, - OnStop: service.Stop, - }) - return service -} - -// DASer constructs a new Data Availability Sampler. -func DASer( - ctx context.Context, - lc fx.Lifecycle, - avail share.Availability, - sub header.Subscriber, - hstore header.Store, - ds datastore.Batching, - fservice fraud.Service, -) *das.DASer { - das := das.NewDASer(avail, sub, hstore, ds, fservice) - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - lc.Append(fx.Hook{ - OnStart: func(startContext context.Context) error { - return FraudLifecycle(startContext, lifecycleCtx, fraud.BadEncoding, fservice, das.Start, das.Stop) - }, - OnStop: das.Stop, - }) - - return das -} - -// FraudService constructs a fraud proof service with the syncer disabled. -func FraudService( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore header.Store, - ds datastore.Batching, -) (fraud.Service, error) { - return newFraudService(lc, sub, host, hstore, ds, false) -} - -// FraudServiceWithSyncer constructs fraud proof service with enabled syncer. -func FraudServiceWithSyncer( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore header.Store, - ds datastore.Batching, -) (fraud.Service, error) { - return newFraudService(lc, sub, host, hstore, ds, true) -} - -func newFraudService( - lc fx.Lifecycle, - sub *pubsub.PubSub, - host host.Host, - hstore header.Store, - ds datastore.Batching, - isEnabled bool) (fraud.Service, error) { - pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled) - lc.Append(fx.Hook{ - OnStart: pservice.Start, - OnStop: pservice.Stop, - }) - return pservice, nil -} - -// LightAvailability constructs light share availability. -func LightAvailability(cfg Config) func( - lc fx.Lifecycle, - bServ blockservice.BlockService, - r routing.ContentRouting, - h host.Host, -) *share.LightAvailability { - return func( - lc fx.Lifecycle, - bServ blockservice.BlockService, - r routing.ContentRouting, - h host.Host, - ) *share.LightAvailability { - disc := share.NewDiscovery( - h, - routingdisc.NewRoutingDiscovery(r), - cfg.PeersLimit, - cfg.DiscoveryInterval, - cfg.AdvertiseInterval, - ) - la := share.NewLightAvailability(bServ, disc) - lc.Append(fx.Hook{ - OnStart: la.Start, - OnStop: la.Stop, - }) - return la - } -} - -// FullAvailability constructs full share availability. -func FullAvailability(cfg Config) func( - lc fx.Lifecycle, - bServ blockservice.BlockService, - r routing.ContentRouting, - h host.Host, -) *share.FullAvailability { - return func( - lc fx.Lifecycle, - bServ blockservice.BlockService, - r routing.ContentRouting, - h host.Host, - ) *share.FullAvailability { - disc := share.NewDiscovery( - h, - routingdisc.NewRoutingDiscovery(r), - cfg.PeersLimit, - cfg.DiscoveryInterval, - cfg.AdvertiseInterval, - ) - fa := share.NewFullAvailability(bServ, disc) - lc.Append(fx.Hook{ - OnStart: fa.Start, - OnStop: fa.Stop, - }) - return fa - } -} - -// CacheAvailability wraps either Full or Light availability with a cache for result sampling. -func CacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batching, avail A) share.Availability { - ca := share.NewCacheAvailability(avail, ds) - lc.Append(fx.Hook{ - OnStop: ca.Close, - }) - return ca -} - -// FraudLifecycle controls the lifecycle of service depending on fraud proofs. -// It starts the service only if no fraud-proof exists and stops the service automatically -// if a proof arrives after the service was started. -func FraudLifecycle( - startCtx, lifecycleCtx context.Context, - p fraud.ProofType, - fservice fraud.Service, - start, stop func(context.Context) error, -) error { - proofs, err := fservice.Get(startCtx, p) - switch err { - default: - return err - case nil: - return &fraud.ErrFraudExists{Proof: proofs} - case datastore.ErrNotFound: - } - err = start(startCtx) - if err != nil { - return err - } - // handle incoming Fraud Proofs - go fraud.OnProof(lifecycleCtx, fservice, p, func(fraud.Proof) { - if err := stop(lifecycleCtx); err != nil { - log.Error(err) - } - }) - return nil -} - -// Metrics enables metrics for services. -func Metrics() fx.Option { - return fx.Options( - fx.Invoke(header.MonitorHead), - // add more monitoring here - ) -} diff --git a/node/settings.go b/node/settings.go deleted file mode 100644 index 0a60329ecd..0000000000 --- a/node/settings.go +++ /dev/null @@ -1,117 +0,0 @@ -package node - -import ( - "encoding/hex" - "time" - - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/host" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/node/services" - - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - "github.com/celestiaorg/celestia-node/core" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/libs/fxutil" - "github.com/celestiaorg/celestia-node/node/p2p" - "github.com/celestiaorg/celestia-node/params" -) - -// settings store values that can be augmented or changed for Node with Options. -type settings struct { - cfg *Config - opts []fx.Option -} - -// Option for Node's Config. -type Option func(*settings) - -// WithNetwork specifies the Network to which the Node should connect to. -// WARNING: Use this option with caution and never run the Node with different networks over the same persisted Store. -func WithNetwork(net params.Network) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fx.Replace(net)) - } -} - -// WithP2PKey sets custom Ed25519 private key for p2p networking. -func WithP2PKey(key crypto.PrivKey) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fxutil.ReplaceAs(key, new(crypto.PrivKey))) - } -} - -// WithP2PKeyStr sets custom hex encoded Ed25519 private key for p2p networking. -func WithP2PKeyStr(key string) Option { - return func(sets *settings) { - decKey, err := hex.DecodeString(key) - if err != nil { - sets.opts = append(sets.opts, fx.Error(err)) - return - } - - key, err := crypto.UnmarshalEd25519PrivateKey(decKey) - if err != nil { - sets.opts = append(sets.opts, fx.Error(err)) - return - } - - sets.opts = append(sets.opts, fxutil.ReplaceAs(key, new(crypto.PrivKey))) - } - -} - -// WithHost sets custom Host's data for p2p networking. -func WithHost(hst host.Host) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fxutil.ReplaceAs(hst, new(p2p.HostBase))) - } -} - -// WithCoreClient sets custom client for core process -func WithCoreClient(client core.Client) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fxutil.ReplaceAs(client, new(core.Client))) - } -} - -// WithHeaderConstructFn sets custom func that creates extended header -func WithHeaderConstructFn(construct header.ConstructFn) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fx.Replace(construct)) - } -} - -// WithKeyringSigner overrides the default keyring signer constructed -// by the node. -func WithKeyringSigner(signer *apptypes.KeyringSigner) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fx.Replace(signer)) - } -} - -// WithBootstrappers sets custom bootstrap peers. -func WithBootstrappers(peers params.Bootstrappers) Option { - return func(sets *settings) { - sets.opts = append(sets.opts, fx.Replace(peers)) - } -} - -// WithRefreshRoutingTablePeriod sets custom refresh period for dht. -// Currently, it is used to speed up tests. -func WithRefreshRoutingTablePeriod(interval time.Duration) Option { - return func(sets *settings) { - sets.cfg.P2P.RoutingTableRefreshPeriod = interval - } -} - -// WithMetrics enables metrics exporting for the node. -func WithMetrics(enable bool) Option { - return func(sets *settings) { - if !enable { - return - } - sets.opts = append(sets.opts, services.Metrics()) - } -} diff --git a/node/state/core.go b/node/state/core.go deleted file mode 100644 index 6a9d645c1a..0000000000 --- a/node/state/core.go +++ /dev/null @@ -1,27 +0,0 @@ -package state - -import ( - "go.uber.org/fx" - - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - - "github.com/celestiaorg/celestia-node/header/sync" - "github.com/celestiaorg/celestia-node/service/state" -) - -// CoreAccessor constructs a new instance of state.Accessor over -// a celestia-core connection. -func CoreAccessor( - coreIP, - coreRPC, - coreGRPC string, -) func(fx.Lifecycle, *apptypes.KeyringSigner, *sync.Syncer) (state.Accessor, error) { - return func(lc fx.Lifecycle, signer *apptypes.KeyringSigner, syncer *sync.Syncer) (state.Accessor, error) { - ca := state.NewCoreAccessor(signer, syncer, coreIP, coreRPC, coreGRPC) - lc.Append(fx.Hook{ - OnStart: ca.Start, - OnStop: ca.Stop, - }) - return ca, nil - } -} diff --git a/node/state/keyring.go b/node/state/keyring.go deleted file mode 100644 index e7da28d776..0000000000 --- a/node/state/keyring.go +++ /dev/null @@ -1,74 +0,0 @@ -package state - -import ( - "fmt" - "os" - - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - - "github.com/celestiaorg/celestia-app/app" - "github.com/celestiaorg/celestia-app/app/encoding" - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - "github.com/celestiaorg/celestia-node/libs/keystore" - "github.com/celestiaorg/celestia-node/node/key" - "github.com/celestiaorg/celestia-node/params" -) - -func Keyring(cfg key.Config) func(keystore.Keystore, params.Network) (*apptypes.KeyringSigner, error) { - return func(ks keystore.Keystore, net params.Network) (*apptypes.KeyringSigner, error) { - // TODO @renaynay: Include option for setting custom `userInput` parameter with - // implementation of https://github.com/celestiaorg/celestia-node/issues/415. - // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed - // here instead of hardcoded `BackendTest`: https://github.com/celestiaorg/celestia-node/issues/603. - encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - ring, err := keyring.New(app.Name, keyring.BackendTest, ks.Path(), os.Stdin, encConf.Codec) - if err != nil { - return nil, err - } - - var info *keyring.Record - // if custom keyringAccName provided, find key for that name - if cfg.KeyringAccName != "" { - keyInfo, err := ring.Key(cfg.KeyringAccName) - if err != nil { - return nil, err - } - info = keyInfo - } else { - // check if key exists for signer - keys, err := ring.List() - if err != nil { - return nil, err - } - // if no key was found in keystore path, generate new key for node - if len(keys) == 0 { - log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ks.Path()) - keyInfo, mn, err := ring.NewMnemonic("my_celes_key", keyring.English, "", - "", hd.Secp256k1) - if err != nil { - return nil, err - } - log.Info("NEW KEY GENERATED...") - addr, err := keyInfo.GetAddress() - if err != nil { - return nil, err - } - fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n", - keyInfo.Name, addr.String(), mn) - - info = keyInfo - } else { - // if one or more keys are present and no keyringAccName was given, use the first key in list - info = keys[0] - } - } - // construct signer using the default key found / generated above - signer := apptypes.NewKeyringSigner(ring, info.Name, string(net)) - signerInfo := signer.GetSignerInfo() - log.Infow("constructed keyring signer", "backend", keyring.BackendTest, "path", ks.Path(), - "key name", signerInfo.Name, "chain-id", string(net)) - - return signer, nil - } -} diff --git a/node/state/state.go b/node/state/state.go deleted file mode 100644 index b57ead9c7b..0000000000 --- a/node/state/state.go +++ /dev/null @@ -1,46 +0,0 @@ -package state - -import ( - "context" - - logging "github.com/ipfs/go-log/v2" - "go.uber.org/fx" - - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/libs/fxutil" - "github.com/celestiaorg/celestia-node/node/core" - "github.com/celestiaorg/celestia-node/node/key" - "github.com/celestiaorg/celestia-node/node/services" - "github.com/celestiaorg/celestia-node/service/state" -) - -var log = logging.Logger("state-access-constructor") - -// Components provides all components necessary to construct the -// state service. -func Components(coreCfg core.Config, keyCfg key.Config) fx.Option { - return fx.Options( - fx.Provide(Keyring(keyCfg)), - fx.Provide(CoreAccessor(coreCfg.IP, coreCfg.RPCPort, coreCfg.GRPCPort)), - fx.Provide(Service), - ) -} - -// Service constructs a new state.Service. -func Service( - ctx context.Context, - lc fx.Lifecycle, - accessor state.Accessor, - fservice fraud.Service, -) *state.Service { - serv := state.NewService(accessor) - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - lc.Append(fx.Hook{ - OnStart: func(startCtx context.Context) error { - return services.FraudLifecycle(startCtx, lifecycleCtx, fraud.BadEncoding, fservice, serv.Start, serv.Stop) - }, - OnStop: serv.Stop, - }) - - return serv -} diff --git a/node/config.go b/nodebuilder/config.go similarity index 51% rename from node/config.go rename to nodebuilder/config.go index e31fe0bf08..152cafb841 100644 --- a/node/config.go +++ b/nodebuilder/config.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "io" @@ -6,10 +6,12 @@ import ( "github.com/BurntSushi/toml" - "github.com/celestiaorg/celestia-node/node/core" - "github.com/celestiaorg/celestia-node/node/key" - "github.com/celestiaorg/celestia-node/node/p2p" - "github.com/celestiaorg/celestia-node/node/services" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" "github.com/celestiaorg/celestia-node/service/rpc" ) @@ -19,41 +21,29 @@ type ConfigLoader func() (*Config, error) // Config is main configuration structure for a Node. // It combines configuration units for all Node subsystems. type Config struct { - Core core.Config - Key key.Config - P2P p2p.Config - RPC rpc.Config - Services services.Config + Core core.Config + State state.Config + P2P p2p.Config + RPC rpc.Config + Share share.Config + Header header.Config } // DefaultConfig provides a default Config for a given Node Type 'tp'. // NOTE: Currently, configs are identical, but this will change. -func DefaultConfig(tp Type) *Config { +func DefaultConfig(tp node.Type) *Config { switch tp { - case Bridge: + case node.Bridge, node.Light, node.Full: return &Config{ - Core: core.DefaultConfig(), - Key: key.DefaultConfig(), - P2P: p2p.DefaultConfig(), - RPC: rpc.DefaultConfig(), - Services: services.DefaultConfig(), - } - case Light: - return &Config{ - Key: key.DefaultConfig(), - RPC: rpc.DefaultConfig(), - P2P: p2p.DefaultConfig(), - Services: services.DefaultConfig(), - } - case Full: - return &Config{ - Key: key.DefaultConfig(), - RPC: rpc.DefaultConfig(), - P2P: p2p.DefaultConfig(), - Services: services.DefaultConfig(), + Core: core.DefaultConfig(), + State: state.DefaultConfig(), + P2P: p2p.DefaultConfig(), + RPC: rpc.DefaultConfig(), + Share: share.DefaultConfig(), + Header: header.DefaultConfig(), } default: - panic("node: unknown Node Type") + panic("node: invalid node type") } } @@ -80,9 +70,9 @@ func LoadConfig(path string) (*Config, error) { return &cfg, cfg.Decode(f) } -// TODO(@Wondertan): We should have a description for each field written into w, -// so users can instantly understand purpose of each field. Ideally, we should have a utility -// program to parse comments from actual sources(*.go files) and generate docs from comments. Hint: use 'ast' package. +// TODO(@Wondertan): We should have a description for each field written into w, +// so users can instantly understand purpose of each field. Ideally, we should have a utility program to parse comments +// from actual sources(*.go files) and generate docs from comments. Hint: use 'ast' package. // Encode encodes a given Config into w. func (cfg *Config) Encode(w io.Writer) error { diff --git a/node/config_test.go b/nodebuilder/config_test.go similarity index 74% rename from node/config_test.go rename to nodebuilder/config_test.go index 941e86fbf3..0a603137a8 100644 --- a/node/config_test.go +++ b/nodebuilder/config_test.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "bytes" @@ -6,11 +6,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) func TestConfigWriteRead(t *testing.T) { buf := bytes.NewBuffer(nil) - in := DefaultConfig(Bridge) + in := DefaultConfig(node.Bridge) err := in.Encode(buf) require.NoError(t, err) diff --git a/nodebuilder/core/config.go b/nodebuilder/core/config.go new file mode 100644 index 0000000000..d46703db40 --- /dev/null +++ b/nodebuilder/core/config.go @@ -0,0 +1,57 @@ +package core + +import ( + "fmt" + "strconv" + "strings" +) + +// Config combines all configuration fields for managing the relationship with a Core node. +type Config struct { + IP string + RPCPort string + GRPCPort string +} + +// DefaultConfig returns default configuration for managing the +// node's connection to a Celestia-Core endpoint. +func DefaultConfig() Config { + return Config{ + IP: "0.0.0.0", + RPCPort: "0", + GRPCPort: "0", + } +} + +// Validate performs basic validation of the config. +func (cfg *Config) Validate() error { + ip, err := sanitizeIP(cfg.IP) + if err != nil { + return err + } + cfg.IP = ip + _, err = strconv.Atoi(cfg.RPCPort) + if err != nil { + return fmt.Errorf("nodebuilder/core: invalid rpc port: %s", err.Error()) + } + _, err = strconv.Atoi(cfg.GRPCPort) + if err != nil { + return fmt.Errorf("nodebuilder/core: invalid grpc port: %s", err.Error()) + } + return nil +} + +// sanitizeIP trims leading protocol scheme and port from the given +// IP address if present. +func sanitizeIP(ip string) (string, error) { + original := ip + ip = strings.TrimPrefix(ip, "http://") + ip = strings.TrimPrefix(ip, "https://") + ip = strings.TrimPrefix(ip, "tcp://") + ip = strings.TrimSuffix(ip, "/") + ip = strings.Split(ip, ":")[0] + if ip == "" { + return "", fmt.Errorf("nodebuilder/core: invalid IP addr given: %s", original) + } + return ip, nil +} diff --git a/nodebuilder/core/core.go b/nodebuilder/core/core.go new file mode 100644 index 0000000000..07a9c69225 --- /dev/null +++ b/nodebuilder/core/core.go @@ -0,0 +1,9 @@ +package core + +import ( + "github.com/celestiaorg/celestia-node/core" +) + +func Remote(cfg Config) (core.Client, error) { + return core.NewRemote(cfg.IP, cfg.RPCPort) +} diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go new file mode 100644 index 0000000000..ceb47c4e42 --- /dev/null +++ b/nodebuilder/core/module.go @@ -0,0 +1,56 @@ +package core + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header" + headercore "github.com/celestiaorg/celestia-node/header/core" + "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +// Module collects all the components and services related to managing the relationship with the Core node. +func Module(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(cfgErr), + fx.Options(options...), + ) + + switch tp { + case node.Light, node.Full: + return fx.Module("core", baseComponents) + case node.Bridge: + return fx.Module("core", + baseComponents, + fx.Provide(core.NewBlockFetcher), + fxutil.ProvideAs(headercore.NewExchange, new(header.Exchange)), + fx.Invoke(fx.Annotate( + headercore.NewListener, + fx.OnStart(func(ctx context.Context, listener *headercore.Listener) error { + return listener.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, listener *headercore.Listener) error { + return listener.Stop(ctx) + }), + )), + fx.Provide(fx.Annotate( + Remote, + fx.OnStart(func(ctx context.Context, client core.Client) error { + return client.Start() + }), + fx.OnStop(func(ctx context.Context, client core.Client) error { + return client.Stop() + }), + )), + ) + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/core/opts.go b/nodebuilder/core/opts.go new file mode 100644 index 0000000000..56347a5cb6 --- /dev/null +++ b/nodebuilder/core/opts.go @@ -0,0 +1,19 @@ +package core + +import ( + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/fxutil" +) + +// WithClient sets custom client for core process +func WithClient(client core.Client) fx.Option { + return fxutil.ReplaceAs(client, new(core.Client)) +} + +// WithHeaderConstructFn sets custom func that creates extended header +func WithHeaderConstructFn(construct header.ConstructFn) fx.Option { + return fx.Replace(construct) +} diff --git a/nodebuilder/daser/daser.go b/nodebuilder/daser/daser.go new file mode 100644 index 0000000000..4cbf6e725d --- /dev/null +++ b/nodebuilder/daser/daser.go @@ -0,0 +1,20 @@ +package daser + +import ( + "github.com/ipfs/go-datastore" + + "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/service/share" +) + +func NewDASer( + da share.Availability, + hsub header.Subscriber, + store header.Store, + batching datastore.Batching, + fraudService fraud.Service, +) *das.DASer { + return das.NewDASer(da, hsub, store, batching, fraudService) +} diff --git a/nodebuilder/daser/module.go b/nodebuilder/daser/module.go new file mode 100644 index 0000000000..6f5ec3b71c --- /dev/null +++ b/nodebuilder/daser/module.go @@ -0,0 +1,37 @@ +package daser + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/das" + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fxutil" + fraudbuilder "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +func Module(tp node.Type) fx.Option { + switch tp { + case node.Light, node.Full: + return fx.Module( + "daser", + fx.Provide(fx.Annotate( + NewDASer, + fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraud.Service, das *das.DASer) error { + lifecycleCtx := fxutil.WithLifecycle(ctx, lc) + return fraudbuilder.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + das.Start, das.Stop) + }), + fx.OnStop(func(ctx context.Context, das *das.DASer) error { + return das.Stop(ctx) + }), + )), + ) + case node.Bridge: + return fx.Options() + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go new file mode 100644 index 0000000000..376cd3c6ec --- /dev/null +++ b/nodebuilder/fraud/fraud.go @@ -0,0 +1,80 @@ +package fraud + +import ( + "context" + + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/header" +) + +// Service constructs a fraud proof service with the syncer disabled. +func Service( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore header.Store, + ds datastore.Batching, +) (fraud.Service, error) { + return newFraudService(lc, sub, host, hstore, ds, false) +} + +// ServiceWithSyncer constructs fraud proof service with enabled syncer. +func ServiceWithSyncer( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore header.Store, + ds datastore.Batching, +) (fraud.Service, error) { + return newFraudService(lc, sub, host, hstore, ds, true) +} + +func newFraudService( + lc fx.Lifecycle, + sub *pubsub.PubSub, + host host.Host, + hstore header.Store, + ds datastore.Batching, + isEnabled bool) (fraud.Service, error) { + pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled) + lc.Append(fx.Hook{ + OnStart: pservice.Start, + OnStop: pservice.Stop, + }) + return pservice, nil +} + +// Lifecycle controls the lifecycle of service depending on fraud proofs. +// It starts the service only if no fraud-proof exists and stops the service automatically +// if a proof arrives after the service was started. +func Lifecycle( + startCtx, lifecycleCtx context.Context, + p fraud.ProofType, + fservice fraud.Service, + start, stop func(context.Context) error, +) error { + proofs, err := fservice.Get(startCtx, p) + switch err { + default: + return err + case nil: + return &fraud.ErrFraudExists{Proof: proofs} + case datastore.ErrNotFound: + } + err = start(startCtx) + if err != nil { + return err + } + // handle incoming Fraud Proofs + go fraud.OnProof(lifecycleCtx, fservice, p, func(fraud.Proof) { + if err := stop(lifecycleCtx); err != nil { + log.Error(err) + } + }) + return nil +} diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go new file mode 100644 index 0000000000..c03a35348f --- /dev/null +++ b/nodebuilder/fraud/module.go @@ -0,0 +1,29 @@ +package fraud + +import ( + logging "github.com/ipfs/go-log/v2" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fxutil" + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +var log = logging.Logger("fraud-module") + +func Module(tp node.Type) fx.Option { + switch tp { + case node.Light: + return fx.Module( + "fraud", + fxutil.ProvideAs(ServiceWithSyncer, new(fraud.Service), new(fraud.Subscriber)), + ) + case node.Full, node.Bridge: + return fx.Module( + "fraud", + fxutil.ProvideAs(Service, new(fraud.Service), new(fraud.Subscriber)), + ) + default: + panic("invalid node type") + } +} diff --git a/node/services/config.go b/nodebuilder/header/config.go similarity index 68% rename from node/services/config.go rename to nodebuilder/header/config.go index 501686d58f..93a292d9c0 100644 --- a/node/services/config.go +++ b/nodebuilder/header/config.go @@ -1,10 +1,8 @@ -package services +package header import ( "encoding/hex" - "time" - logging "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p-core/peer" "github.com/multiformats/go-multiaddr" tmbytes "github.com/tendermint/tendermint/libs/bytes" @@ -12,8 +10,7 @@ import ( "github.com/celestiaorg/celestia-node/params" ) -var log = logging.Logger("node/services") - +// Config contains configuration parameters for header retrieval and management. type Config struct { // TrustedHash is the Block/Header hash that Nodes use as starting point for header synchronization. // Only affects the node once on initial sync. @@ -22,23 +19,12 @@ type Config struct { // Note: The trusted does *not* imply Headers are not verified, but trusted as reliable to fetch headers // at any moment. TrustedPeers []string - // NOTE: All further fields related to share/discovery. - // PeersLimit defines how many peers will be added during discovery. - PeersLimit uint - // DiscoveryInterval is an interval between discovery sessions. - DiscoveryInterval time.Duration - // AdvertiseInterval is a interval between advertising sessions. - // NOTE: only full and bridge can advertise themselves. - AdvertiseInterval time.Duration } func DefaultConfig() Config { return Config{ - TrustedHash: "", - TrustedPeers: make([]string, 0), - PeersLimit: 3, - DiscoveryInterval: time.Second * 30, - AdvertiseInterval: time.Second * 30, + TrustedHash: "", + TrustedPeers: make([]string, 0), } } @@ -60,7 +46,6 @@ func (cfg *Config) trustedPeers(bpeers params.Bootstrappers) (infos []peer.AddrI } infos[i] = *p } - return } @@ -74,3 +59,8 @@ func (cfg *Config) trustedHash(net params.Network) (tmbytes.HexBytes, error) { } return hex.DecodeString(cfg.TrustedHash) } + +// Validate performs basic validation of the config. +func (cfg *Config) Validate() error { + return nil +} diff --git a/nodebuilder/header/header.go b/nodebuilder/header/header.go new file mode 100644 index 0000000000..5c6f307ed2 --- /dev/null +++ b/nodebuilder/header/header.go @@ -0,0 +1,50 @@ +package header + +import ( + "context" + + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/peerstore" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/p2p" + "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/params" +) + +// P2PExchange constructs new Exchange for headers. +func P2PExchange(cfg Config) func(params.Bootstrappers, host.Host) (header.Exchange, error) { + return func(bpeers params.Bootstrappers, host host.Host) (header.Exchange, error) { + peers, err := cfg.trustedPeers(bpeers) + if err != nil { + return nil, err + } + ids := make([]peer.ID, len(peers)) + for index, peer := range peers { + ids[index] = peer.ID + host.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL) + } + return p2p.NewExchange(host, ids), nil + } +} + +// InitStore initializes the store. +func InitStore(ctx context.Context, cfg Config, net params.Network, s header.Store, ex header.Exchange) error { + trustedHash, err := cfg.trustedHash(net) + if err != nil { + return err + } + + err = store.Init(ctx, s, ex, trustedHash) + if err != nil { + // TODO(@Wondertan): Error is ignored, as otherwise unit tests for Node construction fail. + // This is due to requesting step of initialization, which fetches initial Header by trusted hash from + // the network. The step can't be done during unit tests and fixing it would require either + // * Having some test/dev/offline mode for Node that mocks out all the networking + // * Hardcoding full extended header in params pkg, instead of hashes, so we avoid requesting step + log.Errorf("initializing store failed: %s", err) + } + + return nil +} diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go new file mode 100644 index 0000000000..75a416b890 --- /dev/null +++ b/nodebuilder/header/module.go @@ -0,0 +1,107 @@ +package header + +import ( + "context" + + logging "github.com/ipfs/go-log/v2" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/p2p" + "github.com/celestiaorg/celestia-node/header/store" + "github.com/celestiaorg/celestia-node/header/sync" + "github.com/celestiaorg/celestia-node/libs/fxutil" + fraudbuilder "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/params" + headerservice "github.com/celestiaorg/celestia-node/service/header" +) + +var log = logging.Logger("header-module") + +func Module(tp node.Type, cfg *Config) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(cfgErr), + fx.Supply(params.BlockTime), + fx.Provide(headerservice.NewHeaderService), + fx.Provide(fx.Annotate( + store.NewStore, + fx.OnStart(func(ctx context.Context, store header.Store) error { + return store.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, store header.Store) error { + return store.Stop(ctx) + }), + )), + fx.Invoke(InitStore), + fx.Provide(func(subscriber *p2p.Subscriber) header.Subscriber { + return subscriber + }), + fx.Provide(func(subscriber *p2p.Subscriber) header.Broadcaster { + return subscriber + }), + fx.Provide(fx.Annotate( + sync.NewSyncer, + fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraud.Service, syncer *sync.Syncer) error { + syncerStartFunc := func(ctx context.Context) error { + err := syncer.Start(ctx) + switch err { + default: + return err + case header.ErrNoHead: + log.Warnw("Syncer running on uninitialized Store - headers won't be synced") + case nil: + } + return nil + } + lifecycleCtx := fxutil.WithLifecycle(ctx, lc) + return fraudbuilder.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + syncerStartFunc, syncer.Stop) + }), + fx.OnStop(func(ctx context.Context, syncer *sync.Syncer) error { + return syncer.Stop(ctx) + }), + )), + fx.Provide(fx.Annotate( + p2p.NewSubscriber, + fx.OnStart(func(ctx context.Context, sub *p2p.Subscriber) error { + return sub.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, sub *p2p.Subscriber) error { + return sub.Stop(ctx) + }), + )), + + fx.Provide(fx.Annotate( + p2p.NewExchangeServer, + fx.OnStart(func(ctx context.Context, server *p2p.ExchangeServer) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *p2p.ExchangeServer) error { + return server.Stop(ctx) + }), + )), + ) + + switch tp { + case node.Light, node.Full: + return fx.Module( + "header", + baseComponents, + fx.Provide(P2PExchange(*cfg)), + ) + case node.Bridge: + return fx.Module( + "header", + baseComponents, + fx.Supply(header.MakeExtendedHeader), + ) + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go new file mode 100644 index 0000000000..781e63484a --- /dev/null +++ b/nodebuilder/header/opts.go @@ -0,0 +1,16 @@ +package header + +import ( + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/header" +) + +// TODO: Eventually we should have a per-module metrics option. +// WithMetrics enables metrics exporting for the node. +func WithMetrics(enable bool) fx.Option { + if !enable { + return fx.Options() + } + return fx.Options(fx.Invoke(header.MonitorHead)) +} diff --git a/node/init.go b/nodebuilder/init.go similarity index 78% rename from node/init.go rename to nodebuilder/init.go index 37175a23d7..3529d08f97 100644 --- a/node/init.go +++ b/nodebuilder/init.go @@ -1,24 +1,18 @@ -package node +package nodebuilder import ( - "errors" "fmt" "os" "path/filepath" "github.com/celestiaorg/celestia-node/libs/fslock" "github.com/celestiaorg/celestia-node/libs/utils" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/params" ) -// Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under 'path' with -// default Config. Options are applied over default Config and persisted on disk. -func Init(path string, tp Type, options ...Option) error { - sets := &settings{cfg: DefaultConfig(tp)} - for _, option := range options { - option(sets) - } - +// Init initializes the Node FileSystem Store for the given Node Type 'tp' in the directory under 'path'. +func Init(cfg Config, path string, tp node.Type) error { path, err := storePath(path) if err != nil { return err @@ -49,21 +43,12 @@ func Init(path string, tp Type, options ...Option) error { return err } - if sets.cfg == nil { - return errors.New("configuration is missing for the node's initialisation") - } - cfgPath := configPath(path) - if !utils.Exists(cfgPath) { - err = SaveConfig(cfgPath, sets.cfg) - if err != nil { - return err - } - log.Infow("Saving config", "path", cfgPath) - } else { - log.Infow("Config already exists", "path", cfgPath) + err = SaveConfig(cfgPath, &cfg) + if err != nil { + return err } - + log.Infow("Saving config", "path", cfgPath) log.Info("Node Store initialized") return nil } diff --git a/node/init_test.go b/nodebuilder/init_test.go similarity index 69% rename from node/init_test.go rename to nodebuilder/init_test.go index 3fd1064483..5ad9dc45c4 100644 --- a/node/init_test.go +++ b/nodebuilder/init_test.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "os" @@ -8,24 +8,27 @@ import ( "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/libs/fslock" + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) func TestInit(t *testing.T) { dir := t.TempDir() - nodes := []Type{Light, Bridge} + nodes := []node.Type{node.Light, node.Bridge} for _, node := range nodes { - require.NoError(t, Init(dir, node)) + cfg := DefaultConfig(node) + require.NoError(t, Init(*cfg, dir, node)) assert.True(t, IsInit(dir)) } } func TestInitErrForInvalidPath(t *testing.T) { path := "/invalid_path" - nodes := []Type{Light, Bridge} + nodes := []node.Type{node.Light, node.Bridge} for _, node := range nodes { - require.Error(t, Init(path, node)) + cfg := DefaultConfig(node) + require.Error(t, Init(*cfg, path, node)) } } @@ -52,9 +55,10 @@ func TestInitErrForLockedDir(t *testing.T) { flock, err := fslock.Lock(lockPath(dir)) require.NoError(t, err) defer flock.Unlock() //nolint:errcheck - nodes := []Type{Light, Bridge} + nodes := []node.Type{node.Light, node.Bridge} for _, node := range nodes { - require.Error(t, Init(dir, node)) + cfg := DefaultConfig(node) + require.Error(t, Init(*cfg, dir, node)) } } diff --git a/nodebuilder/module.go b/nodebuilder/module.go new file mode 100644 index 0000000000..3aa785dc85 --- /dev/null +++ b/nodebuilder/module.go @@ -0,0 +1,46 @@ +package nodebuilder + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/daser" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" + "github.com/celestiaorg/celestia-node/params" +) + +func Module(tp node.Type, cfg *Config, store Store) fx.Option { + baseComponents := fx.Options( + fx.Provide(params.DefaultNetwork), + fx.Provide(params.BootstrappersFor), + fx.Provide(context.Background), + fx.Supply(cfg), + fx.Supply(store.Config), + fx.Provide(store.Datastore), + fx.Provide(store.Keystore), + fx.Invoke(invokeWatchdog(store.Path())), + // modules provided by the node + p2p.Module(tp, &cfg.P2P), + state.Module(tp, &cfg.State), + header.Module(tp, &cfg.Header), + share.Module(tp, &cfg.Share), + rpc.Module(tp, &cfg.RPC), + core.Module(tp, &cfg.Core), + daser.Module(tp), + fraud.Module(tp), + ) + + return fx.Module( + "node", + fx.Supply(tp), + baseComponents, + ) +} diff --git a/node/node.go b/nodebuilder/node.go similarity index 85% rename from node/node.go rename to nodebuilder/node.go index 42b71edd53..13d667a38c 100644 --- a/node/node.go +++ b/nodebuilder/node.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "context" @@ -16,9 +16,9 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/conngater" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/params" "github.com/celestiaorg/celestia-node/service/header" "github.com/celestiaorg/celestia-node/service/rpc" @@ -39,14 +39,11 @@ var log = logging.Logger("node") type Node struct { fx.In `ignore-unexported:"true"` - Type Type + Type node.Type Network params.Network Bootstrappers params.Bootstrappers Config *Config - // CoreClient provides access to a Core node process. - CoreClient core.Client `optional:"true"` - // rpc components RPCServer *rpc.Server `optional:"true"` // p2p components @@ -69,27 +66,19 @@ type Node struct { } // New assembles a new Node with the given type 'tp' over Store 'store'. -func New(tp Type, store Store, options ...Option) (*Node, error) { +func New(tp node.Type, store Store, options ...fx.Option) (*Node, error) { cfg, err := store.Config() if err != nil { return nil, err } - s := &settings{cfg: cfg} - for _, option := range options { - option(s) - } + return NewWithConfig(tp, store, cfg, options...) +} - switch tp { - case Bridge: - return newNode(bridgeComponents(s.cfg, store), fx.Options(s.opts...)) - case Light: - return newNode(lightComponents(s.cfg, store), fx.Options(s.opts...)) - case Full: - return newNode(fullComponents(s.cfg, store), fx.Options(s.opts...)) - default: - panic("node: unknown Node Type") - } +// NewWithConfig assembles a new Node with the given type 'tp' over Store 'store' and a custom config. +func NewWithConfig(tp node.Type, store Store, cfg *Config, options ...fx.Option) (*Node, error) { + opts := append([]fx.Option{Module(tp, cfg, store)}, options...) + return newNode(opts...) } // Start launches the Node and all its components and services. @@ -132,9 +121,9 @@ func (n *Node) Run(ctx context.Context) error { return ctx.Err() } -// Stop shuts down the Node, all its running Components/Services and returns. +// Stop shuts down the Node, all its running Modules/Services and returns. // Canceling the given context earlier 'ctx' unblocks the Stop and aborts graceful shutdown forcing remaining -// Components/Services to close immediately. +// Modules/Services to close immediately. func (n *Node) Stop(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, Timeout) defer cancel() diff --git a/node/type.go b/nodebuilder/node/type.go similarity index 100% rename from node/type.go rename to nodebuilder/node/type.go diff --git a/node/node_bridge_test.go b/nodebuilder/node_bridge_test.go similarity index 66% rename from node/node_bridge_test.go rename to nodebuilder/node_bridge_test.go index 8101ccd66d..cde251b2fd 100644 --- a/node/node_bridge_test.go +++ b/nodebuilder/node_bridge_test.go @@ -1,29 +1,31 @@ -package node +package nodebuilder import ( "context" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-node/core" + coremodule "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/params" ) func TestBridge_WithMockedCoreClient(t *testing.T) { t.Skip("skipping") // consult https://github.com/celestiaorg/celestia-core/issues/667 for reasoning - repo := MockStore(t, DefaultConfig(Bridge)) + repo := MockStore(t, DefaultConfig(node.Bridge)) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) _, client := core.StartTestClient(ctx, t) - node, err := New(Bridge, repo, WithCoreClient(client), WithNetwork(params.Private)) + node, err := New(node.Bridge, repo, + coremodule.WithClient(client), + WithNetwork(params.Private), + ) require.NoError(t, err) require.NotNil(t, node) - assert.True(t, node.CoreClient.IsRunning()) - err = node.Start(ctx) require.NoError(t, err) diff --git a/node/node_light_test.go b/nodebuilder/node_light_test.go similarity index 70% rename from node/node_light_test.go rename to nodebuilder/node_light_test.go index c9b83d8041..016ea4036f 100644 --- a/node/node_light_test.go +++ b/nodebuilder/node_light_test.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "crypto/rand" @@ -9,19 +9,21 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + nodebuilder "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" "github.com/celestiaorg/celestia-node/params" ) func TestNewLightWithP2PKey(t *testing.T) { key, _, err := crypto.GenerateEd25519Key(rand.Reader) require.NoError(t, err) - node := TestNode(t, Light, WithP2PKey(key)) + node := TestNode(t, nodebuilder.Light, p2p.WithP2PKey(key)) assert.True(t, node.Host.ID().MatchesPrivateKey(key)) } func TestNewLightWithHost(t *testing.T) { nw, _ := mocknet.WithNPeers(1) - node := TestNode(t, Light, WithHost(nw.Hosts()[0])) + node := TestNode(t, nodebuilder.Light, p2p.WithHost(nw.Hosts()[0])) assert.Equal(t, nw.Peers()[0], node.Host.ID()) } @@ -30,13 +32,16 @@ func TestLight_WithMutualPeers(t *testing.T) { "/ip6/100:0:114b:abc5:e13a:c32f:7a9e:f00a/tcp/2121/p2p/12D3KooWSRqDfpLsQxpyUhLC9oXHD2WuZ2y5FWzDri7LT4Dw9fSi", "/ip4/192.168.1.10/tcp/2121/p2p/12D3KooWSRqDfpLsQxpyUhLC9oXHD2WuZ2y5FWzDri7LT4Dw9fSi", } - node := TestNode(t, Light, WithMutualPeers(peers)) + cfg := DefaultConfig(nodebuilder.Light) + cfg.P2P.MutualPeers = peers + node := TestNodeWithConfig(t, nodebuilder.Light, cfg) + require.NotNil(t, node) assert.Equal(t, node.Config.P2P.MutualPeers, peers) } func TestLight_WithNetwork(t *testing.T) { - node := TestNode(t, Light) + node := TestNode(t, nodebuilder.Light) require.NotNil(t, node) assert.Equal(t, params.Private, node.Network) } diff --git a/node/node_test.go b/nodebuilder/node_test.go similarity index 80% rename from node/node_test.go rename to nodebuilder/node_test.go index 2b3fd31fa6..2e6632a823 100644 --- a/node/node_test.go +++ b/nodebuilder/node_test.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "context" @@ -6,16 +6,18 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) func TestLifecycle(t *testing.T) { var test = []struct { - tp Type + tp node.Type coreExpected bool }{ - {tp: Bridge, coreExpected: true}, - {tp: Full}, - {tp: Light}, + {tp: node.Bridge}, + {tp: node.Full}, + {tp: node.Light}, } for i, tt := range test { @@ -28,10 +30,6 @@ func TestLifecycle(t *testing.T) { require.NotNil(t, node.StateServ) require.Equal(t, tt.tp, node.Type) - if tt.coreExpected { - require.NotNil(t, node.CoreClient) - } - ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/node/p2p/addrs.go b/nodebuilder/p2p/addrs.go similarity index 100% rename from node/p2p/addrs.go rename to nodebuilder/p2p/addrs.go diff --git a/nodebuilder/p2p/bitswap.go b/nodebuilder/p2p/bitswap.go new file mode 100644 index 0000000000..c4b3007663 --- /dev/null +++ b/nodebuilder/p2p/bitswap.go @@ -0,0 +1,66 @@ +package p2p + +import ( + "context" + "fmt" + + "github.com/ipfs/go-bitswap" + "github.com/ipfs/go-bitswap/network" + "github.com/ipfs/go-datastore" + blockstore "github.com/ipfs/go-ipfs-blockstore" + exchange "github.com/ipfs/go-ipfs-exchange-interface" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/protocol" + routinghelpers "github.com/libp2p/go-libp2p-routing-helpers" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/libs/fxutil" + nparams "github.com/celestiaorg/celestia-node/params" +) + +const ( + // default size of bloom filter in blockStore + defaultBloomFilterSize = 512 << 10 + // default amount of hash functions defined for bloom filter + defaultBloomFilterHashes = 7 + // default size of arc cache in blockStore + defaultARCCacheSize = 64 << 10 +) + +// DataExchange provides a constructor for IPFS block's DataExchange over BitSwap. +func DataExchange(params bitSwapParams) (exchange.Interface, blockstore.Blockstore, error) { + ctx := fxutil.WithLifecycle(params.Ctx, params.Lc) + bs, err := blockstore.CachedBlockstore( + ctx, + blockstore.NewBlockstore(params.Ds), + blockstore.CacheOpts{ + HasBloomFilterSize: defaultBloomFilterSize, + HasBloomFilterHashes: defaultBloomFilterHashes, + HasARCCacheSize: defaultARCCacheSize, + }, + ) + if err != nil { + return nil, nil, err + } + prefix := protocol.ID(fmt.Sprintf("/celestia/%s", params.Net)) + return bitswap.New( + ctx, + network.NewFromIpfsHost(params.Host, &routinghelpers.Null{}, network.Prefix(prefix)), + bs, + bitswap.ProvideEnabled(false), + // NOTE: These below ar required for our protocol to work reliably. + // See https://github.com/celestiaorg/celestia-node/issues/732 + bitswap.SetSendDontHaves(false), + bitswap.SetSimulateDontHavesOnTimeout(false), + ), bs, nil +} + +type bitSwapParams struct { + fx.In + + Ctx context.Context + Net nparams.Network + Lc fx.Lifecycle + Host host.Host + Ds datastore.Batching +} diff --git a/node/p2p/p2p.go b/nodebuilder/p2p/config.go similarity index 80% rename from node/p2p/p2p.go rename to nodebuilder/p2p/config.go index 346ec52c8c..fb6678b1cd 100644 --- a/node/p2p/p2p.go +++ b/nodebuilder/p2p/config.go @@ -6,7 +6,6 @@ import ( "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" - "go.uber.org/fx" ) const defaultRoutingRefreshPeriod = time.Minute @@ -61,26 +60,6 @@ func DefaultConfig() Config { } } -// Components collects all the components and services related to p2p. -func Components(cfg Config) fx.Option { - return fx.Options( - fx.Provide(Key), - fx.Provide(ID), - fx.Provide(PeerStore), - fx.Provide(ConnectionManager(cfg)), - fx.Provide(ConnectionGater), - fx.Provide(Host(cfg)), - fx.Provide(RoutedHost), - fx.Provide(PubSub(cfg)), - fx.Provide(DataExchange(cfg)), - fx.Provide(BlockService), - fx.Provide(PeerRouting(cfg)), - fx.Provide(ContentRouting), - fx.Provide(AddrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), - fx.Invoke(Listen(cfg.ListenAddresses)), - ) -} - func (cfg *Config) mutualPeers() (_ []peer.AddrInfo, err error) { maddrs := make([]ma.Multiaddr, len(cfg.MutualPeers)) for i, addr := range cfg.MutualPeers { @@ -92,3 +71,12 @@ func (cfg *Config) mutualPeers() (_ []peer.AddrInfo, err error) { return peer.AddrInfosFromP2pAddrs(maddrs...) } + +// Validate performs basic validation of the config. +func (cfg *Config) Validate() error { + if cfg.RoutingTableRefreshPeriod <= 0 { + cfg.RoutingTableRefreshPeriod = defaultRoutingRefreshPeriod + log.Warnf("routingTableRefreshPeriod is not valid. restoring to default value: %d", cfg.RoutingTableRefreshPeriod) + } + return nil +} diff --git a/node/p2p/host.go b/nodebuilder/p2p/host.go similarity index 53% rename from node/p2p/host.go rename to nodebuilder/p2p/host.go index f121041a23..ac250d2717 100644 --- a/node/p2p/host.go +++ b/nodebuilder/p2p/host.go @@ -26,40 +26,38 @@ func RoutedHost(base HostBase, r routing.PeerRouting) host.Host { } // Host returns constructor for Host. -func Host(cfg Config) func(hostParams) (HostBase, error) { - return func(params hostParams) (HostBase, error) { - opts := []libp2p.Option{ - libp2p.NoListenAddrs, // do not listen automatically - libp2p.AddrsFactory(params.AddrF), - libp2p.Identity(params.Key), - libp2p.Peerstore(params.PStore), - libp2p.ConnectionManager(params.ConnMngr), - libp2p.ConnectionGater(params.ConnGater), - libp2p.UserAgent(fmt.Sprintf("celestia-%s", params.Net)), - libp2p.NATPortMap(), // enables upnp - libp2p.DisableRelay(), - // to clearly define what defaults we rely upon - libp2p.DefaultSecurity, - libp2p.DefaultTransports, - libp2p.DefaultMuxers, - } +func Host(cfg Config, params hostParams) (HostBase, error) { + opts := []libp2p.Option{ + libp2p.NoListenAddrs, // do not listen automatically + libp2p.AddrsFactory(params.AddrF), + libp2p.Identity(params.Key), + libp2p.Peerstore(params.PStore), + libp2p.ConnectionManager(params.ConnMngr), + libp2p.ConnectionGater(params.ConnGater), + libp2p.UserAgent(fmt.Sprintf("celestia-%s", params.Net)), + libp2p.NATPortMap(), // enables upnp + libp2p.DisableRelay(), + // to clearly define what defaults we rely upon + libp2p.DefaultSecurity, + libp2p.DefaultTransports, + libp2p.DefaultMuxers, + } - // TODO(@Wondertan): Other, non Celestia bootstrapper may also enable NATService to contribute the network. - if cfg.Bootstrapper { - opts = append(opts, libp2p.EnableNATService()) - } + // TODO(@Wondertan): Other, non Celestia bootstrapper may also enable NATService to contribute the network. + if cfg.Bootstrapper { + opts = append(opts, libp2p.EnableNATService()) + } - h, err := libp2p.NewWithoutDefaults(opts...) - if err != nil { - return nil, err - } + h, err := libp2p.NewWithoutDefaults(opts...) + if err != nil { + return nil, err + } - params.Lc.Append(fx.Hook{OnStop: func(context.Context) error { - return h.Close() - }}) + params.Lc.Append(fx.Hook{OnStop: func(context.Context) error { + return h.Close() + }}) - return h, nil - } + return h, nil } type HostBase host.Host diff --git a/node/p2p/identity.go b/nodebuilder/p2p/identity.go similarity index 100% rename from node/p2p/identity.go rename to nodebuilder/p2p/identity.go diff --git a/node/p2p/ipld.go b/nodebuilder/p2p/ipld.go similarity index 100% rename from node/p2p/ipld.go rename to nodebuilder/p2p/ipld.go diff --git a/node/p2p/misc.go b/nodebuilder/p2p/misc.go similarity index 67% rename from node/p2p/misc.go rename to nodebuilder/p2p/misc.go index d77c5c9868..d0c89e3449 100644 --- a/node/p2p/misc.go +++ b/nodebuilder/p2p/misc.go @@ -31,29 +31,27 @@ func DefaultConnManagerConfig() ConnManagerConfig { } // ConnectionManager provides a constructor for ConnectionManager. -func ConnectionManager(cfg Config) func(params.Bootstrappers) (coreconnmgr.ConnManager, error) { - return func(bpeers params.Bootstrappers) (coreconnmgr.ConnManager, error) { - fpeers, err := cfg.mutualPeers() - if err != nil { - return nil, err - } - cm, err := connmgr.NewConnManager( - cfg.ConnManager.Low, - cfg.ConnManager.High, - connmgr.WithGracePeriod(cfg.ConnManager.GracePeriod), - ) - if err != nil { - return nil, err - } - for _, info := range fpeers { - cm.Protect(info.ID, "protected-mutual") - } - for _, info := range bpeers { - cm.Protect(info.ID, "protected-bootstrap") - } - - return cm, nil +func ConnectionManager(cfg Config, bpeers params.Bootstrappers) (coreconnmgr.ConnManager, error) { + fpeers, err := cfg.mutualPeers() + if err != nil { + return nil, err + } + cm, err := connmgr.NewConnManager( + cfg.ConnManager.Low, + cfg.ConnManager.High, + connmgr.WithGracePeriod(cfg.ConnManager.GracePeriod), + ) + if err != nil { + return nil, err + } + for _, info := range fpeers { + cm.Protect(info.ID, "protected-mutual") } + for _, info := range bpeers { + cm.Protect(info.ID, "protected-bootstrap") + } + + return cm, nil } // ConnectionGater constructs a BasicConnectionGater. diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go new file mode 100644 index 0000000000..bffbd43bd1 --- /dev/null +++ b/nodebuilder/p2p/module.go @@ -0,0 +1,42 @@ +package p2p + +import ( + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" +) + +// Module collects all the components and services related to p2p. +func Module(tp node.Type, cfg *Config) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(cfgErr), + fx.Provide(Key), + fx.Provide(ID), + fx.Provide(PeerStore), + fx.Provide(ConnectionManager), + fx.Provide(ConnectionGater), + fx.Provide(Host), + fx.Provide(RoutedHost), + fx.Provide(PubSub), + fx.Provide(DataExchange), + fx.Provide(BlockService), + fx.Provide(PeerRouting), + fx.Provide(ContentRouting), + fx.Provide(AddrsFactory(cfg.AnnounceAddresses, cfg.NoAnnounceAddresses)), + fx.Invoke(Listen(cfg.ListenAddresses)), + ) + + switch tp { + case node.Light, node.Full, node.Bridge: + return fx.Module( + "p2p", + baseComponents, + ) + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/p2p/opts.go b/nodebuilder/p2p/opts.go new file mode 100644 index 0000000000..8dabe9a954 --- /dev/null +++ b/nodebuilder/p2p/opts.go @@ -0,0 +1,36 @@ +package p2p + +import ( + "encoding/hex" + + "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/host" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/libs/fxutil" +) + +// WithP2PKey sets custom Ed25519 private key for p2p networking. +func WithP2PKey(key crypto.PrivKey) fx.Option { + return fxutil.ReplaceAs(key, new(crypto.PrivKey)) +} + +// WithP2PKeyStr sets custom hex encoded Ed25519 private key for p2p networking. +func WithP2PKeyStr(key string) fx.Option { + decKey, err := hex.DecodeString(key) + if err != nil { + return fx.Error(err) + } + + privKey, err := crypto.UnmarshalEd25519PrivateKey(decKey) + if err != nil { + return fx.Error(err) + } + + return fxutil.ReplaceAs(privKey, new(crypto.PrivKey)) +} + +// WithHost sets custom Host's data for p2p networking. +func WithHost(hst host.Host) fx.Option { + return fxutil.ReplaceAs(hst, new(HostBase)) +} diff --git a/nodebuilder/p2p/pubsub.go b/nodebuilder/p2p/pubsub.go new file mode 100644 index 0000000000..8fc49348f8 --- /dev/null +++ b/nodebuilder/p2p/pubsub.go @@ -0,0 +1,54 @@ +package p2p + +import ( + "context" + + "github.com/libp2p/go-libp2p-core/host" + pubsub "github.com/libp2p/go-libp2p-pubsub" + pubsub_pb "github.com/libp2p/go-libp2p-pubsub/pb" + "go.uber.org/fx" + "golang.org/x/crypto/blake2b" + + "github.com/celestiaorg/celestia-node/libs/fxutil" +) + +// PubSub provides a constructor for PubSub protocol with GossipSub routing. +func PubSub(cfg Config, params pubSubParams) (*pubsub.PubSub, error) { + fpeers, err := cfg.mutualPeers() + if err != nil { + return nil, err + } + + // TODO(@Wondertan) for PubSub options: + // * Hash-based MsgId function. + // * Validate default peer scoring params for our use-case. + // * Strict subscription filter + // * For different network types(mainnet/testnet/devnet) we should have different network topic names. + // * Hardcode positive score for bootstrap peers + // * Bootstrappers should only gossip and PX + // * Peers should trust boostrappers, so peerscore for them should always be high. + opts := []pubsub.Option{ + pubsub.WithPeerExchange(cfg.PeerExchange || cfg.Bootstrapper), + pubsub.WithDirectPeers(fpeers), + pubsub.WithMessageIdFn(hashMsgID), + } + + return pubsub.NewGossipSub( + fxutil.WithLifecycle(params.Ctx, params.Lc), + params.Host, + opts..., + ) +} + +func hashMsgID(m *pubsub_pb.Message) string { + hash := blake2b.Sum256(m.Data) + return string(hash[:]) +} + +type pubSubParams struct { + fx.In + + Ctx context.Context + Lc fx.Lifecycle + Host host.Host +} diff --git a/nodebuilder/p2p/routing.go b/nodebuilder/p2p/routing.go new file mode 100644 index 0000000000..46a622a4e8 --- /dev/null +++ b/nodebuilder/p2p/routing.go @@ -0,0 +1,69 @@ +package p2p + +import ( + "context" + "fmt" + + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p-core/protocol" + "github.com/libp2p/go-libp2p-core/routing" + dht "github.com/libp2p/go-libp2p-kad-dht" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/libs/fxutil" + nparams "github.com/celestiaorg/celestia-node/params" +) + +var log = logging.Logger("p2p-module") + +// ContentRouting constructs nil content routing, +// as for our use-case existing ContentRouting mechanisms, e.g DHT, are unsuitable +func ContentRouting(r routing.PeerRouting) routing.ContentRouting { + return r.(*dht.IpfsDHT) +} + +// PeerRouting provides constructor for PeerRouting over DHT. +// Basically, this provides a way to discover peer addresses by respecting public keys. +func PeerRouting(cfg Config, params routingParams) (routing.PeerRouting, error) { + opts := []dht.Option{ + dht.Mode(dht.ModeAuto), + dht.BootstrapPeers(params.Peers...), + dht.ProtocolPrefix(protocol.ID(fmt.Sprintf("/celestia/%s", params.Net))), + dht.Datastore(params.DataStore), + dht.RoutingTableRefreshPeriod(cfg.RoutingTableRefreshPeriod), + } + + if cfg.Bootstrapper { + // override options for bootstrapper + opts = append(opts, + dht.Mode(dht.ModeServer), // it must accept incoming connections + dht.BootstrapPeers(), // no bootstrappers for a bootstrapper ¯\_(ツ)_/¯ + ) + } + + d, err := dht.New(fxutil.WithLifecycle(params.Ctx, params.Lc), params.Host, opts...) + if err != nil { + return nil, err + } + params.Lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return d.Bootstrap(ctx) + }, + OnStop: func(context.Context) error { + return d.Close() + }, + }) + return d, nil +} + +type routingParams struct { + fx.In + + Ctx context.Context + Net nparams.Network + Peers nparams.Bootstrappers + Lc fx.Lifecycle + Host HostBase + DataStore datastore.Batching +} diff --git a/nodebuilder/rpc/module.go b/nodebuilder/rpc/module.go new file mode 100644 index 0000000000..62fecfb7db --- /dev/null +++ b/nodebuilder/rpc/module.go @@ -0,0 +1,56 @@ +package rpc + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" + headerServ "github.com/celestiaorg/celestia-node/service/header" + rpcServ "github.com/celestiaorg/celestia-node/service/rpc" + shareServ "github.com/celestiaorg/celestia-node/service/share" + stateServ "github.com/celestiaorg/celestia-node/service/state" +) + +func Module(tp node.Type, cfg *rpcServ.Config) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(cfgErr), + fx.Provide(fx.Annotate( + rpcServ.NewServer, + fx.OnStart(func(ctx context.Context, server *rpcServ.Server) error { + return server.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, server *rpcServ.Server) error { + return server.Stop(ctx) + }), + )), + ) + + switch tp { + case node.Light, node.Full: + return fx.Module( + "rpc", + baseComponents, + fx.Invoke(Handler), + ) + case node.Bridge: + return fx.Module( + "rpc", + baseComponents, + fx.Invoke(func( + state *stateServ.Service, + share *shareServ.Service, + header *headerServ.Service, + rpcSrv *rpcServ.Server, + ) { + Handler(state, share, header, rpcSrv, nil) + }), + ) + default: + panic("invalid node type") + } +} diff --git a/node/rpc/component.go b/nodebuilder/rpc/rpc.go similarity index 56% rename from node/rpc/component.go rename to nodebuilder/rpc/rpc.go index 21e4546645..6af85b17a8 100644 --- a/node/rpc/component.go +++ b/nodebuilder/rpc/rpc.go @@ -1,8 +1,6 @@ package rpc import ( - "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/service/header" "github.com/celestiaorg/celestia-node/service/rpc" @@ -10,21 +8,6 @@ import ( "github.com/celestiaorg/celestia-node/service/state" ) -// Server constructs a new RPC Server from the given Config. -// -// TODO(@renaynay/@Wondertan): this component is meant to be removed on implementation -// of https://github.com/celestiaorg/celestia-node/pull/506. -func Server(cfg rpc.Config) func(lc fx.Lifecycle) *rpc.Server { - return func(lc fx.Lifecycle) *rpc.Server { - serv := rpc.NewServer(cfg) - lc.Append(fx.Hook{ - OnStart: serv.Start, - OnStop: serv.Stop, - }) - return serv - } -} - // Handler constructs a new RPC Handler from the given services. func Handler( state *state.Service, diff --git a/node/rpc_test.go b/nodebuilder/rpc_test.go similarity index 84% rename from node/rpc_test.go rename to nodebuilder/rpc_test.go index eb21527a24..1e03385f2d 100644 --- a/node/rpc_test.go +++ b/nodebuilder/rpc_test.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "context" @@ -9,22 +9,19 @@ import ( "strconv" "testing" - "github.com/ipfs/go-datastore" - ds_sync "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/params" - "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/local" "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/header/sync" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/params" service "github.com/celestiaorg/celestia-node/service/header" "github.com/celestiaorg/celestia-node/service/rpc" - "github.com/celestiaorg/celestia-node/service/share" ) // NOTE: The following tests are against common RPC endpoints provided by @@ -200,31 +197,10 @@ func setupNodeWithModifiedRPC(t *testing.T) *Node { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) // create test node with a dummy header service, manually add a dummy header - // service and register it with rpc handler/server hServ := setupHeaderService(ctx, t) - daser := setupDASer() // create overrides - overrideHeaderServ := func(sets *settings) { - sets.opts = append(sets.opts, fx.Replace(hServ)) - } - overrideDASer := func(sets *settings) { - sets.opts = append(sets.opts, fx.Replace(func() func(lc fx.Lifecycle) *das.DASer { - return func(lc fx.Lifecycle) *das.DASer { - lc.Append(fx.Hook{ - OnStart: daser.Start, - OnStop: daser.Stop, - }) - return daser - } - })) - } - overrideRPCHandler := func(sets *settings) { - sets.opts = append(sets.opts, fx.Invoke(func(srv *rpc.Server) { - handler := rpc.NewHandler(nil, nil, hServ, daser) - handler.RegisterEndpoints(srv) - })) - } - nd := TestNode(t, Full, overrideHeaderServ, overrideDASer, overrideRPCHandler) + overrideHeaderServ := fx.Replace(hServ) + nd := TestNode(t, node.Full, overrideHeaderServ) // start node err := nd.Start(ctx) require.NoError(t, err) @@ -248,9 +224,3 @@ func setupHeaderService(ctx context.Context, t *testing.T) *service.Service { return service.NewHeaderService(syncer, nil, nil, nil, localStore) } - -func setupDASer() *das.DASer { - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - sub := &header.DummySubscriber{Headers: make([]*header.ExtendedHeader, 10)} - return das.NewDASer(share.NewTestSuccessfulAvailability(), sub, nil, ds, nil) -} diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go new file mode 100644 index 0000000000..4e9e19a7ee --- /dev/null +++ b/nodebuilder/settings.go @@ -0,0 +1,27 @@ +package nodebuilder + +import ( + "go.uber.org/fx" + + headermodule "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/params" +) + +// WithNetwork specifies the Network to which the Node should connect to. +// WARNING: Use this option with caution and never run the Node with different networks over the same persisted Store. +func WithNetwork(net params.Network) fx.Option { + return fx.Replace(net) +} + +// WithBootstrappers sets custom bootstrap peers. +func WithBootstrappers(peers params.Bootstrappers) fx.Option { + return fx.Replace(peers) +} + +// WithMetrics enables metrics exporting for the node. +func WithMetrics(enable bool) fx.Option { + return fx.Options( + // TODO: Add metrics to more modules and enable here. + headermodule.WithMetrics(enable), + ) +} diff --git a/nodebuilder/share/config.go b/nodebuilder/share/config.go new file mode 100644 index 0000000000..22564dd941 --- /dev/null +++ b/nodebuilder/share/config.go @@ -0,0 +1,37 @@ +package share + +import ( + "errors" + "fmt" + "time" +) + +var ( + ErrNegativeInterval = errors.New("interval must be positive") +) + +type Config struct { + // PeersLimit defines how many peers will be added during discovery. + PeersLimit uint + // DiscoveryInterval is an interval between discovery sessions. + DiscoveryInterval time.Duration + // AdvertiseInterval is a interval between advertising sessions. + // NOTE: only full and bridge can advertise themselves. + AdvertiseInterval time.Duration +} + +func DefaultConfig() Config { + return Config{ + PeersLimit: 3, + DiscoveryInterval: time.Second * 30, + AdvertiseInterval: time.Second * 30, + } +} + +// Validate performs basic validation of the config. +func (cfg *Config) Validate() error { + if cfg.DiscoveryInterval <= 0 || cfg.AdvertiseInterval <= 0 { + return fmt.Errorf("nodebuilder/share: %s", ErrNegativeInterval) + } + return nil +} diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go new file mode 100644 index 0000000000..5eb8327219 --- /dev/null +++ b/nodebuilder/share/module.go @@ -0,0 +1,71 @@ +package share + +import ( + "context" + + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/service/share" +) + +func Module(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(cfgErr), + fx.Options(options...), + fx.Invoke(share.EnsureEmptySquareExists), + fx.Provide(Discovery(*cfg)), + fx.Provide(fx.Annotate( + share.NewService, + fx.OnStart(func(ctx context.Context, service *share.Service) error { + return service.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, service *share.Service) error { + return service.Stop(ctx) + }), + )), + ) + + switch tp { + case node.Light: + return fx.Module( + "share", + baseComponents, + fx.Provide(fx.Annotate( + share.NewLightAvailability, + fx.OnStart(func(ctx context.Context, avail *share.LightAvailability) error { + return avail.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, avail *share.LightAvailability) error { + return avail.Stop(ctx) + }), + )), + // CacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a CacheAvailability but the constructor returns a share.Availability + fx.Provide(CacheAvailability[*share.LightAvailability]), + ) + case node.Bridge, node.Full: + return fx.Module( + "share", + baseComponents, + fx.Provide(fx.Annotate( + share.NewFullAvailability, + fx.OnStart(func(ctx context.Context, avail *share.FullAvailability) error { + return avail.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, avail *share.FullAvailability) error { + return avail.Stop(ctx) + }), + )), + // CacheAvailability's lifecycle continues to use a fx hook, + // since the LC requires a CacheAvailability but the constructor returns a share.Availability + fx.Provide(CacheAvailability[*share.FullAvailability]), + ) + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go new file mode 100644 index 0000000000..f343f6fe3d --- /dev/null +++ b/nodebuilder/share/share.go @@ -0,0 +1,35 @@ +package share + +import ( + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/routing" + routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/service/share" +) + +func Discovery(cfg Config) func(routing.ContentRouting, host.Host) *share.Discovery { + return func( + r routing.ContentRouting, + h host.Host, + ) *share.Discovery { + return share.NewDiscovery( + h, + routingdisc.NewRoutingDiscovery(r), + cfg.PeersLimit, + cfg.DiscoveryInterval, + cfg.AdvertiseInterval, + ) + } +} + +// CacheAvailability wraps either Full or Light availability with a cache for result sampling. +func CacheAvailability[A share.Availability](lc fx.Lifecycle, ds datastore.Batching, avail A) share.Availability { + ca := share.NewCacheAvailability(avail, ds) + lc.Append(fx.Hook{ + OnStop: ca.Close, + }) + return ca +} diff --git a/node/key/key.go b/nodebuilder/state/config.go similarity index 63% rename from node/key/key.go rename to nodebuilder/state/config.go index 490897bf74..93969dcd28 100644 --- a/node/key/key.go +++ b/nodebuilder/state/config.go @@ -1,4 +1,4 @@ -package key +package state // Config contains configuration parameters for constructing // the node's keyring signer. @@ -11,3 +11,8 @@ func DefaultConfig() Config { KeyringAccName: "", } } + +// Validate performs basic validation of the config. +func (cfg *Config) Validate() error { + return nil +} diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go new file mode 100644 index 0000000000..e5c836cb3d --- /dev/null +++ b/nodebuilder/state/core.go @@ -0,0 +1,15 @@ +package state + +import ( + apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + + "github.com/celestiaorg/celestia-node/header/sync" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/service/state" +) + +// CoreAccessor constructs a new instance of state.Accessor over +// a celestia-core connection. +func CoreAccessor(corecfg core.Config, signer *apptypes.KeyringSigner, sync *sync.Syncer) state.Accessor { + return state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) +} diff --git a/nodebuilder/state/keyring.go b/nodebuilder/state/keyring.go new file mode 100644 index 0000000000..ba3bfe429f --- /dev/null +++ b/nodebuilder/state/keyring.go @@ -0,0 +1,72 @@ +package state + +import ( + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + + "github.com/celestiaorg/celestia-node/libs/keystore" + "github.com/celestiaorg/celestia-node/params" +) + +func Keyring(cfg Config, ks keystore.Keystore, net params.Network) (*apptypes.KeyringSigner, error) { + // TODO @renaynay: Include option for setting custom `userInput` parameter with + // implementation of https://github.com/celestiaorg/celestia-node/issues/415. + // TODO @renaynay @Wondertan: ensure that keyring backend from config is passed + // here instead of hardcoded `BackendTest`: https://github.com/celestiaorg/celestia-node/issues/603. + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + ring, err := keyring.New(app.Name, keyring.BackendTest, ks.Path(), os.Stdin, encConf.Codec) + if err != nil { + return nil, err + } + + var info *keyring.Record + // if custom keyringAccName provided, find key for that name + if cfg.KeyringAccName != "" { + keyInfo, err := ring.Key(cfg.KeyringAccName) + if err != nil { + return nil, err + } + info = keyInfo + } else { + // check if key exists for signer + keys, err := ring.List() + if err != nil { + return nil, err + } + // if no key was found in keystore path, generate new key for node + if len(keys) == 0 { + log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ks.Path()) + keyInfo, mn, err := ring.NewMnemonic("my_celes_key", keyring.English, "", + "", hd.Secp256k1) + if err != nil { + return nil, err + } + log.Info("NEW KEY GENERATED...") + addr, err := keyInfo.GetAddress() + if err != nil { + return nil, err + } + fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n", + keyInfo.Name, addr.String(), mn) + + info = keyInfo + } else { + // if one or more keys are present and no keyringAccName was given, use the first key in list + info = keys[0] + } + } + // construct signer using the default key found / generated above + signer := apptypes.NewKeyringSigner(ring, info.Name, string(net)) + signerInfo := signer.GetSignerInfo() + log.Infow("constructed keyring signer", "backend", keyring.BackendTest, "path", ks.Path(), + "key name", signerInfo.Name, "chain-id", string(net)) + + return signer, nil +} diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go new file mode 100644 index 0000000000..f444ba28b6 --- /dev/null +++ b/nodebuilder/state/module.go @@ -0,0 +1,57 @@ +package state + +import ( + "context" + + logging "github.com/ipfs/go-log/v2" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fxutil" + fraudbuilder "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/service/state" +) + +var log = logging.Logger("state-module") + +// Module provides all components necessary to construct the +// state service. +func Module(tp node.Type, cfg *Config) fx.Option { + // sanitize config values before constructing module + cfgErr := cfg.Validate() + + baseComponents := fx.Options( + fx.Supply(*cfg), + fx.Error(cfgErr), + fx.Provide(Keyring), + fx.Provide(fx.Annotate(CoreAccessor, + fx.OnStart(func(ctx context.Context, accessor state.Accessor) error { + return accessor.Start(ctx) + }), + fx.OnStop(func(ctx context.Context, accessor state.Accessor) error { + return accessor.Stop(ctx) + }), + )), + fx.Provide(fx.Annotate(state.NewService, + fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraud.Service, serv *state.Service) error { + lifecycleCtx := fxutil.WithLifecycle(ctx, lc) + return fraudbuilder.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + serv.Start, serv.Stop) + }), + fx.OnStop(func(ctx context.Context, serv *state.Service) error { + return serv.Stop(ctx) + }), + )), + ) + + switch tp { + case node.Light, node.Full, node.Bridge: + return fx.Module( + "state", + baseComponents, + ) + default: + panic("invalid node type") + } +} diff --git a/nodebuilder/state/opts.go b/nodebuilder/state/opts.go new file mode 100644 index 0000000000..b159f95ded --- /dev/null +++ b/nodebuilder/state/opts.go @@ -0,0 +1,13 @@ +package state + +import ( + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-app/x/payment/types" +) + +// WithKeyringSigner overrides the default keyring signer constructed +// by the node. +func WithKeyringSigner(signer *types.KeyringSigner) fx.Option { + return fx.Replace(signer) +} diff --git a/node/store.go b/nodebuilder/store.go similarity index 99% rename from node/store.go rename to nodebuilder/store.go index 8b7995fbe2..2f5ca418ac 100644 --- a/node/store.go +++ b/nodebuilder/store.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "errors" diff --git a/node/store_mem.go b/nodebuilder/store_mem.go similarity index 97% rename from node/store_mem.go rename to nodebuilder/store_mem.go index 84759a882d..61d16c7c8d 100644 --- a/node/store_mem.go +++ b/nodebuilder/store_mem.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "sync" diff --git a/node/store_test.go b/nodebuilder/store_test.go similarity index 80% rename from node/store_test.go rename to nodebuilder/store_test.go index b22014e36c..d16edc1d60 100644 --- a/node/store_test.go +++ b/nodebuilder/store_test.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "strconv" @@ -6,13 +6,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-node/nodebuilder/node" ) func TestRepo(t *testing.T) { var tests = []struct { - tp Type + tp node.Type }{ - {tp: Bridge}, {tp: Light}, {tp: Full}, + {tp: node.Bridge}, {tp: node.Light}, {tp: node.Full}, } for i, tt := range tests { @@ -22,7 +24,7 @@ func TestRepo(t *testing.T) { _, err := OpenStore(dir) assert.ErrorIs(t, err, ErrNotInited) - err = Init(dir, tt.tp) + err = Init(*DefaultConfig(tt.tp), dir, tt.tp) require.NoError(t, err) store, err := OpenStore(dir) diff --git a/node/testing.go b/nodebuilder/testing.go similarity index 68% rename from node/testing.go rename to nodebuilder/testing.go index 914b8d4378..591081ede9 100644 --- a/node/testing.go +++ b/nodebuilder/testing.go @@ -1,4 +1,4 @@ -package node +package nodebuilder import ( "context" @@ -9,11 +9,15 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/stretchr/testify/require" + "go.uber.org/fx" "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/celestia-node/core" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/state" "github.com/celestiaorg/celestia-node/params" ) @@ -26,22 +30,27 @@ func MockStore(t *testing.T, cfg *Config) Store { return store } -func TestNode(t *testing.T, tp Type, opts ...Option) *Node { +func TestNode(t *testing.T, tp node.Type, opts ...fx.Option) *Node { + return TestNodeWithConfig(t, tp, DefaultConfig(tp), opts...) +} + +func TestNodeWithConfig(t *testing.T, tp node.Type, cfg *Config, opts ...fx.Option) *Node { ctx, cancel := context.WithTimeout(context.Background(), time.Second) t.Cleanup(cancel) - store := MockStore(t, DefaultConfig(tp)) - _, _, cfg := core.StartTestKVApp(ctx, t) - endpoint, err := core.GetEndpoint(cfg) + store := MockStore(t, cfg) + _, _, coreCfg := core.StartTestKVApp(ctx, t) + endpoint, err := core.GetEndpoint(coreCfg) require.NoError(t, err) ip, port, err := net.SplitHostPort(endpoint) require.NoError(t, err) + cfg.Core.IP = ip + cfg.Core.RPCPort = port + cfg.RPC.Port = "0" + opts = append(opts, - WithRemoteCoreIP(ip), - WithRemoteCorePort(port), WithNetwork(params.Private), - WithRPCPort("0"), - WithKeyringSigner(TestKeyringSigner(t)), + state.WithKeyringSigner(TestKeyringSigner(t)), ) nd, err := New(tp, store, opts...) require.NoError(t, err) diff --git a/node/tests/README.md b/nodebuilder/tests/README.md similarity index 100% rename from node/tests/README.md rename to nodebuilder/tests/README.md diff --git a/node/tests/fraud_test.go b/nodebuilder/tests/fraud_test.go similarity index 63% rename from node/tests/fraud_test.go rename to nodebuilder/tests/fraud_test.go index e1f96b0bb2..c34308bb23 100644 --- a/node/tests/fraud_test.go +++ b/nodebuilder/tests/fraud_test.go @@ -11,29 +11,29 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/node" - "github.com/celestiaorg/celestia-node/node/tests/swamp" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) /* Test-Case: Full Node will propagate a fraud proof to the network, once ByzantineError will be received from sampling. - Pre-Requisites: - - CoreClient is started by swamp. - +- CoreClient is started by swamp. Steps: - 1. Create a Bridge Node(BN) with broken extended header at height 10. - 2. Start a BN. - 3. Create a Full Node(FN) with a connection to BN as a trusted peer. - 4. Start a FN. - 5. Subscribe to a fraud proof and wait when it will be received. - 6. Check FN is not synced to 15. - Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. +1. Create a Bridge Node(BN) with broken extended header at height 10. +2. Start a BN. +3. Create a Full Node(FN) with a connection to BN as a trusted peer. +4. Start a FN. +5. Subscribe to a fraud proof and wait when it will be received. +6. Check FN is not synced to 15. +Note: 15 is not available because DASer will be stopped before reaching this height due to receiving a fraud proof. */ func TestFraudProofBroadcasting(t *testing.T) { sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) - bridge := sw.NewBridgeNode(node.WithHeaderConstructFn(header.FraudMaker(t, 20))) + bridge := sw.NewBridgeNode(core.WithHeaderConstructFn(header.FraudMaker(t, 20))) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) t.Cleanup(cancel) @@ -43,8 +43,10 @@ func TestFraudProofBroadcasting(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - store := node.MockStore(t, node.DefaultConfig(node.Full)) - full := sw.NewNodeWithStore(node.Full, store, node.WithTrustedPeers(addrs[0].String())) + cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + store := nodebuilder.MockStore(t, cfg) + full := sw.NewNodeWithStore(node.Full, store) err = full.Start(ctx) require.NoError(t, err) @@ -69,7 +71,8 @@ func TestFraudProofBroadcasting(t *testing.T) { require.NoError(t, full.Stop(ctx)) require.NoError(t, sw.RemoveNode(full, node.Full)) - full = sw.NewNodeWithStore(node.Full, store, node.WithTrustedPeers(addrs[0].String())) + full = sw.NewNodeWithStore(node.Full, store) + require.Error(t, full.Start(ctx)) proofs, err := full.FraudServ.Get(ctx, fraud.BadEncoding) require.NoError(t, err) @@ -91,16 +94,18 @@ Steps: */ func TestFraudProofSyncing(t *testing.T) { sw := swamp.NewSwamp(t, swamp.WithBlockTime(time.Millisecond*100)) - cfg := node.DefaultConfig(node.Bridge) - cfg.P2P.Bootstrapper = true + const defaultTimeInterval = time.Second * 5 - bridge := sw.NewBridgeNode( - node.WithConfig(cfg), - node.WithRefreshRoutingTablePeriod(defaultTimeInterval), - node.WithDiscoveryInterval(defaultTimeInterval), - node.WithAdvertiseInterval(defaultTimeInterval), - node.WithHeaderConstructFn(header.FraudMaker(t, 10)), - ) + + cfg := nodebuilder.DefaultConfig(node.Bridge) + cfg.P2P.Bootstrapper = true + cfg.P2P.RoutingTableRefreshPeriod = defaultTimeInterval + cfg.Share.DiscoveryInterval = defaultTimeInterval + cfg.Share.AdvertiseInterval = defaultTimeInterval + + store := nodebuilder.MockStore(t, cfg) + bridge := sw.NewNodeWithStore(node.Bridge, store, core.WithHeaderConstructFn(header.FraudMaker(t, 10))) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) t.Cleanup(cancel) @@ -110,17 +115,15 @@ func TestFraudProofSyncing(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(addr) require.NoError(t, err) - full := sw.NewFullNode( - node.WithTrustedPeers(addrs[0].String()), - node.WithRefreshRoutingTablePeriod(defaultTimeInterval), - node.WithAdvertiseInterval(defaultTimeInterval), - ) - - ln := sw.NewLightNode( - node.WithBootstrappers([]peer.AddrInfo{*addr}), - node.WithRefreshRoutingTablePeriod(defaultTimeInterval), - node.WithDiscoveryInterval(defaultTimeInterval), - ) + fullCfg := nodebuilder.DefaultConfig(node.Full) + fullCfg.Header.TrustedPeers = append(fullCfg.Header.TrustedPeers, addrs[0].String()) + full := sw.NewNodeWithStore(node.Full, nodebuilder.MockStore(t, fullCfg)) + + lightCfg := nodebuilder.DefaultConfig(node.Light) + lightCfg.P2P.RoutingTableRefreshPeriod = defaultTimeInterval + lightCfg.Share.DiscoveryInterval = defaultTimeInterval + ln := sw.NewNodeWithStore(node.Light, nodebuilder.MockStore(t, lightCfg), + nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr})) require.NoError(t, full.Start(ctx)) subsFn, err := full.FraudServ.Subscribe(fraud.BadEncoding) diff --git a/node/tests/p2p_test.go b/nodebuilder/tests/p2p_test.go similarity index 76% rename from node/tests/p2p_test.go rename to nodebuilder/tests/p2p_test.go index 39cdc64725..2a973dda83 100644 --- a/node/tests/p2p_test.go +++ b/nodebuilder/tests/p2p_test.go @@ -5,16 +5,16 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/libp2p/go-libp2p-core/event" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/node" - "github.com/celestiaorg/celestia-node/node/tests/swamp" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) /* @@ -39,9 +39,9 @@ func TestUseBridgeNodeAsBootstraper(t *testing.T) { addr := host.InfoFromHost(bridge.Host) - full := sw.NewFullNode(node.WithBootstrappers([]peer.AddrInfo{*addr})) - light := sw.NewLightNode(node.WithBootstrappers([]peer.AddrInfo{*addr})) - nodes := []*node.Node{full, light} + full := sw.NewFullNode(nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr})) + light := sw.NewLightNode(nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr})) + nodes := []*nodebuilder.Node{full, light} for index := range nodes { require.NoError(t, nodes[index].Start(ctx)) assert.Equal(t, *addr, nodes[index].Bootstrappers[0]) @@ -90,17 +90,12 @@ Steps: */ func TestBootstrapNodesFromBridgeNode(t *testing.T) { sw := swamp.NewSwamp(t) - cfg := node.DefaultConfig(node.Bridge) + cfg := nodebuilder.DefaultConfig(node.Bridge) cfg.P2P.Bootstrapper = true const defaultTimeInterval = time.Second * 10 - var defaultOptions = []node.Option{ - node.WithRefreshRoutingTablePeriod(defaultTimeInterval), - node.WithDiscoveryInterval(defaultTimeInterval), - node.WithAdvertiseInterval(defaultTimeInterval), - } + setTimeInterval(cfg, defaultTimeInterval) - bridgeConfig := append([]node.Option{node.WithConfig(cfg)}, defaultOptions...) - bridge := sw.NewBridgeNode(bridgeConfig...) + bridge := sw.NewNodeWithConfig(node.Bridge, cfg) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) t.Cleanup(cancel) @@ -109,10 +104,22 @@ func TestBootstrapNodesFromBridgeNode(t *testing.T) { require.NoError(t, err) addr := host.InfoFromHost(bridge.Host) - nodesConfig := append([]node.Option{node.WithBootstrappers([]peer.AddrInfo{*addr})}, defaultOptions...) - full := sw.NewFullNode(nodesConfig...) - light := sw.NewLightNode(nodesConfig...) - nodes := []*node.Node{full, light} + cfg = nodebuilder.DefaultConfig(node.Full) + setTimeInterval(cfg, defaultTimeInterval) + full := sw.NewNodeWithConfig( + node.Full, + cfg, + nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr}), + ) + + cfg = nodebuilder.DefaultConfig(node.Light) + setTimeInterval(cfg, defaultTimeInterval) + light := sw.NewNodeWithConfig( + node.Light, + cfg, + nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr}), + ) + nodes := []*nodebuilder.Node{full, light} ch := make(chan struct{}) sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) require.NoError(t, err) @@ -163,18 +170,14 @@ Steps: */ func TestRestartNodeDiscovery(t *testing.T) { sw := swamp.NewSwamp(t) - cfg := node.DefaultConfig(node.Bridge) + cfg := nodebuilder.DefaultConfig(node.Bridge) cfg.P2P.Bootstrapper = true const defaultTimeInterval = time.Second * 2 const fullNodes = 2 - var defaultOptions = []node.Option{ - node.WithPeersLimit(fullNodes), - node.WithRefreshRoutingTablePeriod(defaultTimeInterval), - node.WithDiscoveryInterval(defaultTimeInterval), - node.WithAdvertiseInterval(defaultTimeInterval), - } - bridgeConfig := append([]node.Option{node.WithConfig(cfg)}, defaultOptions...) - bridge := sw.NewBridgeNode(bridgeConfig...) + + setTimeInterval(cfg, defaultTimeInterval) + cfg.Share.PeersLimit = fullNodes + bridge := sw.NewNodeWithConfig(node.Bridge, cfg) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) t.Cleanup(cancel) @@ -182,10 +185,13 @@ func TestRestartNodeDiscovery(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) addr := host.InfoFromHost(bridge.Host) - nodes := make([]*node.Node, fullNodes) - nodesConfig := append([]node.Option{node.WithBootstrappers([]peer.AddrInfo{*addr})}, defaultOptions...) + nodes := make([]*nodebuilder.Node, fullNodes) + cfg = nodebuilder.DefaultConfig(node.Full) + setTimeInterval(cfg, defaultTimeInterval) + cfg.Share.PeersLimit = fullNodes + nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*addr}) for index := 0; index < fullNodes; index++ { - nodes[index] = sw.NewFullNode(nodesConfig...) + nodes[index] = sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) } identitySub, err := nodes[0].Host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) @@ -207,8 +213,10 @@ func TestRestartNodeDiscovery(t *testing.T) { require.True(t, nodes[0].Host.Network().Connectedness(id) == network.Connected) // create one more node with disabled discovery - nodesConfig[1] = node.WithPeersLimit(0) - node := sw.NewFullNode(nodesConfig...) + cfg = nodebuilder.DefaultConfig(node.Full) + setTimeInterval(cfg, defaultTimeInterval) + cfg.Share.PeersLimit = 0 + node := sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) connectSub, err := nodes[0].Host.EventBus().Subscribe(&event.EvtPeerConnectednessChanged{}) require.NoError(t, err) defer connectSub.Close() @@ -228,3 +236,9 @@ func TestRestartNodeDiscovery(t *testing.T) { } } } + +func setTimeInterval(cfg *nodebuilder.Config, interval time.Duration) { + cfg.P2P.RoutingTableRefreshPeriod = interval + cfg.Share.DiscoveryInterval = interval + cfg.Share.AdvertiseInterval = interval +} diff --git a/node/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go similarity index 73% rename from node/tests/reconstruct_test.go rename to nodebuilder/tests/reconstruct_test.go index e2cf98df52..66fc48653f 100644 --- a/node/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -17,8 +17,9 @@ import ( "golang.org/x/sync/errgroup" "github.com/celestiaorg/celestia-node/ipld" - "github.com/celestiaorg/celestia-node/node" - "github.com/celestiaorg/celestia-node/node/tests/swamp" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" "github.com/celestiaorg/celestia-node/service/share" ) @@ -53,7 +54,9 @@ func TestFullReconstructFromBridge(t *testing.T) { err := bridge.Start(ctx) require.NoError(t, err) - full := sw.NewFullNode(node.WithTrustedPeers(getMultiAddr(t, bridge.Host))) + cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, getMultiAddr(t, bridge.Host)) + full := sw.NewNodeWithConfig(node.Full, cfg) err = full.Start(ctx) require.NoError(t, err) @@ -81,13 +84,13 @@ Pre-Reqs: Steps: 1. Create a Bridge Node(BN) 2. Start a BN -3. Create a Full Node(FN) that will act as a bootstraper -4. Create 69 Light Nodes(LNs) with BN as a trusted peer and a bootstaper +3. Create a Full Node(FN) that will act as a bootstrapper +4. Create 69 Light Nodes(LNs) with BN as a trusted peer and a bootstrapper 5. Start 69 LNs -6. Create a Full Node(FN) with a bootstraper +6. Create a Full Node(FN) with a bootstrapper 7. Unlink FN connection to BN 8. Start a FN -9. Check that a FN can retrieve shares from 1 to 20 blocks +9. Check that the FN can retrieve shares from 1 to 20 blocks */ func TestFullReconstructFromLights(t *testing.T) { ipld.RetrieveQuadrantTimeout = time.Millisecond * 100 @@ -108,42 +111,34 @@ func TestFullReconstructFromLights(t *testing.T) { }() const defaultTimeInterval = time.Second * 5 - var defaultOptions = []node.Option{ - node.WithRefreshRoutingTablePeriod(defaultTimeInterval), - node.WithDiscoveryInterval(defaultTimeInterval), - node.WithAdvertiseInterval(defaultTimeInterval), - } - - cfg := node.DefaultConfig(node.Full) + cfg := nodebuilder.DefaultConfig(node.Full) cfg.P2P.Bootstrapper = true + setTimeInterval(cfg, defaultTimeInterval) + bridge := sw.NewBridgeNode() addrsBridge, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - bootstrapConfig := append([]node.Option{node.WithConfig(cfg)}, defaultOptions...) - bootstapFN := sw.NewFullNode(bootstrapConfig...) - require.NoError(t, bootstapFN.Start(ctx)) + bootstrapper := sw.NewNodeWithConfig(node.Full, cfg) + require.NoError(t, bootstrapper.Start(ctx)) require.NoError(t, bridge.Start(ctx)) - addrBootstrapNode := host.InfoFromHost(bootstapFN.Host) + bootstrapperAddr := host.InfoFromHost(bootstrapper.Host) - nodesConfig := append( - []node.Option{ - node.WithTrustedPeers(addrsBridge[0].String()), - node.WithBootstrappers([]peer.AddrInfo{*addrBootstrapNode})}, - defaultOptions..., - ) - full := sw.NewFullNode(nodesConfig...) - lights := make([]*node.Node, lnodes) + cfg = nodebuilder.DefaultConfig(node.Full) + setTimeInterval(cfg, defaultTimeInterval) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrsBridge[0].String()) + nodesConfig := nodebuilder.WithBootstrappers([]peer.AddrInfo{*bootstrapperAddr}) + full := sw.NewNodeWithConfig(node.Full, cfg, nodesConfig) + + lights := make([]*nodebuilder.Node, lnodes) subs := make([]event.Subscription, lnodes) errg, errCtx := errgroup.WithContext(ctx) for i := 0; i < lnodes; i++ { i := i errg.Go(func() error { - lnConfig := append( - []node.Option{ - node.WithTrustedPeers(addrsBridge[0].String())}, - nodesConfig..., - ) - light := sw.NewLightNode(lnConfig...) + lnConfig := nodebuilder.DefaultConfig(node.Light) + setTimeInterval(lnConfig, defaultTimeInterval) + lnConfig.Header.TrustedPeers = append(lnConfig.Header.TrustedPeers, addrsBridge[0].String()) + light := sw.NewNodeWithConfig(node.Light, lnConfig, nodesConfig) sub, err := light.Host.EventBus().Subscribe(&event.EvtPeerIdentificationCompleted{}) if err != nil { return err diff --git a/node/tests/swamp/config.go b/nodebuilder/tests/swamp/config.go similarity index 100% rename from node/tests/swamp/config.go rename to nodebuilder/tests/swamp/config.go diff --git a/node/tests/swamp/img/test_swamp.svg b/nodebuilder/tests/swamp/img/test_swamp.svg similarity index 100% rename from node/tests/swamp/img/test_swamp.svg rename to nodebuilder/tests/swamp/img/test_swamp.svg diff --git a/node/tests/swamp/swamp.go b/nodebuilder/tests/swamp/swamp.go similarity index 81% rename from node/tests/swamp/swamp.go rename to nodebuilder/tests/swamp/swamp.go index 3cd8e4d818..af441739cd 100644 --- a/node/tests/swamp/swamp.go +++ b/nodebuilder/tests/swamp/swamp.go @@ -14,12 +14,16 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/types" + "go.uber.org/fx" "github.com/celestiaorg/celestia-node/core" "github.com/celestiaorg/celestia-node/libs/keystore" "github.com/celestiaorg/celestia-node/logs" - "github.com/celestiaorg/celestia-node/node" - "github.com/celestiaorg/celestia-node/node/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder" + coremodule "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/state" "github.com/celestiaorg/celestia-node/params" ) @@ -38,9 +42,9 @@ type Swamp struct { t *testing.T Network mocknet.Mocknet CoreClient core.Client - BridgeNodes []*node.Node - FullNodes []*node.Node - LightNodes []*node.Node + BridgeNodes []*nodebuilder.Node + FullNodes []*nodebuilder.Node + LightNodes []*nodebuilder.Node trustedHash string comps *Components } @@ -97,7 +101,7 @@ func NewSwamp(t *testing.T, options ...Option) *Swamp { // stopAllNodes goes through all received slices of Nodes and stops one-by-one // this eliminates a manual clean-up in the test-cases itself in the end -func (s *Swamp) stopAllNodes(ctx context.Context, allNodes ...[]*node.Node) { +func (s *Swamp) stopAllNodes(ctx context.Context, allNodes ...[]*nodebuilder.Node) { for _, nodes := range allNodes { for _, node := range nodes { require.NoError(s.t, node.Stop(ctx)) @@ -192,43 +196,54 @@ func (s *Swamp) getTrustedHash(ctx context.Context) (string, error) { // NewBridgeNode creates a new instance of a BridgeNode providing a default config // and a mockstore to the NewNodeWithStore method -func (s *Swamp) NewBridgeNode(options ...node.Option) *node.Node { - cfg := node.DefaultConfig(node.Bridge) - store := node.MockStore(s.t, cfg) +func (s *Swamp) NewBridgeNode(options ...fx.Option) *nodebuilder.Node { + cfg := nodebuilder.DefaultConfig(node.Bridge) + store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Bridge, store, options...) } // NewFullNode creates a new instance of a FullNode providing a default config // and a mockstore to the NewNodeWithStore method -func (s *Swamp) NewFullNode(options ...node.Option) *node.Node { - cfg := node.DefaultConfig(node.Full) - store := node.MockStore(s.t, cfg) +func (s *Swamp) NewFullNode(options ...fx.Option) *nodebuilder.Node { + cfg := nodebuilder.DefaultConfig(node.Full) + store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Full, store, options...) } // NewLightNode creates a new instance of a LightNode providing a default config // and a mockstore to the NewNodeWithStore method -func (s *Swamp) NewLightNode(options ...node.Option) *node.Node { - cfg := node.DefaultConfig(node.Light) - store := node.MockStore(s.t, cfg) +func (s *Swamp) NewLightNode(options ...fx.Option) *nodebuilder.Node { + cfg := nodebuilder.DefaultConfig(node.Light) + store := nodebuilder.MockStore(s.t, cfg) return s.NewNodeWithStore(node.Light, store, options...) } +func (s *Swamp) NewNodeWithConfig(nodeType node.Type, cfg *nodebuilder.Config, options ...fx.Option) *nodebuilder.Node { + store := nodebuilder.MockStore(s.t, cfg) + return s.NewNodeWithStore(nodeType, store, options...) +} + // NewNodeWithStore creates a new instance of Node with predefined Store. // Afterwards, the instance is stored in the swamp's Nodes' slice according to the // node's type provided from the user. -func (s *Swamp) NewNodeWithStore(t node.Type, store node.Store, options ...node.Option) *node.Node { - var n *node.Node +func (s *Swamp) NewNodeWithStore( + t node.Type, + store nodebuilder.Store, + options ...fx.Option, +) *nodebuilder.Node { + var n *nodebuilder.Node - options = append(options, node.WithKeyringSigner(node.TestKeyringSigner(s.t))) + options = append(options, + state.WithKeyringSigner(nodebuilder.TestKeyringSigner(s.t)), + ) switch t { case node.Bridge: options = append(options, - node.WithCoreClient(s.CoreClient), + coremodule.WithClient(s.CoreClient), ) n = s.newNode(node.Bridge, store, options...) s.BridgeNodes = append(s.BridgeNodes, n) @@ -243,21 +258,22 @@ func (s *Swamp) NewNodeWithStore(t node.Type, store node.Store, options ...node. return n } -func (s *Swamp) newNode(t node.Type, store node.Store, options ...node.Option) *node.Node { +func (s *Swamp) newNode(t node.Type, store nodebuilder.Store, options ...fx.Option) *nodebuilder.Node { ks, err := store.Keystore() require.NoError(s.t, err) // TODO(@Bidon15): If for some reason, we receive one of existing options // like from the test case, we need to check them and not use // default that are set here + cfg, _ := store.Config() + cfg.Header.TrustedHash = s.trustedHash + cfg.RPC.Port = "0" options = append(options, - node.WithHost(s.createPeer(ks)), - node.WithTrustedHash(s.trustedHash), - node.WithNetwork(params.Private), - node.WithRPCPort("0"), + p2p.WithHost(s.createPeer(ks)), + nodebuilder.WithNetwork(params.Private), ) - node, err := node.New(t, store, options...) + node, err := nodebuilder.New(t, store, options...) require.NoError(s.t, err) return node @@ -266,7 +282,7 @@ func (s *Swamp) newNode(t node.Type, store node.Store, options ...node.Option) * // RemoveNode removes a node from the swamp's node slice // this allows reusage of the same var in the test scenario // if the user needs to stop and start the same node -func (s *Swamp) RemoveNode(n *node.Node, t node.Type) error { +func (s *Swamp) RemoveNode(n *nodebuilder.Node, t node.Type) error { var err error switch t { case node.Light: @@ -283,7 +299,7 @@ func (s *Swamp) RemoveNode(n *node.Node, t node.Type) error { } } -func (s *Swamp) remove(rn *node.Node, sn []*node.Node) ([]*node.Node, error) { +func (s *Swamp) remove(rn *nodebuilder.Node, sn []*nodebuilder.Node) ([]*nodebuilder.Node, error) { if len(sn) == 1 { return nil, nil } diff --git a/node/tests/swamp/swamp_tx.go b/nodebuilder/tests/swamp/swamp_tx.go similarity index 100% rename from node/tests/swamp/swamp_tx.go rename to nodebuilder/tests/swamp/swamp_tx.go diff --git a/node/tests/sync_test.go b/nodebuilder/tests/sync_test.go similarity index 81% rename from node/tests/sync_test.go rename to nodebuilder/tests/sync_test.go index ad7515ffd1..d6f5a73928 100644 --- a/node/tests/sync_test.go +++ b/nodebuilder/tests/sync_test.go @@ -10,8 +10,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/celestiaorg/celestia-node/node" - "github.com/celestiaorg/celestia-node/node/tests/swamp" + "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" ) // a default timeout for the context that is used in tests @@ -48,7 +49,9 @@ func TestSyncLightWithBridge(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - light := sw.NewLightNode(node.WithTrustedPeers(addrs[0].String())) + cfg := nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + light := sw.NewNodeWithConfig(node.Light, cfg) err = light.Start(ctx) require.NoError(t, err) @@ -96,8 +99,9 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - store := node.MockStore(t, node.DefaultConfig(node.Light)) - light := sw.NewNodeWithStore(node.Light, store, node.WithTrustedPeers(addrs[0].String())) + cfg := nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + light := sw.NewNodeWithConfig(node.Light, cfg) require.NoError(t, light.Start(ctx)) h, err = light.HeaderServ.GetByHeight(ctx, 30) @@ -108,7 +112,9 @@ func TestSyncStartStopLightWithBridge(t *testing.T) { require.NoError(t, light.Stop(ctx)) require.NoError(t, sw.RemoveNode(light, node.Light)) - light = sw.NewNodeWithStore(node.Light, store, node.WithTrustedPeers(addrs[0].String())) + cfg = nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + light = sw.NewNodeWithConfig(node.Light, cfg) require.NoError(t, light.Start(ctx)) h, err = light.HeaderServ.GetByHeight(ctx, 40) @@ -148,7 +154,9 @@ func TestSyncFullWithBridge(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - full := sw.NewFullNode(node.WithTrustedPeers(addrs[0].String())) + cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) h, err = full.HeaderServ.GetByHeight(ctx, 30) @@ -194,7 +202,9 @@ func TestSyncLightWithFull(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - full := sw.NewFullNode(node.WithTrustedPeers(addrs[0].String())) + cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) h, err = full.HeaderServ.GetByHeight(ctx, 30) @@ -205,7 +215,9 @@ func TestSyncLightWithFull(t *testing.T) { addrs, err = peer.AddrInfoToP2pAddrs(host.InfoFromHost(full.Host)) require.NoError(t, err) - light := sw.NewLightNode(node.WithTrustedPeers(addrs[0].String())) + cfg = nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + light := sw.NewNodeWithConfig(node.Light, cfg) err = sw.Network.UnlinkPeers(bridge.Host.ID(), light.Host.ID()) require.NoError(t, err) @@ -256,9 +268,9 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host)) require.NoError(t, err) - trustedPeers := []string{addrs[0].String()} - - full := sw.NewFullNode(node.WithTrustedPeers(addrs[0].String())) + cfg := nodebuilder.DefaultConfig(node.Full) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + full := sw.NewNodeWithConfig(node.Full, cfg) require.NoError(t, full.Start(ctx)) h, err = full.HeaderServ.GetByHeight(ctx, 30) @@ -269,9 +281,9 @@ func TestSyncLightWithTrustedPeers(t *testing.T) { addrs, err = peer.AddrInfoToP2pAddrs(host.InfoFromHost(full.Host)) require.NoError(t, err) - trustedPeers = append(trustedPeers, addrs[0].String()) - - light := sw.NewLightNode(node.WithTrustedPeers(trustedPeers...)) + cfg = nodebuilder.DefaultConfig(node.Light) + cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String()) + light := sw.NewNodeWithConfig(node.Light, cfg) err = light.Start(ctx) require.NoError(t, err) diff --git a/nodebuilder/watchdog.go b/nodebuilder/watchdog.go new file mode 100644 index 0000000000..3931ea2ed9 --- /dev/null +++ b/nodebuilder/watchdog.go @@ -0,0 +1,49 @@ +package nodebuilder + +import ( + "context" + "sync" + "time" + + logging "github.com/ipfs/go-log/v2" + "github.com/raulk/go-watchdog" + "go.uber.org/fx" +) + +var ( + // TODO(@Wondetan): We must start watchdog only once. This is needed for tests where we run multiple instance + // of the Node. Ideally, the Node should have some testing options instead, so we can check for it and run without + // such utilities but it does not hurt to run one instance of watchdog per test. + onceWatchdog = sync.Once{} + logWatchdog = logging.Logger("watchdog") +) + +// invokeWatchdog starts the memory watchdog that helps to prevent some of OOMs by forcing GCing +// It also collects heap profiles in the given directory when heap grows to more than 90% of memory usage +func invokeWatchdog(pprofdir string) func(lc fx.Lifecycle) error { + return func(lc fx.Lifecycle) (errOut error) { + onceWatchdog.Do(func() { + // to get watchdog information logged out + watchdog.Logger = logWatchdog + // these set up heap pprof auto capturing on disk when threshold hit 90% usage + watchdog.HeapProfileDir = pprofdir + watchdog.HeapProfileMaxCaptures = 10 + watchdog.HeapProfileThreshold = 0.9 + + policy := watchdog.NewWatermarkPolicy(0.50, 0.60, 0.70, 0.85, 0.90, 0.925, 0.95) + err, stop := watchdog.SystemDriven(0, time.Second*5, policy) + if err != nil { + errOut = err + return + } + + lc.Append(fx.Hook{ + OnStop: func(context.Context) error { + stop() + return nil + }, + }) + }) + return + } +} diff --git a/service/rpc/config.go b/service/rpc/config.go index 1b8e120e95..39a94cdc4f 100644 --- a/service/rpc/config.go +++ b/service/rpc/config.go @@ -1,5 +1,11 @@ package rpc +import ( + "fmt" + "net" + "strconv" +) + type Config struct { Address string Port string @@ -12,3 +18,14 @@ func DefaultConfig() Config { Port: "26658", } } + +func (cfg *Config) Validate() error { + if ip := net.ParseIP(cfg.Address); ip == nil { + return fmt.Errorf("service/rpc: invalid listen address format: %s", cfg.Address) + } + _, err := strconv.Atoi(cfg.Port) + if err != nil { + return fmt.Errorf("service/rpc: invalid port: %s", err.Error()) + } + return nil +} diff --git a/service/share/discovery.go b/service/share/discovery.go index 38e080721b..07645c21a7 100644 --- a/service/share/discovery.go +++ b/service/share/discovery.go @@ -25,7 +25,7 @@ var waitF = func(ttl time.Duration) time.Duration { } // discovery combines advertise and discover services and allows to store discovered nodes. -type discovery struct { +type Discovery struct { set *limitedSet host host.Host disc core.Discovery @@ -45,8 +45,8 @@ func NewDiscovery( peersLimit uint, discInterval, advertiseInterval time.Duration, -) *discovery { //nolint:revive - return &discovery{ +) *Discovery { + return &Discovery{ newLimitedSet(peersLimit), h, d, @@ -59,7 +59,7 @@ func NewDiscovery( // handlePeersFound receives peers and tries to establish a connection with them. // Peer will be added to PeerCache if connection succeeds. -func (d *discovery) handlePeerFound(ctx context.Context, topic string, peer peer.AddrInfo) { +func (d *Discovery) handlePeerFound(ctx context.Context, topic string, peer peer.AddrInfo) { if peer.ID == d.host.ID() || len(peer.Addrs) == 0 || d.set.Contains(peer.ID) { return } @@ -83,7 +83,7 @@ func (d *discovery) handlePeerFound(ctx context.Context, topic string, peer peer // ensurePeers ensures we always have 'peerLimit' connected peers. // It starts peer discovery every 30 seconds until peer cache reaches peersLimit. // Discovery is restarted if any previously connected peers disconnect. -func (d *discovery) ensurePeers(ctx context.Context) { +func (d *Discovery) ensurePeers(ctx context.Context) { if d.peersLimit == 0 { log.Warn("peers limit is set to 0. Skipping discovery...") return @@ -138,7 +138,7 @@ func (d *discovery) ensurePeers(ctx context.Context) { } // advertise is a utility function that persistently advertises a service through an Advertiser. -func (d *discovery) advertise(ctx context.Context) { +func (d *Discovery) advertise(ctx context.Context) { timer := time.NewTimer(d.advertiseInterval) defer timer.Stop() for { diff --git a/service/share/full_availability.go b/service/share/full_availability.go index edc1b7eeaa..6e4d73a31f 100644 --- a/service/share/full_availability.go +++ b/service/share/full_availability.go @@ -15,13 +15,13 @@ import ( // to download enough shares to fully reconstruct the data square. type FullAvailability struct { rtrv *ipld.Retriever - disc *discovery + disc *Discovery cancel context.CancelFunc } // NewFullAvailability creates a new full Availability. -func NewFullAvailability(bServ blockservice.BlockService, disc *discovery) *FullAvailability { +func NewFullAvailability(bServ blockservice.BlockService, disc *Discovery) *FullAvailability { return &FullAvailability{ rtrv: ipld.NewRetriever(bServ), disc: disc, diff --git a/service/share/light_availability.go b/service/share/light_availability.go index 2c85eb87ad..603c8e407f 100644 --- a/service/share/light_availability.go +++ b/service/share/light_availability.go @@ -22,14 +22,14 @@ type LightAvailability struct { bserv blockservice.BlockService // disc discovers new full nodes in the network. // it is not allowed to call advertise for light nodes (Full nodes only). - disc *discovery + disc *Discovery cancel context.CancelFunc } // NewLightAvailability creates a new light Availability. func NewLightAvailability( bserv blockservice.BlockService, - disc *discovery, + disc *Discovery, ) *LightAvailability { la := &LightAvailability{ bserv: bserv, From b95d48647a3a1da2de7a69c4a071ae85ef9159e3 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:12:09 +0200 Subject: [PATCH 0096/1008] log(header): Add detailed error for non-adjacent header attempt (#1123) * log(header): add detailed log for non-adjacent header attempt * refactor(header): make ErrNonAdjacent more descriptive * refactor(header): remove err non adjacent log * feat(header/sync): add debug log for non adjacent header attempt --- header/interface.go | 13 ++++++++++--- header/sync/sync_head.go | 11 +++++++---- header/verify.go | 5 ++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/header/interface.go b/header/interface.go index 8b10aaf4f8..8d4a6c171f 100644 --- a/header/interface.go +++ b/header/interface.go @@ -66,11 +66,18 @@ var ( // ErrNoHead is returned when Store is empty (does not contain any known header). ErrNoHead = fmt.Errorf("header/store: no chain head") - - // ErrNonAdjacent is returned when Store is appended with a header not adjacent to the stored head. - ErrNonAdjacent = fmt.Errorf("header/store: non-adjacent") ) +// ErrNonAdjacent is returned when Store is appended with a header not adjacent to the stored head. +type ErrNonAdjacent struct { + Head int64 + Attempted int64 +} + +func (ena *ErrNonAdjacent) Error() string { + return fmt.Sprintf("header/store: non-adjacent: head %d, attempted %d", ena.Head, ena.Attempted) +} + // Store encompasses the behavior necessary to store and retrieve ExtendedHeaders // from a node's local storage. type Store interface { diff --git a/header/sync/sync_head.go b/header/sync/sync_head.go index 2bed57b18d..26b8534288 100644 --- a/header/sync/sync_head.go +++ b/header/sync/sync_head.go @@ -104,13 +104,16 @@ func (s *Syncer) networkHead(ctx context.Context) (*header.ExtendedHeader, error func (s *Syncer) incomingNetHead(ctx context.Context, netHead *header.ExtendedHeader) pubsub.ValidationResult { // Try to short-circuit netHead with append. If not adjacent/from future - try it as new network header _, err := s.store.Append(ctx, netHead) - switch err { - case nil: + if err == nil { // a happy case where we appended maybe head directly, so accept return pubsub.ValidationAccept - case header.ErrNonAdjacent: + } + var nonAdj *header.ErrNonAdjacent + if errors.As(err, &nonAdj) { // not adjacent, maybe we've missed a few headers or its from the past - default: + log.Debugw("attempted to append non-adjacent header", "store head", + nonAdj.Head, "attempted", nonAdj.Attempted) + } else { var verErr *header.VerifyError if errors.As(err, &verErr) { return pubsub.ValidationReject diff --git a/header/verify.go b/header/verify.go index a99198c7ed..d035308c14 100644 --- a/header/verify.go +++ b/header/verify.go @@ -50,7 +50,10 @@ func (eh *ExtendedHeader) VerifyNonAdjacent(untrst *ExtendedHeader) error { // VerifyAdjacent validates adjacent untrusted header against trusted 'eh'. func (eh *ExtendedHeader) VerifyAdjacent(untrst *ExtendedHeader) error { if untrst.Height != eh.Height+1 { - return ErrNonAdjacent + return &ErrNonAdjacent{ + Head: eh.Height, + Attempted: untrst.Height, + } } if err := eh.verify(untrst); err != nil { From 892d684a01af6df5dc6ed2fcb4bbfaa10a85b862 Mon Sep 17 00:00:00 2001 From: John Adler Date: Mon, 26 Sep 2022 18:33:46 -0400 Subject: [PATCH 0097/1008] Fix markdown lint workflow to run on directories --- .github/workflows/markdown-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml index f694e6d122..f1600989e2 100644 --- a/.github/workflows/markdown-lint.yml +++ b/.github/workflows/markdown-lint.yml @@ -19,4 +19,4 @@ jobs: node-version: 18 - run: | npm install -g markdownlint-cli@0.32.1 - markdownlint --config .markdownlint.yaml **/*.md + markdownlint --config .markdownlint.yaml '**/*.md' From b63bae29f42a988a26fb5ab62a2bbce3a49cc7d0 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 27 Sep 2022 12:37:43 +0300 Subject: [PATCH 0098/1008] bugfix: remove fraud.Subscriber from module (#1169) --- nodebuilder/fraud/module.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index c03a35348f..32eaf6e540 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -4,8 +4,6 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/libs/fxutil" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -16,12 +14,12 @@ func Module(tp node.Type) fx.Option { case node.Light: return fx.Module( "fraud", - fxutil.ProvideAs(ServiceWithSyncer, new(fraud.Service), new(fraud.Subscriber)), + fx.Provide(ServiceWithSyncer), ) case node.Full, node.Bridge: return fx.Module( "fraud", - fxutil.ProvideAs(Service, new(fraud.Service), new(fraud.Subscriber)), + fx.Provide(Service), ) default: panic("invalid node type") From 21aed31559598b1cc47a0e2de6edc4f82d657b99 Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:05:27 +0300 Subject: [PATCH 0099/1008] das: add metrics to DASer (#1125) add metrics to DASer --- das/coordinator.go | 9 ++- das/metrics.go | 138 ++++++++++++++++++++++++++++++++++++++ das/worker.go | 7 ++ nodebuilder/daser/opts.go | 16 +++++ nodebuilder/settings.go | 3 + 5 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 das/metrics.go create mode 100644 nodebuilder/daser/opts.go diff --git a/das/coordinator.go b/das/coordinator.go index 0611453338..33d4cc3633 100644 --- a/das/coordinator.go +++ b/das/coordinator.go @@ -23,6 +23,7 @@ type samplingCoordinator struct { waitCh chan *sync.WaitGroup workersWg sync.WaitGroup + metrics *metrics done } @@ -67,8 +68,10 @@ func (sc *samplingCoordinator) run(ctx context.Context, cp checkpoint) { } select { - case max := <-sc.updHeadCh: - sc.state.updateHead(max) + case head := <-sc.updHeadCh: + if sc.state.updateHead(head) { + sc.metrics.observeNewHead(ctx) + } case res := <-sc.resultCh: sc.state.handleResult(res) case wg := <-sc.waitCh: @@ -90,7 +93,7 @@ func (sc *samplingCoordinator) runWorker(ctx context.Context, j job) { sc.workersWg.Add(1) go func() { defer sc.workersWg.Done() - w.run(ctx, sc.getter, sc.sampleFn, sc.resultCh) + w.run(ctx, sc.getter, sc.sampleFn, sc.metrics, sc.resultCh) }() } diff --git a/das/metrics.go b/das/metrics.go new file mode 100644 index 0000000000..b417cb05c1 --- /dev/null +++ b/das/metrics.go @@ -0,0 +1,138 @@ +package das + +import ( + "context" + "fmt" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncint64" + + "github.com/celestiaorg/celestia-node/header" +) + +var ( + meter = global.MeterProvider().Meter("das") +) + +type metrics struct { + sampled syncint64.Counter + sampleTime syncfloat64.Histogram + getHeaderTime syncfloat64.Histogram + newHead syncint64.Counter + lastSampledTS int64 +} + +func (d *DASer) InitMetrics() error { + sampled, err := meter.SyncInt64().Counter("das_sampled_headers_counter", + instrument.WithDescription("sampled headers counter")) + if err != nil { + return err + } + + sampleTime, err := meter.SyncFloat64().Histogram("das_sample_time_hist", + instrument.WithDescription("duration of sampling a single header")) + if err != nil { + return err + } + + getHeaderTime, err := meter.SyncFloat64().Histogram("das_get_header_time_hist", + instrument.WithDescription("duration of getting header from header store")) + if err != nil { + return err + } + + newHead, err := meter.SyncInt64().Counter("das_head_updated_counter", + instrument.WithDescription("amount of times DAS'er advanced network head")) + if err != nil { + return err + } + + lastSampledTS, err := meter.AsyncInt64().Gauge("das_latest_sampled_ts", + instrument.WithDescription("latest sampled timestamp")) + if err != nil { + return err + } + + busyWorkers, err := meter.AsyncInt64().Gauge("das_busy_workers_amount", + instrument.WithDescription("number of active parallel workers in DAS'er")) + if err != nil { + return err + } + + networkHead, err := meter.AsyncInt64().Gauge("das_network_head", + instrument.WithDescription("most recent network head")) + if err != nil { + return err + } + + sampledChainHead, err := meter.AsyncInt64().Gauge("das_sampled_chain_head", + instrument.WithDescription("height of the sampled chain - all previous headers have been successfully sampled")) + if err != nil { + return err + } + + d.sampler.metrics = &metrics{ + sampled: sampled, + sampleTime: sampleTime, + getHeaderTime: getHeaderTime, + newHead: newHead, + } + + err = meter.RegisterCallback( + []instrument.Asynchronous{ + lastSampledTS, busyWorkers, networkHead, sampledChainHead, + }, + func(ctx context.Context) { + stats, err := d.sampler.stats(ctx) + if err != nil { + log.Errorf("observing stats: %s", err.Error()) + } + + busyWorkers.Observe(ctx, int64(len(stats.Workers))) + networkHead.Observe(ctx, int64(stats.NetworkHead)) + sampledChainHead.Observe(ctx, int64(stats.SampledChainHead)) + + if ts := atomic.LoadInt64(&d.sampler.metrics.lastSampledTS); ts != 0 { + lastSampledTS.Observe(ctx, ts) + } + }, + ) + + if err != nil { + return fmt.Errorf("regestering metrics callback: %w", err) + } + + return nil +} + +func (m *metrics) observeSample(ctx context.Context, h *header.ExtendedHeader, sampleTime time.Duration, err error) { + if m == nil { + return + } + m.sampleTime.Record(ctx, sampleTime.Seconds(), + attribute.Bool("failed", err != nil), + attribute.Int("header_width", len(h.DAH.RowsRoots))) + m.sampled.Add(ctx, 1, + attribute.Bool("failed", err != nil), + attribute.Int("header_width", len(h.DAH.RowsRoots))) + atomic.StoreInt64(&m.lastSampledTS, time.Now().UTC().Unix()) +} + +func (m *metrics) observeGetHeader(ctx context.Context, d time.Duration) { + if m == nil { + return + } + m.getHeaderTime.Record(ctx, d.Seconds()) +} + +func (m *metrics) observeNewHead(ctx context.Context) { + if m == nil { + return + } + m.newHead.Add(ctx, 1) +} diff --git a/das/worker.go b/das/worker.go index 86fec42aa6..53af203787 100644 --- a/das/worker.go +++ b/das/worker.go @@ -38,15 +38,21 @@ func (w *worker) run( ctx context.Context, getter header.Getter, sample sampleFn, + metrics *metrics, resultCh chan<- result) { jobStart := time.Now() log.Debugw("start sampling worker", "from", w.state.From, "to", w.state.To) for curr := w.state.From; curr <= w.state.To; curr++ { // TODO: get headers in batches + getHeaderStart := time.Now() h, err := getter.GetByHeight(ctx, curr) + metrics.observeGetHeader(ctx, time.Since(getHeaderStart)) + if err == nil { + start := time.Now() err = sample(ctx, h) + metrics.observeSample(ctx, h, time.Since(start), err) } if errors.Is(err, context.Canceled) { @@ -54,6 +60,7 @@ func (w *worker) run( break } w.setResult(curr, err) + } if w.state.Curr > w.state.From { diff --git a/nodebuilder/daser/opts.go b/nodebuilder/daser/opts.go new file mode 100644 index 0000000000..cb1c8ec142 --- /dev/null +++ b/nodebuilder/daser/opts.go @@ -0,0 +1,16 @@ +package daser + +import ( + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/das" +) + +func WithMetrics(enable bool) fx.Option { + if !enable { + return fx.Options() + } + return fx.Invoke(func(d *das.DASer) error { + return d.InitMetrics() + }) +} diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 4e9e19a7ee..fa84578258 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -3,6 +3,8 @@ package nodebuilder import ( "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/nodebuilder/daser" + headermodule "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/params" ) @@ -23,5 +25,6 @@ func WithMetrics(enable bool) fx.Option { return fx.Options( // TODO: Add metrics to more modules and enable here. headermodule.WithMetrics(enable), + daser.WithMetrics(enable), ) } From 81c63db9b6a80e176561d11eac363ee56c57a8fa Mon Sep 17 00:00:00 2001 From: Vlad <13818348+walldiss@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:08:02 +0300 Subject: [PATCH 0100/1008] das: add extra debug logs for sampled headers (#1157) add debug logs for DASer sampled headers --- das/daser_test.go | 3 +++ das/worker.go | 27 +++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/das/daser_test.go b/das/daser_test.go index 0b1a8a4d6b..83720178df 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/tendermint/tendermint/types" + "github.com/ipfs/go-blockservice" "github.com/ipfs/go-datastore" ds_sync "github.com/ipfs/go-datastore/sync" @@ -297,6 +299,7 @@ func (m getterStub) Head(context.Context) (*header.ExtendedHeader, error) { func (m getterStub) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { return &header.ExtendedHeader{ + Commit: &types.Commit{}, RawHeader: header.RawHeader{Height: int64(height)}, DAH: &header.DataAvailabilityHeader{RowsRoots: make([][]byte, 0)}}, nil } diff --git a/das/worker.go b/das/worker.go index 53af203787..5828097e68 100644 --- a/das/worker.go +++ b/das/worker.go @@ -44,23 +44,34 @@ func (w *worker) run( log.Debugw("start sampling worker", "from", w.state.From, "to", w.state.To) for curr := w.state.From; curr <= w.state.To; curr++ { + startGet := time.Now() // TODO: get headers in batches - getHeaderStart := time.Now() h, err := getter.GetByHeight(ctx, curr) - metrics.observeGetHeader(ctx, time.Since(getHeaderStart)) - - if err == nil { - start := time.Now() - err = sample(ctx, h) - metrics.observeSample(ctx, h, time.Since(start), err) + if err != nil { + if errors.Is(err, context.Canceled) { + // sampling worker will resume upon restart + break + } + w.setResult(curr, err) + log.Errorw("failed to get header from header store", "height", curr, + "finished (s)", time.Since(startGet)) + continue } + metrics.observeGetHeader(ctx, time.Since(startGet)) + log.Debugw("got header from header store", "height", h.Height, "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startGet)) + + startSample := time.Now() + err = sample(ctx, h) if errors.Is(err, context.Canceled) { // sampling worker will resume upon restart break } w.setResult(curr, err) - + metrics.observeSample(ctx, h, time.Since(startSample), err) + log.Debugw("sampled header", "height", h.Height, "hash", h.Hash(), + "square width", len(h.DAH.RowsRoots), "data root", h.DAH.Hash(), "finished (s)", time.Since(startSample)) } if w.state.Curr > w.state.From { From 61202a165e7f9c4b6eb9b1e813a32d4584181dbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:20:48 +0000 Subject: [PATCH 0101/1008] chore(deps): bump go.opentelemetry.io/otel/metric from 0.32.0 to 0.32.1 Bumps [go.opentelemetry.io/otel/metric](https://github.com/open-telemetry/opentelemetry-go) from 0.32.0 to 0.32.1. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/metric/v0.32.0...metric/v0.32.1) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/metric dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b018889df1..d57ff978c9 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( go.opentelemetry.io/otel v1.10.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 - go.opentelemetry.io/otel/metric v0.32.0 + go.opentelemetry.io/otel/metric v0.32.1 go.opentelemetry.io/otel/sdk v1.10.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.10.0 diff --git a/go.sum b/go.sum index 07f0e18a55..f65d95c115 100644 --- a/go.sum +++ b/go.sum @@ -1501,8 +1501,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXo go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0 h1:S8DedULB3gp93Rh+9Z+7NTEv+6Id/KYS7LDyipZ9iCE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.10.0/go.mod h1:5WV40MLWwvWlGP7Xm8g3pMcg0pKOUY609qxJn8y7LmM= -go.opentelemetry.io/otel/metric v0.32.0 h1:lh5KMDB8xlMM4kwE38vlZJ3rZeiWrjw3As1vclfC01k= -go.opentelemetry.io/otel/metric v0.32.0/go.mod h1:PVDNTt297p8ehm949jsIzd+Z2bIZJYQQG/uuHTeWFHY= +go.opentelemetry.io/otel/metric v0.32.1 h1:ftff5LSBCIDwL0UkhBuDg8j9NNxx2IusvJ18q9h6RC4= +go.opentelemetry.io/otel/metric v0.32.1/go.mod h1:iLPP7FaKMAD5BIxJ2VX7f2KTuz//0QK2hEUyti5psqQ= go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q= From 1bf38f3257a84573981ebeb84c63212567206f8f Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Fri, 30 Sep 2022 16:47:18 +0300 Subject: [PATCH 0102/1008] bugfix: fix flakiness in TestFullReconstructFromLights (#1171) --- nodebuilder/tests/reconstruct_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 66fc48653f..499bc384ae 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -102,7 +102,7 @@ func TestFullReconstructFromLights(t *testing.T) { lnodes = 69 ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) t.Cleanup(cancel) sw := swamp.NewSwamp(t, swamp.WithBlockTime(btime)) errCh := make(chan error) From 326024e37f36998248f8ee04b47b83b5ae7e021d Mon Sep 17 00:00:00 2001 From: HoytRen Date: Fri, 30 Sep 2022 22:10:25 +0800 Subject: [PATCH 0103/1008] Disable verbose log on Debug Level for badger, watchdog, and basichost. --- logs/logs.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logs/logs.go b/logs/logs.go index 5f8bd68f06..5932a97a75 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -15,6 +15,9 @@ func SetAllLoggers(level logging.LogLevel) { _ = logging.SetLogLevel("nat", "INFO") _ = logging.SetLogLevel("dht/RtRefreshManager", "FATAL") _ = logging.SetLogLevel("bitswap_network", "ERROR") + _ = logging.SetLogLevel("badger", "INFO") + _ = logging.SetLogLevel("watchdog", "INFO") + _ = logging.SetLogLevel("basichost", "INFO") } func SetDebugLogging() { From 0a3216772a945e118308fdc1d73b884770e0064c Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 30 Sep 2022 16:37:22 +0200 Subject: [PATCH 0104/1008] refactor(nodebuilder): Move parse methods and flag definitions to nodebuilder subpackages (#1161) Closes #1126 Co-authored-by: rene <41963722+renaynay@users.noreply.github.com> --- cmd/celestia/bridge.go | 29 ++++++---- cmd/celestia/full.go | 36 +++++++----- cmd/celestia/light.go | 37 ++++++------ .../core/flags.go | 20 +++---- .../header/flags.go | 57 ++++++++----------- cmd/flags_p2p.go => nodebuilder/p2p/flags.go | 16 +++--- cmd/flags_rpc.go => nodebuilder/rpc/flags.go | 16 +++--- .../state/flags.go | 12 ++-- 8 files changed, 112 insertions(+), 111 deletions(-) rename cmd/flags_core.go => nodebuilder/core/flags.go (76%) rename cmd/flags_header.go => nodebuilder/header/flags.go (54%) rename cmd/flags_p2p.go => nodebuilder/p2p/flags.go (74%) rename cmd/flags_rpc.go => nodebuilder/rpc/flags.go (57%) rename cmd/flags_key.go => nodebuilder/state/flags.go (58%) diff --git a/cmd/celestia/bridge.go b/cmd/celestia/bridge.go index 6ca7633085..a2e8238019 100644 --- a/cmd/celestia/bridge.go +++ b/cmd/celestia/bridge.go @@ -3,6 +3,11 @@ package main import ( "github.com/spf13/cobra" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/state" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -14,19 +19,19 @@ func init() { bridgeCmd.AddCommand( cmdnode.Init( cmdnode.NodeFlags(), - cmdnode.P2PFlags(), - cmdnode.CoreFlags(), + p2p.Flags(), + core.Flags(), cmdnode.MiscFlags(), - cmdnode.RPCFlags(), - cmdnode.KeyFlags(), + rpc.Flags(), + state.Flags(), ), cmdnode.Start( cmdnode.NodeFlags(), - cmdnode.P2PFlags(), - cmdnode.CoreFlags(), + p2p.Flags(), + core.Flags(), cmdnode.MiscFlags(), - cmdnode.RPCFlags(), - cmdnode.KeyFlags(), + rpc.Flags(), + state.Flags(), ), ) } @@ -50,12 +55,12 @@ var bridgeCmd = &cobra.Command{ cfg := cmdnode.NodeConfig(ctx) - err = cmdnode.ParseP2PFlags(cmd, &cfg) + err = p2p.ParseFlags(cmd, &cfg.P2P) if err != nil { return err } - err = cmdnode.ParseCoreFlags(cmd, &cfg) + err = core.ParseFlags(cmd, &cfg.Core) if err != nil { return err } @@ -65,8 +70,8 @@ var bridgeCmd = &cobra.Command{ return err } - cmdnode.ParseRPCFlags(cmd, &cfg) - cmdnode.ParseKeyFlags(cmd, &cfg) + rpc.ParseFlags(cmd, &cfg.RPC) + state.ParseFlags(cmd, &cfg.State) // set config ctx = cmdnode.WithNodeConfig(ctx, &cfg) diff --git a/cmd/celestia/full.go b/cmd/celestia/full.go index ec4e4fea3d..310253cc4c 100644 --- a/cmd/celestia/full.go +++ b/cmd/celestia/full.go @@ -4,6 +4,12 @@ package main import ( "github.com/spf13/cobra" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/state" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -15,25 +21,25 @@ func init() { fullCmd.AddCommand( cmdnode.Init( cmdnode.NodeFlags(), - cmdnode.P2PFlags(), - cmdnode.HeadersFlags(), + p2p.Flags(), + header.Flags(), cmdnode.MiscFlags(), // NOTE: for now, state-related queries can only be accessed // over an RPC connection with a celestia-core node. - cmdnode.CoreFlags(), - cmdnode.RPCFlags(), - cmdnode.KeyFlags(), + core.Flags(), + rpc.Flags(), + state.Flags(), ), cmdnode.Start( cmdnode.NodeFlags(), - cmdnode.P2PFlags(), - cmdnode.HeadersFlags(), + p2p.Flags(), + header.Flags(), cmdnode.MiscFlags(), // NOTE: for now, state-related queries can only be accessed // over an RPC connection with a celestia-core node. - cmdnode.CoreFlags(), - cmdnode.RPCFlags(), - cmdnode.KeyFlags(), + core.Flags(), + rpc.Flags(), + state.Flags(), ), ) } @@ -57,17 +63,17 @@ var fullCmd = &cobra.Command{ cfg := cmdnode.NodeConfig(ctx) - err = cmdnode.ParseP2PFlags(cmd, &cfg) + err = p2p.ParseFlags(cmd, &cfg.P2P) if err != nil { return err } - err = cmdnode.ParseCoreFlags(cmd, &cfg) + err = core.ParseFlags(cmd, &cfg.Core) if err != nil { return err } - ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd, &cfg) + err = header.ParseFlags(cmd, &cfg.Header) if err != nil { return err } @@ -77,8 +83,8 @@ var fullCmd = &cobra.Command{ return err } - cmdnode.ParseRPCFlags(cmd, &cfg) - cmdnode.ParseKeyFlags(cmd, &cfg) + rpc.ParseFlags(cmd, &cfg.RPC) + state.ParseFlags(cmd, &cfg.State) // set config ctx = cmdnode.WithNodeConfig(ctx, &cfg) diff --git a/cmd/celestia/light.go b/cmd/celestia/light.go index a93e58a829..00e2f84d2f 100644 --- a/cmd/celestia/light.go +++ b/cmd/celestia/light.go @@ -4,6 +4,12 @@ package main import ( "github.com/spf13/cobra" + "github.com/celestiaorg/celestia-node/nodebuilder/core" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/p2p" + "github.com/celestiaorg/celestia-node/nodebuilder/rpc" + "github.com/celestiaorg/celestia-node/nodebuilder/state" + cmdnode "github.com/celestiaorg/celestia-node/cmd" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) @@ -15,25 +21,25 @@ func init() { lightCmd.AddCommand( cmdnode.Init( cmdnode.NodeFlags(), - cmdnode.P2PFlags(), - cmdnode.HeadersFlags(), + p2p.Flags(), + header.Flags(), cmdnode.MiscFlags(), // NOTE: for now, state-related queries can only be accessed // over an RPC connection with a celestia-core node. - cmdnode.CoreFlags(), - cmdnode.RPCFlags(), - cmdnode.KeyFlags(), + core.Flags(), + rpc.Flags(), + state.Flags(), ), cmdnode.Start( cmdnode.NodeFlags(), - cmdnode.P2PFlags(), - cmdnode.HeadersFlags(), + p2p.Flags(), + header.Flags(), cmdnode.MiscFlags(), // NOTE: for now, state-related queries can only be accessed // over an RPC connection with a celestia-core node. - cmdnode.CoreFlags(), - cmdnode.RPCFlags(), - cmdnode.KeyFlags(), + core.Flags(), + rpc.Flags(), + state.Flags(), ), ) } @@ -58,17 +64,17 @@ var lightCmd = &cobra.Command{ cfg := cmdnode.NodeConfig(ctx) - err = cmdnode.ParseP2PFlags(cmd, &cfg) + err = p2p.ParseFlags(cmd, &cfg.P2P) if err != nil { return err } - err = cmdnode.ParseCoreFlags(cmd, &cfg) + err = core.ParseFlags(cmd, &cfg.Core) if err != nil { return err } - ctx, err = cmdnode.ParseHeadersFlags(ctx, cmd, &cfg) + err = header.ParseFlags(cmd, &cfg.Header) if err != nil { return err } @@ -77,9 +83,8 @@ var lightCmd = &cobra.Command{ if err != nil { return err } - - cmdnode.ParseRPCFlags(cmd, &cfg) - cmdnode.ParseKeyFlags(cmd, &cfg) + rpc.ParseFlags(cmd, &cfg.RPC) + state.ParseFlags(cmd, &cfg.State) // set config ctx = cmdnode.WithNodeConfig(ctx, &cfg) diff --git a/cmd/flags_core.go b/nodebuilder/core/flags.go similarity index 76% rename from cmd/flags_core.go rename to nodebuilder/core/flags.go index 1d41669251..b98c7acd07 100644 --- a/cmd/flags_core.go +++ b/nodebuilder/core/flags.go @@ -1,12 +1,10 @@ -package cmd +package core import ( "fmt" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - - "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -15,8 +13,8 @@ var ( coreGRPCFlag = "core.grpc.port" ) -// CoreFlags gives a set of hardcoded Core flags. -func CoreFlags() *flag.FlagSet { +// Flags gives a set of hardcoded Core flags. +func Flags() *flag.FlagSet { flags := &flag.FlagSet{} flags.String( @@ -38,10 +36,10 @@ func CoreFlags() *flag.FlagSet { return flags } -// ParseCoreFlags parses Core flags from the given cmd and applies values to Env. -func ParseCoreFlags( +// ParseFlags parses Core flags from the given cmd and saves them to the passed config. +func ParseFlags( cmd *cobra.Command, - cfg *nodebuilder.Config, + cfg *Config, ) error { coreIP := cmd.Flag(coreFlag).Value.String() if coreIP == "" { @@ -54,8 +52,8 @@ func ParseCoreFlags( rpc := cmd.Flag(coreRPCFlag).Value.String() grpc := cmd.Flag(coreGRPCFlag).Value.String() - cfg.Core.IP = coreIP - cfg.Core.RPCPort = rpc - cfg.Core.GRPCPort = grpc + cfg.IP = coreIP + cfg.RPCPort = rpc + cfg.GRPCPort = grpc return nil } diff --git a/cmd/flags_header.go b/nodebuilder/header/flags.go similarity index 54% rename from cmd/flags_header.go rename to nodebuilder/header/flags.go index 7e5c2afc64..db9b5958e8 100644 --- a/cmd/flags_header.go +++ b/nodebuilder/header/flags.go @@ -1,15 +1,12 @@ -package cmd +package header import ( - "context" "encoding/hex" "fmt" "github.com/multiformats/go-multiaddr" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - - "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( @@ -17,8 +14,8 @@ var ( headersTrustedPeersFlag = "headers.trusted-peers" ) -// HeadersFlags gives a set of hardcoded Header package flags. -func HeadersFlags() *flag.FlagSet { +// Flags gives a set of hardcoded Header package flags. +func Flags() *flag.FlagSet { flags := &flag.FlagSet{} flags.AddFlagSet(TrustedPeersFlags()) @@ -27,16 +24,16 @@ func HeadersFlags() *flag.FlagSet { return flags } -// ParseHeadersFlags parses Header package flags from the given cmd and applies values to Env. -func ParseHeadersFlags(ctx context.Context, cmd *cobra.Command, cfg *nodebuilder.Config) (context.Context, error) { - if ctx, err := ParseTrustedHashFlags(ctx, cmd, cfg); err != nil { - return ctx, err +// ParseFlags parses Header package flags from the given cmd and applies them to the passed config. +func ParseFlags(cmd *cobra.Command, cfg *Config) error { + if err := ParseTrustedHashFlags(cmd, cfg); err != nil { + return err } - if ctx, err := ParseTrustedPeerFlags(ctx, cmd, cfg); err != nil { - return ctx, err + if err := ParseTrustedPeerFlags(cmd, cfg); err != nil { + return err } - return ctx, nil + return nil } // TrustedPeersFlags returns a set of flags. @@ -50,28 +47,24 @@ func TrustedPeersFlags() *flag.FlagSet { return flags } -// ParseTrustedPeerFlags parses Header package flags from the given cmd and applies values to Env. +// ParseTrustedPeerFlags parses Header package flags from the given cmd and applies them to the passed config. func ParseTrustedPeerFlags( - ctx context.Context, cmd *cobra.Command, - cfg *nodebuilder.Config, -) (setCtx context.Context, err error) { - defer func() { - setCtx = WithNodeConfig(ctx, cfg) - }() + cfg *Config, +) error { tpeers, err := cmd.Flags().GetStringSlice(headersTrustedPeersFlag) if err != nil { - return ctx, err + return err } for _, tpeer := range tpeers { _, err := multiaddr.NewMultiaddr(tpeer) if err != nil { - return ctx, fmt.Errorf("cmd: while parsing '%s' with peer addr '%s': %w", headersTrustedPeersFlag, tpeer, err) + return fmt.Errorf("cmd: while parsing '%s' with peer addr '%s': %w", headersTrustedPeersFlag, tpeer, err) } } - cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, tpeers...) - return + cfg.TrustedPeers = append(cfg.TrustedPeers, tpeers...) + return nil } // TrustedHashFlags returns a set of flags related to configuring a `TrustedHash`. @@ -86,23 +79,19 @@ func TrustedHashFlags() *flag.FlagSet { return flags } -// ParseTrustedHashFlags parses Header package flags from the given cmd and applies values to Env. +// ParseTrustedHashFlags parses Header package flags from the given cmd and saves them to the passed config. func ParseTrustedHashFlags( - ctx context.Context, cmd *cobra.Command, - cfg *nodebuilder.Config, -) (setCtx context.Context, err error) { - defer func() { - setCtx = WithNodeConfig(ctx, cfg) - }() + cfg *Config, +) error { hash := cmd.Flag(headersTrustedHashFlag).Value.String() if hash != "" { _, err := hex.DecodeString(hash) if err != nil { - return ctx, fmt.Errorf("cmd: while parsing '%s': %w", headersTrustedHashFlag, err) + return fmt.Errorf("cmd: while parsing '%s': %w", headersTrustedHashFlag, err) } - cfg.Header.TrustedHash = hash + cfg.TrustedHash = hash } - return + return nil } diff --git a/cmd/flags_p2p.go b/nodebuilder/p2p/flags.go similarity index 74% rename from cmd/flags_p2p.go rename to nodebuilder/p2p/flags.go index bb0bba941c..1b66c5b910 100644 --- a/cmd/flags_p2p.go +++ b/nodebuilder/p2p/flags.go @@ -1,4 +1,4 @@ -package cmd +package p2p import ( "fmt" @@ -6,16 +6,14 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - - "github.com/celestiaorg/celestia-node/nodebuilder" ) var ( p2pMutualFlag = "p2p.mutual" ) -// P2PFlags gives a set of p2p flags. -func P2PFlags() *flag.FlagSet { +// Flags gives a set of p2p flags. +func Flags() *flag.FlagSet { flags := &flag.FlagSet{} flags.StringSlice( @@ -30,10 +28,10 @@ Peers must bidirectionally point to each other. (Format: multiformats.io/multiad return flags } -// ParseP2PFlags parses P2P flags from the given cmd and applies values to Env. -func ParseP2PFlags( +// ParseFlags parses P2P flags from the given cmd and saves them to the passed config. +func ParseFlags( cmd *cobra.Command, - cfg *nodebuilder.Config, + cfg *Config, ) error { mutualPeers, err := cmd.Flags().GetStringSlice(p2pMutualFlag) if err != nil { @@ -48,7 +46,7 @@ func ParseP2PFlags( } if len(mutualPeers) != 0 { - cfg.P2P.MutualPeers = mutualPeers + cfg.MutualPeers = mutualPeers } return nil } diff --git a/cmd/flags_rpc.go b/nodebuilder/rpc/flags.go similarity index 57% rename from cmd/flags_rpc.go rename to nodebuilder/rpc/flags.go index 629d89a0d1..3d9abb86e5 100644 --- a/cmd/flags_rpc.go +++ b/nodebuilder/rpc/flags.go @@ -1,10 +1,10 @@ -package cmd +package rpc import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "github.com/celestiaorg/celestia-node/nodebuilder" + "github.com/celestiaorg/celestia-node/service/rpc" ) var ( @@ -12,8 +12,8 @@ var ( portFlag = "rpc.port" ) -// RPCFlags gives a set of hardcoded node/rpc package flags. -func RPCFlags() *flag.FlagSet { +// Flags gives a set of hardcoded node/rpc package flags. +func Flags() *flag.FlagSet { flags := &flag.FlagSet{} flags.String( @@ -30,14 +30,14 @@ func RPCFlags() *flag.FlagSet { return flags } -// ParseRPCFlags parses RPC flags from the given cmd and applies values to Env. -func ParseRPCFlags(cmd *cobra.Command, cfg *nodebuilder.Config) { +// ParseFlags parses RPC flags from the given cmd and saves them to the passed config. +func ParseFlags(cmd *cobra.Command, cfg *rpc.Config) { addr := cmd.Flag(addrFlag).Value.String() if addr != "" { - cfg.RPC.Address = addr + cfg.Address = addr } port := cmd.Flag(portFlag).Value.String() if port != "" { - cfg.RPC.Port = port + cfg.Port = port } } diff --git a/cmd/flags_key.go b/nodebuilder/state/flags.go similarity index 58% rename from cmd/flags_key.go rename to nodebuilder/state/flags.go index 1470430a5a..7b9123eda2 100644 --- a/cmd/flags_key.go +++ b/nodebuilder/state/flags.go @@ -1,15 +1,14 @@ -package cmd +package state import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - - "github.com/celestiaorg/celestia-node/nodebuilder" ) var keyringAccNameFlag = "keyring.accname" -func KeyFlags() *flag.FlagSet { +// Flags gives a set of hardcoded State flags. +func Flags() *flag.FlagSet { flags := &flag.FlagSet{} flags.String(keyringAccNameFlag, "", "Directs node's keyring signer to use the key prefixed with the "+ @@ -17,9 +16,10 @@ func KeyFlags() *flag.FlagSet { return flags } -func ParseKeyFlags(cmd *cobra.Command, cfg *nodebuilder.Config) { +// ParseFlags parses State flags from the given cmd and saves them to the passed config. +func ParseFlags(cmd *cobra.Command, cfg *Config) { keyringAccName := cmd.Flag(keyringAccNameFlag).Value.String() if keyringAccName != "" { - cfg.State.KeyringAccName = keyringAccName + cfg.KeyringAccName = keyringAccName } } From f759268b011621881299c1a08e59f8e56513d664 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:19:53 +0000 Subject: [PATCH 0105/1008] chore(deps): bump codecov/codecov-action from 3.1.0 to 3.1.1 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3.1.0...v3.1.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index 71e882a48d..be9f6a7c9a 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -64,7 +64,7 @@ jobs: run: | go install github.com/ory/go-acc@v0.2.6 go-acc -o coverage.txt `go list ./... | grep -v node/tests` -- -v - - uses: codecov/codecov-action@v3.1.0 + - uses: codecov/codecov-action@v3.1.1 with: file: ./coverage.txt From 09127c0ea74f174da89d7cfab65d82e40c83e779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 10:24:28 +0000 Subject: [PATCH 0106/1008] chore(deps): bump go.uber.org/fx from 1.18.1 to 1.18.2 Bumps [go.uber.org/fx](https://github.com/uber-go/fx) from 1.18.1 to 1.18.2. - [Release notes](https://github.com/uber-go/fx/releases) - [Changelog](https://github.com/uber-go/fx/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/fx/compare/v1.18.1...v1.18.2) --- updated-dependencies: - dependency-name: go.uber.org/fx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d57ff978c9..9480df8a50 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( go.opentelemetry.io/otel/sdk v1.10.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/otel/trace v1.10.0 - go.uber.org/fx v1.18.1 + go.uber.org/fx v1.18.2 go.uber.org/multierr v1.8.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f diff --git a/go.sum b/go.sum index f65d95c115..82c3adacb9 100644 --- a/go.sum +++ b/go.sum @@ -1521,8 +1521,8 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/fx v1.18.1 h1:I7VWkdv4iKcbpH7KVSi9Fe1LGmpJv+pbBIb9NidPb+E= -go.uber.org/fx v1.18.1/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= +go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= From 646dd9e8055730a475ec960ad4f629f45a90d727 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 30 Sep 2022 17:20:56 +0200 Subject: [PATCH 0107/1008] refactor(nodebuilder): moving service/ services to respective node sub-packages (#1056) --- README.md | 2 +- das/daser.go | 2 +- das/daser_test.go | 26 ++-- header/p2p/subscriber.go | 4 +- header/p2p/subscription_test.go | 2 +- ipld/retriever.go | 2 +- nodebuilder/core/module.go | 4 +- nodebuilder/daser/daser.go | 6 +- nodebuilder/daser/module.go | 8 +- nodebuilder/fraud/fraud.go | 20 +-- nodebuilder/fraud/module.go | 6 +- nodebuilder/fraud/service.go | 9 ++ nodebuilder/header/module.go | 11 +- nodebuilder/header/service.go | 59 +++++++++ nodebuilder/module.go | 18 +-- nodebuilder/node.go | 21 ++-- nodebuilder/p2p/module.go | 4 +- nodebuilder/rpc/module.go | 14 +-- nodebuilder/rpc/rpc.go | 12 +- nodebuilder/rpc_test.go | 87 +++++++++++-- nodebuilder/share/module.go | 14 +-- nodebuilder/share/service.go | 43 +++++++ nodebuilder/share/share.go | 2 +- nodebuilder/state/core.go | 31 ++++- nodebuilder/state/module.go | 29 +---- .../state/service.go | 50 +++++--- nodebuilder/tests/reconstruct_test.go | 2 +- service/header/service.go | 69 ----------- service/rpc/availability.go | 4 +- service/rpc/handler.go | 18 +-- service/rpc/middleware.go | 4 +- service/rpc/share.go | 2 +- service/rpc/state.go | 4 +- service/state/service.go | 115 ------------------ {service/share => share}/backoff.go | 0 {service/share => share}/backoff_test.go | 0 .../share => share}/cache_availability.go | 0 .../cache_availability_test.go | 6 +- {service/share => share}/discovery.go | 0 {service/share => share}/doc.go | 0 {service/share => share}/empty.go | 0 {service/share => share}/full_availability.go | 0 .../share => share}/full_availability_test.go | 4 +- .../full_reconstruction_test.go | 0 {service/share => share}/interface.go | 0 .../share => share}/light_availability.go | 0 .../light_availability_test.go | 8 +- {service/share => share}/sample.go | 0 {service/share => share}/sample_test.go | 0 {service/share => share}/set.go | 0 {service/share => share}/set_test.go | 0 {service/share => share}/share.go | 19 +-- {service/share => share}/share_test.go | 12 +- .../testdata/block-825320.json | 0 {service/share => share}/testing.go | 20 +-- {service/state => state}/core_access.go | 13 +- {service/state => state}/doc.go | 0 {service/state => state}/helpers.go | 0 {service/state => state}/state.go | 0 59 files changed, 390 insertions(+), 396 deletions(-) create mode 100644 nodebuilder/fraud/service.go create mode 100644 nodebuilder/header/service.go create mode 100644 nodebuilder/share/service.go rename service/state/interface.go => nodebuilder/state/service.go (57%) delete mode 100644 service/header/service.go delete mode 100644 service/state/service.go rename {service/share => share}/backoff.go (100%) rename {service/share => share}/backoff_test.go (100%) rename {service/share => share}/cache_availability.go (100%) rename {service/share => share}/cache_availability_test.go (95%) rename {service/share => share}/discovery.go (100%) rename {service/share => share}/doc.go (100%) rename {service/share => share}/empty.go (100%) rename {service/share => share}/full_availability.go (100%) rename {service/share => share}/full_availability_test.go (86%) rename {service/share => share}/full_reconstruction_test.go (100%) rename {service/share => share}/interface.go (100%) rename {service/share => share}/light_availability.go (100%) rename {service/share => share}/light_availability_test.go (79%) rename {service/share => share}/sample.go (100%) rename {service/share => share}/sample_test.go (100%) rename {service/share => share}/set.go (100%) rename {service/share => share}/set_test.go (100%) rename {service/share => share}/share.go (89%) rename {service/share => share}/share_test.go (96%) rename {service/share => share}/testdata/block-825320.json (100%) rename {service/share => share}/testing.go (92%) rename {service/state => state}/core_access.go (96%) rename {service/state => state}/doc.go (100%) rename {service/state => state}/helpers.go (100%) rename {service/state => state}/state.go (100%) diff --git a/README.md b/README.md index c4853e81c1..d5e755a1e7 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ celestia start ## Package-specific documentation - [Header](./service/header/doc.go) -- [Share](./service/share/doc.go) +- [Share](./share/doc.go) - [DAS](./das/doc.go) ## Code of Conduct diff --git a/das/daser.go b/das/daser.go index 7124acec4a..2ea1bfda2c 100644 --- a/das/daser.go +++ b/das/daser.go @@ -14,7 +14,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" ) var log = logging.Logger("das") diff --git a/das/daser_test.go b/das/daser_test.go index 83720178df..8a6e9d45b7 100644 --- a/das/daser_test.go +++ b/das/daser_test.go @@ -19,7 +19,7 @@ import ( "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" ) var timeout = time.Second * 15 @@ -31,12 +31,12 @@ func TestDASerLifecycle(t *testing.T) { bServ := mdutils.Bserv() avail := share.TestLightAvailability(bServ) // 15 headers from the past and 15 future headers - mockGet, shareServ, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15, avail) + mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) ctx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - daser := NewDASer(shareServ, sub, mockGet, ds, mockService) + daser := NewDASer(avail, sub, mockGet, ds, mockService) err := daser.Start(ctx) require.NoError(t, err) @@ -65,12 +65,12 @@ func TestDASer_Restart(t *testing.T) { bServ := mdutils.Bserv() avail := share.TestLightAvailability(bServ) // 15 headers from the past and 15 future headers - mockGet, shareServ, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15, avail) + mockGet, sub, mockService := createDASerSubcomponents(t, bServ, 15, 15) ctx, cancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(cancel) - daser := NewDASer(shareServ, sub, mockGet, ds, mockService) + daser := NewDASer(avail, sub, mockGet, ds, mockService) err := daser.Start(ctx) require.NoError(t, err) @@ -97,7 +97,7 @@ func TestDASer_Restart(t *testing.T) { restartCtx, restartCancel := context.WithTimeout(context.Background(), timeout) t.Cleanup(restartCancel) - daser = NewDASer(shareServ, sub, mockGet, ds, mockService) + daser = NewDASer(avail, sub, mockGet, ds, mockService) err = daser.Start(restartCtx) require.NoError(t, err) @@ -134,16 +134,16 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { require.NoError(t, err) avail := share.TestFullAvailability(bServ) // 15 headers from the past and 15 future headers - mockGet, shareServ, sub, _ := createDASerSubcomponents(t, bServ, 15, 15, avail) + mockGet, sub, _ := createDASerSubcomponents(t, bServ, 15, 15) - // create fraud service and break one header + // create fraud share and break one header f := fraud.NewProofService(ps, net.Hosts()[0], mockGet.GetByHeight, ds, false) require.NoError(t, f.Start(ctx)) mockGet.headers[1] = header.CreateFraudExtHeader(t, mockGet.headers[1], bServ) newCtx := context.Background() // create and start DASer - daser := NewDASer(shareServ, sub, mockGet, ds, f) + daser := NewDASer(avail, sub, mockGet, ds, f) resultCh := make(chan error) go fraud.OnProof(newCtx, f, fraud.BadEncoding, func(fraud.Proof) { @@ -165,18 +165,16 @@ func TestDASer_stopsAfter_BEFP(t *testing.T) { // createDASerSubcomponents takes numGetter (number of headers // to store in mockGetter) and numSub (number of headers to store // in the mock header.Subscriber), returning a newly instantiated -// mockGetter, share.Service, and mock header.Subscriber. +// mockGetter, share.Availability, and mock header.Subscriber. func createDASerSubcomponents( t *testing.T, bServ blockservice.BlockService, numGetter, numSub int, - availability share.Availability, -) (*mockGetter, *share.Service, *header.DummySubscriber, *fraud.DummyService) { - shareServ := share.NewService(bServ, availability) +) (*mockGetter, *header.DummySubscriber, *fraud.DummyService) { mockGet, sub := createMockGetterAndSub(t, bServ, numGetter, numSub) fraud := new(fraud.DummyService) - return mockGet, shareServ, sub, fraud + return mockGet, sub, fraud } func createMockGetterAndSub( diff --git a/header/p2p/subscriber.go b/header/p2p/subscriber.go index 3200e11bcf..64a4c2787e 100644 --- a/header/p2p/subscriber.go +++ b/header/p2p/subscriber.go @@ -12,14 +12,14 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -// Subscriber manages the lifecycle and relationship of header Service +// Subscriber manages the lifecycle and relationship of header Module // with the "header-sub" gossipsub topic. type Subscriber struct { pubsub *pubsub.PubSub topic *pubsub.Topic } -// NewSubscriber returns a Subscriber that manages the header Service's +// NewSubscriber returns a Subscriber that manages the header Module's // relationship with the "header-sub" gossipsub topic. func NewSubscriber(ps *pubsub.PubSub) *Subscriber { return &Subscriber{ diff --git a/header/p2p/subscription_test.go b/header/p2p/subscription_test.go index a21d80283b..b1ff09ccd3 100644 --- a/header/p2p/subscription_test.go +++ b/header/p2p/subscription_test.go @@ -14,7 +14,7 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -// TestSubscriber tests the header Service's implementation of Subscriber. +// TestSubscriber tests the header Module's implementation of Subscriber. func TestSubscriber(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() diff --git a/ipld/retriever.go b/ipld/retriever.go index f2aa568d24..855a35e16e 100644 --- a/ipld/retriever.go +++ b/ipld/retriever.go @@ -44,7 +44,7 @@ type Retriever struct { bServ blockservice.BlockService } -// NewRetriever creates a new instance of the Retriever over IPLD Service and rmst2d.Codec +// NewRetriever creates a new instance of the Retriever over IPLD BlockService and rmst2d.Codec func NewRetriever(bServ blockservice.BlockService) *Retriever { return &Retriever{bServ: bServ} } diff --git a/nodebuilder/core/module.go b/nodebuilder/core/module.go index ceb47c4e42..59d6db273c 100644 --- a/nodebuilder/core/module.go +++ b/nodebuilder/core/module.go @@ -12,8 +12,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -// Module collects all the components and services related to managing the relationship with the Core node. -func Module(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { +// ConstructModule collects all the components and services related to managing the relationship with the Core node. +func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() diff --git a/nodebuilder/daser/daser.go b/nodebuilder/daser/daser.go index 4cbf6e725d..9582e39a63 100644 --- a/nodebuilder/daser/daser.go +++ b/nodebuilder/daser/daser.go @@ -4,9 +4,9 @@ import ( "github.com/ipfs/go-datastore" "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + "github.com/celestiaorg/celestia-node/share" ) func NewDASer( @@ -14,7 +14,7 @@ func NewDASer( hsub header.Subscriber, store header.Store, batching datastore.Batching, - fraudService fraud.Service, + fraudService fraud.Module, ) *das.DASer { return das.NewDASer(da, hsub, store, batching, fraudService) } diff --git a/nodebuilder/daser/module.go b/nodebuilder/daser/module.go index 6f5ec3b71c..15f37cee4f 100644 --- a/nodebuilder/daser/module.go +++ b/nodebuilder/daser/module.go @@ -8,20 +8,20 @@ import ( "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/libs/fxutil" - fraudbuilder "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -func Module(tp node.Type) fx.Option { +func ConstructModule(tp node.Type) fx.Option { switch tp { case node.Light, node.Full: return fx.Module( "daser", fx.Provide(fx.Annotate( NewDASer, - fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraud.Service, das *das.DASer) error { + fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, das *das.DASer) error { lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudbuilder.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, das.Start, das.Stop) }), fx.OnStop(func(ctx context.Context, das *das.DASer) error { diff --git a/nodebuilder/fraud/fraud.go b/nodebuilder/fraud/fraud.go index 376cd3c6ec..aebcbbbde2 100644 --- a/nodebuilder/fraud/fraud.go +++ b/nodebuilder/fraud/fraud.go @@ -12,25 +12,25 @@ import ( "github.com/celestiaorg/celestia-node/header" ) -// Service constructs a fraud proof service with the syncer disabled. -func Service( +// NewModule constructs a fraud proof service with the syncer disabled. +func NewModule( lc fx.Lifecycle, sub *pubsub.PubSub, host host.Host, hstore header.Store, ds datastore.Batching, -) (fraud.Service, error) { +) (Module, error) { return newFraudService(lc, sub, host, hstore, ds, false) } -// ServiceWithSyncer constructs fraud proof service with enabled syncer. -func ServiceWithSyncer( +// ModuleWithSyncer constructs fraud proof service with enabled syncer. +func ModuleWithSyncer( lc fx.Lifecycle, sub *pubsub.PubSub, host host.Host, hstore header.Store, ds datastore.Batching, -) (fraud.Service, error) { +) (Module, error) { return newFraudService(lc, sub, host, hstore, ds, true) } @@ -40,7 +40,7 @@ func newFraudService( host host.Host, hstore header.Store, ds datastore.Batching, - isEnabled bool) (fraud.Service, error) { + isEnabled bool) (Module, error) { pservice := fraud.NewProofService(sub, host, hstore.GetByHeight, ds, isEnabled) lc.Append(fx.Hook{ OnStart: pservice.Start, @@ -55,10 +55,10 @@ func newFraudService( func Lifecycle( startCtx, lifecycleCtx context.Context, p fraud.ProofType, - fservice fraud.Service, + fraudModule Module, start, stop func(context.Context) error, ) error { - proofs, err := fservice.Get(startCtx, p) + proofs, err := fraudModule.Get(startCtx, p) switch err { default: return err @@ -71,7 +71,7 @@ func Lifecycle( return err } // handle incoming Fraud Proofs - go fraud.OnProof(lifecycleCtx, fservice, p, func(fraud.Proof) { + go fraud.OnProof(lifecycleCtx, fraudModule, p, func(fraud.Proof) { if err := stop(lifecycleCtx); err != nil { log.Error(err) } diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index 32eaf6e540..a072931c73 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -9,17 +9,17 @@ import ( var log = logging.Logger("fraud-module") -func Module(tp node.Type) fx.Option { +func ConstructModule(tp node.Type) fx.Option { switch tp { case node.Light: return fx.Module( "fraud", - fx.Provide(ServiceWithSyncer), + fx.Provide(ModuleWithSyncer), ) case node.Full, node.Bridge: return fx.Module( "fraud", - fx.Provide(Service), + fx.Provide(NewModule), ) default: panic("invalid node type") diff --git a/nodebuilder/fraud/service.go b/nodebuilder/fraud/service.go new file mode 100644 index 0000000000..be28981d36 --- /dev/null +++ b/nodebuilder/fraud/service.go @@ -0,0 +1,9 @@ +package fraud + +import "github.com/celestiaorg/celestia-node/fraud" + +// Module encompasses the behavior necessary to subscribe and broadcast +// fraud proofs within the network. +type Module interface { + fraud.Service +} diff --git a/nodebuilder/header/module.go b/nodebuilder/header/module.go index 75a416b890..c726ef0aca 100644 --- a/nodebuilder/header/module.go +++ b/nodebuilder/header/module.go @@ -12,15 +12,14 @@ import ( "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/libs/fxutil" - fraudbuilder "github.com/celestiaorg/celestia-node/nodebuilder/fraud" + fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/params" - headerservice "github.com/celestiaorg/celestia-node/service/header" ) var log = logging.Logger("header-module") -func Module(tp node.Type, cfg *Config) fx.Option { +func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() @@ -28,7 +27,7 @@ func Module(tp node.Type, cfg *Config) fx.Option { fx.Supply(*cfg), fx.Error(cfgErr), fx.Supply(params.BlockTime), - fx.Provide(headerservice.NewHeaderService), + fx.Provide(NewHeaderService), fx.Provide(fx.Annotate( store.NewStore, fx.OnStart(func(ctx context.Context, store header.Store) error { @@ -47,7 +46,7 @@ func Module(tp node.Type, cfg *Config) fx.Option { }), fx.Provide(fx.Annotate( sync.NewSyncer, - fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraud.Service, syncer *sync.Syncer) error { + fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, syncer *sync.Syncer) error { syncerStartFunc := func(ctx context.Context) error { err := syncer.Start(ctx) switch err { @@ -60,7 +59,7 @@ func Module(tp node.Type, cfg *Config) fx.Option { return nil } lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudbuilder.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, + return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, syncerStartFunc, syncer.Stop) }), fx.OnStop(func(ctx context.Context, syncer *sync.Syncer) error { diff --git a/nodebuilder/header/service.go b/nodebuilder/header/service.go new file mode 100644 index 0000000000..e7e31ed998 --- /dev/null +++ b/nodebuilder/header/service.go @@ -0,0 +1,59 @@ +package header + +import ( + "context" + + "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/header/p2p" + "github.com/celestiaorg/celestia-node/header/sync" +) + +type Module interface { + // GetByHeight returns the ExtendedHeader at the given height, blocking + // until header has been processed by the store or context deadline is exceeded. + GetByHeight(context.Context, uint64) (*header.ExtendedHeader, error) + // Head returns the ExtendedHeader of the chain head. + Head(context.Context) (*header.ExtendedHeader, error) + // IsSyncing returns the status of sync + IsSyncing() bool +} + +// service represents the header service that can be started / stopped on a node. +// service's main function is to manage its sub-services. service can contain several +// sub-services, such as Exchange, ExchangeServer, Syncer, and so forth. +type service struct { + ex header.Exchange + + syncer *sync.Syncer + sub header.Subscriber + p2pServer *p2p.ExchangeServer + store header.Store +} + +// NewHeaderService creates a new instance of header service. +func NewHeaderService( + syncer *sync.Syncer, + sub header.Subscriber, + p2pServer *p2p.ExchangeServer, + ex header.Exchange, + store header.Store) Module { + return &service{ + syncer: syncer, + sub: sub, + p2pServer: p2pServer, + ex: ex, + store: store, + } +} + +func (s *service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { + return s.store.GetByHeight(ctx, height) +} + +func (s *service) Head(ctx context.Context) (*header.ExtendedHeader, error) { + return s.store.Head(ctx) +} + +func (s *service) IsSyncing() bool { + return !s.syncer.State().Finished() +} diff --git a/nodebuilder/module.go b/nodebuilder/module.go index 3aa785dc85..3dd2efcb17 100644 --- a/nodebuilder/module.go +++ b/nodebuilder/module.go @@ -17,7 +17,7 @@ import ( "github.com/celestiaorg/celestia-node/params" ) -func Module(tp node.Type, cfg *Config, store Store) fx.Option { +func ConstructModule(tp node.Type, cfg *Config, store Store) fx.Option { baseComponents := fx.Options( fx.Provide(params.DefaultNetwork), fx.Provide(params.BootstrappersFor), @@ -28,14 +28,14 @@ func Module(tp node.Type, cfg *Config, store Store) fx.Option { fx.Provide(store.Keystore), fx.Invoke(invokeWatchdog(store.Path())), // modules provided by the node - p2p.Module(tp, &cfg.P2P), - state.Module(tp, &cfg.State), - header.Module(tp, &cfg.Header), - share.Module(tp, &cfg.Share), - rpc.Module(tp, &cfg.RPC), - core.Module(tp, &cfg.Core), - daser.Module(tp), - fraud.Module(tp), + p2p.ConstructModule(tp, &cfg.P2P), + state.ConstructModule(tp, &cfg.State), + header.ConstructModule(tp, &cfg.Header), + share.ConstructModule(tp, &cfg.Share), + rpc.ConstructModule(tp, &cfg.RPC), + core.ConstructModule(tp, &cfg.Core), + daser.ConstructModule(tp), + fraud.ConstructModule(tp), ) return fx.Module( diff --git a/nodebuilder/node.go b/nodebuilder/node.go index 13d667a38c..fc3ea6e01e 100644 --- a/nodebuilder/node.go +++ b/nodebuilder/node.go @@ -6,6 +6,8 @@ import ( "strings" "time" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/ipfs/go-blockservice" exchange "github.com/ipfs/go-ipfs-exchange-interface" logging "github.com/ipfs/go-log/v2" @@ -17,13 +19,12 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" "github.com/celestiaorg/celestia-node/params" - "github.com/celestiaorg/celestia-node/service/header" "github.com/celestiaorg/celestia-node/service/rpc" - "github.com/celestiaorg/celestia-node/service/share" - "github.com/celestiaorg/celestia-node/service/state" ) const Timeout = time.Second * 15 @@ -55,11 +56,11 @@ type Node struct { // p2p protocols PubSub *pubsub.PubSub // services - ShareServ *share.Service // not optional - HeaderServ *header.Service // not optional - StateServ *state.Service // not optional - FraudServ fraud.Service // not optional - DASer *das.DASer `optional:"true"` + ShareServ share.Module // not optional + HeaderServ header.Module // not optional + StateServ state.Module // not optional + FraudServ fraud.Module // not optional + DASer *das.DASer `optional:"true"` // start and stop control ref internal fx.App lifecycle funcs to be called from Start and Stop start, stop lifecycleFunc @@ -77,7 +78,7 @@ func New(tp node.Type, store Store, options ...fx.Option) (*Node, error) { // NewWithConfig assembles a new Node with the given type 'tp' over Store 'store' and a custom config. func NewWithConfig(tp node.Type, store Store, cfg *Config, options ...fx.Option) (*Node, error) { - opts := append([]fx.Option{Module(tp, cfg, store)}, options...) + opts := append([]fx.Option{ConstructModule(tp, cfg, store)}, options...) return newNode(opts...) } diff --git a/nodebuilder/p2p/module.go b/nodebuilder/p2p/module.go index bffbd43bd1..a84bdfded2 100644 --- a/nodebuilder/p2p/module.go +++ b/nodebuilder/p2p/module.go @@ -6,8 +6,8 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/node" ) -// Module collects all the components and services related to p2p. -func Module(tp node.Type, cfg *Config) fx.Option { +// ConstructModule collects all the components and services related to p2p. +func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() diff --git a/nodebuilder/rpc/module.go b/nodebuilder/rpc/module.go index 62fecfb7db..551d3879f2 100644 --- a/nodebuilder/rpc/module.go +++ b/nodebuilder/rpc/module.go @@ -5,14 +5,14 @@ import ( "go.uber.org/fx" + headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" - headerServ "github.com/celestiaorg/celestia-node/service/header" + shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" + stateServ "github.com/celestiaorg/celestia-node/nodebuilder/state" rpcServ "github.com/celestiaorg/celestia-node/service/rpc" - shareServ "github.com/celestiaorg/celestia-node/service/share" - stateServ "github.com/celestiaorg/celestia-node/service/state" ) -func Module(tp node.Type, cfg *rpcServ.Config) fx.Option { +func ConstructModule(tp node.Type, cfg *rpcServ.Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() @@ -42,9 +42,9 @@ func Module(tp node.Type, cfg *rpcServ.Config) fx.Option { "rpc", baseComponents, fx.Invoke(func( - state *stateServ.Service, - share *shareServ.Service, - header *headerServ.Service, + state stateServ.Module, + share shareServ.Module, + header headerServ.Module, rpcSrv *rpcServ.Server, ) { Handler(state, share, header, rpcSrv, nil) diff --git a/nodebuilder/rpc/rpc.go b/nodebuilder/rpc/rpc.go index 6af85b17a8..55b4ac0724 100644 --- a/nodebuilder/rpc/rpc.go +++ b/nodebuilder/rpc/rpc.go @@ -2,17 +2,17 @@ package rpc import ( "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/service/header" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" "github.com/celestiaorg/celestia-node/service/rpc" - "github.com/celestiaorg/celestia-node/service/share" - "github.com/celestiaorg/celestia-node/service/state" ) // Handler constructs a new RPC Handler from the given services. func Handler( - state *state.Service, - share *share.Service, - header *header.Service, + state state.Module, + share share.Module, + header header.Module, serv *rpc.Server, daser *das.DASer, ) { diff --git a/nodebuilder/rpc_test.go b/nodebuilder/rpc_test.go index 1e03385f2d..beb1139967 100644 --- a/nodebuilder/rpc_test.go +++ b/nodebuilder/rpc_test.go @@ -9,19 +9,25 @@ import ( "strconv" "testing" + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tmbytes "github.com/tendermint/tendermint/libs/bytes" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/das" + das "github.com/celestiaorg/celestia-node/das" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/header/local" "github.com/celestiaorg/celestia-node/header/store" "github.com/celestiaorg/celestia-node/header/sync" + headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" "github.com/celestiaorg/celestia-node/nodebuilder/node" + shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" "github.com/celestiaorg/celestia-node/params" - service "github.com/celestiaorg/celestia-node/service/header" "github.com/celestiaorg/celestia-node/service/rpc" + "github.com/celestiaorg/celestia-node/share" ) // NOTE: The following tests are against common RPC endpoints provided by @@ -189,18 +195,35 @@ func TestDASStateRequest(t *testing.T) { dasStateResp := new(das.SamplingStats) err = json.NewDecoder(resp.Body).Decode(dasStateResp) require.NoError(t, err) - // ensure daser is running - assert.True(t, dasStateResp.IsRunning) + // ensure daser has run (or is still running) + assert.True(t, dasStateResp.SampledChainHead == 10 || dasStateResp.IsRunning) } func setupNodeWithModifiedRPC(t *testing.T) *Node { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - // create test node with a dummy header service, manually add a dummy header + // create test node with a dummy header headerServ, manually add a dummy header + daser := setupDASer(t) hServ := setupHeaderService(ctx, t) // create overrides overrideHeaderServ := fx.Replace(hServ) - nd := TestNode(t, node.Full, overrideHeaderServ) + // fx.Decorate(func() *das.DASer {return daser}) == fx.Replace(daser) + overrideDASer := fx.Decorate(fx.Annotate( + func() *das.DASer { + return daser + }, + fx.OnStart(func(ctx context.Context) error { + return daser.Start(ctx) + }), + fx.OnStop(func(ctx context.Context) error { + return daser.Stop(ctx) + }), + )) + overrideRPCHandler := fx.Invoke(func(srv *rpc.Server, state state.Module, share shareServ.Module) { + handler := rpc.NewHandler(state, share, hServ, daser) + handler.RegisterEndpoints(srv) + }) + nd := TestNode(t, node.Full, overrideHeaderServ, overrideDASer, overrideRPCHandler) // start node err := nd.Start(ctx) require.NoError(t, err) @@ -211,7 +234,7 @@ func setupNodeWithModifiedRPC(t *testing.T) *Node { return nd } -func setupHeaderService(ctx context.Context, t *testing.T) *service.Service { +func setupHeaderService(ctx context.Context, t *testing.T) headerServ.Module { suite := header.NewTestSuite(t, 1) head := suite.Head() // create header stores @@ -222,5 +245,53 @@ func setupHeaderService(ctx context.Context, t *testing.T) *service.Service { // create syncer syncer := sync.NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, params.BlockTime) - return service.NewHeaderService(syncer, nil, nil, nil, localStore) + return headerServ.NewHeaderService(syncer, nil, nil, nil, localStore) +} + +func setupDASer(t *testing.T) *das.DASer { + suite := header.NewTestSuite(t, 1) + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + // store has 10 headers + mockGet := &mockGetter{ + headers: make(map[int64]*header.ExtendedHeader), + doneCh: make(chan struct{}), + brokenHeightCh: make(chan struct{}), + } + for _, h := range suite.GenExtendedHeaders(10) { + mockGet.headers[h.Height] = h + mockGet.head = h.Height + } + // subscription has 10 "new" headers + sub := &header.DummySubscriber{Headers: suite.GenExtendedHeaders(10)} + return das.NewDASer(share.NewTestSuccessfulAvailability(), sub, mockGet, ds, nil) +} + +// taken from daser_test.go +type mockGetter struct { + doneCh chan struct{} // signals all stored headers have been retrieved + + brokenHeightCh chan struct{} + + head int64 + headers map[int64]*header.ExtendedHeader +} + +func (m *mockGetter) Head(context.Context) (*header.ExtendedHeader, error) { + return m.headers[m.head], nil +} + +func (m *mockGetter) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { + h, ok := m.headers[int64(height)] + if !ok { + return nil, header.ErrNotFound + } + return h, nil +} + +func (m *mockGetter) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { + panic("implement me") +} + +func (m *mockGetter) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { + panic("implement me") } diff --git a/nodebuilder/share/module.go b/nodebuilder/share/module.go index 5eb8327219..129f7168cd 100644 --- a/nodebuilder/share/module.go +++ b/nodebuilder/share/module.go @@ -6,10 +6,10 @@ import ( "go.uber.org/fx" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" ) -func Module(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { +func ConstructModule(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() @@ -19,15 +19,7 @@ func Module(tp node.Type, cfg *Config, options ...fx.Option) fx.Option { fx.Options(options...), fx.Invoke(share.EnsureEmptySquareExists), fx.Provide(Discovery(*cfg)), - fx.Provide(fx.Annotate( - share.NewService, - fx.OnStart(func(ctx context.Context, service *share.Service) error { - return service.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, service *share.Service) error { - return service.Stop(ctx) - }), - )), + fx.Provide(NewModule), ) switch tp { diff --git a/nodebuilder/share/service.go b/nodebuilder/share/service.go new file mode 100644 index 0000000000..4e2be8f26b --- /dev/null +++ b/nodebuilder/share/service.go @@ -0,0 +1,43 @@ +package share + +import ( + "context" + + "github.com/ipfs/go-blockservice" + "go.uber.org/fx" + + "github.com/celestiaorg/celestia-node/share" + "github.com/celestiaorg/nmt/namespace" +) + +// Module provides access to any data square or block share on the network. +// +// All Get methods provided on Module follow the following flow: +// 1. Check local storage for the requested Share. +// 2. If exists +// * Load from disk +// * Return +// 3. If not +// * Find provider on the network +// * Fetch the Share from the provider +// * Store the Share +// * Return +type Module interface { + share.Availability + GetShare(ctx context.Context, dah *share.Root, row, col int) (share.Share, error) + GetShares(ctx context.Context, root *share.Root) ([][]share.Share, error) + GetSharesByNamespace(ctx context.Context, root *share.Root, namespace namespace.ID) ([]share.Share, error) +} + +func NewModule(lc fx.Lifecycle, bServ blockservice.BlockService, avail share.Availability) Module { + service := share.NewService(bServ, avail) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return service.Start(ctx) + }, + OnStop: func(ctx context.Context) error { + return service.Stop(ctx) + }, + }) + return service +} diff --git a/nodebuilder/share/share.go b/nodebuilder/share/share.go index f343f6fe3d..d6ad66898e 100644 --- a/nodebuilder/share/share.go +++ b/nodebuilder/share/share.go @@ -7,7 +7,7 @@ import ( routingdisc "github.com/libp2p/go-libp2p/p2p/discovery/routing" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" ) func Discovery(cfg Config) func(routing.ContentRouting, host.Host) *share.Discovery { diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index e5c836cb3d..9de9dc32c3 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -1,15 +1,38 @@ package state import ( + "context" + + "go.uber.org/fx" + apptypes "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fxutil" + fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/core" - "github.com/celestiaorg/celestia-node/service/state" + "github.com/celestiaorg/celestia-node/state" ) -// CoreAccessor constructs a new instance of state.Accessor over +// CoreAccessor constructs a new instance of state.Module over // a celestia-core connection. -func CoreAccessor(corecfg core.Config, signer *apptypes.KeyringSigner, sync *sync.Syncer) state.Accessor { - return state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) +func CoreAccessor( + lc fx.Lifecycle, + fService fraudServ.Module, + corecfg core.Config, + signer *apptypes.KeyringSigner, + sync *sync.Syncer, +) Module { + service := state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + lifecycleCtx := fxutil.WithLifecycle(ctx, lc) + return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fService, service.Start, service.Stop) + }, + OnStop: func(ctx context.Context) error { + return service.Stop(ctx) + }, + }) + return service } diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index f444ba28b6..b0d020776c 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -1,23 +1,17 @@ package state import ( - "context" - logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/libs/fxutil" - fraudbuilder "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" - "github.com/celestiaorg/celestia-node/service/state" ) var log = logging.Logger("state-module") -// Module provides all components necessary to construct the +// ConstructModule provides all components necessary to construct the // state service. -func Module(tp node.Type, cfg *Config) fx.Option { +func ConstructModule(tp node.Type, cfg *Config) fx.Option { // sanitize config values before constructing module cfgErr := cfg.Validate() @@ -25,24 +19,7 @@ func Module(tp node.Type, cfg *Config) fx.Option { fx.Supply(*cfg), fx.Error(cfgErr), fx.Provide(Keyring), - fx.Provide(fx.Annotate(CoreAccessor, - fx.OnStart(func(ctx context.Context, accessor state.Accessor) error { - return accessor.Start(ctx) - }), - fx.OnStop(func(ctx context.Context, accessor state.Accessor) error { - return accessor.Stop(ctx) - }), - )), - fx.Provide(fx.Annotate(state.NewService, - fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraud.Service, serv *state.Service) error { - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudbuilder.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, - serv.Start, serv.Stop) - }), - fx.OnStop(func(ctx context.Context, serv *state.Service) error { - return serv.Stop(ctx) - }), - )), + fx.Provide(CoreAccessor), ) switch tp { diff --git a/service/state/interface.go b/nodebuilder/state/service.go similarity index 57% rename from service/state/interface.go rename to nodebuilder/state/service.go index 023c76d86e..5da7b872a1 100644 --- a/service/state/interface.go +++ b/nodebuilder/state/service.go @@ -6,57 +6,67 @@ import ( "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/celestiaorg/celestia-node/state" + "github.com/celestiaorg/nmt/namespace" ) -// Accessor represents the behaviors necessary for a user to +// Module represents the behaviors necessary for a user to // query for state-related information and submit transactions/ // messages to the Celestia network. -type Accessor interface { - // Start starts the state Accessor. - Start(context.Context) error - // Stop stops the state Accessor. - Stop(context.Context) error +type Module interface { + // IsStopped checks if the Module's context has been stopped + IsStopped() bool // Balance retrieves the Celestia coin balance for the node's account/signer // and verifies it against the corresponding block's AppHash. - Balance(ctx context.Context) (*Balance, error) + Balance(ctx context.Context) (*state.Balance, error) // BalanceForAddress retrieves the Celestia coin balance for the given address and verifies // the returned balance against the corresponding block's AppHash. // // NOTE: the balance returned is the balance reported by the block right before // the node's current head (head-1). This is due to the fact that for block N, the block's // `AppHash` is the result of applying the previous block's transaction list. - BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) + BalanceForAddress(ctx context.Context, addr state.Address) (*state.Balance, error) // Transfer sends the given amount of coins from default wallet of the node to the given account address. - Transfer(ctx context.Context, to AccAddress, amount math.Int, gasLimit uint64) (*TxResponse, error) + Transfer(ctx context.Context, to state.AccAddress, amount math.Int, gasLimit uint64) (*state.TxResponse, error) // SubmitTx submits the given transaction/message to the // Celestia network and blocks until the tx is included in // a block. - SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) + SubmitTx(ctx context.Context, tx state.Tx) (*state.TxResponse, error) // SubmitPayForData builds, signs and submits a PayForData transaction. - SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*TxResponse, error) + SubmitPayForData(ctx context.Context, nID namespace.ID, data []byte, gasLim uint64) (*state.TxResponse, error) // CancelUnbondingDelegation cancels a user's pending undelegation from a validator. CancelUnbondingDelegation( ctx context.Context, - valAddr ValAddress, + valAddr state.ValAddress, amount, - height Int, + height state.Int, gasLim uint64, - ) (*TxResponse, error) + ) (*state.TxResponse, error) // BeginRedelegate sends a user's delegated tokens to a new validator for redelegation. - BeginRedelegate(ctx context.Context, srcValAddr, dstValAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) + BeginRedelegate( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + amount state.Int, + gasLim uint64, + ) (*state.TxResponse, error) // Undelegate undelegates a user's delegated tokens, unbonding them from the current validator. - Undelegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) + Undelegate(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) (*state.TxResponse, error) // Delegate sends a user's liquid tokens to a validator for delegation. - Delegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) + Delegate(ctx context.Context, delAddr state.ValAddress, amount state.Int, gasLim uint64) (*state.TxResponse, error) // QueryDelegation retrieves the delegation information between a delegator and a validator. - QueryDelegation(ctx context.Context, valAddr ValAddress) (*types.QueryDelegationResponse, error) + QueryDelegation(ctx context.Context, valAddr state.ValAddress) (*types.QueryDelegationResponse, error) // QueryUnbonding retrieves the unbonding status between a delegator and a validator. - QueryUnbonding(ctx context.Context, valAddr ValAddress) (*types.QueryUnbondingDelegationResponse, error) + QueryUnbonding(ctx context.Context, valAddr state.ValAddress) (*types.QueryUnbondingDelegationResponse, error) // QueryRedelegations retrieves the status of the redelegations between a delegator and a validator. - QueryRedelegations(ctx context.Context, srcValAddr, dstValAddr ValAddress) (*types.QueryRedelegationsResponse, error) + QueryRedelegations( + ctx context.Context, + srcValAddr, + dstValAddr state.ValAddress, + ) (*types.QueryRedelegationsResponse, error) } diff --git a/nodebuilder/tests/reconstruct_test.go b/nodebuilder/tests/reconstruct_test.go index 499bc384ae..7f5c88a0ce 100644 --- a/nodebuilder/tests/reconstruct_test.go +++ b/nodebuilder/tests/reconstruct_test.go @@ -20,7 +20,7 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder" "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" ) /* diff --git a/service/header/service.go b/service/header/service.go deleted file mode 100644 index 6166e51d85..0000000000 --- a/service/header/service.go +++ /dev/null @@ -1,69 +0,0 @@ -package header - -import ( - "context" - - logging "github.com/ipfs/go-log/v2" - - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/p2p" - "github.com/celestiaorg/celestia-node/header/sync" -) - -var log = logging.Logger("service/header") - -// Service represents the header service that can be started / stopped on a node. -// Service's main function is to manage its sub-services. Service can contain several -// sub-services, such as Exchange, ExchangeServer, Syncer, and so forth. -type Service struct { - ex header.Exchange - - syncer *sync.Syncer - sub header.Subscriber - p2pServer *p2p.ExchangeServer - store header.Store -} - -// NewHeaderService creates a new instance of header Service. -func NewHeaderService( - syncer *sync.Syncer, - sub header.Subscriber, - p2pServer *p2p.ExchangeServer, - ex header.Exchange, - store header.Store) *Service { - return &Service{ - syncer: syncer, - sub: sub, - p2pServer: p2pServer, - ex: ex, - store: store, - } -} - -// Start starts the header Service. -func (s *Service) Start(context.Context) error { - log.Info("starting header service") - return nil -} - -// Stop stops the header Service. -func (s *Service) Stop(context.Context) error { - log.Info("stopping header service") - return nil -} - -// GetByHeight returns the ExtendedHeader at the given height, blocking -// until header has been processed by the store or context deadline is exceeded. -func (s *Service) GetByHeight(ctx context.Context, height uint64) (*header.ExtendedHeader, error) { - return s.store.GetByHeight(ctx, height) -} - -// Head returns the ExtendedHeader of the chain head. -func (s *Service) Head(ctx context.Context) (*header.ExtendedHeader, error) { - return s.store.Head(ctx) -} - -// IsSyncing returns the status of sync -func (s *Service) IsSyncing() bool { - return !s.syncer.State().Finished() -} diff --git a/service/rpc/availability.go b/service/rpc/availability.go index 116a61755e..9a9a25088f 100644 --- a/service/rpc/availability.go +++ b/service/rpc/availability.go @@ -7,7 +7,7 @@ import ( "github.com/gorilla/mux" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" ) const heightAvailabilityEndpoint = "/data_available" @@ -38,7 +38,7 @@ func (h *Handler) handleHeightAvailabilityRequest(w http.ResponseWriter, r *http h.share.ProbabilityOfAvailability(), 'g', -1, 64), } - err = h.share.Availability.SharesAvailable(r.Context(), header.DAH) + err = h.share.SharesAvailable(r.Context(), header.DAH) switch err { case nil: availResp.Available = true diff --git a/service/rpc/handler.go b/service/rpc/handler.go index fc3d9406c1..e1013cad1b 100644 --- a/service/rpc/handler.go +++ b/service/rpc/handler.go @@ -4,24 +4,24 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/service/header" - "github.com/celestiaorg/celestia-node/service/share" - "github.com/celestiaorg/celestia-node/service/state" + "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/share" + "github.com/celestiaorg/celestia-node/nodebuilder/state" ) var log = logging.Logger("rpc") type Handler struct { - state *state.Service - share *share.Service - header *header.Service + state state.Module + share share.Module + header header.Module das *das.DASer } func NewHandler( - state *state.Service, - share *share.Service, - header *header.Service, + state state.Module, + share share.Module, + header header.Module, das *das.DASer, ) *Handler { return &Handler{ diff --git a/service/rpc/middleware.go b/service/rpc/middleware.go index ce1189580c..c8981bcc05 100644 --- a/service/rpc/middleware.go +++ b/service/rpc/middleware.go @@ -6,7 +6,7 @@ import ( "github.com/gorilla/mux" - "github.com/celestiaorg/celestia-node/service/state" + "github.com/celestiaorg/celestia-node/nodebuilder/state" ) func (h *Handler) RegisterMiddleware(rpc *Server) { @@ -22,7 +22,7 @@ func setContentType(next http.Handler) http.Handler { } // checkPostDisabled ensures that context was canceled and prohibit POST requests. -func checkPostDisabled(state *state.Service) mux.MiddlewareFunc { +func checkPostDisabled(state state.Module) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // check if state service was halted and deny the transaction diff --git a/service/rpc/share.go b/service/rpc/share.go index 51bf2684c9..3c14b34a84 100644 --- a/service/rpc/share.go +++ b/service/rpc/share.go @@ -11,7 +11,7 @@ import ( "github.com/tendermint/tendermint/types" "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/service/share" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/nmt/namespace" ) diff --git a/service/rpc/state.go b/service/rpc/state.go index ad4d9a47fe..86a9592c3f 100644 --- a/service/rpc/state.go +++ b/service/rpc/state.go @@ -6,10 +6,10 @@ import ( "errors" "net/http" + "github.com/celestiaorg/celestia-node/state" + "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" - - "github.com/celestiaorg/celestia-node/service/state" ) const ( diff --git a/service/state/service.go b/service/state/service.go deleted file mode 100644 index 35e635c665..0000000000 --- a/service/state/service.go +++ /dev/null @@ -1,115 +0,0 @@ -package state - -import ( - "context" - - "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/celestiaorg/nmt/namespace" -) - -// Service can access state-related information via the given -// Accessor. -type Service struct { - ctx context.Context - cancel context.CancelFunc - - accessor Accessor -} - -// NewService constructs a new state Service. -func NewService(accessor Accessor) *Service { - return &Service{ - accessor: accessor, - } -} - -func (s *Service) SubmitPayForData( - ctx context.Context, - nID namespace.ID, - data []byte, - gasLim uint64, -) (*TxResponse, error) { - return s.accessor.SubmitPayForData(ctx, nID, data, gasLim) -} - -func (s *Service) Balance(ctx context.Context) (*Balance, error) { - return s.accessor.Balance(ctx) -} - -func (s *Service) BalanceForAddress(ctx context.Context, addr Address) (*Balance, error) { - return s.accessor.BalanceForAddress(ctx, addr) -} - -func (s *Service) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error) { - return s.accessor.SubmitTx(ctx, tx) -} - -func (s *Service) Transfer(ctx context.Context, to AccAddress, amount Int, gasLimit uint64) (*TxResponse, error) { - return s.accessor.Transfer(ctx, to, amount, gasLimit) -} - -func (s *Service) CancelUnbondingDelegation( - ctx context.Context, - valAddr ValAddress, - amount, - height Int, - gasLim uint64, -) (*TxResponse, error) { - return s.accessor.CancelUnbondingDelegation(ctx, valAddr, amount, height, gasLim) -} - -func (s *Service) BeginRedelegate( - ctx context.Context, - srcValAddr, - dstValAddr ValAddress, - amount Int, - gasLim uint64, -) (*TxResponse, error) { - return s.accessor.BeginRedelegate(ctx, srcValAddr, dstValAddr, amount, gasLim) -} - -func (s *Service) Undelegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) { - return s.accessor.Undelegate(ctx, delAddr, amount, gasLim) -} - -func (s *Service) Delegate(ctx context.Context, delAddr ValAddress, amount Int, gasLim uint64) (*TxResponse, error) { - return s.accessor.Delegate(ctx, delAddr, amount, gasLim) -} - -func (s *Service) QueryDelegation( - ctx context.Context, - valAddr ValAddress, -) (*types.QueryDelegationResponse, error) { - return s.accessor.QueryDelegation(ctx, valAddr) -} - -func (s *Service) QueryUnbonding( - ctx context.Context, - valAddr ValAddress, -) (*types.QueryUnbondingDelegationResponse, error) { - return s.accessor.QueryUnbonding(ctx, valAddr) -} - -func (s *Service) QueryRedelegations( - ctx context.Context, - srcValAddr, - dstValAddr ValAddress, -) (*types.QueryRedelegationsResponse, error) { - return s.accessor.QueryRedelegations(ctx, srcValAddr, dstValAddr) -} - -func (s *Service) Start(context.Context) error { - s.ctx, s.cancel = context.WithCancel(context.Background()) - return nil -} - -func (s *Service) Stop(context.Context) error { - s.cancel() - return nil -} - -// IsStopped checks if context was canceled. -func (s *Service) IsStopped() bool { - return s.ctx.Err() != nil -} diff --git a/service/share/backoff.go b/share/backoff.go similarity index 100% rename from service/share/backoff.go rename to share/backoff.go diff --git a/service/share/backoff_test.go b/share/backoff_test.go similarity index 100% rename from service/share/backoff_test.go rename to share/backoff_test.go diff --git a/service/share/cache_availability.go b/share/cache_availability.go similarity index 100% rename from service/share/cache_availability.go rename to share/cache_availability.go diff --git a/service/share/cache_availability_test.go b/share/cache_availability_test.go similarity index 95% rename from service/share/cache_availability_test.go rename to share/cache_availability_test.go index d2519ff841..bdc7eeb5d9 100644 --- a/service/share/cache_availability_test.go +++ b/share/cache_availability_test.go @@ -21,8 +21,8 @@ func TestCacheAvailability(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fullLocalServ, dah0 := RandFullLocalServiceWithSquare(t, 16) - lightLocalServ, dah1 := RandLightLocalServiceWithSquare(t, 16) + fullLocalServ, dah0 := randFullLocalServiceWithSquare(t, 16) + lightLocalServ, dah1 := randLightLocalServiceWithSquare(t, 16) var tests = []struct { service *Service @@ -106,7 +106,7 @@ func TestCacheAvailability_MinRoot(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fullLocalServ, _ := RandFullLocalServiceWithSquare(t, 16) + fullLocalServ, _ := randFullLocalServiceWithSquare(t, 16) minDAH := da.MinDataAvailabilityHeader() err := fullLocalServ.SharesAvailable(ctx, &minDAH) diff --git a/service/share/discovery.go b/share/discovery.go similarity index 100% rename from service/share/discovery.go rename to share/discovery.go diff --git a/service/share/doc.go b/share/doc.go similarity index 100% rename from service/share/doc.go rename to share/doc.go diff --git a/service/share/empty.go b/share/empty.go similarity index 100% rename from service/share/empty.go rename to share/empty.go diff --git a/service/share/full_availability.go b/share/full_availability.go similarity index 100% rename from service/share/full_availability.go rename to share/full_availability.go diff --git a/service/share/full_availability_test.go b/share/full_availability_test.go similarity index 86% rename from service/share/full_availability_test.go rename to share/full_availability_test.go index 8f19915220..311e0e90b4 100644 --- a/service/share/full_availability_test.go +++ b/share/full_availability_test.go @@ -18,8 +18,8 @@ func TestSharesAvailable_Full(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // RandFullServiceWithSquare creates a NewFullAvailability inside, so we can test it - service, dah := RandFullServiceWithSquare(t, 16) + // randFullServiceWithSquare creates a NewFullAvailability inside, so we can test it + service, dah := randFullServiceWithSquare(t, 16) err := service.SharesAvailable(ctx, dah) assert.NoError(t, err) } diff --git a/service/share/full_reconstruction_test.go b/share/full_reconstruction_test.go similarity index 100% rename from service/share/full_reconstruction_test.go rename to share/full_reconstruction_test.go diff --git a/service/share/interface.go b/share/interface.go similarity index 100% rename from service/share/interface.go rename to share/interface.go diff --git a/service/share/light_availability.go b/share/light_availability.go similarity index 100% rename from service/share/light_availability.go rename to share/light_availability.go diff --git a/service/share/light_availability_test.go b/share/light_availability_test.go similarity index 79% rename from service/share/light_availability_test.go rename to share/light_availability_test.go index 33e4051f0e..963cf24258 100644 --- a/service/share/light_availability_test.go +++ b/share/light_availability_test.go @@ -13,8 +13,8 @@ func TestSharesAvailable(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // RandLightServiceWithSquare creates a NewLightAvailability inside, so we can test it - service, dah := RandLightServiceWithSquare(t, 16) + // randLightServiceWithSquare creates a NewLightAvailability inside, so we can test it + service, dah := randLightServiceWithSquare(t, 16) err := service.SharesAvailable(ctx, dah) assert.NoError(t, err) } @@ -23,8 +23,8 @@ func TestSharesAvailableFailed(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // RandLightServiceWithSquare creates a NewLightAvailability inside, so we can test it - s, _ := RandLightServiceWithSquare(t, 16) + // randLightServiceWithSquare creates a NewLightAvailability inside, so we can test it + s, _ := randLightServiceWithSquare(t, 16) empty := header.EmptyDAH() err := s.SharesAvailable(ctx, &empty) assert.Error(t, err) diff --git a/service/share/sample.go b/share/sample.go similarity index 100% rename from service/share/sample.go rename to share/sample.go diff --git a/service/share/sample_test.go b/share/sample_test.go similarity index 100% rename from service/share/sample_test.go rename to share/sample_test.go diff --git a/service/share/set.go b/share/set.go similarity index 100% rename from service/share/set.go rename to share/set.go diff --git a/service/share/set_test.go b/share/set_test.go similarity index 100% rename from service/share/set_test.go rename to share/set_test.go diff --git a/service/share/share.go b/share/share.go similarity index 89% rename from service/share/share.go rename to share/share.go index fc2433733e..922fbe73fd 100644 --- a/service/share/share.go +++ b/share/share.go @@ -34,19 +34,6 @@ var GetData = ipld.ShareData // In practice, it is a commitment to all the Data in a square. type Root = da.DataAvailabilityHeader -// Service provides access to any data square or block share on the network. -// -// All Get methods provided on Service follow the following flow: -// 1. Check local storage for the requested Share. -// 2. If exists -// * Load from disk -// * Return -// 3. If not -// * Find provider on the network -// * Fetch the Share from the provider -// * Store the Share -// * Return -// // TODO(@Wondertan): Simple thread safety for Start and Stop would not hurt. type Service struct { Availability @@ -58,7 +45,7 @@ type Service struct { cancel context.CancelFunc } -// NewService creates new basic share.Service. +// NewService creates a new basic share.Module. func NewService(bServ blockservice.BlockService, avail Availability) *Service { return &Service{ rtrv: ipld.NewRetriever(bServ), @@ -69,7 +56,7 @@ func NewService(bServ blockservice.BlockService, avail Availability) *Service { func (s *Service) Start(context.Context) error { if s.session != nil || s.cancel != nil { - return fmt.Errorf("share: Service already started") + return fmt.Errorf("share: service already started") } // NOTE: The ctx given as param is used to control Start flow and only needed when Start is blocking, @@ -84,7 +71,7 @@ func (s *Service) Start(context.Context) error { func (s *Service) Stop(context.Context) error { if s.session == nil || s.cancel == nil { - return fmt.Errorf("share: Service already stopped") + return fmt.Errorf("share: service already stopped") } s.cancel() diff --git a/service/share/share_test.go b/share/share_test.go similarity index 96% rename from service/share/share_test.go rename to share/share_test.go index 3cf47e5fff..1db384c1d9 100644 --- a/service/share/share_test.go +++ b/share/share_test.go @@ -24,7 +24,7 @@ func TestGetShare(t *testing.T) { defer cancel() n := 16 - serv, dah := RandLightServiceWithSquare(t, n) + serv, dah := randLightServiceWithSquare(t, n) err := serv.Start(ctx) require.NoError(t, err) @@ -52,7 +52,7 @@ func TestService_GetSharesByNamespace(t *testing.T) { for _, tt := range tests { t.Run("size: "+strconv.Itoa(tt.squareSize), func(t *testing.T) { - serv, bServ := RandLightService() + serv, bServ := randLightService() n := tt.squareSize * tt.squareSize randShares := RandShares(t, n) idx1 := (n - 1) / 2 @@ -84,7 +84,7 @@ func TestGetShares(t *testing.T) { defer cancel() n := 16 - serv, dah := RandLightServiceWithSquare(t, n) + serv, dah := randLightServiceWithSquare(t, n) err := serv.Start(ctx) require.NoError(t, err) @@ -109,7 +109,7 @@ func TestGetShares(t *testing.T) { } func TestService_GetSharesByNamespaceNotFound(t *testing.T) { - serv, root := RandLightServiceWithSquare(t, 1) + serv, root := randLightServiceWithSquare(t, 1) root.RowsRoots = nil shares, err := serv.GetSharesByNamespace(context.Background(), root, []byte{1, 1, 1, 1, 1, 1, 1, 1}) @@ -129,7 +129,7 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { for _, tt := range tests { b.Run(strconv.Itoa(tt.amountShares), func(b *testing.B) { t := &testing.T{} - serv, root := RandLightServiceWithSquare(t, tt.amountShares) + serv, root := randLightServiceWithSquare(t, tt.amountShares) randNID := root.RowsRoots[(len(root.RowsRoots)-1)/2][:8] root.RowsRoots[(len(root.RowsRoots) / 2)] = root.RowsRoots[(len(root.RowsRoots)-1)/2] b.ResetTimer() @@ -142,7 +142,7 @@ func BenchmarkService_GetSharesByNamespace(b *testing.B) { } func TestSharesRoundTrip(t *testing.T) { - serv, store := RandLightService() + serv, store := randLightService() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/service/share/testdata/block-825320.json b/share/testdata/block-825320.json similarity index 100% rename from service/share/testdata/block-825320.json rename to share/testdata/block-825320.json diff --git a/service/share/testing.go b/share/testing.go similarity index 92% rename from service/share/testing.go rename to share/testing.go index 49d21a6107..f375573a56 100644 --- a/service/share/testing.go +++ b/share/testing.go @@ -26,30 +26,30 @@ import ( "github.com/celestiaorg/celestia-node/ipld" ) -// RandLightServiceWithSquare provides a share.Service filled with 'n' NMT +// randLightServiceWithSquare provides a share.Service filled with 'n' NMT // trees of 'n' random shares, essentially storing a whole square. -func RandLightServiceWithSquare(t *testing.T, n int) (*Service, *Root) { +func randLightServiceWithSquare(t *testing.T, n int) (*Service, *Root) { bServ := mdutils.Bserv() return NewService(bServ, TestLightAvailability(bServ)), RandFillBS(t, n, bServ) } -// RandLightService provides an unfilled share.Service with corresponding +// randLightService provides an unfilled share.Service with corresponding // blockservice.BlockService than can be filled by the test. -func RandLightService() (*Service, blockservice.BlockService) { +func randLightService() (*Service, blockservice.BlockService) { bServ := mdutils.Bserv() return NewService(bServ, TestLightAvailability(bServ)), bServ } -// RandFullServiceWithSquare provides a share.Service filled with 'n' NMT +// randFullServiceWithSquare provides a share.Service filled with 'n' NMT // trees of 'n' random shares, essentially storing a whole square. -func RandFullServiceWithSquare(t *testing.T, n int) (*Service, *Root) { +func randFullServiceWithSquare(t *testing.T, n int) (*Service, *Root) { bServ := mdutils.Bserv() return NewService(bServ, TestFullAvailability(bServ)), RandFillBS(t, n, bServ) } -// RandLightLocalServiceWithSquare is the same as RandLightServiceWithSquare, except +// randLightLocalServiceWithSquare is the same as randLightServiceWithSquare, except // the Availability is wrapped with CacheAvailability. -func RandLightLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { +func randLightLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { bServ := mdutils.Bserv() ds := dssync.MutexWrap(ds.NewMapDatastore()) ca := NewCacheAvailability( @@ -59,9 +59,9 @@ func RandLightLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { return NewService(bServ, ca), RandFillBS(t, n, bServ) } -// RandFullLocalServiceWithSquare is the same as RandFullServiceWithSquare, except +// randFullLocalServiceWithSquare is the same as randFullServiceWithSquare, except // the Availability is wrapped with CacheAvailability. -func RandFullLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { +func randFullLocalServiceWithSquare(t *testing.T, n int) (*Service, *Root) { bServ := mdutils.Bserv() ds := dssync.MutexWrap(ds.NewMapDatastore()) ca := NewCacheAvailability( diff --git a/service/state/core_access.go b/state/core_access.go similarity index 96% rename from service/state/core_access.go rename to state/core_access.go index 24228674e0..4c7b4b1798 100644 --- a/service/state/core_access.go +++ b/state/core_access.go @@ -26,9 +26,12 @@ import ( var log = logging.Logger("state") -// CoreAccessor implements Accessor over a gRPC connection +// CoreAccessor implements service over a gRPC connection // with a celestia-core node. type CoreAccessor struct { + ctx context.Context + cancel context.CancelFunc + signer *apptypes.KeyringSigner getter header.Head @@ -43,7 +46,7 @@ type CoreAccessor struct { } // NewCoreAccessor dials the given celestia-core endpoint and -// constructs and returns a new CoreAccessor with the active +// constructs and returns a new CoreAccessor (state service) with the active // connection. func NewCoreAccessor( signer *apptypes.KeyringSigner, @@ -62,6 +65,7 @@ func NewCoreAccessor( } func (ca *CoreAccessor) Start(ctx context.Context) error { + ca.ctx, ca.cancel = context.WithCancel(ctx) if ca.coreConn != nil { return fmt.Errorf("core-access: already connected to core endpoint") } @@ -88,6 +92,7 @@ func (ca *CoreAccessor) Start(ctx context.Context) error { } func (ca *CoreAccessor) Stop(context.Context) error { + defer ca.cancel() if ca.coreConn == nil { return fmt.Errorf("core-access: no connection found to close") } @@ -351,3 +356,7 @@ func (ca *CoreAccessor) QueryRedelegations( DstValidatorAddr: dstValAddr.String(), }) } + +func (ca *CoreAccessor) IsStopped() bool { + return ca.ctx.Err() != nil +} diff --git a/service/state/doc.go b/state/doc.go similarity index 100% rename from service/state/doc.go rename to state/doc.go diff --git a/service/state/helpers.go b/state/helpers.go similarity index 100% rename from service/state/helpers.go rename to state/helpers.go diff --git a/service/state/state.go b/state/state.go similarity index 100% rename from service/state/state.go rename to state/state.go From 72d91f872f956e0d7340252acf1032fb0bd46741 Mon Sep 17 00:00:00 2001 From: Rootul P Date: Mon, 3 Oct 2022 22:55:26 -0400 Subject: [PATCH 0108/1008] fix: README link for header doc This doc was moved in https://github.com/celestiaorg/celestia-node/commit/a0232625250c9128c0d6fa74d27329c113603138 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5e755a1e7..ab707fe3e8 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ celestia start ## Package-specific documentation -- [Header](./service/header/doc.go) +- [Header](./header/doc.go) - [Share](./share/doc.go) - [DAS](./das/doc.go) From 228977ccae494161cd5d73adb93c14ea8e89fc73 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:26:12 +0200 Subject: [PATCH 0109/1008] fix (.github/workflows): Skip over integration tests in unit test action (#1193) Actions for unit test has been running integration tests as well since #997 was merged, fixing here. --- .github/workflows/go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index be9f6a7c9a..7acd9147d4 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -63,7 +63,7 @@ jobs: - name: Test & Coverage run: | go install github.com/ory/go-acc@v0.2.6 - go-acc -o coverage.txt `go list ./... | grep -v node/tests` -- -v + go-acc -o coverage.txt `go list ./... | grep -v nodebuilder/tests` -- -v - uses: codecov/codecov-action@v3.1.1 with: file: ./coverage.txt From b7e4b3d1db7cb6a809a40ff0afb2ec556ed98cc9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 4 Oct 2022 22:06:34 +0200 Subject: [PATCH 0110/1008] feat(metrics): metrics for PFD transactions, sampling, and broadcasts (#1083) --- cmd/flags_misc.go | 32 +------------- header/metrics.go | 4 +- nodebuilder/daser/opts.go | 5 +-- nodebuilder/header/opts.go | 2 +- nodebuilder/settings.go | 87 +++++++++++++++++++++++++++++++++++--- state/core_access.go | 6 +++ state/metrics.go | 35 +++++++++++++++ 7 files changed, 126 insertions(+), 45 deletions(-) create mode 100644 state/metrics.go diff --git a/cmd/flags_misc.go b/cmd/flags_misc.go index c1337c8a43..cf0a40e835 100644 --- a/cmd/flags_misc.go +++ b/cmd/flags_misc.go @@ -6,7 +6,6 @@ import ( "net/http" "net/http/pprof" "strings" - "time" logging "github.com/ipfs/go-log/v2" "github.com/spf13/cobra" @@ -14,10 +13,6 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" - "go.opentelemetry.io/otel/metric/global" - controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" - processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" - selector "go.opentelemetry.io/otel/sdk/metric/selector/simple" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" @@ -203,32 +198,7 @@ func ParseMiscFlags(ctx context.Context, cmd *cobra.Command) (context.Context, e opts = append(opts, otlpmetrichttp.WithInsecure()) } - exp, err := otlpmetrichttp.New(cmd.Context(), opts...) - if err != nil { - return ctx, err - } - - pusher := controller.New( - processor.NewFactory( - selector.NewWithHistogramDistribution(), - exp, - ), - controller.WithExporter(exp), - controller.WithCollectPeriod(2*time.Second), - controller.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", NodeType(ctx).String())), - // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey - )), - ) - - err = pusher.Start(cmd.Context()) - if err != nil { - return ctx, err - } - global.SetMeterProvider(pusher) - - ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(true)) + ctx = WithNodeOptions(ctx, nodebuilder.WithMetrics(opts, NodeType(ctx))) } return ctx, err diff --git a/header/metrics.go b/header/metrics.go index 93407b1147..3284d85867 100644 --- a/header/metrics.go +++ b/header/metrics.go @@ -11,8 +11,8 @@ import ( var meter = global.MeterProvider().Meter("header") -// MonitorHead enables Otel metrics to monitor head. -func MonitorHead(store Store) { +// WithMetrics enables Otel metrics to monitor head. +func WithMetrics(store Store) { headC, _ := meter.AsyncInt64().Counter( "head", instrument.WithUnit(unit.Dimensionless), diff --git a/nodebuilder/daser/opts.go b/nodebuilder/daser/opts.go index cb1c8ec142..3117e15035 100644 --- a/nodebuilder/daser/opts.go +++ b/nodebuilder/daser/opts.go @@ -6,10 +6,7 @@ import ( "github.com/celestiaorg/celestia-node/das" ) -func WithMetrics(enable bool) fx.Option { - if !enable { - return fx.Options() - } +func WithMetrics() fx.Option { return fx.Invoke(func(d *das.DASer) error { return d.InitMetrics() }) diff --git a/nodebuilder/header/opts.go b/nodebuilder/header/opts.go index 781e63484a..95524056e8 100644 --- a/nodebuilder/header/opts.go +++ b/nodebuilder/header/opts.go @@ -12,5 +12,5 @@ func WithMetrics(enable bool) fx.Option { if !enable { return fx.Options() } - return fx.Options(fx.Invoke(header.MonitorHead)) + return fx.Options(fx.Invoke(header.WithMetrics)) } diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index fa84578258..6d9b36245a 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -1,12 +1,25 @@ package nodebuilder import ( + "context" + "fmt" + "time" + + "github.com/libp2p/go-libp2p-core/peer" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/metric/global" + controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" + processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" + selector "go.opentelemetry.io/otel/sdk/metric/selector/simple" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/daser" - - headermodule "github.com/celestiaorg/celestia-node/nodebuilder/header" + "github.com/celestiaorg/celestia-node/nodebuilder/node" "github.com/celestiaorg/celestia-node/params" + "github.com/celestiaorg/celestia-node/state" ) // WithNetwork specifies the Network to which the Node should connect to. @@ -21,10 +34,70 @@ func WithBootstrappers(peers params.Bootstrappers) fx.Option { } // WithMetrics enables metrics exporting for the node. -func WithMetrics(enable bool) fx.Option { - return fx.Options( - // TODO: Add metrics to more modules and enable here. - headermodule.WithMetrics(enable), - daser.WithMetrics(enable), +func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Option { + baseComponents := fx.Options( + fx.Supply(metricOpts), + fx.Invoke(initializeMetrics), + fx.Invoke(header.WithMetrics), + fx.Invoke(state.WithMetrics), ) + + var opts fx.Option + switch nodeType { + case node.Full, node.Light: + opts = fx.Options( + baseComponents, + fx.Invoke(daser.WithMetrics), + // add more monitoring here + ) + case node.Bridge: + opts = fx.Options( + baseComponents, + // add more monitoring here + ) + default: + panic("invalid node type") + } + return opts +} + +// initializeMetrics initializes the global meter provider. +func initializeMetrics( + ctx context.Context, + lc fx.Lifecycle, + peerID peer.ID, + nodeType node.Type, + opts []otlpmetrichttp.Option, +) error { + exp, err := otlpmetrichttp.New(ctx, opts...) + if err != nil { + return err + } + + pusher := controller.New( + processor.NewFactory( + selector.NewWithHistogramDistribution(), + exp, + ), + controller.WithExporter(exp), + controller.WithCollectPeriod(2*time.Second), + controller.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(fmt.Sprintf("Celestia-%s", nodeType.String())), + // TODO(@Wondertan): Versioning: semconv.ServiceVersionKey + semconv.ServiceInstanceIDKey.String(peerID.String()), + )), + ) + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return pusher.Start(ctx) + }, + OnStop: func(ctx context.Context) error { + return pusher.Stop(ctx) + }, + }) + + global.SetMeterProvider(pusher) + return nil } diff --git a/state/core_access.go b/state/core_access.go index 4c7b4b1798..af1e624105 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -3,6 +3,7 @@ package state import ( "context" "fmt" + "time" "github.com/cosmos/cosmos-sdk/api/tendermint/abci" "github.com/cosmos/cosmos-sdk/store/rootmulti" @@ -43,6 +44,9 @@ type CoreAccessor struct { coreIP string rpcPort string grpcPort string + + lastPayForData int64 + payForDataCount int64 } // NewCoreAccessor dials the given celestia-core endpoint and @@ -130,6 +134,8 @@ func (ca *CoreAccessor) SubmitPayForData( data []byte, gasLim uint64, ) (*TxResponse, error) { + ca.lastPayForData = time.Now().UnixMilli() + ca.payForDataCount++ return payment.SubmitPayForData(ctx, ca.signer, ca.coreConn, nID, data, gasLim) } diff --git a/state/metrics.go b/state/metrics.go new file mode 100644 index 0000000000..6fecb034cb --- /dev/null +++ b/state/metrics.go @@ -0,0 +1,35 @@ +package state + +import ( + "context" + + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/unit" +) + +var meter = global.MeterProvider().Meter("state") + +func WithMetrics(ca *CoreAccessor) { + pfdCounter, _ := meter.AsyncInt64().Counter( + "pfd_count", + instrument.WithUnit(unit.Dimensionless), + instrument.WithDescription("Total count of submitted PayForData transactions"), + ) + lastPfdTimestamp, _ := meter.AsyncInt64().Counter( + "last_pfd_timestamp", + instrument.WithUnit(unit.Milliseconds), + instrument.WithDescription("Timestamp of the last submitted PayForData transaction"), + ) + + err := meter.RegisterCallback( + []instrument.Asynchronous{pfdCounter, lastPfdTimestamp}, + func(ctx context.Context) { + pfdCounter.Observe(ctx, ca.payForDataCount) + lastPfdTimestamp.Observe(ctx, ca.lastPayForData) + }, + ) + if err != nil { + panic(err) + } +} From 1593ae13008de5e2a9f76b451e8b5683af1c65f9 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 5 Oct 2022 13:09:54 +0200 Subject: [PATCH 0111/1008] nodebuilder: Remove current borked RPC tests in favour of implementing better ones with #962 (#1190) New tests will replace these outdated ones in https://github.com/celestiaorg/celestia-node/issues/962. The goal was to remove these tests from the start. --- nodebuilder/rpc_test.go | 297 ---------------------------------------- 1 file changed, 297 deletions(-) delete mode 100644 nodebuilder/rpc_test.go diff --git a/nodebuilder/rpc_test.go b/nodebuilder/rpc_test.go deleted file mode 100644 index beb1139967..0000000000 --- a/nodebuilder/rpc_test.go +++ /dev/null @@ -1,297 +0,0 @@ -package nodebuilder - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "testing" - - "github.com/ipfs/go-datastore" - ds_sync "github.com/ipfs/go-datastore/sync" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - tmbytes "github.com/tendermint/tendermint/libs/bytes" - "go.uber.org/fx" - - das "github.com/celestiaorg/celestia-node/das" - "github.com/celestiaorg/celestia-node/header" - "github.com/celestiaorg/celestia-node/header/local" - "github.com/celestiaorg/celestia-node/header/store" - "github.com/celestiaorg/celestia-node/header/sync" - headerServ "github.com/celestiaorg/celestia-node/nodebuilder/header" - "github.com/celestiaorg/celestia-node/nodebuilder/node" - shareServ "github.com/celestiaorg/celestia-node/nodebuilder/share" - "github.com/celestiaorg/celestia-node/nodebuilder/state" - "github.com/celestiaorg/celestia-node/params" - "github.com/celestiaorg/celestia-node/service/rpc" - "github.com/celestiaorg/celestia-node/share" -) - -// NOTE: The following tests are against common RPC endpoints provided by -// celestia-node. They will be removed upon refactoring of the RPC -// architecture and Public API. @renaynay @Wondertan. - -const testHeight = uint64(2) - -// TestNamespacedSharesRequest tests the `/namespaced_shares` endpoint. -func TestNamespacedSharesRequest(t *testing.T) { - testGetNamespacedRequest(t, "namespaced_shares", func(t *testing.T, resp *http.Response) { - t.Helper() - namespacedShares := new(rpc.NamespacedSharesResponse) - err := json.NewDecoder(resp.Body).Decode(namespacedShares) - assert.NoError(t, err) - assert.Equal(t, testHeight, namespacedShares.Height) - }) -} - -// TestNamespacedDataRequest tests the `/namespaced_shares` endpoint. -func TestNamespacedDataRequest(t *testing.T) { - testGetNamespacedRequest(t, "namespaced_data", func(t *testing.T, resp *http.Response) { - t.Helper() - namespacedData := new(rpc.NamespacedDataResponse) - err := json.NewDecoder(resp.Body).Decode(namespacedData) - assert.NoError(t, err) - assert.Equal(t, testHeight, namespacedData.Height) - }) -} - -func testGetNamespacedRequest(t *testing.T, endpointName string, assertResponseOK func(*testing.T, *http.Response)) { - t.Helper() - nd := setupNodeWithModifiedRPC(t) - // create several requests for header at height 2 - var tests = []struct { - nID string - expectedErr bool - errMsg string - }{ - { - nID: "0000000000000001", - expectedErr: false, - }, - { - nID: "00000000000001", - expectedErr: true, - errMsg: "expected namespace ID of size 8, got 7", - }, - { - nID: "000000000000000001", - expectedErr: true, - errMsg: "expected namespace ID of size 8, got 9", - }, - } - - for i, tt := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - endpoint := fmt.Sprintf("http://127.0.0.1:%s/%s/%s/height/%d", - nd.RPCServer.ListenAddr()[5:], endpointName, tt.nID, testHeight) - resp, err := http.Get(endpoint) - defer func() { - err = resp.Body.Close() - require.NoError(t, err) - }() - // check resp - if tt.expectedErr { - require.False(t, resp.StatusCode == http.StatusOK) - require.Equal(t, "application/json", resp.Header.Get("Content-Type")) - - var errorMessage string - err := json.NewDecoder(resp.Body).Decode(&errorMessage) - - require.NoError(t, err) - require.Equal(t, tt.errMsg, errorMessage) - - return - } - require.NoError(t, err) - require.True(t, resp.StatusCode == http.StatusOK) - - assertResponseOK(t, resp) - }) - } -} - -// TestHeadRequest rests the `/head` endpoint. -func TestHeadRequest(t *testing.T) { - nd := setupNodeWithModifiedRPC(t) - endpoint := fmt.Sprintf("http://127.0.0.1:%s/head", nd.RPCServer.ListenAddr()[5:]) - resp, err := http.Get(endpoint) - require.NoError(t, err) - defer func() { - err = resp.Body.Close() - require.NoError(t, err) - }() - require.True(t, resp.StatusCode == http.StatusOK) -} - -// TestHeaderRequest tests the `/header` endpoint. -func TestHeaderRequest(t *testing.T) { - nd := setupNodeWithModifiedRPC(t) - // create several requests for headers - var tests = []struct { - height uint64 - expectedErr bool - }{ - { - height: uint64(2), - expectedErr: false, - }, - { - height: uint64(0), - expectedErr: true, - }, - } - - for i, tt := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - endpoint := fmt.Sprintf("http://127.0.0.1:%s/header/%d", nd.RPCServer.ListenAddr()[5:], tt.height) - resp, err := http.Get(endpoint) - require.NoError(t, err) - defer func() { - err = resp.Body.Close() - require.NoError(t, err) - }() - - require.Equal(t, tt.expectedErr, resp.StatusCode != http.StatusOK) - }) - } -} - -// TestAvailabilityRequest tests the /data_available endpoint. -func TestAvailabilityRequest(t *testing.T) { - nd := setupNodeWithModifiedRPC(t) - - height := 5 - endpoint := fmt.Sprintf("http://127.0.0.1:%s/data_available/%d", nd.RPCServer.ListenAddr()[5:], height) - resp, err := http.Get(endpoint) - require.NoError(t, err) - defer func() { - err = resp.Body.Close() - require.NoError(t, err) - }() - - buf, err := io.ReadAll(resp.Body) - require.NoError(t, err) - - availResp := new(rpc.AvailabilityResponse) - err = json.Unmarshal(buf, &availResp) - require.NoError(t, err) - - assert.True(t, availResp.Available) -} - -func TestDASStateRequest(t *testing.T) { - nd := setupNodeWithModifiedRPC(t) - - endpoint := fmt.Sprintf("http://127.0.0.1:%s/daser/state", nd.RPCServer.ListenAddr()[5:]) - resp, err := http.Get(endpoint) - require.NoError(t, err) - defer func() { - err = resp.Body.Close() - require.NoError(t, err) - }() - dasStateResp := new(das.SamplingStats) - err = json.NewDecoder(resp.Body).Decode(dasStateResp) - require.NoError(t, err) - // ensure daser has run (or is still running) - assert.True(t, dasStateResp.SampledChainHead == 10 || dasStateResp.IsRunning) -} - -func setupNodeWithModifiedRPC(t *testing.T) *Node { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - // create test node with a dummy header headerServ, manually add a dummy header - daser := setupDASer(t) - hServ := setupHeaderService(ctx, t) - // create overrides - overrideHeaderServ := fx.Replace(hServ) - // fx.Decorate(func() *das.DASer {return daser}) == fx.Replace(daser) - overrideDASer := fx.Decorate(fx.Annotate( - func() *das.DASer { - return daser - }, - fx.OnStart(func(ctx context.Context) error { - return daser.Start(ctx) - }), - fx.OnStop(func(ctx context.Context) error { - return daser.Stop(ctx) - }), - )) - overrideRPCHandler := fx.Invoke(func(srv *rpc.Server, state state.Module, share shareServ.Module) { - handler := rpc.NewHandler(state, share, hServ, daser) - handler.RegisterEndpoints(srv) - }) - nd := TestNode(t, node.Full, overrideHeaderServ, overrideDASer, overrideRPCHandler) - // start node - err := nd.Start(ctx) - require.NoError(t, err) - t.Cleanup(func() { - err = nd.Stop(ctx) - require.NoError(t, err) - }) - return nd -} - -func setupHeaderService(ctx context.Context, t *testing.T) headerServ.Module { - suite := header.NewTestSuite(t, 1) - head := suite.Head() - // create header stores - remoteStore := store.NewTestStore(ctx, t, head) - localStore := store.NewTestStore(ctx, t, head) - _, err := localStore.Append(ctx, suite.GenExtendedHeaders(5)...) - require.NoError(t, err) - // create syncer - syncer := sync.NewSyncer(local.NewExchange(remoteStore), localStore, &header.DummySubscriber{}, params.BlockTime) - - return headerServ.NewHeaderService(syncer, nil, nil, nil, localStore) -} - -func setupDASer(t *testing.T) *das.DASer { - suite := header.NewTestSuite(t, 1) - ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) - // store has 10 headers - mockGet := &mockGetter{ - headers: make(map[int64]*header.ExtendedHeader), - doneCh: make(chan struct{}), - brokenHeightCh: make(chan struct{}), - } - for _, h := range suite.GenExtendedHeaders(10) { - mockGet.headers[h.Height] = h - mockGet.head = h.Height - } - // subscription has 10 "new" headers - sub := &header.DummySubscriber{Headers: suite.GenExtendedHeaders(10)} - return das.NewDASer(share.NewTestSuccessfulAvailability(), sub, mockGet, ds, nil) -} - -// taken from daser_test.go -type mockGetter struct { - doneCh chan struct{} // signals all stored headers have been retrieved - - brokenHeightCh chan struct{} - - head int64 - headers map[int64]*header.ExtendedHeader -} - -func (m *mockGetter) Head(context.Context) (*header.ExtendedHeader, error) { - return m.headers[m.head], nil -} - -func (m *mockGetter) GetByHeight(_ context.Context, height uint64) (*header.ExtendedHeader, error) { - h, ok := m.headers[int64(height)] - if !ok { - return nil, header.ErrNotFound - } - return h, nil -} - -func (m *mockGetter) GetRangeByHeight(ctx context.Context, from, to uint64) ([]*header.ExtendedHeader, error) { - panic("implement me") -} - -func (m *mockGetter) Get(context.Context, tmbytes.HexBytes) (*header.ExtendedHeader, error) { - panic("implement me") -} From 15a5eaafba8d1c776d8f2ebe3a10fd469289e0e4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 5 Oct 2022 13:15:46 +0200 Subject: [PATCH 0112/1008] fix(state): returning CoreAccessor from nodebuilder for state.WithMetrics (#1200) #1083 introduced `state.WithMetrics` that takes a `CoreAccessor` instead of the `Module`, so the DI currently fails to build. This fixes the DI for nodes running metrics --- nodebuilder/settings.go | 4 +++- nodebuilder/state/core.go | 24 ++---------------------- nodebuilder/state/module.go | 21 ++++++++++++++++++++- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 6d9b36245a..865b1aa334 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -90,7 +90,9 @@ func initializeMetrics( ) lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { + // here we take the context from fx.Invoke because pusher uses it for its entire lifetime, + // instead of only for the Start operation + OnStart: func(context.Context) error { return pusher.Start(ctx) }, OnStop: func(ctx context.Context) error { diff --git a/nodebuilder/state/core.go b/nodebuilder/state/core.go index 9de9dc32c3..e44dddef66 100644 --- a/nodebuilder/state/core.go +++ b/nodebuilder/state/core.go @@ -1,15 +1,7 @@ package state import ( - "context" - - "go.uber.org/fx" - apptypes "github.com/celestiaorg/celestia-app/x/payment/types" - "github.com/celestiaorg/celestia-node/fraud" - "github.com/celestiaorg/celestia-node/libs/fxutil" - fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" - "github.com/celestiaorg/celestia-node/header/sync" "github.com/celestiaorg/celestia-node/nodebuilder/core" "github.com/celestiaorg/celestia-node/state" @@ -18,21 +10,9 @@ import ( // CoreAccessor constructs a new instance of state.Module over // a celestia-core connection. func CoreAccessor( - lc fx.Lifecycle, - fService fraudServ.Module, corecfg core.Config, signer *apptypes.KeyringSigner, sync *sync.Syncer, -) Module { - service := state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - lifecycleCtx := fxutil.WithLifecycle(ctx, lc) - return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fService, service.Start, service.Stop) - }, - OnStop: func(ctx context.Context) error { - return service.Stop(ctx) - }, - }) - return service +) *state.CoreAccessor { + return state.NewCoreAccessor(signer, sync, corecfg.IP, corecfg.RPCPort, corecfg.GRPCPort) } diff --git a/nodebuilder/state/module.go b/nodebuilder/state/module.go index b0d020776c..678c9d0779 100644 --- a/nodebuilder/state/module.go +++ b/nodebuilder/state/module.go @@ -1,10 +1,16 @@ package state import ( + "context" + logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/fraud" + "github.com/celestiaorg/celestia-node/libs/fxutil" + fraudServ "github.com/celestiaorg/celestia-node/nodebuilder/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" + "github.com/celestiaorg/celestia-node/state" ) var log = logging.Logger("state-module") @@ -19,7 +25,20 @@ func ConstructModule(tp node.Type, cfg *Config) fx.Option { fx.Supply(*cfg), fx.Error(cfgErr), fx.Provide(Keyring), - fx.Provide(CoreAccessor), + fx.Provide(fx.Annotate( + CoreAccessor, + fx.OnStart(func(ctx context.Context, lc fx.Lifecycle, fservice fraudServ.Module, ca *state.CoreAccessor) error { + lifecycleCtx := fxutil.WithLifecycle(ctx, lc) + return fraudServ.Lifecycle(ctx, lifecycleCtx, fraud.BadEncoding, fservice, ca.Start, ca.Stop) + }), + fx.OnStop(func(ctx context.Context, ca *state.CoreAccessor) error { + return ca.Stop(ctx) + }), + )), + // the module is needed for the handler + fx.Provide(func(ca *state.CoreAccessor) Module { + return ca + }), ) switch tp { From 069b47f247edb76a1e0e3f2487a8c76dbab13b1b Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Wed, 5 Oct 2022 15:52:45 +0200 Subject: [PATCH 0113/1008] fix(ipld/plugin): don't truncate a type byte when it's not in the data (#1196) --- go.mod | 2 +- ipld/plugin/namespace_hasher.go | 76 ++++++++++++++++++++++++++++ ipld/plugin/namespace_hasher_test.go | 67 ++++++++++++++++++++++++ ipld/plugin/nmt.go | 73 +++++--------------------- 4 files changed, 157 insertions(+), 61 deletions(-) create mode 100644 ipld/plugin/namespace_hasher.go create mode 100644 ipld/plugin/namespace_hasher_test.go diff --git a/go.mod b/go.mod index 9480df8a50..27f736b41c 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/libp2p/go-libp2p-pubsub v0.7.0 github.com/libp2p/go-libp2p-record v0.1.3 github.com/libp2p/go-libp2p-routing-helpers v0.2.3 + github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 github.com/multiformats/go-multiaddr v0.7.0 @@ -202,7 +203,6 @@ require ( github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect github.com/minio/highwayhash v1.0.2 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect diff --git a/ipld/plugin/namespace_hasher.go b/ipld/plugin/namespace_hasher.go new file mode 100644 index 0000000000..3a307e1bfe --- /dev/null +++ b/ipld/plugin/namespace_hasher.go @@ -0,0 +1,76 @@ +package plugin + +import ( + "fmt" + "hash" + + "github.com/minio/sha256-simd" + mhcore "github.com/multiformats/go-multihash/core" + "github.com/tendermint/tendermint/pkg/consts" + + "github.com/celestiaorg/nmt" +) + +func init() { + // Register custom hasher in the multihash register. + // Required for the Bitswap to hash and verify inbound data correctly + mhcore.Register(sha256Namespace8Flagged, func() hash.Hash { + return defaultHasher() + }) +} + +// namespaceHasher implements hash.Hash over NMT Hasher. +// TODO: Move to NMT repo? +type namespaceHasher struct { + *nmt.Hasher + tp byte + data []byte +} + +// defaultHasher constructs the namespaceHasher with default configuration +func defaultHasher() *namespaceHasher { + return &namespaceHasher{Hasher: nmt.NewNmtHasher(sha256.New(), nmt.DefaultNamespaceIDLen, true)} +} + +// Write writes the namespaced data to be hashed. +// +// Requires data of fixed size to match leaf or inner NMT nodes. +// Only one write is allowed. +func (n *namespaceHasher) Write(data []byte) (int, error) { + if n.data != nil { + return 0, fmt.Errorf("ipld: only one write to hasher is allowed") + } + + ln, nln, hln := len(data), int(n.NamespaceLen), n.Hash.Size() + innerNodeSize, leafNodeSize := (nln*2+hln)*2, nln+consts.ShareSize + switch ln { + default: + return 0, fmt.Errorf("ipld: wrong sized data written to the hasher") + case innerNodeSize: + n.tp = nmt.NodePrefix + case leafNodeSize: + n.tp = nmt.LeafPrefix + case innerNodeSize + typeSize: // w/ additional type byte + n.tp = nmt.NodePrefix + data = data[typeSize:] + case leafNodeSize + typeSize: // w/ additional type byte + n.tp = nmt.LeafPrefix + data = data[typeSize:] + } + + n.data = data + return len(n.data), nil +} + +// Sum computes the hash. +// Does not append the given suffix and violating the interface. +func (n *namespaceHasher) Sum([]byte) []byte { + isLeafData := n.tp == nmt.LeafPrefix + if isLeafData { + return n.Hasher.HashLeaf(n.data) + } + + flagLen := int(n.NamespaceLen * 2) + sha256Len := n.Hasher.Size() + return n.Hasher.HashNode(n.data[:flagLen+sha256Len], n.data[flagLen+sha256Len:]) +} diff --git a/ipld/plugin/namespace_hasher_test.go b/ipld/plugin/namespace_hasher_test.go new file mode 100644 index 0000000000..d23fd2fb6e --- /dev/null +++ b/ipld/plugin/namespace_hasher_test.go @@ -0,0 +1,67 @@ +package plugin + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/pkg/consts" +) + +func TestNamespaceHasherWrite(t *testing.T) { + leafSize := consts.ShareSize + consts.NamespaceSize + innerSize := nmtHashSize * 2 + tt := []struct { + name string + expectedSize int + writtenSize int + }{ + { + "Leaf", + leafSize, + leafSize, + }, + { + "Inner", + innerSize, + innerSize, + }, + { + "LeafAndType", + leafSize, + leafSize + typeSize, + }, + { + "InnerAndType", + innerSize, + innerSize + typeSize, + }, + } + + for _, ts := range tt { + t.Run("Success"+ts.name, func(t *testing.T) { + h := defaultHasher() + n, err := h.Write(make([]byte, ts.writtenSize)) + assert.NoError(t, err) + assert.Equal(t, ts.expectedSize, n) + assert.Equal(t, ts.expectedSize, len(h.data)) + }) + } + + t.Run("ErrorSecondWrite", func(t *testing.T) { + h := defaultHasher() + n, err := h.Write(make([]byte, leafSize)) + assert.NoError(t, err) + assert.Equal(t, leafSize, n) + + n, err = h.Write(make([]byte, leafSize)) + assert.Error(t, err) + assert.Equal(t, 0, n) + }) + + t.Run("ErrorIncorrectSize", func(t *testing.T) { + h := defaultHasher() + n, err := h.Write(make([]byte, 13)) + assert.Error(t, err) + assert.Equal(t, 0, n) + }) +} diff --git a/ipld/plugin/nmt.go b/ipld/plugin/nmt.go index c0700390a8..6b03096ae2 100644 --- a/ipld/plugin/nmt.go +++ b/ipld/plugin/nmt.go @@ -6,14 +6,12 @@ import ( "crypto/sha256" "errors" "fmt" - "hash" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" mh "github.com/multiformats/go-multihash" - mhcore "github.com/multiformats/go-multihash/core" "github.com/tendermint/tendermint/pkg/consts" "github.com/celestiaorg/nmt" @@ -23,61 +21,19 @@ const ( // Below used multiformats (one codec, one multihash) seem free: // https://github.com/multiformats/multicodec/blob/master/table.csv - // NmtCodec is the codec used for leaf and inner nodes of a Namespaced Merkle Tree. - NmtCodec = 0x7700 + // nmtCodec is the codec used for leaf and inner nodes of a Namespaced Merkle Tree. + nmtCodec = 0x7700 // Sha256Namespace8Flagged is the multihash code used to hash blocks // that contain an NMT node (inner and leaf nodes). - Sha256Namespace8Flagged = 0x7701 + sha256Namespace8Flagged = 0x7701 // nmtHashSize is the size of a digest created by an NMT in bytes. nmtHashSize = 2*consts.NamespaceSize + sha256.Size -) - -func init() { - mhcore.Register(Sha256Namespace8Flagged, func() hash.Hash { - return NewNamespaceHasher(nmt.NewNmtHasher(sha256.New(), nmt.DefaultNamespaceIDLen, true)) - }) -} - -type namespaceHasher struct { - *nmt.Hasher - tp byte - data []byte -} -func NewNamespaceHasher(hasher *nmt.Hasher) hash.Hash { - return &namespaceHasher{ - Hasher: hasher, - } -} - -func (n *namespaceHasher) Write(data []byte) (int, error) { - ln, nln, hln := len(data), int(n.NamespaceLen), n.Hash.Size() - innerNodeSize, leafNodeSize := (nln*2+hln)*2, nln+consts.ShareSize - switch ln { - default: - return 0, fmt.Errorf("wrong data size") - case innerNodeSize, innerNodeSize + 1: // w/ and w/o additional type byte - n.tp = nmt.NodePrefix - case leafNodeSize, leafNodeSize + 1: // w/ and w/o additional type byte - n.tp = nmt.LeafPrefix - } - - n.data = data[1:] - return ln, nil -} - -func (n *namespaceHasher) Sum([]byte) []byte { - isLeafData := n.tp == nmt.LeafPrefix - if isLeafData { - return n.Hasher.HashLeaf(n.data) - } - - flagLen := int(n.NamespaceLen * 2) - sha256Len := n.Hasher.Size() - return n.Hasher.HashNode(n.data[:flagLen+sha256Len], n.data[flagLen+sha256Len:]) -} + // typeSize defines the size of the serialized NMT Node type + typeSize = 1 +) func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid) (ipld.Node, error) { block, err := bGetter.GetBlock(ctx, root) @@ -93,8 +49,6 @@ func GetNode(ctx context.Context, bGetter blockservice.BlockGetter, root cid.Cid } func decodeBlock(block blocks.Block) (ipld.Node, error) { - // length of the domain separator for leaf and inner nodes: - const prefixOffset = 1 var ( leafPrefix = []byte{nmt.LeafPrefix} innerPrefix = []byte{nmt.NodePrefix} @@ -106,18 +60,18 @@ func decodeBlock(block blocks.Block) (ipld.Node, error) { Data: nil, }, nil } - domainSeparator := data[:prefixOffset] + domainSeparator := data[:typeSize] if bytes.Equal(domainSeparator, leafPrefix) { return &nmtLeafNode{ cid: block.Cid(), - Data: data[prefixOffset:], + Data: data[typeSize:], }, nil } if bytes.Equal(domainSeparator, innerPrefix) { return &nmtNode{ cid: block.Cid(), - l: data[prefixOffset : prefixOffset+nmtHashSize], - r: data[prefixOffset+nmtHashSize:], + l: data[typeSize : typeSize+nmtHashSize], + r: data[typeSize+nmtHashSize:], }, nil } return nil, fmt.Errorf( @@ -132,7 +86,6 @@ var _ ipld.Node = (*nmtNode)(nil) var _ ipld.Node = (*nmtLeafNode)(nil) type nmtNode struct { - // TODO(ismail): we might want to export these later cid cid.Cid l, r []byte } @@ -305,11 +258,11 @@ func CidFromNamespacedSha256(namespacedHash []byte) (cid.Cid, error) { if got, want := len(namespacedHash), nmtHashSize; got != want { return cid.Cid{}, fmt.Errorf("invalid namespaced hash length, got: %v, want: %v", got, want) } - buf, err := mh.Encode(namespacedHash, Sha256Namespace8Flagged) + buf, err := mh.Encode(namespacedHash, sha256Namespace8Flagged) if err != nil { return cid.Undef, err } - return cid.NewCidV1(NmtCodec, buf), nil + return cid.NewCidV1(nmtCodec, buf), nil } // MustCidFromNamespacedSha256 is a wrapper around cidFromNamespacedSha256 that panics @@ -320,7 +273,7 @@ func MustCidFromNamespacedSha256(hash []byte) cid.Cid { panic( fmt.Sprintf("malformed hash: %s, codec: %v", err, - mh.Codes[Sha256Namespace8Flagged]), + mh.Codes[sha256Namespace8Flagged]), ) } return cidFromHash From 02cd7ca92df5a9184871b37dc3b1a842feac3485 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Thu, 6 Oct 2022 12:00:11 +0300 Subject: [PATCH 0114/1008] metrics: add metrics to fraud package (#1047) --- fraud/metrics.go | 45 +++++++++++++++++++++++++++++++++++++ fraud/proof.go | 8 +++++++ fraud/registry.go | 4 ++-- fraud/service.go | 28 ++++++++++++++++++++++- fraud/sync.go | 14 ++++++++++++ header/p2p/exchange.go | 3 ++- nodebuilder/fraud/module.go | 6 +++++ nodebuilder/settings.go | 2 ++ 8 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 fraud/metrics.go diff --git a/fraud/metrics.go b/fraud/metrics.go new file mode 100644 index 0000000000..74127c265d --- /dev/null +++ b/fraud/metrics.go @@ -0,0 +1,45 @@ +package fraud + +import ( + "context" + + "github.com/ipfs/go-datastore" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/unit" +) + +// WithMetrics enables metrics to monitor fraud proofs. +func WithMetrics(store Getter) { + proofTypes := registeredProofTypes() + for _, proofType := range proofTypes { + counter, _ := meter.AsyncInt64().Gauge(string(proofType), + instrument.WithUnit(unit.Dimensionless), + instrument.WithDescription("Stored fraud proof"), + ) + err := meter.RegisterCallback( + []instrument.Asynchronous{ + counter, + }, + func(ctx context.Context) { + proofs, err := store.Get(ctx, proofType) + switch err { + case nil: + counter.Observe(ctx, + int64(len(proofs)), + attribute.String("proof_type", string(proofType)), + ) + case datastore.ErrNotFound: + counter.Observe(ctx, 0, attribute.String("err", "not_found")) + return + default: + counter.Observe(ctx, 0, attribute.String("err", "unknown")) + } + }, + ) + + if err != nil { + panic(err) + } + } +} diff --git a/fraud/proof.go b/fraud/proof.go index 2261d3cdf9..65e7a183a1 100644 --- a/fraud/proof.go +++ b/fraud/proof.go @@ -5,9 +5,17 @@ import ( "encoding" "fmt" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric/global" + "github.com/celestiaorg/celestia-node/header" ) +var ( + meter = global.MeterProvider().Meter("fraud") + tracer = otel.Tracer("fraud") +) + type ErrFraudExists struct { Proof []Proof } diff --git a/fraud/registry.go b/fraud/registry.go index ab6110505e..afc7cb5097 100644 --- a/fraud/registry.go +++ b/fraud/registry.go @@ -28,8 +28,8 @@ func Register(p Proof) { } } -// getRegisteredProofTypes returns all available proofTypes. -func getRegisteredProofTypes() []ProofType { +// registeredProofTypes returns all available proofTypes. +func registeredProofTypes() []ProofType { unmarshalersLk.Lock() defer unmarshalersLk.Unlock() proofs := make([]ProofType, 0, len(defaultUnmarshalers)) diff --git a/fraud/service.go b/fraud/service.go index 82ca1298de..5a0ec3e509 100644 --- a/fraud/service.go +++ b/fraud/service.go @@ -15,6 +15,10 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/celestiaorg/celestia-node/params" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) // fraudRequests is the amount of external requests that will be tried to get fraud proofs from other peers. @@ -77,7 +81,7 @@ func (f *ProofService) registerProofTopics(proofTypes ...ProofType) error { // Start joins fraud proofs topics, sets the stream handler for fraudProtocolID and starts syncing if syncer is enabled. func (f *ProofService) Start(context.Context) error { f.ctx, f.cancel = context.WithCancel(context.Background()) - if err := f.registerProofTopics(getRegisteredProofTypes()...); err != nil { + if err := f.registerProofTopics(registeredProofTypes()...); err != nil { return err } f.host.SetStreamHandler(fraudProtocolID, f.handleFraudMessageRequest) @@ -129,6 +133,11 @@ func (f *ProofService) processIncoming( from peer.ID, msg *pubsub.Message, ) pubsub.ValidationResult { + ctx, span := tracer.Start(ctx, "process_proof", trace.WithAttributes( + attribute.String("proof_type", string(proofType)), + )) + defer span.End() + // unmarshal message to the Proof. // Peer will be added to black list if unmarshalling fails. proof, err := Unmarshal(proofType, msg.Data) @@ -137,10 +146,17 @@ func (f *ProofService) processIncoming( if !errors.Is(err, &errNoUnmarshaler{}) { f.pubsub.BlacklistPeer(from) } + span.RecordError(err) return pubsub.ValidationReject } // check the fraud proof locally and ignore if it has been already stored locally. if f.verifyLocal(ctx, proofType, hex.EncodeToString(proof.HeaderHash()), msg.Data) { + span.AddEvent("received_known_fraud_proof", trace.WithAttributes( + attribute.String("proof_type", string(proof.Type())), + attribute.Int("block_height", int(proof.Height())), + attribute.String("block_hash", hex.EncodeToString(proof.HeaderHash())), + attribute.String("from_peer", from.String()), + )) return pubsub.ValidationIgnore } @@ -160,15 +176,25 @@ func (f *ProofService) processIncoming( log.Errorw("proof validation err: ", "err", err, "proofType", proof.Type(), "height", proof.Height()) f.pubsub.BlacklistPeer(from) + span.RecordError(err) return pubsub.ValidationReject } + span.AddEvent("received_valid_proof", trace.WithAttributes( + attribute.String("proof_type", string(proof.Type())), + attribute.Int("block_height", int(proof.Height())), + attribute.String("block_hash", hex.EncodeToString(proof.HeaderHash())), + attribute.String("from_peer", from.String()), + )) + // add the fraud proof to storage. err = f.put(ctx, proof.Type(), hex.EncodeToString(proof.HeaderHash()), msg.Data) if err != nil { log.Errorw("failed to store fraud proof", "err", err) + span.RecordError(err) } + span.SetStatus(codes.Ok, "") return pubsub.ValidationAccept } diff --git a/fraud/sync.go b/fraud/sync.go index 21f5fe4fb4..9b639c7f52 100644 --- a/fraud/sync.go +++ b/fraud/sync.go @@ -9,6 +9,8 @@ import ( "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" pubsub "github.com/libp2p/go-libp2p-pubsub" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "github.com/celestiaorg/go-libp2p-messenger/serde" @@ -57,14 +59,24 @@ func (f *ProofService) syncFraudProofs(ctx context.Context) { peerCache[connStatus.Peer] = struct{}{} // valid peer found, so go send proof requests go func(pid peer.ID) { + ctx, span := tracer.Start(ctx, "sync_proofs") + defer span.End() + + span.SetAttributes( + attribute.String("peer_id", pid.String()), + attribute.StringSlice("proof_types", proofTypes), + ) log.Debugw("requesting proofs from peer", "pid", pid) respProofs, err := requestProofs(ctx, f.host, pid, proofTypes) if err != nil { log.Errorw("error while requesting fraud proofs", "err", err, "peer", pid) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return } if len(respProofs) == 0 { log.Debugw("peer did not return any proofs", "pid", pid) + span.SetStatus(codes.Ok, "") return } log.Debugw("got fraud proofs from peer", "pid", connStatus.Peer) @@ -87,9 +99,11 @@ func (f *ProofService) syncFraudProofs(ctx context.Context) { ) if err != nil { log.Error(err) + span.RecordError(err) } } } + span.SetStatus(codes.Ok, "") }(connStatus.Peer) } } diff --git a/header/p2p/exchange.go b/header/p2p/exchange.go index de0646cf14..c0559802fe 100644 --- a/header/p2p/exchange.go +++ b/header/p2p/exchange.go @@ -13,11 +13,12 @@ import ( "github.com/libp2p/go-libp2p-core/protocol" tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/celestiaorg/go-libp2p-messenger/serde" + "github.com/celestiaorg/celestia-node/header" p2p_pb "github.com/celestiaorg/celestia-node/header/p2p/pb" header_pb "github.com/celestiaorg/celestia-node/header/pb" "github.com/celestiaorg/celestia-node/params" - "github.com/celestiaorg/go-libp2p-messenger/serde" ) var log = logging.Logger("header/p2p") diff --git a/nodebuilder/fraud/module.go b/nodebuilder/fraud/module.go index a072931c73..b115990345 100644 --- a/nodebuilder/fraud/module.go +++ b/nodebuilder/fraud/module.go @@ -4,21 +4,27 @@ import ( logging "github.com/ipfs/go-log/v2" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/nodebuilder/node" ) var log = logging.Logger("fraud-module") func ConstructModule(tp node.Type) fx.Option { + baseComponent := fx.Provide(func(module Module) fraud.Getter { + return module + }) switch tp { case node.Light: return fx.Module( "fraud", + baseComponent, fx.Provide(ModuleWithSyncer), ) case node.Full, node.Bridge: return fx.Module( "fraud", + baseComponent, fx.Provide(NewModule), ) default: diff --git a/nodebuilder/settings.go b/nodebuilder/settings.go index 865b1aa334..b735dec8c7 100644 --- a/nodebuilder/settings.go +++ b/nodebuilder/settings.go @@ -15,6 +15,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.uber.org/fx" + "github.com/celestiaorg/celestia-node/fraud" "github.com/celestiaorg/celestia-node/header" "github.com/celestiaorg/celestia-node/nodebuilder/daser" "github.com/celestiaorg/celestia-node/nodebuilder/node" @@ -40,6 +41,7 @@ func WithMetrics(metricOpts []otlpmetrichttp.Option, nodeType node.Type) fx.Opti fx.Invoke(initializeMetrics), fx.Invoke(header.WithMetrics), fx.Invoke(state.WithMetrics), + fx.Invoke(fraud.WithMetrics), ) var opts fx.Option From 49a3f58e5e4d3e34627b7214432e8e10d06114df Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 15 Sep 2022 12:55:26 +0300 Subject: [PATCH 0115/1008] add ADR for DASer --- docs/adr/adr-012-daser-parallelization.md | 78 ++++++++++++++++++++ docs/adr/img/daser-architecture-diagram.png | Bin 0 -> 114949 bytes 2 files changed, 78 insertions(+) create mode 100644 docs/adr/adr-012-daser-parallelization.md create mode 100644 docs/adr/img/daser-architecture-diagram.png diff --git a/docs/adr/adr-012-daser-parallelization.md b/docs/adr/adr-012-daser-parallelization.md new file mode 100644 index 0000000000..9882351c0b --- /dev/null +++ b/docs/adr/adr-012-daser-parallelization.md @@ -0,0 +1,78 @@ +# ADR #012: DASer parallelization + +## Changelog + +- 2022-9-12: Started + +## Authors + +@walldiss + +## Context + +DAS is the process of verifying the availability of +block data by sampling chunks or shares of those blocks. The `das` package implements an engine to continuously ensure the availability of the chain's block data via the `Availability` interface. +Verifying the availability of block data is a priority functionality for celestia-node. Its performance could benefit significantly from parallelization optimisation to make it able to fully utilise network bandwidth. + +## Previous approach + +Share sampling, by its nature, is a network-bound operation that implies multiple network round-trips. +The previous implementation of DAS'er used a single-thread synchronous approach, +meaning there was only one process sequentially performing sampling operations over past headers that were blocked by awaiting a response. + +## Decision + +Using multiple coordinated workers running in parallel drastically improves the DASer's performance through better network bandwidth utilization. On the downside, the proposed solution brings concurrency complexity. + +## Detailed Design + +To achieve parallelization, the DASer was split into the following core components: + +1. The `Coordinator` holds the current state of sampled headers and defines what headers should be sampled next. +2. `Workers` perform sampling over a range of headers and communicate the results back to the coordinator. Workers are created on demand, when `Jobs` are available. The amount of concurrently running workers is limited by the const `concurrencyLimit`. Length of the sampling range is defined by DASer configuration param `samplingRange`. +3. The `Subscriber` subscribes to network head updates. When new headers are found, it will notify the `Coordinator`. Recent network head blocks will be prioritized for sampling to increase the availability of the most demanded blocks. +4. The `CheckpointStore` stores/loads the `Coordinator` state as a checkpoint to allow for seamless resuming upon restart. The `Coordinator` stores the state as a checkpoint on exit and resumes sampling from the latest state. +It also periodically stores checkpoints to storage to avoid the situation when no checkpoint is stored upon a hard shutdown of the node. + +![image](./img/daser-architecture-diagram.png) + +Sampling flow: + +1. `Coordinator` checks if worker concurrency limit is reached. +2. If the limit is not reached, `Coordinator` forms a new sampling `Job` + 1. Looks if there is a `Job` in the top of the priority stack. + 2. If nothing is in the priority stack, picks the next not sampled range for `Job`. +3. Launches new `Worker` with formed `Job`. +4. `Worker` gets headers for given ranges and sample shares. +5. After `Worker` is done, it communicates results back to `Coordinator` +6. `Coordinator` updates sampling state according to worker results. + +## Concurrency limit + +The maximum amount of concurrently running workers is defined by the const `concurrencyLimit` = 16. This value is an approximation that came from the first basic performance tests. +During the test, samples/sec rate was observed with moving average over 30 sec window for period of 5min. The metric was triggered only by sampled header with width > 2. + +```text +amount of workers: 8, speed: 8.66 + +amount of workers: 16, speed: 11.13 + +amount of workers: 32, speed: 11.33 + +amount of workers: 64, speed: 11.83 +``` + +Based on basic experiment results, values higher than 16 don’t bring much benefit. At the same time, increased parallelization comes with a cost of higher memory consumption. +Future improvements discussed later. + +## Status + +Implemented + +## Future plans + +Several params values that come hardcoded in DASer (`samplingRange`, `concurrencyLimit`, `priorityQueueSize`, `genesisHeight`, `backgroundStoreInterval`) should become configurable so the node runner can define them based on the specific node setup. Default values should be optimized by performance testing for most common setups and could potentially vary for different node types. + +## References + +- [DASer PR](https://github.com/celestiaorg/celestia-node/pull/988/) diff --git a/docs/adr/img/daser-architecture-diagram.png b/docs/adr/img/daser-architecture-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..9effa2f566947a343352d87c39ab601d3cbdb5d3 GIT binary patch literal 114949 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